/* $NetBSD: hdafg.c,v 1.18.2.3 2024/08/23 15:34:47 martin Exp $ */

/*
 * Copyright (c) 2009 Precedence Technologies Ltd <support@precedence.co.uk>
 * Copyright (c) 2009-2011 Jared D. McNeill <jmcneill@invisible.ca>
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Precedence Technologies Ltd
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Widget parsing from FreeBSD hdac.c:
 *
 * Copyright (c) 2006 Stephane E. Potvin <sepotvin@videotron.ca>
 * Copyright (c) 2006 Ariff Abdullah <ariff@FreeBSD.org>
 * Copyright (c) 2008 Alexander Motin <mav@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hdafg.c,v 1.18.2.3 2024/08/23 15:34:47 martin Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <sys/kmem.h>
#include <sys/module.h>
#include <sys/condvar.h>
#include <sys/kthread.h>
#include <sys/mutex.h>

#include <sys/audioio.h>
#include <dev/audio/audio_if.h>

#ifdef _KERNEL_OPT
#include "opt_hdaudio.h"
#endif

#include "hdaudiovar.h"
#include "hdaudioreg.h"
#include "hdaudio_mixer.h"
#include "hdaudioio.h"
#include "hdaudio_verbose.h"
#include "hdaudiodevs.h"
#include "hdafg_dd.h"
#include "hdmireg.h"

#ifndef AUFMT_SURROUND_7_1
#define AUFMT_SURROUND_7_1 (AUFMT_DOLBY_5_1|AUFMT_SIDE_LEFT|AUFMT_SIDE_RIGHT)
#endif

#if defined(HDAFG_DEBUG)
static int hdafg_debug = HDAFG_DEBUG;
#else
static int hdafg_debug = 0;
#endif

#define hda_debug(sc, ...)		\
	if (hdafg_debug) hda_print(sc, __VA_ARGS__)
#define hda_debug1(sc, ...)		\
	if (hdafg_debug) hda_print1(sc, __VA_ARGS__)

#define HDAUDIO_MIXER_CLASS_OUTPUTS	0
#define HDAUDIO_MIXER_CLASS_INPUTS	1
#define HDAUDIO_MIXER_CLASS_RECORD	2
#define HDAUDIO_MIXER_CLASS_LAST	HDAUDIO_MIXER_CLASS_RECORD

#define HDAUDIO_GPIO_MASK	0
#define HDAUDIO_GPIO_DIR	1
#define HDAUDIO_GPIO_DATA	2

#define HDAUDIO_UNSOLTAG_EVENT_HP	0x01
#define HDAUDIO_UNSOLTAG_EVENT_DD	0x02

#define HDAUDIO_HP_SENSE_PERIOD		hz

const u_int hdafg_possible_rates[] = {
	8000, 11025, 16000, 22050, 32000, 44100,
	48000, 88200, 96000, 176500, 192000, /* 384000, */
};

static const char *hdafg_mixer_names[] = HDAUDIO_DEVICE_NAMES;

static const char *hdafg_port_connectivity[] = {
	"Jack",
	"Unconnected",
	"Built-In",
	"Jack & Built-In"
};
static const char *hdafg_default_device[] = {
	"Line Out",
	"Speaker",
	"HP Out",
	"CD",
	"SPDIF Out",
	"Digital Out",
	"Modem Line Side",
	"Modem Handset Side",
	"Line In",
	"AUX",
	"Mic In",
	"Telephony",
	"SPDIF In",
	"Digital In",
	"Reserved",
	"Other"
};
static const char *hdafg_color[] = {
	"Unknown",
	"Black",
	"Grey",
	"Blue",
	"Green",
	"Red",
	"Orange",
	"Yellow",
	"Purple",
	"Pink",
	"ReservedA",
	"ReservedB",
	"ReservedC",
	"ReservedD",
	"White",
	"Other"
};

#define HDAUDIO_MAXFORMATS	24
#define HDAUDIO_MAXCONNECTIONS	32
#define HDAUDIO_MAXPINS		16
#define HDAUDIO_PARSE_MAXDEPTH	10

#define HDAUDIO_AMP_VOL_DEFAULT (-1)
#define HDAUDIO_AMP_MUTE_DEFAULT (0xffffffff)
#define HDAUDIO_AMP_MUTE_NONE	0
#define HDAUDIO_AMP_MUTE_LEFT	(1 << 0)
#define HDAUDIO_AMP_MUTE_RIGHT	(1 << 1)
#define HDAUDIO_AMP_MUTE_ALL	(HDAUDIO_AMP_MUTE_LEFT | HDAUDIO_AMP_MUTE_RIGHT)
#define HDAUDIO_AMP_LEFT_MUTED(x)	((x) & HDAUDIO_AMP_MUTE_LEFT)
#define HDAUDIO_AMP_RIGHT_MUTED(x)	(((x) & HDAUDIO_AMP_MUTE_RIGHT) >> 1)

#define HDAUDIO_ADC_MONITOR	1

enum hdaudio_pindir {
	HDAUDIO_PINDIR_NONE = 0,
	HDAUDIO_PINDIR_OUT = 1,
	HDAUDIO_PINDIR_IN = 2,
	HDAUDIO_PINDIR_INOUT = 3,
};

#define hda_get_param(sc, cop)					\
	hdaudio_command((sc)->sc_codec, (sc)->sc_nid,		\
	  CORB_GET_PARAMETER, COP_##cop)
#define hda_get_wparam(w, cop)					\
	hdaudio_command((w)->w_afg->sc_codec, (w)->w_nid,	\
	  CORB_GET_PARAMETER, COP_##cop)

struct hdaudio_assoc {
	bool			as_enable;
	bool			as_activated;
	u_char			as_index;
	enum hdaudio_pindir	as_dir;
	u_char			as_pincnt;
	u_char			as_fakeredir;
	int			as_digital;
#define HDAFG_AS_ANALOG		0
#define HDAFG_AS_SPDIF		1
#define HDAFG_AS_HDMI		2
#define HDAFG_AS_DISPLAYPORT	3
	bool			as_displaydev;
	int			as_hpredir;
	int			as_pins[HDAUDIO_MAXPINS];
	int			as_dacs[HDAUDIO_MAXPINS];
};

struct hdaudio_widget {
	struct hdafg_softc	*w_afg;
	char				w_name[32];
	int				w_nid;
	bool				w_enable;
	bool				w_waspin;
	int				w_selconn;
	int				w_bindas;
	int				w_bindseqmask;
	int				w_pflags;
	int				w_audiodev;
	uint32_t			w_audiomask;

	int				w_nconns;
	int				w_conns[HDAUDIO_MAXCONNECTIONS];
	bool				w_connsenable[HDAUDIO_MAXCONNECTIONS];

	int				w_type;
	struct {
		uint32_t		aw_cap;
		uint32_t		pcm_size_rate;
		uint32_t		stream_format;
		uint32_t		outamp_cap;
		uint32_t		inamp_cap;
		uint32_t		eapdbtl;
	} w_p;
	struct {
		uint32_t		config;
		uint32_t		biosconfig;
		uint32_t		cap;
		uint32_t		ctrl;
	} w_pin;
};

struct hdaudio_control {
	struct hdaudio_widget	*ctl_widget, *ctl_childwidget;
	bool			ctl_enable;
	int			ctl_index;
	enum hdaudio_pindir	ctl_dir, ctl_ndir;
	int			ctl_mute, ctl_step, ctl_size, ctl_offset;
	int			ctl_left, ctl_right, ctl_forcemute;
	uint32_t		ctl_muted;
	uint32_t		ctl_audiomask, ctl_paudiomask;
};

#define HDAUDIO_CONTROL_GIVE(ctl)	((ctl)->ctl_step ? 1 : 0)

struct hdaudio_mixer {
	struct hdaudio_control		*mx_ctl;
	mixer_devinfo_t			mx_di;
};

struct hdaudio_audiodev {
	struct hdafg_softc	*ad_sc;
	device_t			ad_audiodev;
	int				ad_nformats;
	struct audio_format		ad_formats[HDAUDIO_MAXFORMATS];

	struct hdaudio_stream		*ad_playback;
	void				(*ad_playbackintr)(void *);
	void				*ad_playbackintrarg;
	int				ad_playbacknid[HDAUDIO_MAXPINS];
	struct hdaudio_assoc		*ad_playbackassoc;
	struct hdaudio_stream		*ad_capture;
	void				(*ad_captureintr)(void *);
	void				*ad_captureintrarg;
	int				ad_capturenid[HDAUDIO_MAXPINS];
	struct hdaudio_assoc		*ad_captureassoc;
};

struct hdafg_softc {
	device_t			sc_dev;
	kmutex_t			sc_lock;
	kmutex_t			sc_intr_lock;
	struct hdaudio_softc		*sc_host;
	struct hdaudio_codec		*sc_codec;
	struct hdaudio_function_group	*sc_fg;
	int				sc_nid;
	uint16_t			sc_vendor, sc_product;

	prop_array_t			sc_config;

	int				sc_startnode, sc_endnode;
	int				sc_nwidgets;
	struct hdaudio_widget		*sc_widgets;
	int				sc_nassocs;
	struct hdaudio_assoc		*sc_assocs;
	int				sc_nctls;
	struct hdaudio_control		*sc_ctls;
	int				sc_nmixers;
	struct hdaudio_mixer		*sc_mixers;
	bool				sc_has_beepgen;

	int				sc_pchan, sc_rchan;
	audio_params_t			sc_pparam, sc_rparam;

	kmutex_t			sc_jack_lock;
	kcondvar_t			sc_jack_cv;
	struct lwp			*sc_jack_thread;
	bool				sc_jack_polling;
	bool				sc_jack_suspended;
	bool				sc_jack_dying;

	struct {
		uint32_t		afg_cap;
		uint32_t		pcm_size_rate;
		uint32_t		stream_format;
		uint32_t		outamp_cap;
		uint32_t		inamp_cap;
		uint32_t		power_states;
		uint32_t		gpio_cnt;
	} sc_p;

	struct hdaudio_audiodev		sc_audiodev;

	uint16_t			sc_fixed_rate;
	bool				sc_disable_dip;
};

static int	hdafg_match(device_t, cfdata_t, void *);
static void	hdafg_attach(device_t, device_t, void *);
static int	hdafg_detach(device_t, int);
static void	hdafg_childdet(device_t, device_t);
static bool	hdafg_suspend(device_t, const pmf_qual_t *);
static bool	hdafg_resume(device_t, const pmf_qual_t *);

static int	hdafg_unsol(device_t, uint8_t);
static int	hdafg_widget_info(void *, prop_dictionary_t,
					prop_dictionary_t);
static int	hdafg_codec_info(void *, prop_dictionary_t,
				       prop_dictionary_t);
static void	hdafg_enable_analog_beep(struct hdafg_softc *);

CFATTACH_DECL2_NEW(
    hdafg,
    sizeof(struct hdafg_softc),
    hdafg_match,
    hdafg_attach,
    hdafg_detach,
    NULL,
    NULL,
    hdafg_childdet
);

static int	hdafg_query_format(void *, audio_format_query_t *);
static int	hdafg_set_format(void *, int,
				   const audio_params_t *,
				   const audio_params_t *,
				   audio_filter_reg_t *,
				   audio_filter_reg_t *);
static int	hdafg_round_blocksize(void *, int, int,
					const audio_params_t *);
static int	hdafg_commit_settings(void *);
static int	hdafg_halt_output(void *);
static int	hdafg_halt_input(void *);
static int	hdafg_set_port(void *, mixer_ctrl_t *);
static int	hdafg_get_port(void *, mixer_ctrl_t *);
static int	hdafg_query_devinfo(void *, mixer_devinfo_t *);
static void *	hdafg_allocm(void *, int, size_t);
static void	hdafg_freem(void *, void *, size_t);
static int	hdafg_getdev(void *, struct audio_device *);
static int	hdafg_get_props(void *);
static int	hdafg_trigger_output(void *, void *, void *, int,
				       void (*)(void *), void *,
				       const audio_params_t *);
static int	hdafg_trigger_input(void *, void *, void *, int,
				      void (*)(void *), void *,
				      const audio_params_t *);
static void	hdafg_get_locks(void *, kmutex_t **, kmutex_t **);

static const struct audio_hw_if hdafg_hw_if = {
	.query_format		= hdafg_query_format,
	.set_format		= hdafg_set_format,
	.round_blocksize	= hdafg_round_blocksize,
	.commit_settings	= hdafg_commit_settings,
	.halt_output		= hdafg_halt_output,
	.halt_input		= hdafg_halt_input,
	.getdev			= hdafg_getdev,
	.set_port		= hdafg_set_port,
	.get_port		= hdafg_get_port,
	.query_devinfo		= hdafg_query_devinfo,
	.allocm			= hdafg_allocm,
	.freem			= hdafg_freem,
	.get_props		= hdafg_get_props,
	.trigger_output		= hdafg_trigger_output,
	.trigger_input		= hdafg_trigger_input,
	.get_locks		= hdafg_get_locks,
};

static int
hdafg_append_formats(struct hdaudio_audiodev *ad,
    const struct audio_format *format)
{
	if (ad->ad_nformats + 1 >= HDAUDIO_MAXFORMATS) {
		hda_print1(ad->ad_sc, "[ENOMEM] ");
		return ENOMEM;
	}
	ad->ad_formats[ad->ad_nformats++] = *format;

	return 0;
}

static struct hdaudio_widget *
hdafg_widget_lookup(struct hdafg_softc *sc, int nid)
{
	if (sc->sc_widgets == NULL || sc->sc_nwidgets == 0) {
		hda_error(sc, "lookup failed; widgets %p nwidgets %d\n",
		    sc->sc_widgets, sc->sc_nwidgets);
		return NULL;
	}
	if (nid < sc->sc_startnode || nid >= sc->sc_endnode) {
		hda_debug(sc, "nid %02X out of range (%02X-%02X)\n",
		    nid, sc->sc_startnode, sc->sc_endnode);
		return NULL;
	}
	return &sc->sc_widgets[nid - sc->sc_startnode];
}

static struct hdaudio_control *
hdafg_control_lookup(struct hdafg_softc *sc, int nid,
    enum hdaudio_pindir dir, int index, int cnt)
{
	struct hdaudio_control *ctl;
	int i, found = 0;

	if (sc->sc_ctls == NULL)
		return NULL;
	for (i = 0; i < sc->sc_nctls; i++) {
		ctl = &sc->sc_ctls[i];
		if (ctl->ctl_enable == false)
			continue;
		if (ctl->ctl_widget->w_nid != nid)
			continue;
		if (dir && ctl->ctl_ndir != dir)
			continue;
		if (index >= 0 && ctl->ctl_ndir == HDAUDIO_PINDIR_IN &&
		    ctl->ctl_dir == ctl->ctl_ndir && ctl->ctl_index != index)
			continue;
		found++;
		if (found == cnt || cnt <= 0)
			return ctl;
	}

	return NULL;
}

static void
hdafg_widget_connection_parse(struct hdaudio_widget *w)
{
	struct hdafg_softc *sc = w->w_afg;
	uint32_t res;
	int i, j, maxconns, ents, entnum;
	int cnid, addcnid, prevcnid;

	w->w_nconns = 0;

	res = hda_get_wparam(w, CONNECTION_LIST_LENGTH);
	ents = COP_CONNECTION_LIST_LENGTH_LEN(res);
	if (ents < 1)
		return;
	if (res & COP_CONNECTION_LIST_LENGTH_LONG_FORM)
		entnum = 2;
	else
		entnum = 4;
	maxconns = (sizeof(w->w_conns) / sizeof(w->w_conns[0])) - 1;
	prevcnid = 0;

#define CONN_RMASK(e)		(1 << ((32 / (e)) - 1))
#define CONN_NMASK(e)		(CONN_RMASK(e) - 1)
#define CONN_RESVAL(r, e, n)	((r) >> ((32 / (e)) * (n)))
#define CONN_RANGE(r, e, n)	(CONN_RESVAL(r, e, n) & CONN_RMASK(e))
#define CONN_CNID(r, e, n)	(CONN_RESVAL(r, e, n) & CONN_NMASK(e))

	for (i = 0; i < ents; i += entnum) {
		res = hdaudio_command(sc->sc_codec, w->w_nid,
		    CORB_GET_CONNECTION_LIST_ENTRY, i);
		for (j = 0; j < entnum; j++) {
			cnid = CONN_CNID(res, entnum, j);
			if (cnid == 0) {
				if (w->w_nconns < ents) {
					hda_error(sc, "WARNING: zero cnid\n");
				} else {
					goto getconns_out;
				}
			}
			if (cnid < sc->sc_startnode || cnid >= sc->sc_endnode)
				hda_debug(sc, "ghost nid=%02X\n", cnid);
			if (CONN_RANGE(res, entnum, j) == 0)
				addcnid = cnid;
			else if (prevcnid == 0 || prevcnid >= cnid) {
				hda_error(sc, "invalid child range\n");
				addcnid = cnid;
			} else
				addcnid = prevcnid + 1;
			while (addcnid <= cnid) {
				if (w->w_nconns > maxconns) {
					hda_error(sc,
					    "max connections reached\n");
					goto getconns_out;
				}
				w->w_connsenable[w->w_nconns] = true;
				w->w_conns[w->w_nconns++] = addcnid++;
				hda_trace(sc, "add connection %02X->%02X\n",
				    w->w_nid, addcnid - 1);
			}
			prevcnid = cnid;
		}
	}
#undef CONN_RMASK
#undef CONN_NMASK
#undef CONN_RESVAL
#undef CONN_RANGE
#undef CONN_CNID

getconns_out:
	return;
}

static void
hdafg_widget_pin_dump(struct hdafg_softc *sc)
{
	struct hdaudio_widget *w;
	int i, conn;

	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			continue;
		conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
		if (conn != 1) {
#ifdef HDAUDIO_DEBUG
			int color = COP_CFG_COLOR(w->w_pin.config);
			int defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
			hda_trace(sc, "io %02X: %s (%s, %s)\n",
			    w->w_nid,
			    hdafg_default_device[defdev],
			    hdafg_color[color],
			    hdafg_port_connectivity[conn]);
#endif
		}
	}
}

static void
hdafg_widget_setconfig(struct hdaudio_widget *w, uint32_t cfg)
{
	struct hdafg_softc *sc = w->w_afg;

	hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_SET_CONFIGURATION_DEFAULT_1, (cfg >>  0) & 0xff);
	hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_SET_CONFIGURATION_DEFAULT_2, (cfg >>  8) & 0xff);
	hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_SET_CONFIGURATION_DEFAULT_3, (cfg >> 16) & 0xff);
	hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_SET_CONFIGURATION_DEFAULT_4, (cfg >> 24) & 0xff);
}

static uint32_t
hdafg_widget_getconfig(struct hdaudio_widget *w)
{
	struct hdafg_softc *sc = w->w_afg;
	uint32_t config = 0;
	prop_object_iterator_t iter;
	prop_dictionary_t dict;
	prop_object_t obj;
	int16_t nid;

	if (sc->sc_config == NULL)
		goto biosconfig;

	iter = prop_array_iterator(sc->sc_config);
	if (iter == NULL)
		goto biosconfig;
	prop_object_iterator_reset(iter);
	while ((obj = prop_object_iterator_next(iter)) != NULL) {
		if (prop_object_type(obj) != PROP_TYPE_DICTIONARY)
			continue;
		dict = (prop_dictionary_t)obj;
		if (!prop_dictionary_get_int16(dict, "nid", &nid) ||
		    !prop_dictionary_get_uint32(dict, "config", &config))
			continue;
		if (nid == w->w_nid)
			return config;
	}

biosconfig:
	return hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_GET_CONFIGURATION_DEFAULT, 0);
}

static void
hdafg_widget_pin_parse(struct hdaudio_widget *w)
{
	struct hdafg_softc *sc = w->w_afg;
	int conn, color, defdev;

	w->w_pin.cap = hda_get_wparam(w, PIN_CAPABILITIES);
	w->w_pin.config = hdafg_widget_getconfig(w);
	w->w_pin.biosconfig = hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_GET_CONFIGURATION_DEFAULT, 0);
	w->w_pin.ctrl = hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_GET_PIN_WIDGET_CONTROL, 0);

	/* treat line-out as speaker, unless connection type is RCA */
	if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) == COP_DEVICE_LINE_OUT &&
	    COP_CFG_CONNECTION_TYPE(w->w_pin.config) != COP_CONN_TYPE_RCA) {
		w->w_pin.config &= ~COP_DEVICE_MASK;
		w->w_pin.config |= (COP_DEVICE_SPEAKER << COP_DEVICE_SHIFT);
	}

	if (w->w_pin.cap & COP_PINCAP_EAPD_CAPABLE) {
		w->w_p.eapdbtl = hdaudio_command(sc->sc_codec, w->w_nid,
		    CORB_GET_EAPD_BTL_ENABLE, 0);
		w->w_p.eapdbtl &= 0x7;
		w->w_p.eapdbtl |= COP_EAPD_ENABLE_EAPD;
	} else
		w->w_p.eapdbtl = 0xffffffff;

#if 0
	/* XXX VT1708 */
	if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) == COP_DEVICE_SPEAKER &&
	    COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config) == 15) {
		hda_trace(sc, "forcing speaker nid %02X to assoc=14\n",
		    w->w_nid);
		/* set assoc=14 */
		w->w_pin.config &= ~0xf0;
		w->w_pin.config |= 0xe0;
	}
	if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) == COP_DEVICE_HP_OUT &&
	    COP_CFG_PORT_CONNECTIVITY(w->w_pin.config) == COP_PORT_NONE) {
		hda_trace(sc, "forcing hp out nid %02X to assoc=14\n",
		    w->w_nid);
		/* set connectivity to 'jack' */
		w->w_pin.config &= ~(COP_PORT_BOTH << 30);
		w->w_pin.config |= (COP_PORT_JACK << 30);
		/* set seq=15 */
		w->w_pin.config &= ~0xf;
		w->w_pin.config |= 15;
		/* set assoc=14 */
		w->w_pin.config &= ~0xf0;
		w->w_pin.config |= 0xe0;
	}
#endif

	conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
	color = COP_CFG_COLOR(w->w_pin.config);
	defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);

	strlcat(w->w_name, ": ", sizeof(w->w_name));
	strlcat(w->w_name, hdafg_default_device[defdev], sizeof(w->w_name));
	strlcat(w->w_name, " (", sizeof(w->w_name));
	if (conn == 0 && color != 0 && color != 15) {
		strlcat(w->w_name, hdafg_color[color], sizeof(w->w_name));
		strlcat(w->w_name, " ", sizeof(w->w_name));
	}
	strlcat(w->w_name, hdafg_port_connectivity[conn], sizeof(w->w_name));
	strlcat(w->w_name, ")", sizeof(w->w_name));
}

static uint32_t
hdafg_widget_getcaps(struct hdaudio_widget *w)
{
	struct hdafg_softc *sc = w->w_afg;
	uint32_t wcap, config;
	bool pcbeep = false;

	wcap = hda_get_wparam(w, AUDIO_WIDGET_CAPABILITIES);
	config = hdafg_widget_getconfig(w);

	w->w_waspin = false;

	switch (sc->sc_vendor) {
	case HDAUDIO_VENDOR_ANALOG:
		/*
		 * help the parser by marking the analog
		 * beeper as a beep generator
		 */
		if (w->w_nid == 0x1a &&
		    COP_CFG_SEQUENCE(config) == 0x0 &&
		    COP_CFG_DEFAULT_ASSOCIATION(config) == 0xf &&
		    COP_CFG_PORT_CONNECTIVITY(config) ==
		      COP_PORT_FIXED_FUNCTION &&
		    COP_CFG_DEFAULT_DEVICE(config) ==
		      COP_DEVICE_OTHER) {
			pcbeep = true;
		}
		break;
	}

	if (pcbeep ||
	    (sc->sc_has_beepgen == false &&
	    COP_CFG_DEFAULT_DEVICE(config) == COP_DEVICE_SPEAKER &&
	    (wcap & (COP_AWCAP_INAMP_PRESENT|COP_AWCAP_OUTAMP_PRESENT)) == 0)) {
		wcap &= ~COP_AWCAP_TYPE_MASK;
		wcap |= (COP_AWCAP_TYPE_BEEP_GENERATOR << COP_AWCAP_TYPE_SHIFT);
		w->w_waspin = true;
	}

	return wcap;
}

static void
hdafg_widget_parse(struct hdaudio_widget *w)
{
	struct hdafg_softc *sc = w->w_afg;
	const char *tstr;

	w->w_p.aw_cap = hdafg_widget_getcaps(w);
	w->w_type = COP_AWCAP_TYPE(w->w_p.aw_cap);

	switch (w->w_type) {
	case COP_AWCAP_TYPE_AUDIO_OUTPUT:	tstr = "audio output"; break;
	case COP_AWCAP_TYPE_AUDIO_INPUT:	tstr = "audio input"; break;
	case COP_AWCAP_TYPE_AUDIO_MIXER:	tstr = "audio mixer"; break;
	case COP_AWCAP_TYPE_AUDIO_SELECTOR:	tstr = "audio selector"; break;
	case COP_AWCAP_TYPE_PIN_COMPLEX:	tstr = "pin"; break;
	case COP_AWCAP_TYPE_POWER_WIDGET:	tstr = "power widget"; break;
	case COP_AWCAP_TYPE_VOLUME_KNOB:	tstr = "volume knob"; break;
	case COP_AWCAP_TYPE_BEEP_GENERATOR:	tstr = "beep generator"; break;
	case COP_AWCAP_TYPE_VENDOR_DEFINED:	tstr = "vendor defined"; break;
	default:				tstr = "unknown"; break;
	}

	strlcpy(w->w_name, tstr, sizeof(w->w_name));

	hdafg_widget_connection_parse(w);

	if (w->w_p.aw_cap & COP_AWCAP_INAMP_PRESENT) {
		if (w->w_p.aw_cap & COP_AWCAP_AMP_PARAM_OVERRIDE)
			w->w_p.inamp_cap = hda_get_wparam(w,
			    AMPLIFIER_CAPABILITIES_INAMP);
		else
			w->w_p.inamp_cap = sc->sc_p.inamp_cap;
	}
	if (w->w_p.aw_cap & COP_AWCAP_OUTAMP_PRESENT) {
		if (w->w_p.aw_cap & COP_AWCAP_AMP_PARAM_OVERRIDE)
			w->w_p.outamp_cap = hda_get_wparam(w,
			    AMPLIFIER_CAPABILITIES_OUTAMP);
		else
			w->w_p.outamp_cap = sc->sc_p.outamp_cap;
	}

	w->w_p.stream_format = 0;
	w->w_p.pcm_size_rate = 0;
	switch (w->w_type) {
	case COP_AWCAP_TYPE_AUDIO_OUTPUT:
	case COP_AWCAP_TYPE_AUDIO_INPUT:
		if (w->w_p.aw_cap & COP_AWCAP_FORMAT_OVERRIDE) {
			w->w_p.stream_format = hda_get_wparam(w,
			    SUPPORTED_STREAM_FORMATS);
			w->w_p.pcm_size_rate = hda_get_wparam(w,
			    SUPPORTED_PCM_SIZE_RATES);
		} else {
			w->w_p.stream_format = sc->sc_p.stream_format;
			w->w_p.pcm_size_rate = sc->sc_p.pcm_size_rate;
		}
		break;
	case COP_AWCAP_TYPE_PIN_COMPLEX:
		hdafg_widget_pin_parse(w);
		hdafg_widget_setconfig(w, w->w_pin.config);
		break;
	}
}

static int
hdafg_assoc_count_channels(struct hdafg_softc *sc,
    struct hdaudio_assoc *as, enum hdaudio_pindir dir)
{
	struct hdaudio_widget *w;
	int *dacmap;
	int i, dacmapsz = sizeof(*dacmap) * sc->sc_endnode;
	int nchans = 0;

	if (as->as_enable == false || as->as_dir != dir)
		return 0;

	dacmap = kmem_zalloc(dacmapsz, KM_SLEEP);

	for (i = 0; i < HDAUDIO_MAXPINS; i++)
		if (as->as_dacs[i])
			dacmap[as->as_dacs[i]] = 1;

	for (i = 1; i < sc->sc_endnode; i++) {
		if (!dacmap[i])
			continue;
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		nchans += COP_AWCAP_CHANNEL_COUNT(w->w_p.aw_cap);
	}

	kmem_free(dacmap, dacmapsz);

	return nchans;
}

static const char *
hdafg_assoc_type_string(struct hdaudio_assoc *as)
{
	switch (as->as_digital) {
	case HDAFG_AS_ANALOG:
		return as->as_dir == HDAUDIO_PINDIR_IN ?
		    "ADC" : "DAC";
	case HDAFG_AS_SPDIF:
		return as->as_dir == HDAUDIO_PINDIR_IN ?
		    "DIG-In" : "DIG";
	case HDAFG_AS_HDMI:
		return as->as_dir == HDAUDIO_PINDIR_IN ?
		    "HDMI-In" : "HDMI";
	case HDAFG_AS_DISPLAYPORT:
		return as->as_dir == HDAUDIO_PINDIR_IN ?
		    "DP-In" : "DP";
	default:
		return as->as_dir == HDAUDIO_PINDIR_IN ?
		    "Unknown-In" : "Unknown-Out";
	}
}

static void
hdafg_assoc_dump_dd(struct hdafg_softc *sc, struct hdaudio_assoc *as, int pin,
	int lock)
{
	struct hdafg_dd_info hdi;
	struct hdaudio_widget *w;
	uint8_t elddata[256];
	unsigned int elddatalen = 0, i;
	uint32_t res;
	uint32_t (*cmd)(struct hdaudio_codec *, int, uint32_t, uint32_t) =
	    lock ? hdaudio_command : hdaudio_command_unlocked;

	w = hdafg_widget_lookup(sc, as->as_pins[pin]);

	if (w->w_pin.cap & COP_PINCAP_TRIGGER_REQD) {
		(*cmd)(sc->sc_codec, as->as_pins[pin],
		    CORB_SET_PIN_SENSE, 0);
	}
	res = (*cmd)(sc->sc_codec, as->as_pins[pin],
	    CORB_GET_PIN_SENSE, 0);

#ifdef HDAFG_HDMI_DEBUG
	hda_print(sc, "Display Device, pin=%02X\n", as->as_pins[pin]);
	hda_print(sc, "  COP_GET_PIN_SENSE_PRESENSE_DETECT=%d\n",
	    !!(res & COP_GET_PIN_SENSE_PRESENSE_DETECT));
	hda_print(sc, "  COP_GET_PIN_SENSE_ELD_VALID=%d\n",
	    !!(res & COP_GET_PIN_SENSE_ELD_VALID));
#endif

	if ((res &
	    (COP_GET_PIN_SENSE_PRESENSE_DETECT|COP_GET_PIN_SENSE_ELD_VALID)) ==
	    (COP_GET_PIN_SENSE_PRESENSE_DETECT|COP_GET_PIN_SENSE_ELD_VALID)) {
		res = (*cmd)(sc->sc_codec, as->as_pins[pin],
		    CORB_GET_HDMI_DIP_SIZE, COP_DIP_ELD_SIZE);
		elddatalen = COP_DIP_BUFFER_SIZE(res);
		if (elddatalen == 0)
			elddatalen = sizeof(elddata); /* paranoid */
		for (i = 0; i < elddatalen; i++) {
			res = (*cmd)(sc->sc_codec, as->as_pins[pin],
			    CORB_GET_HDMI_ELD_DATA, i);
			if (!(res & COP_ELD_VALID)) {
#ifdef HDAFG_HDMI_DEBUG
				hda_error(sc, "bad ELD size (%u/%u)\n",
				    i, elddatalen);
#endif
				break;
			}
			elddata[i] = COP_ELD_DATA(res);
		}

		if (hdafg_dd_parse_info(elddata, elddatalen, &hdi) != 0) {
#ifdef HDAFG_HDMI_DEBUG
			hda_error(sc, "failed to parse ELD data\n");
#endif
			return;
		}

		hda_print(sc, "  ELD version=0x%x", ELD_VER(&hdi.eld));
		hda_print1(sc, ",len=%u", hdi.eld.header.baseline_eld_len * 4);
		hda_print1(sc, ",edid=0x%x", ELD_CEA_EDID_VER(&hdi.eld));
		hda_print1(sc, ",port=0x%" PRIx64, hdi.eld.port_id);
		hda_print1(sc, ",vendor=0x%04x", hdi.eld.vendor);
		hda_print1(sc, ",product=0x%04x", hdi.eld.product);
		hda_print1(sc, "\n");
		hda_print(sc, "  Monitor = '%s'\n", hdi.monitor);
		for (i = 0; i < hdi.nsad; i++) {
			hda_print(sc, "  SAD id=%u", i);
			hda_print1(sc, ",format=%u",
			    CEA_AUDIO_FORMAT(&hdi.sad[i]));
			hda_print1(sc, ",channels=%u",
			    CEA_MAX_CHANNELS(&hdi.sad[i]));
			hda_print1(sc, ",rate=0x%02x",
			    CEA_SAMPLE_RATE(&hdi.sad[i]));
			if (CEA_AUDIO_FORMAT(&hdi.sad[i]) ==
			    CEA_AUDIO_FORMAT_LPCM)
				hda_print1(sc, ",precision=0x%x",
				    CEA_PRECISION(&hdi.sad[i]));
			else
				hda_print1(sc, ",maxbitrate=%u",
				    CEA_MAX_BITRATE(&hdi.sad[i]));
			hda_print1(sc, "\n");
		}
	}
}

static char *
hdafg_mixer_mask2allname(uint32_t mask, char *buf, size_t len)
{
	static const char *audioname[] = HDAUDIO_DEVICE_NAMES;
	int i, first = 1;

	memset(buf, 0, len);
	for (i = 0; i < HDAUDIO_MIXER_NRDEVICES; i++) {
		if (mask & (1 << i)) {
			if (first == 0)
				strlcat(buf, ", ", len);
			strlcat(buf, audioname[i], len);
			first = 0;
		}
	}

	return buf;
}

static void
hdafg_dump_dst_nid(struct hdafg_softc *sc, int nid, int depth)
{
	struct hdaudio_widget *w, *cw;
	char buf[64];
	int i;

	if (depth > HDAUDIO_PARSE_MAXDEPTH)
		return;

	w = hdafg_widget_lookup(sc, nid);
	if (w == NULL || w->w_enable == false)
		return;

	aprint_debug("%*s", 4 + depth * 7, "");
	aprint_debug("nid=%02X [%s]", w->w_nid, w->w_name);

	if (depth > 0) {
		if (w->w_audiomask == 0) {
			aprint_debug("\n");
			return;
		}
		aprint_debug(" [source: %s]",
		    hdafg_mixer_mask2allname(w->w_audiomask, buf, sizeof(buf)));
		if (w->w_audiodev >= 0) {
			aprint_debug("\n");
			return;
		}
	}

	aprint_debug("\n");

	for (i = 0; i < w->w_nconns; i++) {
		if (w->w_connsenable[i] == 0)
			continue;
		cw = hdafg_widget_lookup(sc, w->w_conns[i]);
		if (cw == NULL || cw->w_enable == false || cw->w_bindas == -1)
			continue;
		hdafg_dump_dst_nid(sc, w->w_conns[i], depth + 1);
	}
}

static void
hdafg_assoc_dump(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	uint32_t conn, defdev, curdev, curport;
	int maxassocs = sc->sc_nassocs;
	int i, j;

	for (i = 0; i < maxassocs; i++) {
		uint32_t devmask = 0, portmask = 0;
		bool firstdev = true;
		int nchan;

		if (as[i].as_enable == false)
			continue;

		hda_print(sc, "%s%02X",
		    hdafg_assoc_type_string(&as[i]), i);

		nchan = hdafg_assoc_count_channels(sc, &as[i],
		    as[i].as_dir);
		hda_print1(sc, " %dch:", nchan);

		for (j = 0; j < HDAUDIO_MAXPINS; j++) {
			if (as[i].as_dacs[j] == 0)
				continue;
			w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
			if (w == NULL)
				continue;
			conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
			defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
			if (conn != COP_PORT_NONE) {
				devmask |= (1 << defdev);
				portmask |= (1 << conn);
			}
		}
		for (curdev = 0; curdev < 16; curdev++) {
			bool firstport = true;
			if ((devmask & (1 << curdev)) == 0)
				continue;

			if (firstdev == false)
				hda_print1(sc, ",");
			firstdev = false;
			hda_print1(sc, " %s",
			    hdafg_default_device[curdev]);

			for (curport = 0; curport < 4; curport++) {
				bool devonport = false;
				if ((portmask & (1 << curport)) == 0)
					continue;

				for (j = 0; j < HDAUDIO_MAXPINS; j++) {
					if (as[i].as_dacs[j] == 0)
						continue;

					w = hdafg_widget_lookup(sc,
					    as[i].as_pins[j]);
					if (w == NULL)
						continue;
					conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
					defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
					if (conn != curport || defdev != curdev)
						continue;

					devonport = true;
				}

				if (devonport == false)
					continue;

				hda_print1(sc, " [%s",
				    hdafg_port_connectivity[curport]);
				for (j = 0; j < HDAUDIO_MAXPINS; j++) {
					if (as[i].as_dacs[j] == 0)
						continue;

					w = hdafg_widget_lookup(sc,
					    as[i].as_pins[j]);
					if (w == NULL)
						continue;
					conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
					defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
					if (conn != curport || defdev != curdev)
						continue;

					if (firstport == false)
						hda_trace1(sc, ",");
					else
						hda_trace1(sc, " ");
					firstport = false;
#ifdef HDAUDIO_DEBUG
					int color =
					    COP_CFG_COLOR(w->w_pin.config);
					hda_trace1(sc, "%s",
					    hdafg_color[color]);
#endif
					hda_trace1(sc, "(%02X)", w->w_nid);
				}
				hda_print1(sc, "]");
			}
		}
		hda_print1(sc, "\n");

		for (j = 0; j < HDAUDIO_MAXPINS; j++) {
			if (as[i].as_pins[j] == 0)
				continue;
			hdafg_dump_dst_nid(sc, as[i].as_pins[j], 0);
		}

		if (as[i].as_displaydev == true) {
			for (j = 0; j < HDAUDIO_MAXPINS; j++) {
				if (as[i].as_pins[j] == 0)
					continue;
				hdafg_assoc_dump_dd(sc, &as[i], j, 1);
			}
		}
	}
}

static void
hdafg_assoc_parse(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as;
	struct hdaudio_widget *w;
	int i, j, cnt, maxassocs, type, assoc, seq, first, hpredir;
	enum hdaudio_pindir dir;

	hda_debug(sc, "  count present associations\n");
	/* Count present associations */
	maxassocs = 0;
	for (j = 1; j < HDAUDIO_MAXPINS; j++) {
		for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
			w = hdafg_widget_lookup(sc, i);
			if (w == NULL || w->w_enable == false)
				continue;
			if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
				continue;
			if (COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config) != j)
				continue;
			maxassocs++;
			if (j != 15) /* There could be many 1-pin assocs #15 */
				break;
		}
	}

	hda_debug(sc, "  maxassocs %d\n", maxassocs);
	sc->sc_nassocs = maxassocs;

	if (maxassocs < 1)
		return;

	hda_debug(sc, "  allocating memory\n");
	as = kmem_zalloc(maxassocs * sizeof(*as), KM_SLEEP);
	for (i = 0; i < maxassocs; i++) {
		as[i].as_hpredir = -1;
		/* as[i].as_chan = NULL; */
		as[i].as_digital = HDAFG_AS_SPDIF;
	}

	hda_debug(sc, "  scan associations, skipping as=0\n");
	/* Scan associations skipping as=0 */
	cnt = 0;
	for (j = 1; j < HDAUDIO_MAXPINS && cnt < maxassocs; j++) {
		first = 16;
		hpredir = 0;
		for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
			w = hdafg_widget_lookup(sc, i);
			if (w == NULL || w->w_enable == false)
				continue;
			if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
				continue;
			assoc = COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config);
			seq = COP_CFG_SEQUENCE(w->w_pin.config);
			if (assoc != j)
				continue;
			KASSERT(cnt < maxassocs);
			type = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
			/* Get pin direction */
			switch (type) {
			case COP_DEVICE_LINE_OUT:
			case COP_DEVICE_SPEAKER:
			case COP_DEVICE_HP_OUT:
			case COP_DEVICE_SPDIF_OUT:
			case COP_DEVICE_DIGITAL_OTHER_OUT:
				dir = HDAUDIO_PINDIR_OUT;
				break;
			default:
				dir = HDAUDIO_PINDIR_IN;
				break;
			}
			/* If this is a first pin, create new association */
			if (as[cnt].as_pincnt == 0) {
				as[cnt].as_enable = true;
				as[cnt].as_activated = true;
				as[cnt].as_index = j;
				as[cnt].as_dir = dir;
			}
			if (seq < first)
				first = seq;
			/* Check association correctness */
			if (as[cnt].as_pins[seq] != 0) {
				hda_error(sc, "duplicate pin in association\n");
				as[cnt].as_enable = false;
			}
			if (dir != as[cnt].as_dir) {
				hda_error(sc,
				    "pin %02X has wrong direction for %02X\n",
				    w->w_nid, j);
				as[cnt].as_enable = false;
			}
			if ((w->w_p.aw_cap & COP_AWCAP_DIGITAL) == 0)
				as[cnt].as_digital = HDAFG_AS_ANALOG;
			if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP))
				as[cnt].as_displaydev = true;
			if (w->w_pin.cap & COP_PINCAP_HDMI)
				as[cnt].as_digital = HDAFG_AS_HDMI;
			if (w->w_pin.cap & COP_PINCAP_DP)
				as[cnt].as_digital = HDAFG_AS_DISPLAYPORT;
			/* Headphones with seq=15 may mean redirection */
			if (type == COP_DEVICE_HP_OUT && seq == 15)
				hpredir = 1;
			as[cnt].as_pins[seq] = w->w_nid;
			as[cnt].as_pincnt++;
			if (j == 15)
				cnt++;
		}
		if (j != 15 && cnt < maxassocs && as[cnt].as_pincnt > 0) {
			if (hpredir && as[cnt].as_pincnt > 1)
				as[cnt].as_hpredir = first;
			cnt++;
		}
	}

	hda_debug(sc, "  all done\n");
	sc->sc_assocs = as;
}

static void
hdafg_control_parse(struct hdafg_softc *sc)
{
	struct hdaudio_control *ctl;
	struct hdaudio_widget *w, *cw;
	int i, j, cnt, maxctls, ocap, icap;
	int mute, offset, step, size;

	maxctls = 0;
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_p.outamp_cap)
			maxctls++;
		if (w->w_p.inamp_cap) {
			switch (w->w_type) {
			case COP_AWCAP_TYPE_AUDIO_SELECTOR:
			case COP_AWCAP_TYPE_AUDIO_MIXER:
				for (j = 0; j < w->w_nconns; j++) {
					cw = hdafg_widget_lookup(sc,
					    w->w_conns[j]);
					if (cw == NULL || cw->w_enable == false)
						continue;
					maxctls++;
				}
				break;
			default:
				maxctls++;
				break;
			}
		}
	}

	sc->sc_nctls = maxctls;
	if (maxctls < 1)
		return;

	ctl = kmem_zalloc(sc->sc_nctls * sizeof(*ctl), KM_SLEEP);

	cnt = 0;
	for (i = sc->sc_startnode; cnt < maxctls && i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		ocap = w->w_p.outamp_cap;
		icap = w->w_p.inamp_cap;
		if (ocap) {
			hda_trace(sc, "add ctrl outamp %d:%02X:FF\n",
			    cnt, w->w_nid);
			mute = COP_AMPCAP_MUTE_CAPABLE(ocap);
			step = COP_AMPCAP_NUM_STEPS(ocap);
			size = COP_AMPCAP_STEP_SIZE(ocap);
			offset = COP_AMPCAP_OFFSET(ocap);
			ctl[cnt].ctl_enable = true;
			ctl[cnt].ctl_widget = w;
			ctl[cnt].ctl_mute = mute;
			ctl[cnt].ctl_step = step;
			ctl[cnt].ctl_size = size;
			ctl[cnt].ctl_offset = offset;
			ctl[cnt].ctl_left = offset;
			ctl[cnt].ctl_right = offset;
			if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX ||
			    w->w_waspin == true)
				ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_IN;
			else
				ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_OUT;
			ctl[cnt++].ctl_dir = HDAUDIO_PINDIR_OUT;
		}
		if (icap) {
			mute = COP_AMPCAP_MUTE_CAPABLE(icap);
			step = COP_AMPCAP_NUM_STEPS(icap);
			size = COP_AMPCAP_STEP_SIZE(icap);
			offset = COP_AMPCAP_OFFSET(icap);
			switch (w->w_type) {
			case COP_AWCAP_TYPE_AUDIO_SELECTOR:
			case COP_AWCAP_TYPE_AUDIO_MIXER:
				for (j = 0; j < w->w_nconns; j++) {
					if (cnt >= maxctls)
						break;
					cw = hdafg_widget_lookup(sc,
					    w->w_conns[j]);
					if (cw == NULL || cw->w_enable == false)
						continue;
					hda_trace(sc, "add ctrl inamp selmix "
					    "%d:%02X:%02X\n", cnt, w->w_nid,
					    cw->w_nid);
					ctl[cnt].ctl_enable = true;
					ctl[cnt].ctl_widget = w;
					ctl[cnt].ctl_childwidget = cw;
					ctl[cnt].ctl_index = j;
					ctl[cnt].ctl_mute = mute;
					ctl[cnt].ctl_step = step;
					ctl[cnt].ctl_size = size;
					ctl[cnt].ctl_offset = offset;
					ctl[cnt].ctl_left = offset;
					ctl[cnt].ctl_right = offset;
					ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_IN;
					ctl[cnt++].ctl_dir = HDAUDIO_PINDIR_IN;
				}
				break;
			default:
				if (cnt >= maxctls)
					break;
				hda_trace(sc, "add ctrl inamp "
				    "%d:%02X:FF\n", cnt, w->w_nid);
				ctl[cnt].ctl_enable = true;
				ctl[cnt].ctl_widget = w;
				ctl[cnt].ctl_mute = mute;
				ctl[cnt].ctl_step = step;
				ctl[cnt].ctl_size = size;
				ctl[cnt].ctl_offset = offset;
				ctl[cnt].ctl_left = offset;
				ctl[cnt].ctl_right = offset;
				if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX)
					ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_OUT;
				else
					ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_IN;
				ctl[cnt++].ctl_dir = HDAUDIO_PINDIR_IN;
				break;
			}
		}
	}

	sc->sc_ctls = ctl;
}

static void
hdafg_parse(struct hdafg_softc *sc)
{
	struct hdaudio_widget *w;
	uint32_t nodecnt, wcap;
	int nid;

	nodecnt = hda_get_param(sc, SUBORDINATE_NODE_COUNT);
	sc->sc_startnode = COP_NODECNT_STARTNODE(nodecnt);
	sc->sc_nwidgets = COP_NODECNT_NUMNODES(nodecnt);
	sc->sc_endnode = sc->sc_startnode + sc->sc_nwidgets;
	hda_debug(sc, "afg start %02X end %02X nwidgets %d\n",
	    sc->sc_startnode, sc->sc_endnode, sc->sc_nwidgets);

	hda_debug(sc, "powering up widgets\n");
	hdaudio_command(sc->sc_codec, sc->sc_nid,
	    CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
	hda_delay(100);
	for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++)
		hdaudio_command(sc->sc_codec, nid,
		    CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
	hda_delay(1000);

	sc->sc_p.afg_cap = hda_get_param(sc, AUDIO_FUNCTION_GROUP_CAPABILITIES);
	sc->sc_p.stream_format = hda_get_param(sc, SUPPORTED_STREAM_FORMATS);
	sc->sc_p.pcm_size_rate = hda_get_param(sc, SUPPORTED_PCM_SIZE_RATES);
	sc->sc_p.outamp_cap = hda_get_param(sc, AMPLIFIER_CAPABILITIES_OUTAMP);
	sc->sc_p.inamp_cap = hda_get_param(sc, AMPLIFIER_CAPABILITIES_INAMP);
	sc->sc_p.power_states = hda_get_param(sc, SUPPORTED_POWER_STATES);
	sc->sc_p.gpio_cnt = hda_get_param(sc, GPIO_COUNT);

	sc->sc_widgets = kmem_zalloc(sc->sc_nwidgets * sizeof(*w), KM_SLEEP);
	hda_debug(sc, "afg widgets %p-%p\n",
	    sc->sc_widgets, sc->sc_widgets + sc->sc_nwidgets);

	for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
		w = hdafg_widget_lookup(sc, nid);
		if (w == NULL)
			continue;
		wcap = hdaudio_command(sc->sc_codec, nid, CORB_GET_PARAMETER,
		    COP_AUDIO_WIDGET_CAPABILITIES);
		switch (COP_AWCAP_TYPE(wcap)) {
		case COP_AWCAP_TYPE_BEEP_GENERATOR:
			sc->sc_has_beepgen = true;
			break;
		}
	}

	for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
		w = hdafg_widget_lookup(sc, nid);
		if (w == NULL)
			continue;
		w->w_afg = sc;
		w->w_nid = nid;
		w->w_enable = true;
		w->w_pflags = 0;
		w->w_audiodev = -1;
		w->w_selconn = -1;
		w->w_bindas = -1;
		w->w_p.eapdbtl = 0xffffffff;
		hdafg_widget_parse(w);
	}
}

static void
hdafg_disable_nonaudio(struct hdafg_softc *sc)
{
	struct hdaudio_widget *w;
	int i;

	/* Disable power and volume widgets */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_type == COP_AWCAP_TYPE_POWER_WIDGET ||
		    w->w_type == COP_AWCAP_TYPE_VOLUME_KNOB) {
			hda_trace(w->w_afg, "disable %02X [nonaudio]\n",
			    w->w_nid);
			w->w_enable = false;
		}
	}
}

static void
hdafg_disable_useless(struct hdafg_softc *sc)
{
	struct hdaudio_widget *w, *cw;
	struct hdaudio_control *ctl;
	int done, found, i, j, k;
	int conn, assoc;

	/* Disable useless pins */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			continue;
		conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
		assoc = COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config);
		if (conn == COP_PORT_NONE) {
			hda_trace(w->w_afg, "disable %02X [no connectivity]\n",
			    w->w_nid);
			w->w_enable = false;
		}
		if (assoc == 0) {
			hda_trace(w->w_afg, "disable %02X [no association]\n",
			    w->w_nid);
			w->w_enable = false;
		}
	}

	do {
		done = 1;
		/* Disable and mute controls for disabled widgets */
		for (i = 0; i < sc->sc_nctls; i++) {
			ctl = &sc->sc_ctls[i];
			if (ctl->ctl_enable == false)
				continue;
			if (ctl->ctl_widget->w_enable == false ||
			    (ctl->ctl_childwidget != NULL &&
			     ctl->ctl_childwidget->w_enable == false)) {
				ctl->ctl_forcemute = 1;
				ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
				ctl->ctl_left = ctl->ctl_right = 0;
				ctl->ctl_enable = false;
				if (ctl->ctl_ndir == HDAUDIO_PINDIR_IN)
					ctl->ctl_widget->w_connsenable[
					    ctl->ctl_index] = false;
				done = 0;
				hda_trace(ctl->ctl_widget->w_afg,
				    "disable ctl %d:%02X:%02X [widget disabled]\n",
				    i, ctl->ctl_widget->w_nid,
				    ctl->ctl_childwidget ?
				    ctl->ctl_childwidget->w_nid : 0xff);
			}
		}
		/* Disable useless widgets */
		for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
			w = hdafg_widget_lookup(sc, i);
			if (w == NULL || w->w_enable == false)
				continue;
			/* Disable inputs with disabled child widgets */
			for (j = 0; j < w->w_nconns; j++) {
				if (!w->w_connsenable[j])
					continue;
				cw = hdafg_widget_lookup(sc,
				    w->w_conns[j]);
				if (cw == NULL || cw->w_enable == false) {
					w->w_connsenable[j] = false;
					hda_trace(w->w_afg,
					    "disable conn %02X->%02X "
					    "[disabled child]\n",
					    w->w_nid, w->w_conns[j]);
				}
			}
			if (w->w_type != COP_AWCAP_TYPE_AUDIO_SELECTOR &&
			    w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER)
				continue;
			/* Disable mixers and selectors without inputs */
			found = 0;
			for (j = 0; j < w->w_nconns; j++)
				if (w->w_connsenable[j]) {
					found = 1;
					break;
				}
			if (found == 0) {
				w->w_enable = false;
				done = 0;
				hda_trace(w->w_afg,
				    "disable %02X [inputs disabled]\n",
				    w->w_nid);
			}
			/* Disable nodes without consumers */
			if (w->w_type != COP_AWCAP_TYPE_AUDIO_SELECTOR &&
			    w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER)
				continue;
			found = 0;
			for (k = sc->sc_startnode; k < sc->sc_endnode; k++) {
				cw = hdafg_widget_lookup(sc, k);
				if (cw == NULL || cw->w_enable == false)
					continue;
				for (j = 0; j < cw->w_nconns; j++) {
					if (cw->w_connsenable[j] &&
					    cw->w_conns[j] == i) {
						found = 1;
						break;
					}
				}
			}
			if (found == 0) {
				w->w_enable = false;
				done = 0;
				hda_trace(w->w_afg,
				    "disable %02X [consumers disabled]\n",
				    w->w_nid);
			}
		}
	} while (done == 0);
}

static void
hdafg_assoc_trace_undo(struct hdafg_softc *sc, int as, int seq)
{
	struct hdaudio_widget *w;
	int i;

	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_bindas != as)
			continue;
		if (seq >= 0) {
			w->w_bindseqmask &= ~(1 << seq);
			if (w->w_bindseqmask == 0) {
				w->w_bindas = -1;
				w->w_selconn = -1;
			}
		} else {
			w->w_bindas = -1;
			w->w_bindseqmask = 0;
			w->w_selconn = -1;
		}
	}
}

static int
hdafg_assoc_trace_dac(struct hdafg_softc *sc, int as, int seq,
    int nid, int dupseq, int minassoc, int only, int depth)
{
	struct hdaudio_widget *w;
	int i, im = -1;
	int m = 0, ret;

	if (depth >= HDAUDIO_PARSE_MAXDEPTH)
		return 0;
	w = hdafg_widget_lookup(sc, nid);
	if (w == NULL || w->w_enable == false)
		return 0;
	/* We use only unused widgets */
	if (w->w_bindas >= 0 && w->w_bindas != as) {
		if (!only)
			hda_trace(sc, "depth %d nid %02X busy by assoc %d\n",
			    depth + 1, nid, w->w_bindas);
		return 0;
	}
	if (dupseq < 0) {
		if (w->w_bindseqmask != 0) {
			if (!only)
				hda_trace(sc,
				    "depth %d nid %02X busy by seqmask %x\n",
				    depth + 1, nid, w->w_bindas);
			return 0;
		}
	} else {
		/* If this is headphones, allow duplicate first pin */
		if (w->w_bindseqmask != 0 &&
		    (w->w_bindseqmask & (1 << dupseq)) == 0)
			return 0;
	}

	switch (w->w_type) {
	case COP_AWCAP_TYPE_AUDIO_INPUT:
		break;
	case COP_AWCAP_TYPE_AUDIO_OUTPUT:
		/* If we are tracing HP take only dac of first pin */
		if ((only == 0 || only == w->w_nid) &&
		    (w->w_nid >= minassoc) && (dupseq < 0 || w->w_nid ==
		    sc->sc_assocs[as].as_dacs[dupseq]))
			m = w->w_nid;
		break;
	case COP_AWCAP_TYPE_PIN_COMPLEX:
		if (depth > 0)
			break;
		/* FALLTHROUGH */
	default:
		for (i = 0; i < w->w_nconns; i++) {
			if (w->w_connsenable[i] == false)
				continue;
			if (w->w_selconn != -1 && w->w_selconn != i)
				continue;
			ret = hdafg_assoc_trace_dac(sc, as, seq,
			    w->w_conns[i], dupseq, minassoc, only, depth + 1);
			if (ret) {
				if (m == 0 || ret < m) {
					m = ret;
					im = i;
				}
				if (only || dupseq >= 0)
					break;
			}
		}
		if (m && only && ((w->w_nconns > 1 &&
		    w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER) ||
		    w->w_type == COP_AWCAP_TYPE_AUDIO_SELECTOR))
			w->w_selconn = im;
		break;
	}
	if (m && only) {
		w->w_bindas = as;
		w->w_bindseqmask |= (1 << seq);
	}
	if (!only)
		hda_trace(sc, "depth %d nid %02X dupseq %d returned %02X\n",
		    depth + 1, nid, dupseq, m);

	return m;
}

static int
hdafg_assoc_trace_out(struct hdafg_softc *sc, int as, int seq)
{
	struct hdaudio_assoc *assocs = sc->sc_assocs;
	int i, hpredir;
	int minassoc, res;

	/* Find next pin */
	for (i = seq; i < HDAUDIO_MAXPINS && assocs[as].as_pins[i] == 0; i++)
		;
	/* Check if there is any left, if not then we have succeeded */
	if (i == HDAUDIO_MAXPINS)
		return 1;

	hpredir = (i == 15 && assocs[as].as_fakeredir == 0) ?
	    assocs[as].as_hpredir : -1;
	minassoc = res = 0;
	do {
		/* Trace this pin taking min nid into account */
		res = hdafg_assoc_trace_dac(sc, as, i,
		    assocs[as].as_pins[i], hpredir, minassoc, 0, 0);
		if (res == 0) {
			/* If we failed, return to previous and redo it */
			hda_trace(sc, "  trace failed as=%d seq=%d pin=%02X "
			    "hpredir=%d minassoc=%d\n",
			    as, seq, assocs[as].as_pins[i], hpredir, minassoc);
			return 0;
		}
		/* Trace again to mark the path */
		hdafg_assoc_trace_dac(sc, as, i,
		    assocs[as].as_pins[i], hpredir, minassoc, res, 0);
		assocs[as].as_dacs[i] = res;
		/* We succeeded, so call next */
		if (hdafg_assoc_trace_out(sc, as, i + 1))
			return 1;
		/* If next failed, we should retry with next min */
		hdafg_assoc_trace_undo(sc, as, i);
		assocs[as].as_dacs[i] = 0;
		minassoc = res + 1;
	} while (1);
}

static int
hdafg_assoc_trace_adc(struct hdafg_softc *sc, int assoc, int seq,
    int nid, int only, int depth)
{
	struct hdaudio_widget *w, *wc;
	int i, j;
	int res = 0;

	if (depth > HDAUDIO_PARSE_MAXDEPTH)
		return 0;
	w = hdafg_widget_lookup(sc, nid);
	if (w == NULL || w->w_enable == false)
		return 0;
	/* Use only unused widgets */
	if (w->w_bindas >= 0 && w->w_bindas != assoc)
		return 0;

	switch (w->w_type) {
	case COP_AWCAP_TYPE_AUDIO_INPUT:
		if (only == w->w_nid)
			res = 1;
		break;
	case COP_AWCAP_TYPE_PIN_COMPLEX:
		if (depth > 0)
			break;
		/* FALLTHROUGH */
	default:
		/* Try to find reachable ADCs with specified nid */
		for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
			wc = hdafg_widget_lookup(sc, j);
			if (w == NULL || w->w_enable == false)
				continue;
			for (i = 0; i < wc->w_nconns; i++) {
				if (wc->w_connsenable[i] == false)
					continue;
				if (wc->w_conns[i] != nid)
					continue;
				if (hdafg_assoc_trace_adc(sc, assoc, seq,
				    j, only, depth + 1) != 0) {
					res = 1;
					if (((wc->w_nconns > 1 &&
					    wc->w_type != COP_AWCAP_TYPE_AUDIO_MIXER) ||
					    wc->w_type != COP_AWCAP_TYPE_AUDIO_SELECTOR)
					    && wc->w_selconn == -1)
						wc->w_selconn = i;
				}
			}
		}
		break;
	}
	if (res) {
		w->w_bindas = assoc;
		w->w_bindseqmask |= (1 << seq);
	}
	return res;
}

static int
hdafg_assoc_trace_in(struct hdafg_softc *sc, int assoc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	int i, j, k;

	for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
		w = hdafg_widget_lookup(sc, j);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_AUDIO_INPUT)
			continue;
		if (w->w_bindas >= 0 && w->w_bindas != assoc)
			continue;

		/* Find next pin */
		for (i = 0; i < HDAUDIO_MAXPINS; i++) {
			if (as[assoc].as_pins[i] == 0)
				continue;
			/* Trace this pin taking goal into account */
			if (hdafg_assoc_trace_adc(sc, assoc, i,
			    as[assoc].as_pins[i], j, 0) == 0) {
				hdafg_assoc_trace_undo(sc, assoc, -1);
				for (k = 0; k < HDAUDIO_MAXPINS; k++)
					as[assoc].as_dacs[k] = 0;
				break;
			}
			as[assoc].as_dacs[i] = j;
		}
		if (i == HDAUDIO_MAXPINS)
			return 1;
	}
	return 0;
}

static int
hdafg_assoc_trace_to_out(struct hdafg_softc *sc, int nid, int depth)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w, *wc;
	int i, j;
	int res = 0;

	if (depth > HDAUDIO_PARSE_MAXDEPTH)
		return 0;
	w = hdafg_widget_lookup(sc, nid);
	if (w == NULL || w->w_enable == false)
		return 0;

	/* Use only unused widgets */
	if (depth > 0 && w->w_bindas != -1) {
		if (w->w_bindas < 0 ||
		    as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT) {
			return 1;
		} else {
			return 0;
		}
	}

	switch (w->w_type) {
	case COP_AWCAP_TYPE_AUDIO_INPUT:
		/* Do not traverse input (not yet supported) */
		break;
	case COP_AWCAP_TYPE_PIN_COMPLEX:
		if (depth > 0)
			break;
		/* FALLTHROUGH */
	default:
		/* Try to find reachable ADCs with specified nid */
		for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
			wc = hdafg_widget_lookup(sc, j);
			if (wc == NULL || wc->w_enable == false)
				continue;
			for (i = 0; i < wc->w_nconns; i++) {
				if (wc->w_connsenable[i] == false)
					continue;
				if (wc->w_conns[i] != nid)
					continue;
				if (hdafg_assoc_trace_to_out(sc,
				    j, depth + 1) != 0) {
					res = 1;
					if (wc->w_type ==
					    COP_AWCAP_TYPE_AUDIO_SELECTOR &&
					    wc->w_selconn == -1)
						wc->w_selconn = i;
				}
			}
		}
		break;
	}
	if (res)
		w->w_bindas = -2;
	return res;
}

static void
hdafg_assoc_trace_misc(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	int j;

	/* Input monitor */
	/*
	 * Find mixer associated with input, but supplying signal
	 * for output associations. Hope it will be input monitor.
	 */
	for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
		w = hdafg_widget_lookup(sc, j);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER)
			continue;
		if (w->w_bindas < 0 ||
		    as[w->w_bindas].as_dir != HDAUDIO_PINDIR_IN)
			continue;
		if (hdafg_assoc_trace_to_out(sc, w->w_nid, 0)) {
			w->w_pflags |= HDAUDIO_ADC_MONITOR;
			w->w_audiodev = HDAUDIO_MIXER_IMIX;
		}
	}

	/* Beeper */
	for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
		w = hdafg_widget_lookup(sc, j);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_BEEP_GENERATOR)
			continue;
		if (hdafg_assoc_trace_to_out(sc, w->w_nid, 0)) {
			hda_debug(sc, "beeper %02X traced to out\n", w->w_nid);
		}
		w->w_bindas = -2;
	}
}

static void
hdafg_build_tree(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	int i, j, res;

	/* Trace all associations in order of their numbers */

	/* Trace DACs first */
	for (j = 0; j < sc->sc_nassocs; j++) {
		if (as[j].as_enable == false)
			continue;
		if (as[j].as_dir != HDAUDIO_PINDIR_OUT)
			continue;
retry:
		res = hdafg_assoc_trace_out(sc, j, 0);
		if (res == 0 && as[j].as_hpredir >= 0 &&
		    as[j].as_fakeredir == 0) {
			/*
			 * If codec can't do analog HP redirection
			 * try to make it using one more DAC
			 */
			as[j].as_fakeredir = 1;
			goto retry;
		}
		if (!res) {
			hda_debug(sc, "disable assoc %d (%d) [trace failed]\n",
			    j, as[j].as_index);
			for (i = 0; i < HDAUDIO_MAXPINS; i++) {
				if (as[j].as_pins[i] == 0)
					continue;
				hda_debug(sc, "  assoc %d pin%d: %02X\n", j, i,
				    as[j].as_pins[i]);
			}
			for (i = 0; i < HDAUDIO_MAXPINS; i++) {
				if (as[j].as_dacs[i] == 0)
					continue;
				hda_debug(sc, "  assoc %d dac%d: %02X\n", j, i,
				    as[j].as_dacs[i]);
			}

			as[j].as_enable = false;
		}
	}

	/* Trace ADCs */
	for (j = 0; j < sc->sc_nassocs; j++) {
		if (as[j].as_enable == false)
			continue;
		if (as[j].as_dir != HDAUDIO_PINDIR_IN)
			continue;
		res = hdafg_assoc_trace_in(sc, j);
		if (!res) {
			hda_debug(sc, "disable assoc %d (%d) [trace failed]\n",
			    j, as[j].as_index);
			for (i = 0; i < HDAUDIO_MAXPINS; i++) {
				if (as[j].as_pins[i] == 0)
					continue;
				hda_debug(sc, "  assoc %d pin%d: %02X\n", j, i,
				    as[j].as_pins[i]);
			}
			for (i = 0; i < HDAUDIO_MAXPINS; i++) {
				if (as[j].as_dacs[i] == 0)
					continue;
				hda_debug(sc, "  assoc %d adc%d: %02X\n", j, i,
				    as[j].as_dacs[i]);
			}

			as[j].as_enable = false;
		}
	}

	/* Trace mixer and beeper pseudo associations */
	hdafg_assoc_trace_misc(sc);
}

static void
hdafg_prepare_pin_controls(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	uint32_t pincap;
	int i;

	hda_debug(sc, "*** prepare pin controls, nwidgets = %d\n",
	    sc->sc_nwidgets);

	for (i = 0; i < sc->sc_nwidgets; i++) {
		w = &sc->sc_widgets[i];
		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX) {
			hda_debug(sc, "  skipping pin %02X type 0x%x\n",
			    w->w_nid, w->w_type);
			continue;
		}
		pincap = w->w_pin.cap;

		/* Disable everything */
		w->w_pin.ctrl &= ~(
		    COP_PWC_VREF_ENABLE_MASK |
		    COP_PWC_IN_ENABLE |
		    COP_PWC_OUT_ENABLE |
		    COP_PWC_HPHN_ENABLE);

		if (w->w_enable == false ||
		    w->w_bindas < 0 || as[w->w_bindas].as_enable == false) {
			/* Pin is unused so leave it disabled */
			if ((pincap & (COP_PINCAP_OUTPUT_CAPABLE |
			    COP_PINCAP_INPUT_CAPABLE)) ==
			    (COP_PINCAP_OUTPUT_CAPABLE |
			    COP_PINCAP_INPUT_CAPABLE)) {
				hda_debug(sc, "pin %02X off, "
				    "in/out capable (bindas=%d "
				    "enable=%d as_enable=%d)\n",
				    w->w_nid, w->w_bindas, w->w_enable,
				    w->w_bindas >= 0 ?
				    as[w->w_bindas].as_enable : -1);
				w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
			} else
				hda_debug(sc, "pin %02X off\n", w->w_nid);
			continue;
		} else if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN) {
			/* Input pin, configure for input */
			if (pincap & COP_PINCAP_INPUT_CAPABLE)
				w->w_pin.ctrl |= COP_PWC_IN_ENABLE;

			hda_debug(sc, "pin %02X in ctrl 0x%x\n", w->w_nid,
			    w->w_pin.ctrl);

			if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) !=
			    COP_DEVICE_MIC_IN)
				continue;
			if (COP_PINCAP_VREF_CONTROL(pincap) & COP_VREF_80)
				w->w_pin.ctrl |= COP_PWC_VREF_80;
			else if (COP_PINCAP_VREF_CONTROL(pincap) & COP_VREF_50)
				w->w_pin.ctrl |= COP_PWC_VREF_50;
		} else {
			/* Output pin, configure for output */
			if (pincap & COP_PINCAP_OUTPUT_CAPABLE)
				w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
			if ((pincap & COP_PINCAP_HEADPHONE_DRIVE_CAPABLE) &&
			    (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) ==
			    COP_DEVICE_HP_OUT))
				w->w_pin.ctrl |= COP_PWC_HPHN_ENABLE;
			/* XXX VREF */
			hda_debug(sc, "pin %02X out ctrl 0x%x\n", w->w_nid,
			    w->w_pin.ctrl);
		}
	}
}

#if defined(HDAFG_DEBUG) && HDAFG_DEBUG > 1
static void
hdafg_dump_ctl(const struct hdafg_softc *sc, const struct hdaudio_control *ctl)
{
	int type = ctl->ctl_widget ? ctl->ctl_widget->w_type : -1;
	int i = (int)(ctl - sc->sc_ctls);

	hda_print(sc, "%03X: nid %02X type %d %s (%s) index %d",
	    i, (ctl->ctl_widget ? ctl->ctl_widget->w_nid : -1), type,
	    ctl->ctl_ndir == HDAUDIO_PINDIR_IN ? "in " : "out",
	    ctl->ctl_dir == HDAUDIO_PINDIR_IN ? "in " : "out",
	    ctl->ctl_index);

	if (ctl->ctl_childwidget)
		hda_print1(sc, " cnid %02X", ctl->ctl_childwidget->w_nid);
	else
		hda_print1(sc, "          ");
	hda_print1(sc, "\n");
	hda_print(sc, "     mute: %d step: %3d size: %3d off: %3d%s\n",
	    ctl->ctl_mute, ctl->ctl_step, ctl->ctl_size,
	    ctl->ctl_offset, ctl->ctl_enable == false ? " [DISABLED]" : "");
}
#endif

static void
hdafg_dump(const struct hdafg_softc *sc)
{
#if defined(HDAFG_DEBUG) && HDAFG_DEBUG > 1
	for (int i = 0; i < sc->sc_nctls; i++)
		hdafg_dump_ctl(sc, &sc->sc_ctls[i]);
#endif
}

static int
hdafg_match(device_t parent, cfdata_t match, void *opaque)
{
	prop_dictionary_t args = opaque;
	uint8_t fgtype;
	bool rv;

	rv = prop_dictionary_get_uint8(args, "function-group-type", &fgtype);
	if (rv == false || fgtype != HDAUDIO_GROUP_TYPE_AFG)
		return 0;

	return 1;
}

static void
hdafg_disable_unassoc(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w, *cw;
	struct hdaudio_control *ctl;
	int i, j, k;

	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		
		/* Disable unassociated widgets */
		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX) {
			if (w->w_bindas == -1) {
				w->w_enable = 0;
				hda_trace(sc, "disable %02X [unassociated]\n",
				    w->w_nid);
			}
			continue;
		}

		/*
		 * Disable input connections on input pin
		 * and output on output pin
		 */
		if (w->w_bindas < 0)
			continue;
		if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN) {
			hda_trace(sc, "disable %02X input connections\n",
			    w->w_nid);
			for (j = 0; j < w->w_nconns; j++)
				w->w_connsenable[j] = false;
			ctl = hdafg_control_lookup(sc, w->w_nid,
			    HDAUDIO_PINDIR_IN, -1, 1);
			if (ctl && ctl->ctl_enable == true) {
				ctl->ctl_forcemute = 1;
				ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
				ctl->ctl_left = ctl->ctl_right = 0;
				ctl->ctl_enable = false;
			}
		} else {
			ctl = hdafg_control_lookup(sc, w->w_nid,
			    HDAUDIO_PINDIR_OUT, -1, 1);
			if (ctl && ctl->ctl_enable == true) {
				ctl->ctl_forcemute = 1;
				ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
				ctl->ctl_left = ctl->ctl_right = 0;
				ctl->ctl_enable = false;
			}
			for (k = sc->sc_startnode; k < sc->sc_endnode; k++) {
				cw = hdafg_widget_lookup(sc, k);
				if (cw == NULL || cw->w_enable == false)
					continue;
				for (j = 0; j < cw->w_nconns; j++) {
					if (!cw->w_connsenable[j])
						continue;
					if (cw->w_conns[j] != i)
						continue;
					hda_trace(sc, "disable %02X -> %02X "
					    "output connection\n",
					    cw->w_nid, cw->w_conns[j]);
					cw->w_connsenable[j] = false;
					if (cw->w_type ==
					    COP_AWCAP_TYPE_PIN_COMPLEX &&
					    cw->w_nconns > 1)
						continue;
					ctl = hdafg_control_lookup(sc,
					    k, HDAUDIO_PINDIR_IN, j, 1);
					if (ctl && ctl->ctl_enable == true) {
						ctl->ctl_forcemute = 1;
						ctl->ctl_muted =
						    HDAUDIO_AMP_MUTE_ALL;
						ctl->ctl_left =
						    ctl->ctl_right = 0;
						ctl->ctl_enable = false;
					}
				}
			}
		}
	}
}

static void
hdafg_disable_unsel(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	int i, j;

	/* On playback path we can safely disable all unselected inputs */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_nconns <= 1)
			continue;
		if (w->w_type == COP_AWCAP_TYPE_AUDIO_MIXER)
			continue;
		if (w->w_bindas < 0 ||
		    as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN)
			continue;
		for (j = 0; j < w->w_nconns; j++) {
			if (w->w_connsenable[j] == false)
				continue;
			if (w->w_selconn < 0 || w->w_selconn == j)
				continue;
			hda_trace(sc, "disable %02X->%02X [unselected]\n",
			    w->w_nid, w->w_conns[j]);
			w->w_connsenable[j] = false;
		}
	}
}

static void
hdafg_disable_crossassoc(struct hdafg_softc *sc)
{
	struct hdaudio_widget *w, *cw;
	struct hdaudio_control *ctl;
	int i, j;

	/* Disable cross associated and unwanted cross channel connections */

	/* ... using selectors */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_nconns <= 1)
			continue;
		if (w->w_type == COP_AWCAP_TYPE_AUDIO_MIXER)
			continue;
		if (w->w_bindas == -2)
			continue;
		for (j = 0; j < w->w_nconns; j++) {
			if (w->w_connsenable[j] == false)
				continue;
			cw = hdafg_widget_lookup(sc, w->w_conns[j]);
			if (cw == NULL || cw->w_enable == false)
				continue;
			if (cw->w_bindas == -2)
				continue;
			if (w->w_bindas == cw->w_bindas &&
			    (w->w_bindseqmask & cw->w_bindseqmask) != 0)
				continue;
			hda_trace(sc, "disable %02X->%02X [crossassoc]\n",
			    w->w_nid, w->w_conns[j]);
			w->w_connsenable[j] = false;
		}
	}
	/* ... using controls */
	for (i = 0; i < sc->sc_nctls; i++) {
		ctl = &sc->sc_ctls[i];
		if (ctl->ctl_enable == false || ctl->ctl_childwidget == NULL)
			continue;
		if (ctl->ctl_widget->w_bindas == -2 ||
		    ctl->ctl_childwidget->w_bindas == -2)
			continue;
		if (ctl->ctl_widget->w_bindas !=
		    ctl->ctl_childwidget->w_bindas ||
		    (ctl->ctl_widget->w_bindseqmask &
		    ctl->ctl_childwidget->w_bindseqmask) == 0) {
			ctl->ctl_forcemute = 1;
			ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
			ctl->ctl_left = ctl->ctl_right = 0;
			ctl->ctl_enable = false;
			if (ctl->ctl_ndir == HDAUDIO_PINDIR_IN) {
				hda_trace(sc, "disable ctl %d:%02X:%02X "
				    "[crossassoc]\n",
				    i, ctl->ctl_widget->w_nid,
				    ctl->ctl_widget->w_conns[ctl->ctl_index]);
				ctl->ctl_widget->w_connsenable[
				    ctl->ctl_index] = false;
			}
		}
	}
}

static struct hdaudio_control *
hdafg_control_amp_get(struct hdafg_softc *sc, int nid,
    enum hdaudio_pindir dir, int index, int cnt)
{
	struct hdaudio_control *ctl;
	int i, found = 0;

	for (i = 0; i < sc->sc_nctls; i++) {
		ctl = &sc->sc_ctls[i];
		if (ctl->ctl_enable == false)
			continue;
		if (ctl->ctl_widget->w_nid != nid)
			continue;
		if (dir && ctl->ctl_ndir != dir)
			continue;
		if (index >= 0 && ctl->ctl_ndir == HDAUDIO_PINDIR_IN &&
		    ctl->ctl_dir == ctl->ctl_ndir &&
		    ctl->ctl_index != index)
			continue;
		++found;
		if (found == cnt || cnt <= 0)
			return ctl;
	}

	return NULL;
}

static void
hdafg_control_amp_set1(struct hdaudio_control *ctl, int lmute, int rmute,
    int left, int right, int dir)
{
	struct hdafg_softc *sc = ctl->ctl_widget->w_afg;
	int index = ctl->ctl_index;
	uint16_t v = 0;

	if (left != right || lmute != rmute) {
		v = (1 << (15 - dir)) | (1 << 13) | (index << 8) |
		    (lmute << 7) | left;
		hdaudio_command(sc->sc_codec, ctl->ctl_widget->w_nid,
		    CORB_SET_AMPLIFIER_GAIN_MUTE, v);
		v = (1 << (15 - dir)) | (1 << 12) | (index << 8) |
		    (rmute << 7) | right;
	} else
		v = (1 << (15 - dir)) | (3 << 12) | (index << 8) |
		    (lmute << 7) | left;
	hdaudio_command(sc->sc_codec, ctl->ctl_widget->w_nid,
	    CORB_SET_AMPLIFIER_GAIN_MUTE, v);
}

static void
hdafg_control_amp_set(struct hdaudio_control *ctl, uint32_t mute,
    int left, int right)
{
	int lmute, rmute;

	/* Save new values if valid */
	if (mute != HDAUDIO_AMP_MUTE_DEFAULT)
		ctl->ctl_muted = mute;
	if (left != HDAUDIO_AMP_VOL_DEFAULT)
		ctl->ctl_left = left;
	if (right != HDAUDIO_AMP_VOL_DEFAULT)
		ctl->ctl_right = right;

	/* Prepare effective values */
	if (ctl->ctl_forcemute) {
		lmute = rmute = 1;
		left = right = 0;
	} else {
		lmute = HDAUDIO_AMP_LEFT_MUTED(ctl->ctl_muted);
		rmute = HDAUDIO_AMP_RIGHT_MUTED(ctl->ctl_muted);
		left = ctl->ctl_left;
		right = ctl->ctl_right;
	}

	/* Apply effective values */
	if (ctl->ctl_dir & HDAUDIO_PINDIR_OUT)
		hdafg_control_amp_set1(ctl, lmute, rmute, left, right, 0);
	if (ctl->ctl_dir & HDAUDIO_PINDIR_IN)
		hdafg_control_amp_set1(ctl, lmute, rmute, left, right, 1);
}

/*
 * Muting the input pins directly does not work, we mute the mixers which
 * are parents to them
 */
static bool
hdafg_mixer_child_is_input(const struct hdafg_softc *sc,
    const struct hdaudio_control *ctl)
{
	const struct hdaudio_widget *w;
	const struct hdaudio_assoc *as = sc->sc_assocs;

	switch (ctl->ctl_widget->w_type) {
	case COP_AWCAP_TYPE_AUDIO_INPUT:
		return true;

	case COP_AWCAP_TYPE_AUDIO_MIXER:
		w = ctl->ctl_childwidget;
		if (w == NULL)
			return false;

		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			return false;

		if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
			return false;

		switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
		case COP_DEVICE_MIC_IN:
		case COP_DEVICE_LINE_IN:
		case COP_DEVICE_SPDIF_IN:
		case COP_DEVICE_DIGITAL_OTHER_IN:
			return true;
		default:
			return false;
		}

	default:
		return false;
	}
}

static void
hdafg_control_commit(struct hdafg_softc *sc)
{
	struct hdaudio_control *ctl;
	int i, z;

	for (i = 0; i < sc->sc_nctls; i++) {
		ctl = &sc->sc_ctls[i];
		//if (ctl->ctl_enable == false || ctl->ctl_audiomask != 0)
		if (ctl->ctl_enable == false)
			continue;
		/* Init fixed controls to 0dB amplification */
		z = ctl->ctl_offset;
		if (z > ctl->ctl_step)
			z = ctl->ctl_step;

		if (hdafg_mixer_child_is_input(sc, ctl))
			hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_ALL, z, z);
		else
			hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_NONE, z, z);
	}
}

static void
hdafg_widget_connection_select(struct hdaudio_widget *w, uint8_t index)
{
	struct hdafg_softc *sc = w->w_afg;

	if (w->w_nconns < 1 || index > (w->w_nconns - 1))
		return;

	hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_SET_CONNECTION_SELECT_CONTROL, index);
	w->w_selconn = index;
}

static void
hdafg_assign_names(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	int i, j;
	int type = -1, use, used =0;
	static const int types[7][13] = {
	    { HDAUDIO_MIXER_LINE, HDAUDIO_MIXER_LINE1, HDAUDIO_MIXER_LINE2,
	      HDAUDIO_MIXER_LINE3, -1 },
	    { HDAUDIO_MIXER_MONITOR, HDAUDIO_MIXER_MIC, -1 }, /* int mic */
	    { HDAUDIO_MIXER_MIC, HDAUDIO_MIXER_MONITOR, -1 }, /* ext mic */
	    { HDAUDIO_MIXER_CD, -1 },
	    { HDAUDIO_MIXER_SPEAKER, -1 },
	    { HDAUDIO_MIXER_DIGITAL1, HDAUDIO_MIXER_DIGITAL2,
	      HDAUDIO_MIXER_DIGITAL3, -1 },
	    { HDAUDIO_MIXER_LINE, HDAUDIO_MIXER_LINE1, HDAUDIO_MIXER_LINE2,
	      HDAUDIO_MIXER_LINE3, HDAUDIO_MIXER_PHONEIN,
	      HDAUDIO_MIXER_PHONEOUT, HDAUDIO_MIXER_VIDEO, HDAUDIO_MIXER_RADIO,
	      HDAUDIO_MIXER_DIGITAL1, HDAUDIO_MIXER_DIGITAL2,
	      HDAUDIO_MIXER_DIGITAL3, HDAUDIO_MIXER_MONITOR, -1 } /* others */
	};

	/* Surely known names */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_bindas == -1)
			continue;
		use = -1;
		switch (w->w_type) {
		case COP_AWCAP_TYPE_PIN_COMPLEX:
			if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
				break;
			type = -1;
			switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
			case COP_DEVICE_LINE_IN:
				type = 0;
				break;
			case COP_DEVICE_MIC_IN:
				if (COP_CFG_PORT_CONNECTIVITY(w->w_pin.config)
				    == COP_PORT_JACK)
					break;
				type = 1;
				break;
			case COP_DEVICE_CD:
				type = 3;
				break;
			case COP_DEVICE_SPEAKER:
				type = 4;
				break;
			case COP_DEVICE_SPDIF_IN:
			case COP_DEVICE_DIGITAL_OTHER_IN:
				type = 5;
				break;
			}
			if (type == -1)
				break;
			j = 0;
			while (types[type][j] >= 0 &&
			    (used & (1 << types[type][j])) != 0) {
				j++;
			}
			if (types[type][j] >= 0)
				use = types[type][j];
			break;
		case COP_AWCAP_TYPE_AUDIO_OUTPUT:
			use = HDAUDIO_MIXER_PCM;
			break;
		case COP_AWCAP_TYPE_BEEP_GENERATOR:
			use = HDAUDIO_MIXER_SPEAKER;
			break;
		default:
			break;
		}
		if (use >= 0) {
			w->w_audiodev = use;
			used |= (1 << use);
		}
	}
	/* Semi-known names */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_audiodev >= 0)
			continue;
		if (w->w_bindas == -1)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			continue;
		if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
			continue;
		type = -1;
		switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
		case COP_DEVICE_LINE_OUT:
		case COP_DEVICE_SPEAKER:
		case COP_DEVICE_HP_OUT:
		case COP_DEVICE_AUX:
			type = 0;
			break;
		case COP_DEVICE_MIC_IN:
			type = 2;
			break;
		case COP_DEVICE_SPDIF_OUT:
		case COP_DEVICE_DIGITAL_OTHER_OUT:
			type = 5;
			break;
		}
		if (type == -1)
			break;
		j = 0;
		while (types[type][j] >= 0 &&
		    (used & (1 << types[type][j])) != 0) {
			j++;
		}
		if (types[type][j] >= 0) {
			w->w_audiodev = types[type][j];
			used |= (1 << types[type][j]);
		}
	}
	/* Others */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_audiodev >= 0)
			continue;
		if (w->w_bindas == -1)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			continue;
		if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
			continue;
		j = 0;
		while (types[6][j] >= 0 &&
		    (used & (1 << types[6][j])) != 0) {
			j++;
		}
		if (types[6][j] >= 0) {
			w->w_audiodev = types[6][j];
			used |= (1 << types[6][j]);
		}
	}
}

static int
hdafg_control_source_amp(struct hdafg_softc *sc, int nid, int index,
    int audiodev, int ctlable, int depth, int need)
{
	struct hdaudio_widget *w, *wc;
	struct hdaudio_control *ctl;
	int i, j, conns = 0, rneed;

	if (depth >= HDAUDIO_PARSE_MAXDEPTH)
		return need;

	w = hdafg_widget_lookup(sc, nid);
	if (w == NULL || w->w_enable == false)
		return need;

	/* Count number of active inputs */
	if (depth > 0) {
		for (j = 0; j < w->w_nconns; j++) {
			if (w->w_connsenable[j])
				++conns;
		}
	}

	/*
	 * If this is not a first step, use input mixer. Pins have common
	 * input ctl so care must be taken
	 */
	if (depth > 0 && ctlable && (conns == 1 ||
	    w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)) {
		ctl = hdafg_control_amp_get(sc, w->w_nid,
		    HDAUDIO_PINDIR_IN, index, 1);
		if (ctl) {
			if (HDAUDIO_CONTROL_GIVE(ctl) & need)
				ctl->ctl_audiomask |= (1 << audiodev);
			else
				ctl->ctl_paudiomask |= (1 << audiodev);
			need &= ~HDAUDIO_CONTROL_GIVE(ctl);
		}
	}

	/* If widget has own audiodev, don't traverse it. */
	if (w->w_audiodev >= 0 && depth > 0)
		return need;

	/* We must not traverse pins */
	if ((w->w_type == COP_AWCAP_TYPE_AUDIO_INPUT ||
	    w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX) && depth > 0)
		return need;

	/* Record that this widget exports such signal */
	w->w_audiomask |= (1 << audiodev);

	/*
	 * If signals mixed, we can't assign controls further. Ignore this
	 * on depth zero. Caller must know why. Ignore this for static
	 * selectors if this input is selected.
	 */
	if (conns > 1)
		ctlable = 0;

	if (ctlable) {
		ctl = hdafg_control_amp_get(sc, w->w_nid,
		    HDAUDIO_PINDIR_OUT, -1, 1);
		if (ctl) {
			if (HDAUDIO_CONTROL_GIVE(ctl) & need)
				ctl->ctl_audiomask |= (1 << audiodev);
			else
				ctl->ctl_paudiomask |= (1 << audiodev);
			need &= ~HDAUDIO_CONTROL_GIVE(ctl);
		}
	}

	rneed = 0;
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		wc = hdafg_widget_lookup(sc, i);
		if (wc == NULL || wc->w_enable == false)
			continue;
		for (j = 0; j < wc->w_nconns; j++) {
			if (wc->w_connsenable[j] && wc->w_conns[j] == nid) {
				rneed |= hdafg_control_source_amp(sc,
				    wc->w_nid, j, audiodev, ctlable, depth + 1,
				    need);
			}
		}
	}
	rneed &= need;

	return rneed;
}

static void
hdafg_control_dest_amp(struct hdafg_softc *sc, int nid,
    int audiodev, int depth, int need)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w, *wc;
	struct hdaudio_control *ctl;
	int i, j, consumers;

	if (depth > HDAUDIO_PARSE_MAXDEPTH)
		return;

	w = hdafg_widget_lookup(sc, nid);
	if (w == NULL || w->w_enable == false)
		return;

	if (depth > 0) {
		/*
		 * If this node produces output for several consumers,
		 * we can't touch it
		 */
		consumers = 0;
		for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
			wc = hdafg_widget_lookup(sc, i);
			if (wc == NULL || wc->w_enable == false)
				continue;
			for (j = 0; j < wc->w_nconns; j++) {
				if (wc->w_connsenable[j] &&
				    wc->w_conns[j] == nid)
					++consumers;
			}
		}
		/*
		 * The only exception is if real HP redirection is configured
		 * and this is a duplication point.
		 * XXX: Not completely correct.
		 */
		if ((consumers == 2 && (w->w_bindas < 0 ||
		    as[w->w_bindas].as_hpredir < 0 ||
		    as[w->w_bindas].as_fakeredir ||
		    (w->w_bindseqmask & (1 << 15)) == 0)) ||
		    consumers > 2)
			return;

		/* Else use its output mixer */
		ctl = hdafg_control_amp_get(sc, w->w_nid,
		    HDAUDIO_PINDIR_OUT, -1, 1);
		if (ctl) {
			if (HDAUDIO_CONTROL_GIVE(ctl) & need)
				ctl->ctl_audiomask |= (1 << audiodev);
			else
				ctl->ctl_paudiomask |= (1 << audiodev);
			need &= ~HDAUDIO_CONTROL_GIVE(ctl);
		}
	}

	/* We must not traverse pin */
	if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX && depth > 0)
		return;

	for (i = 0; i < w->w_nconns; i++) {
		int tneed = need;
		if (w->w_connsenable[i] == false)
			continue;
		ctl = hdafg_control_amp_get(sc, w->w_nid,
		    HDAUDIO_PINDIR_IN, i, 1);
		if (ctl) {
			if (HDAUDIO_CONTROL_GIVE(ctl) & tneed)
				ctl->ctl_audiomask |= (1 << audiodev);
			else
				ctl->ctl_paudiomask |= (1 << audiodev);
			tneed &= ~HDAUDIO_CONTROL_GIVE(ctl);
		}
		hdafg_control_dest_amp(sc, w->w_conns[i], audiodev,
		    depth + 1, tneed);
	}
}

static void
hdafg_assign_mixers(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_control *ctl;
	struct hdaudio_widget *w;
	int i;

	/* Assign mixers to the tree */
	for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
		w = hdafg_widget_lookup(sc, i);
		if (w == NULL || w->w_enable == FALSE)
			continue;
		if (w->w_type == COP_AWCAP_TYPE_AUDIO_OUTPUT ||
		    w->w_type == COP_AWCAP_TYPE_BEEP_GENERATOR ||
		    (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX &&
		    as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN)) {
			if (w->w_audiodev < 0)
				continue;
			hdafg_control_source_amp(sc, w->w_nid, -1,
			    w->w_audiodev, 1, 0, 1);
		} else if (w->w_pflags & HDAUDIO_ADC_MONITOR) {
			if (w->w_audiodev < 0)
				continue;
			if (hdafg_control_source_amp(sc, w->w_nid, -1,
			    w->w_audiodev, 1, 0, 1)) {
				/* If we are unable to control input monitor
				   as source, try to control it as dest */
				hdafg_control_dest_amp(sc, w->w_nid,
				    w->w_audiodev, 0, 1);
			}
		} else if (w->w_type == COP_AWCAP_TYPE_AUDIO_INPUT) {
			hdafg_control_dest_amp(sc, w->w_nid,
			    HDAUDIO_MIXER_RECLEV, 0, 1);
		} else if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX &&
		    as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT) {
			hdafg_control_dest_amp(sc, w->w_nid,
			    HDAUDIO_MIXER_VOLUME, 0, 1);
		}
	}
	/* Treat unrequired as possible */
	for (i = 0; i < sc->sc_nctls; i++) {
		ctl = &sc->sc_ctls[i];
		if (ctl->ctl_audiomask == 0)
			ctl->ctl_audiomask = ctl->ctl_paudiomask;
	}
}

static void
hdafg_build_mixers(struct hdafg_softc *sc)
{
	struct hdaudio_mixer *mx;
	struct hdaudio_control *ctl, *masterctl = NULL;
	uint32_t audiomask = 0;
	int nmixers = 0;
	int i, j, index = 0;
	int ndac, nadc;
	int ctrlcnt[HDAUDIO_MIXER_NRDEVICES];

	memset(ctrlcnt, 0, sizeof(ctrlcnt));

	/* Count the number of required mixers */
	for (i = 0; i < sc->sc_nctls; i++) {
		ctl = &sc->sc_ctls[i];
		if (ctl->ctl_enable == false ||
		    ctl->ctl_audiomask == 0)
			continue;
		audiomask |= ctl->ctl_audiomask;
		++nmixers;
		if (ctl->ctl_mute)
			++nmixers;
	}

	/* XXXJDM TODO: softvol */
	/* Declare master volume if needed */
	if ((audiomask & (HDAUDIO_MASK(VOLUME) | HDAUDIO_MASK(PCM))) ==
	    HDAUDIO_MASK(PCM)) {
		audiomask |= HDAUDIO_MASK(VOLUME);
		for (i = 0; i < sc->sc_nctls; i++) {
			if (sc->sc_ctls[i].ctl_audiomask == HDAUDIO_MASK(PCM)) {
				masterctl = &sc->sc_ctls[i];
				++nmixers;
				if (masterctl->ctl_mute)
					++nmixers;
				break;
			}
		}
	}

	/* Make room for mixer classes */
	nmixers += (HDAUDIO_MIXER_CLASS_LAST + 1);

	/* count DACs and ADCs for selectors */
	ndac = nadc = 0;
	for (i = 0; i < sc->sc_nassocs; i++) {
		if (sc->sc_assocs[i].as_enable == false)
			continue;
		if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_OUT)
			++ndac;
		else if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_IN)
			++nadc;
	}

	/* Make room for selectors */
	if (ndac > 0)
		++nmixers;
	if (nadc > 0)
		++nmixers;

	hda_trace(sc, "  need %d mixers (3 classes%s)\n",
	    nmixers, masterctl ? " + fake master" : "");

	/* Allocate memory for the mixers */
	mx = kmem_zalloc(nmixers * sizeof(*mx), KM_SLEEP);
	sc->sc_nmixers = nmixers;

	/* Build class mixers */
	for (i = 0; i <= HDAUDIO_MIXER_CLASS_LAST; i++) {
		mx[index].mx_ctl = NULL;
		mx[index].mx_di.index = index;
		mx[index].mx_di.type = AUDIO_MIXER_CLASS;
		mx[index].mx_di.mixer_class = i;
		mx[index].mx_di.next = mx[index].mx_di.prev = AUDIO_MIXER_LAST;
		switch (i) {
		case HDAUDIO_MIXER_CLASS_OUTPUTS:
			strcpy(mx[index].mx_di.label.name, AudioCoutputs);
			break;
		case HDAUDIO_MIXER_CLASS_INPUTS:
			strcpy(mx[index].mx_di.label.name, AudioCinputs);
			break;
		case HDAUDIO_MIXER_CLASS_RECORD:
			strcpy(mx[index].mx_di.label.name, AudioCrecord);
			break;
		}
		++index;
	}

	/* Shadow master control */
	if (masterctl != NULL) {
		mx[index].mx_ctl = masterctl;
		mx[index].mx_di.index = index;
		mx[index].mx_di.type = AUDIO_MIXER_VALUE;
		mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
		mx[index].mx_di.un.v.num_channels = 2;	/* XXX */
		mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_OUTPUTS;
		mx[index].mx_di.un.v.delta = 256 / (masterctl->ctl_step + 1);
		strcpy(mx[index].mx_di.label.name, AudioNmaster);
		strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);
		hda_trace(sc, "  adding outputs.%s\n",
		    mx[index].mx_di.label.name);
		++index;
		if (masterctl->ctl_mute) {
			mx[index] = mx[index - 1];
			mx[index].mx_di.index = index;
			mx[index].mx_di.type = AUDIO_MIXER_ENUM;
			mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
			strcpy(mx[index].mx_di.label.name, AudioNmaster "." AudioNmute);
			mx[index].mx_di.un.e.num_mem = 2;
			strcpy(mx[index].mx_di.un.e.member[0].label.name, AudioNoff);
			mx[index].mx_di.un.e.member[0].ord = 0;
			strcpy(mx[index].mx_di.un.e.member[1].label.name, AudioNon);
			mx[index].mx_di.un.e.member[1].ord = 1;
			++index;
		}
	}

	/* Build volume mixers */
	for (i = 0; i < sc->sc_nctls; i++) {
		uint32_t audiodev;

		ctl = &sc->sc_ctls[i];
		if (ctl->ctl_enable == false ||
		    ctl->ctl_audiomask == 0)
			continue;
		audiodev = ffs(ctl->ctl_audiomask) - 1;
		mx[index].mx_ctl = ctl;
		mx[index].mx_di.index = index;
		mx[index].mx_di.type = AUDIO_MIXER_VALUE;
		mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
		mx[index].mx_di.un.v.num_channels = 2;	/* XXX */
		mx[index].mx_di.un.v.delta = 256 / (ctl->ctl_step + 1);
		if (ctrlcnt[audiodev] > 0)
			snprintf(mx[index].mx_di.label.name,
			    sizeof(mx[index].mx_di.label.name),
			    "%s%d",
			    hdafg_mixer_names[audiodev],
			    ctrlcnt[audiodev] + 1);
		else
			strcpy(mx[index].mx_di.label.name,
			    hdafg_mixer_names[audiodev]);
		ctrlcnt[audiodev]++;

		switch (audiodev) {
		case HDAUDIO_MIXER_VOLUME:
		case HDAUDIO_MIXER_BASS:
		case HDAUDIO_MIXER_TREBLE:
		case HDAUDIO_MIXER_OGAIN:
			mx[index].mx_di.mixer_class =
			    HDAUDIO_MIXER_CLASS_OUTPUTS;
			hda_trace(sc, "  adding outputs.%s\n",
			    mx[index].mx_di.label.name);
			break;
		case HDAUDIO_MIXER_MIC:
		case HDAUDIO_MIXER_MONITOR:
			mx[index].mx_di.mixer_class =
			    HDAUDIO_MIXER_CLASS_RECORD;
			hda_trace(sc, "  adding record.%s\n",
			    mx[index].mx_di.label.name);
			break;
		default:
			mx[index].mx_di.mixer_class =
			    HDAUDIO_MIXER_CLASS_INPUTS;
			hda_trace(sc, "  adding inputs.%s\n",
			    mx[index].mx_di.label.name);
			break;
		}
		strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);

		++index;

		if (ctl->ctl_mute) {
			mx[index] = mx[index - 1];
			mx[index].mx_di.index = index;
			mx[index].mx_di.type = AUDIO_MIXER_ENUM;
			mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
			snprintf(mx[index].mx_di.label.name,
			    sizeof(mx[index].mx_di.label.name),
			    "%s." AudioNmute,
			    mx[index - 1].mx_di.label.name);
			mx[index].mx_di.un.e.num_mem = 2;
			strcpy(mx[index].mx_di.un.e.member[0].label.name, AudioNoff);
			mx[index].mx_di.un.e.member[0].ord = 0;
			strcpy(mx[index].mx_di.un.e.member[1].label.name, AudioNon);
			mx[index].mx_di.un.e.member[1].ord = 1;
			++index;
		}
	}

	/* DAC selector */
	if (ndac > 0) {
		mx[index].mx_ctl = NULL;
		mx[index].mx_di.index = index;
		mx[index].mx_di.type = AUDIO_MIXER_SET;
		mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_OUTPUTS;
		mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
		strcpy(mx[index].mx_di.label.name, "dacsel"); /* AudioNselect */
		mx[index].mx_di.un.s.num_mem = ndac;
		for (i = 0, j = 0; i < sc->sc_nassocs; i++) {
			if (sc->sc_assocs[i].as_enable == false)
				continue;
			if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_OUT)
				continue;
			mx[index].mx_di.un.s.member[j].mask = 1 << i;
			snprintf(mx[index].mx_di.un.s.member[j].label.name,
			    sizeof(mx[index].mx_di.un.s.member[j].label.name),
			    "%s%02X",
			    hdafg_assoc_type_string(&sc->sc_assocs[i]), i);
			++j;
		}
		++index;
	}

	/* ADC selector */
	if (nadc > 0) {
		mx[index].mx_ctl = NULL;
		mx[index].mx_di.index = index;
		mx[index].mx_di.type = AUDIO_MIXER_SET;
		mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_RECORD;
		mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
		strcpy(mx[index].mx_di.label.name, AudioNsource);
		mx[index].mx_di.un.s.num_mem = nadc;
		for (i = 0, j = 0; i < sc->sc_nassocs; i++) {
			if (sc->sc_assocs[i].as_enable == false)
				continue;
			if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_IN)
				continue;
			mx[index].mx_di.un.s.member[j].mask = 1 << i;
			snprintf(mx[index].mx_di.un.s.member[j].label.name,
			    sizeof(mx[index].mx_di.un.s.member[j].label.name),
			    "%s%02X",
			    hdafg_assoc_type_string(&sc->sc_assocs[i]), i);
			++j;
		}
		++index;
	}

	sc->sc_mixers = mx;
}

static void
hdafg_commit(struct hdafg_softc *sc)
{
	struct hdaudio_widget *w;
	uint32_t gdata, gmask, gdir;
	int commitgpio;
	int i;

	/* Commit controls */
	hdafg_control_commit(sc);

	/* Commit selectors, pins, and EAPD */
	for (i = 0; i < sc->sc_nwidgets; i++) {
		w = &sc->sc_widgets[i];
		if (w->w_selconn == -1)
			w->w_selconn = 0;
		if (w->w_nconns > 0)
			hdafg_widget_connection_select(w, w->w_selconn);
		if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX)
			hdaudio_command(sc->sc_codec, w->w_nid,
			    CORB_SET_PIN_WIDGET_CONTROL, w->w_pin.ctrl);
		if (w->w_p.eapdbtl != 0xffffffff)
			hdaudio_command(sc->sc_codec, w->w_nid,
			    CORB_SET_EAPD_BTL_ENABLE, w->w_p.eapdbtl);
	}

	gdata = gmask = gdir = commitgpio = 0;
#ifdef notyet
	int numgpio = COP_GPIO_COUNT_NUM_GPIO(sc->sc_p.gpio_cnt);

	hda_trace(sc, "found %d GPIOs\n", numgpio);
	for (i = 0; i < numgpio && i < 8; i++) {
		if (commitgpio == 0)
			commitgpio = 1;
		gdata |= 1 << i;
		gmask |= 1 << i;
		gdir |= 1 << i;
	}
#endif

	if (commitgpio) {
		hda_trace(sc, "GPIO commit: data=%08X mask=%08X dir=%08X\n",
		    gdata, gmask, gdir);
		hdaudio_command(sc->sc_codec, sc->sc_nid,
		    CORB_SET_GPIO_ENABLE_MASK, gmask);
		hdaudio_command(sc->sc_codec, sc->sc_nid,
		    CORB_SET_GPIO_DIRECTION, gdir);
		hdaudio_command(sc->sc_codec, sc->sc_nid,
		    CORB_SET_GPIO_DATA, gdata);
	}
}

static void
hdafg_stream_connect_hdmi(struct hdafg_softc *sc, struct hdaudio_assoc *as,
    struct hdaudio_widget *w, const audio_params_t *params)
{
	struct hdmi_audio_infoframe hdmi;
	/* TODO struct displayport_audio_infoframe dp; */
	uint8_t *dip = NULL;
	size_t diplen = 0;
	int i;

#ifdef HDAFG_HDMI_DEBUG
	uint32_t res;
	res = hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_GET_HDMI_DIP_XMIT_CTRL, 0);
	hda_print(sc, "connect HDMI nid %02X, xmitctrl = 0x%08X\n",
	    w->w_nid, res);
#endif

	/* disable infoframe transmission */
	hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_SET_HDMI_DIP_XMIT_CTRL, COP_DIP_XMIT_CTRL_DISABLE);

	if (sc->sc_disable_dip)
		return;

	/* build new infoframe */
	if (as->as_digital == HDAFG_AS_HDMI) {
		dip = (uint8_t *)&hdmi;
		diplen = sizeof(hdmi);
		memset(&hdmi, 0, sizeof(hdmi));
		hdmi.header.packet_type = HDMI_AI_PACKET_TYPE;
		hdmi.header.version = HDMI_AI_VERSION;
		hdmi.header.length = HDMI_AI_LENGTH;
		hdmi.ct_cc = params->channels - 1;
		if (params->channels > 2) {
			hdmi.ca = 0x1f;
		} else {
			hdmi.ca = 0x00;
		}
		hdafg_dd_hdmi_ai_cksum(&hdmi);
	}
	/* update data island with new audio infoframe */
	if (dip) {
		hdaudio_command(sc->sc_codec, w->w_nid,
		    CORB_SET_HDMI_DIP_INDEX, 0);
		for (i = 0; i < diplen; i++) {
			hdaudio_command(sc->sc_codec, w->w_nid,
			    CORB_SET_HDMI_DIP_DATA, dip[i]);
		}
	}

	/* enable infoframe transmission */
	hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_SET_HDMI_DIP_XMIT_CTRL, COP_DIP_XMIT_CTRL_BEST_EFFORT);
}

static void
hdafg_stream_connect(struct hdafg_softc *sc, int mode)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	const audio_params_t *params;
	uint16_t fmt, dfmt;
	int tag, chn, maxchan, c;
	int i, j, k;

	KASSERT(mode == AUMODE_PLAY || mode == AUMODE_RECORD);

	if (mode == AUMODE_PLAY) {
		fmt = hdaudio_stream_param(sc->sc_audiodev.ad_playback,
		    &sc->sc_pparam);
		params = &sc->sc_pparam;
	} else {
		fmt = hdaudio_stream_param(sc->sc_audiodev.ad_capture,
		    &sc->sc_rparam);
		params = &sc->sc_rparam;
	}

	for (i = 0; i < sc->sc_nassocs; i++) {
		if (as[i].as_enable == false)
			continue;

		if (mode == AUMODE_PLAY && as[i].as_dir != HDAUDIO_PINDIR_OUT)
			continue;
		if (mode == AUMODE_RECORD && as[i].as_dir != HDAUDIO_PINDIR_IN)
			continue;

		fmt &= ~HDAUDIO_FMT_CHAN_MASK;
		if (as[i].as_dir == HDAUDIO_PINDIR_OUT &&
		    sc->sc_audiodev.ad_playback != NULL) {
			tag = hdaudio_stream_tag(sc->sc_audiodev.ad_playback);
			fmt |= HDAUDIO_FMT_CHAN(sc->sc_pparam.channels);
			maxchan = sc->sc_pparam.channels;
		} else if (as[i].as_dir == HDAUDIO_PINDIR_IN &&
		    sc->sc_audiodev.ad_capture != NULL) {
			tag = hdaudio_stream_tag(sc->sc_audiodev.ad_capture);
			fmt |= HDAUDIO_FMT_CHAN(sc->sc_rparam.channels);
			maxchan = sc->sc_rparam.channels;
		} else {
			tag = 0;
			if (as[i].as_dir == HDAUDIO_PINDIR_OUT) {
				fmt |= HDAUDIO_FMT_CHAN(sc->sc_pchan);
				maxchan = sc->sc_pchan;
			} else {
				fmt |= HDAUDIO_FMT_CHAN(sc->sc_rchan);
				maxchan = sc->sc_rchan;
			}
		}

		chn = 0;
		for (j = 0; j < HDAUDIO_MAXPINS; j++) {
			if (as[i].as_dacs[j] == 0)
				continue;
			w = hdafg_widget_lookup(sc, as[i].as_dacs[j]);
			if (w == NULL || w->w_enable == FALSE)
				continue;
			if (as[i].as_hpredir >= 0 && i == as[i].as_pincnt)
				chn = 0;
			if (chn >= maxchan)
				chn = 0;	/* XXX */
			c = (tag << 4) | chn;

			if (as[i].as_activated == false)
				c = 0;

			/*
			 * If a non-PCM stream is being connected, and the
			 * analog converter doesn't support non-PCM streams,
			 * then don't decode it
			 */
			if (!(w->w_p.aw_cap & COP_AWCAP_DIGITAL) &&
			    !(w->w_p.stream_format & COP_STREAM_FORMAT_AC3) &&
			    (fmt & HDAUDIO_FMT_TYPE_NONPCM)) {
				hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_SET_CONVERTER_STREAM_CHANNEL, 0);
				continue;
			}

			hdaudio_command(sc->sc_codec, w->w_nid,
			    CORB_SET_CONVERTER_FORMAT, fmt);
			if (w->w_p.aw_cap & COP_AWCAP_DIGITAL) {
				dfmt = hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_GET_DIGITAL_CONVERTER_CONTROL, 0) &
				    0xff;
				dfmt |= COP_DIGITAL_CONVCTRL1_DIGEN;
				if (fmt & HDAUDIO_FMT_TYPE_NONPCM)
					dfmt |= COP_DIGITAL_CONVCTRL1_NAUDIO;
				else
					dfmt &= ~COP_DIGITAL_CONVCTRL1_NAUDIO;
				if (sc->sc_vendor == HDAUDIO_VENDOR_NVIDIA)
					dfmt |= COP_DIGITAL_CONVCTRL1_COPY;
				hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_SET_DIGITAL_CONVERTER_CONTROL_1, dfmt);
			}
			if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP)) {
				hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_SET_CONVERTER_CHANNEL_COUNT,
				    maxchan - 1);
				for (k = 0; k < maxchan; k++) {
					hdaudio_command(sc->sc_codec, w->w_nid,
					    CORB_ASP_SET_CHANNEL_MAPPING,
					    (k << 4) | k);
				}
			}
			hdaudio_command(sc->sc_codec, w->w_nid,
			    CORB_SET_CONVERTER_STREAM_CHANNEL, c);
			chn += COP_AWCAP_CHANNEL_COUNT(w->w_p.aw_cap);
		}

		for (j = 0; j < HDAUDIO_MAXPINS; j++) {
			if (as[i].as_pins[j] == 0)
				continue;
			w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
			if (w == NULL || w->w_enable == FALSE)
				continue;
			if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP))
				hdafg_stream_connect_hdmi(sc, &as[i],
				    w, params);
		}
	}
}

static int
hdafg_stream_intr(struct hdaudio_stream *st)
{
	struct hdaudio_audiodev *ad = st->st_cookie;
	int handled = 0;

	(void)hda_read1(ad->ad_sc->sc_host, HDAUDIO_SD_STS(st->st_shift));
	hda_write1(ad->ad_sc->sc_host, HDAUDIO_SD_STS(st->st_shift),
	    HDAUDIO_STS_DESE | HDAUDIO_STS_FIFOE | HDAUDIO_STS_BCIS);

	mutex_spin_enter(&ad->ad_sc->sc_intr_lock);
	/* XXX test (sts & HDAUDIO_STS_BCIS)? */
	if (st == ad->ad_playback && ad->ad_playbackintr) {
		ad->ad_playbackintr(ad->ad_playbackintrarg);
		handled = 1;
	} else if (st == ad->ad_capture && ad->ad_captureintr) {
		ad->ad_captureintr(ad->ad_captureintrarg);
		handled = 1;
	}
	mutex_spin_exit(&ad->ad_sc->sc_intr_lock);

	return handled;
}

static bool
hdafg_rate_supported(struct hdafg_softc *sc, u_int frequency)
{
	uint32_t caps = sc->sc_p.pcm_size_rate;

	if (sc->sc_fixed_rate)
		return frequency == sc->sc_fixed_rate;

#define ISFREQOK(shift) ((caps & (1 << (shift))) ? true : false)
	switch (frequency) {
	case 8000:
		return ISFREQOK(0);
	case 11025:
		return ISFREQOK(1);
	case 16000:
		return ISFREQOK(2);
	case 22050:
		return ISFREQOK(3);
	case 32000:
		return ISFREQOK(4);
	case 44100:
		return ISFREQOK(5);
		return true;
	case 48000:
		return true;	/* Must be supported by all codecs */
	case 88200:
		return ISFREQOK(7);
	case 96000:
		return ISFREQOK(8);
	case 176400:
		return ISFREQOK(9);
	case 192000:
		return ISFREQOK(10);
	case 384000:
		return ISFREQOK(11);
	default:
		return false;
	}
#undef ISFREQOK
}

static bool
hdafg_bits_supported(struct hdafg_softc *sc, u_int bits)
{
	uint32_t caps = sc->sc_p.pcm_size_rate;
#define ISBITSOK(shift) ((caps & (1 << (shift))) ? true : false)
	switch (bits) {
	case 8:
		return ISBITSOK(16);
	case 16:
		return ISBITSOK(17);
	case 20:
		return ISBITSOK(18);
	case 24:
		return ISBITSOK(19);
	case 32:
		return ISBITSOK(20);
	default:
		return false;
	}
#undef ISBITSOK
}

static bool
hdafg_probe_encoding(struct hdafg_softc *sc,
    u_int validbits, u_int precision, int encoding, bool force)
{
	struct audio_format f;
	int i;

	if (!force && hdafg_bits_supported(sc, validbits) == false)
		return false;

	memset(&f, 0, sizeof(f));
	f.driver_data = NULL;
	f.mode = 0;
	f.encoding = encoding;
	f.validbits = validbits;
	f.precision = precision;
	f.channels = 0;
	f.channel_mask = 0;
	f.frequency_type = 0;
	for (i = 0; i < __arraycount(hdafg_possible_rates); i++) {
		u_int rate = hdafg_possible_rates[i];
		if (hdafg_rate_supported(sc, rate))
			f.frequency[f.frequency_type++] = rate;
	}
	/* XXX ad hoc.. */
	if (encoding == AUDIO_ENCODING_AC3)
		f.priority = -1;

#define HDAUDIO_INITFMT(ch, chmask)			\
	do {						\
		f.channels = (ch);			\
		f.channel_mask = (chmask);		\
		f.mode = 0;				\
		if (sc->sc_pchan >= (ch))		\
			f.mode |= AUMODE_PLAY;		\
		if (sc->sc_rchan >= (ch))		\
			f.mode |= AUMODE_RECORD;	\
		if (f.mode != 0)			\
			hdafg_append_formats(&sc->sc_audiodev, &f); \
	} while (0)

	/* Commented out, otherwise monaural samples play through left
	 * channel only
	 */
	/* HDAUDIO_INITFMT(1, AUFMT_MONAURAL); */
	HDAUDIO_INITFMT(2, AUFMT_STEREO);
	HDAUDIO_INITFMT(4, AUFMT_SURROUND4);
	HDAUDIO_INITFMT(6, AUFMT_DOLBY_5_1);
	HDAUDIO_INITFMT(8, AUFMT_SURROUND_7_1);

#undef HDAUDIO_INITFMT

	return true;
}


static void
hdafg_configure_encodings(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	struct audio_format f;
	uint32_t stream_format, caps;
	int nchan, i, nid;

	sc->sc_pchan = sc->sc_rchan = 0;

	for (i = 0; i < sc->sc_nassocs; i++) {
		nchan = hdafg_assoc_count_channels(sc, &as[i],
		    HDAUDIO_PINDIR_OUT);
		if (nchan > sc->sc_pchan)
			sc->sc_pchan = nchan;
	}
	for (i = 0; i < sc->sc_nassocs; i++) {
		nchan = hdafg_assoc_count_channels(sc, &as[i],
		    HDAUDIO_PINDIR_IN);
		if (nchan > sc->sc_rchan)
			sc->sc_rchan = nchan;
	}
	hda_print(sc, "%dch/%dch", sc->sc_pchan, sc->sc_rchan);

	for (i = 0; i < __arraycount(hdafg_possible_rates); i++)
		if (hdafg_rate_supported(sc,
		    hdafg_possible_rates[i]))
			hda_print1(sc, " %uHz", hdafg_possible_rates[i]);

	stream_format = sc->sc_p.stream_format;
	caps = 0;
	for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
		w = hdafg_widget_lookup(sc, nid);
		if (w == NULL)
			continue;
		stream_format |= w->w_p.stream_format;
		caps |= w->w_p.aw_cap;
	}
	if (stream_format == 0) {
		hda_print(sc,
		    "WARNING: unsupported stream format mask 0x%X, assuming PCM\n",
		    stream_format);
		stream_format |= COP_STREAM_FORMAT_PCM;
	}

	if (stream_format & COP_STREAM_FORMAT_PCM) {
		int e = AUDIO_ENCODING_SLINEAR_LE;
		if (hdafg_probe_encoding(sc, 8, 16, e, false))
			hda_print1(sc, " PCM8");
		if (hdafg_probe_encoding(sc, 16, 16, e, false))
			hda_print1(sc, " PCM16");
		if (hdafg_probe_encoding(sc, 20, 32, e, false))
			hda_print1(sc, " PCM20");
		if (hdafg_probe_encoding(sc, 24, 32, e, false))
			hda_print1(sc, " PCM24");
		if (hdafg_probe_encoding(sc, 32, 32, e, false))
			hda_print1(sc, " PCM32");
	}

	if ((stream_format & COP_STREAM_FORMAT_AC3) ||
	    (caps & COP_AWCAP_DIGITAL)) {
		int e = AUDIO_ENCODING_AC3;
		if (hdafg_probe_encoding(sc, 16, 16, e, false))
			hda_print1(sc, " AC3");
	}

	if (sc->sc_audiodev.ad_nformats == 0) {
		hdafg_probe_encoding(sc, 16, 16, AUDIO_ENCODING_SLINEAR_LE, true);
		hda_print1(sc, " PCM16*");
	}

	/*
	 * XXX JDM 20090614
	 * MI audio assumes that at least one playback and one capture format
	 * is reported by the hw driver; until this bug is resolved just
	 * report 2ch capabilities if the function group does not support
	 * the direction.
	 */
	if (sc->sc_rchan == 0 || sc->sc_pchan == 0) {
		memset(&f, 0, sizeof(f));
		f.driver_data = NULL;
		f.mode = 0;
		f.encoding = AUDIO_ENCODING_SLINEAR_LE;
		f.validbits = 16;
		f.precision = 16;
		f.channels = 2;
		f.channel_mask = AUFMT_STEREO;
		f.frequency_type = 0;
		f.frequency[0] = f.frequency[1] = sc->sc_fixed_rate ?
		    sc->sc_fixed_rate : 48000;
		f.mode = AUMODE_PLAY|AUMODE_RECORD;
		hdafg_append_formats(&sc->sc_audiodev, &f);
	}

	hda_print1(sc, "\n");
}

static void
hdafg_hp_switch_handler(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	uint32_t res = 0;
	int i, j;

	KASSERT(sc->sc_jack_polling);
	KASSERT(mutex_owned(&sc->sc_jack_lock));

	if (!device_is_active(sc->sc_dev))
		return;

	for (i = 0; i < sc->sc_nassocs; i++) {
		if (as[i].as_digital != HDAFG_AS_ANALOG &&
		    as[i].as_digital != HDAFG_AS_SPDIF)
			continue;
		for (j = 0; j < HDAUDIO_MAXPINS; j++) {
			if (as[i].as_pins[j] == 0)
				continue;
			w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
			if (w == NULL || w->w_enable == false)
				continue;
			if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
				continue;
			if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) !=
			    COP_DEVICE_HP_OUT)
				continue;
			res |= hdaudio_command(sc->sc_codec, as[i].as_pins[j],
			    CORB_GET_PIN_SENSE, 0) &
			    COP_GET_PIN_SENSE_PRESENSE_DETECT;
		}
	}

	for (i = 0; i < sc->sc_nassocs; i++) {
		if (as[i].as_digital != HDAFG_AS_ANALOG &&
		    as[i].as_digital != HDAFG_AS_SPDIF)
			continue;
		for (j = 0; j < HDAUDIO_MAXPINS; j++) {
			if (as[i].as_pins[j] == 0)
				continue;
			w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
			if (w == NULL || w->w_enable == false)
				continue;
			if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
				continue;
			switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
			case COP_DEVICE_HP_OUT:
				if (res & COP_GET_PIN_SENSE_PRESENSE_DETECT)
					w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
				else
					w->w_pin.ctrl &= ~COP_PWC_OUT_ENABLE;
				hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_SET_PIN_WIDGET_CONTROL, w->w_pin.ctrl);
				break;
			case COP_DEVICE_LINE_OUT:
			case COP_DEVICE_SPEAKER:
			case COP_DEVICE_AUX:
				if (res & COP_GET_PIN_SENSE_PRESENSE_DETECT)
					w->w_pin.ctrl &= ~COP_PWC_OUT_ENABLE;
				else
					w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
				hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_SET_PIN_WIDGET_CONTROL, w->w_pin.ctrl);
				break;
			default:
				break;
			}
		}
	}
}

static void
hdafg_hp_switch_thread(void *opaque)
{
	struct hdafg_softc *sc = opaque;

	KASSERT(sc->sc_jack_polling);

	mutex_enter(&sc->sc_jack_lock);
	while (!sc->sc_jack_dying) {
		if (sc->sc_jack_suspended) {
			cv_wait(&sc->sc_jack_cv, &sc->sc_jack_lock);
			continue;
		}
		hdafg_hp_switch_handler(sc);
		(void)cv_timedwait(&sc->sc_jack_cv, &sc->sc_jack_lock,
		    HDAUDIO_HP_SENSE_PERIOD);
	}
	mutex_exit(&sc->sc_jack_lock);

	kthread_exit(0);
}

static void
hdafg_hp_switch_init(struct hdafg_softc *sc)
{
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_widget *w;
	bool enable = false;
	int i, j;
	int error;

	for (i = 0; i < sc->sc_nassocs; i++) {
		if (as[i].as_hpredir < 0 && as[i].as_displaydev == false)
			continue;
		if (as[i].as_displaydev == false)
			w = hdafg_widget_lookup(sc, as[i].as_pins[15]);
		else {
			w = NULL;
			for (j = 0; j < HDAUDIO_MAXPINS; j++) {
				if (as[i].as_pins[j] == 0)
					continue;
				w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
				if (w && w->w_enable &&
				    w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX)
					break;
				w = NULL;
			}
		}
		if (w == NULL || w->w_enable == false)
			continue;
		if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			continue;
		if (!(w->w_pin.cap & COP_PINCAP_PRESENSE_DETECT_CAPABLE)) {
			continue;
		}
		if (COP_CFG_MISC(w->w_pin.config) & 1) {
			hda_trace(sc, "no presence detect on pin %02X\n",
			    w->w_nid);
			continue;
		}
		if ((w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP)) == 0)
			enable = true;

		if (w->w_p.aw_cap & COP_AWCAP_UNSOL_CAPABLE) {
			uint8_t val = COP_SET_UNSOLICITED_RESPONSE_ENABLE;
			if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP))
				val |= HDAUDIO_UNSOLTAG_EVENT_DD;
			else
				val |= HDAUDIO_UNSOLTAG_EVENT_HP;

			hdaudio_command(sc->sc_codec, w->w_nid,
			    CORB_SET_UNSOLICITED_RESPONSE, val);

			hdaudio_command(sc->sc_codec, w->w_nid,
			    CORB_SET_AMPLIFIER_GAIN_MUTE, 0xb000);
		}

		hda_trace(sc, "presence detect [pin=%02X,%s",
		    w->w_nid,
		    (w->w_p.aw_cap & COP_AWCAP_UNSOL_CAPABLE) ?
		     "unsol" : "poll"
		    );
		if (w->w_pin.cap & COP_PINCAP_HDMI)
			hda_trace1(sc, ",hdmi");
		if (w->w_pin.cap & COP_PINCAP_DP)
			hda_trace1(sc, ",displayport");
		hda_trace1(sc, "]\n");
	}
	if (!enable) {
		hda_trace(sc, "jack detect not enabled\n");
		return;
	}

	mutex_init(&sc->sc_jack_lock, MUTEX_DEFAULT, IPL_NONE);
	cv_init(&sc->sc_jack_cv, "hdafghp");
	sc->sc_jack_polling = true;
	error = kthread_create(PRI_NONE, KTHREAD_MPSAFE, /*ci*/NULL,
	    hdafg_hp_switch_thread, sc, &sc->sc_jack_thread,
	    "%s hotplug detect", device_xname(sc->sc_dev));
	if (error) {
		aprint_error_dev(sc->sc_dev, "failed to create hotplug thread:"
		    " %d", error);
		sc->sc_jack_polling = false;
		cv_destroy(&sc->sc_jack_cv);
		mutex_destroy(&sc->sc_jack_lock);
	}
}

static void
hdafg_attach(device_t parent, device_t self, void *opaque)
{
	struct hdafg_softc *sc = device_private(self);
	audio_params_t defparams;
	prop_dictionary_t args = opaque;
	char vendor[MAX_AUDIO_DEV_LEN], product[MAX_AUDIO_DEV_LEN];
	uint64_t fgptr = 0;
	uint32_t astype = 0;
	uint8_t nid = 0;
	int i;
	bool rv;

	aprint_naive("\n");
	sc->sc_dev = self;

	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);

	if (!pmf_device_register(self, hdafg_suspend, hdafg_resume))
		aprint_error_dev(self, "couldn't establish power handler\n");

	sc->sc_config = prop_dictionary_get(args, "pin-config");
	if (sc->sc_config && prop_object_type(sc->sc_config) != PROP_TYPE_ARRAY)
		sc->sc_config = NULL;

	prop_dictionary_get_uint16(args, "vendor-id", &sc->sc_vendor);
	prop_dictionary_get_uint16(args, "product-id", &sc->sc_product);
	hdaudio_findvendor(vendor, sizeof(vendor), sc->sc_vendor);
	hdaudio_findproduct(product, sizeof(product), sc->sc_vendor,
	    sc->sc_product);
	hda_print1(sc, ": %s %s%s\n", vendor, product,
	    sc->sc_config ? " (custom configuration)" : "");

	switch (sc->sc_vendor) {
	case HDAUDIO_VENDOR_NVIDIA:
		switch (sc->sc_product) {
		case HDAUDIO_PRODUCT_NVIDIA_TEGRA124_HDMI:
			sc->sc_fixed_rate = 44100;
			sc->sc_disable_dip = true;
			break;
		}
		break;
	}

	rv = prop_dictionary_get_uint64(args, "function-group", &fgptr);
	if (rv == false || fgptr == 0) {
		hda_error(sc, "missing function-group property\n");
		return;
	}
	rv = prop_dictionary_get_uint8(args, "node-id", &nid);
	if (rv == false || nid == 0) {
		hda_error(sc, "missing node-id property\n");
		return;
	}

	prop_dictionary_set_uint64(device_properties(self),
	    "codecinfo-callback",
	    (uint64_t)(uintptr_t)hdafg_codec_info);
	prop_dictionary_set_uint64(device_properties(self),
	    "widgetinfo-callback",
	    (uint64_t)(uintptr_t)hdafg_widget_info);

	sc->sc_nid = nid;
	sc->sc_fg = (struct hdaudio_function_group *)(vaddr_t)fgptr;
	sc->sc_fg->fg_unsol = hdafg_unsol;
	sc->sc_codec = sc->sc_fg->fg_codec;
	KASSERT(sc->sc_codec != NULL);
	sc->sc_host = sc->sc_codec->co_host;
	KASSERT(sc->sc_host != NULL);

	hda_debug(sc, "parsing widgets\n");
	hdafg_parse(sc);
	hda_debug(sc, "parsing controls\n");
	hdafg_control_parse(sc);
	hda_debug(sc, "disabling non-audio devices\n");
	hdafg_disable_nonaudio(sc);
	hda_debug(sc, "disabling useless devices\n");
	hdafg_disable_useless(sc);
	hda_debug(sc, "parsing associations\n");
	hdafg_assoc_parse(sc);
	hda_debug(sc, "building tree\n");
	hdafg_build_tree(sc);
	hda_debug(sc, "disabling unassociated pins\n");
	hdafg_disable_unassoc(sc);
	hda_debug(sc, "disabling unselected pins\n");
	hdafg_disable_unsel(sc);
	hda_debug(sc, "disabling useless devices\n");
	hdafg_disable_useless(sc);
	hda_debug(sc, "disabling cross-associated pins\n");
	hdafg_disable_crossassoc(sc);
	hda_debug(sc, "disabling useless devices\n");
	hdafg_disable_useless(sc);

	hda_debug(sc, "assigning mixer names to sound sources\n");
	hdafg_assign_names(sc);
	hda_debug(sc, "assigning mixers to device tree\n");
	hdafg_assign_mixers(sc);

	hda_debug(sc, "preparing pin controls\n");
	hdafg_prepare_pin_controls(sc);
	hda_debug(sc, "commiting settings\n");
	hdafg_commit(sc);

	hda_debug(sc, "setup jack sensing\n");
	hdafg_hp_switch_init(sc);

	hda_debug(sc, "building mixer controls\n");
	hdafg_build_mixers(sc);

	hdafg_dump(sc);
	if (1) hdafg_widget_pin_dump(sc);
	hdafg_assoc_dump(sc);

	hda_debug(sc, "enabling analog beep\n");
	hdafg_enable_analog_beep(sc);

	hda_debug(sc, "configuring encodings\n");
	sc->sc_audiodev.ad_sc = sc;
	hdafg_configure_encodings(sc);

	hda_debug(sc, "reserving streams\n");
	sc->sc_audiodev.ad_capture = hdaudio_stream_establish(sc->sc_host,
	    HDAUDIO_STREAM_ISS, hdafg_stream_intr, &sc->sc_audiodev);
	sc->sc_audiodev.ad_playback = hdaudio_stream_establish(sc->sc_host,
	    HDAUDIO_STREAM_OSS, hdafg_stream_intr, &sc->sc_audiodev);

	hda_debug(sc, "connecting streams\n");
	defparams.channels = 2;
	defparams.sample_rate = sc->sc_fixed_rate ? sc->sc_fixed_rate : 48000;
	defparams.precision = defparams.validbits = 16;
	defparams.encoding = AUDIO_ENCODING_SLINEAR_LE;
	sc->sc_pparam = sc->sc_rparam = defparams;
	hdafg_stream_connect(sc, AUMODE_PLAY);
	hdafg_stream_connect(sc, AUMODE_RECORD);

	for (i = 0; i < sc->sc_nassocs; i++) {
		astype |= (1 << sc->sc_assocs[i].as_digital);
	}
	hda_debug(sc, "assoc type mask: %x\n", astype);

#ifndef HDAUDIO_ENABLE_HDMI
	astype &= ~(1 << HDAFG_AS_HDMI);
#endif
#ifndef HDAUDIO_ENABLE_DISPLAYPORT
	astype &= ~(1 << HDAFG_AS_DISPLAYPORT);
#endif

	if (astype == 0)
		return;

	hda_debug(sc, "attaching audio device\n");
	sc->sc_audiodev.ad_audiodev = audio_attach_mi(&hdafg_hw_if,
	    &sc->sc_audiodev, self);
}

static int
hdafg_detach(device_t self, int flags)
{
	struct hdafg_softc *sc = device_private(self);
	struct hdaudio_widget *wl, *w = sc->sc_widgets;
	struct hdaudio_assoc *as = sc->sc_assocs;
	struct hdaudio_control *ctl = sc->sc_ctls;
	struct hdaudio_mixer *mx = sc->sc_mixers;
	int nid;

	if (sc->sc_jack_polling) {
		int error __diagused;

		mutex_enter(&sc->sc_jack_lock);
		sc->sc_jack_dying = true;
		cv_broadcast(&sc->sc_jack_cv);
		mutex_exit(&sc->sc_jack_lock);
		error = kthread_join(sc->sc_jack_thread);
		KASSERTMSG(error == 0, "error=%d", error);
	}

	if (sc->sc_config)
		prop_object_release(sc->sc_config);
	if (sc->sc_audiodev.ad_audiodev)
		config_detach(sc->sc_audiodev.ad_audiodev, flags);
	if (sc->sc_audiodev.ad_playback)
		hdaudio_stream_disestablish(sc->sc_audiodev.ad_playback);
	if (sc->sc_audiodev.ad_capture)
		hdaudio_stream_disestablish(sc->sc_audiodev.ad_capture);

	/* restore bios pin widget configuration */
	for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
		wl = hdafg_widget_lookup(sc, nid);
		if (wl == NULL || wl->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			continue;
		hdafg_widget_setconfig(wl, wl->w_pin.biosconfig);
	}

	if (w)
		kmem_free(w, sc->sc_nwidgets * sizeof(*w));
	if (as)
		kmem_free(as, sc->sc_nassocs * sizeof(*as));
	if (ctl)
		kmem_free(ctl, sc->sc_nctls * sizeof(*ctl));
	if (mx)
		kmem_free(mx, sc->sc_nmixers * sizeof(*mx));

	mutex_destroy(&sc->sc_lock);
	mutex_destroy(&sc->sc_intr_lock);

	pmf_device_deregister(self);

	return 0;
}

static void
hdafg_childdet(device_t self, device_t child)
{
	struct hdafg_softc *sc = device_private(self);

	if (child == sc->sc_audiodev.ad_audiodev)
		sc->sc_audiodev.ad_audiodev = NULL;
}

static bool
hdafg_suspend(device_t self, const pmf_qual_t *qual)
{
	struct hdafg_softc *sc = device_private(self);

	if (sc->sc_jack_polling) {
		mutex_enter(&sc->sc_jack_lock);
		KASSERT(!sc->sc_jack_suspended);
		sc->sc_jack_suspended = true;
		mutex_exit(&sc->sc_jack_lock);
	}

	return true;
}

static bool
hdafg_resume(device_t self, const pmf_qual_t *qual)
{
	struct hdafg_softc *sc = device_private(self);
	struct hdaudio_widget *w;
	int nid;

	hdaudio_command(sc->sc_codec, sc->sc_nid,
	    CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
	hda_delay(100);
	for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
		hdaudio_command(sc->sc_codec, nid,
		    CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
		w = hdafg_widget_lookup(sc, nid);

		/* restore pin widget configuration */
		if (w == NULL || w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
			continue;
		hdafg_widget_setconfig(w, w->w_pin.config);
	}
	hda_delay(1000);

	hdafg_commit(sc);
	hdafg_stream_connect(sc, AUMODE_PLAY);
	hdafg_stream_connect(sc, AUMODE_RECORD);

	if (sc->sc_jack_polling) {
		mutex_enter(&sc->sc_jack_lock);
		KASSERT(sc->sc_jack_suspended);
		sc->sc_jack_suspended = false;
		cv_broadcast(&sc->sc_jack_cv);
		mutex_exit(&sc->sc_jack_lock);
	}

	return true;
}

static int
hdafg_query_format(void *opaque, audio_format_query_t *afp)
{
	struct hdaudio_audiodev *ad = opaque;

	return audio_query_format(ad->ad_formats, ad->ad_nformats, afp);
}

static int
hdafg_set_format(void *opaque, int setmode,
    const audio_params_t *play, const audio_params_t *rec,
    audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
	struct hdaudio_audiodev *ad = opaque;

	if (play && (setmode & AUMODE_PLAY)) {
		ad->ad_sc->sc_pparam = *play;
		hdafg_stream_connect(ad->ad_sc, AUMODE_PLAY);
	}
	if (rec && (setmode & AUMODE_RECORD)) {
		ad->ad_sc->sc_rparam = *rec;
		hdafg_stream_connect(ad->ad_sc, AUMODE_RECORD);
	}
	return 0;
}

/* LCM for round_blocksize */
static u_int gcd(u_int, u_int);
static u_int lcm(u_int, u_int);

static u_int gcd(u_int a, u_int b)
{

	return (b == 0) ? a : gcd(b, a % b);
}
static u_int lcm(u_int a, u_int b)
{

	return a * b / gcd(a, b);
}

static int
hdafg_round_blocksize(void *opaque, int blksize, int mode,
    const audio_params_t *param)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdaudio_stream *st;
	u_int minblksize;
	int bufsize;

	st = (mode == AUMODE_PLAY) ? ad->ad_playback : ad->ad_capture;
	if (st == NULL) {
		hda_trace(ad->ad_sc,
		    "round_blocksize called for invalid stream\n");
		return 128;
	}

	if (blksize > 8192)
		blksize = 8192;

	/* Make sure there are enough BDL descriptors */
	bufsize = st->st_data.dma_size;
	if (bufsize > HDAUDIO_BDL_MAX * blksize) {
		blksize = bufsize / HDAUDIO_BDL_MAX;
	}

	/*
	 * HD audio's buffer constraint looks like following:
	 * - The buffer MUST start on a 128bytes boundary.
	 * - The buffer size MUST be one sample or more.
	 * - The buffer size is preferred multiple of 128bytes for efficiency.
	 *
	 * https://www.intel.co.jp/content/www/jp/ja/standards/high-definition-audio-specification.html , p70.
	 *
	 * Also, the audio layer requires that the blocksize must be a
	 * multiple of the number of channels.
	 */
	minblksize = lcm(128, param->channels);
	blksize = rounddown(blksize, minblksize);
	if (blksize < minblksize)
		blksize = minblksize;

	return blksize;
}

static int
hdafg_commit_settings(void *opaque)
{
	return 0;
}

static int
hdafg_halt_output(void *opaque)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdafg_softc *sc = ad->ad_sc;
	struct hdaudio_assoc *as = ad->ad_sc->sc_assocs;
	struct hdaudio_widget *w;
	uint16_t dfmt;
	int i, j;

	/* Disable digital outputs */
	for (i = 0; i < sc->sc_nassocs; i++) {
		if (as[i].as_enable == false)
			continue;
		if (as[i].as_dir != HDAUDIO_PINDIR_OUT)
			continue;
		for (j = 0; j < HDAUDIO_MAXPINS; j++) {
			if (as[i].as_dacs[j] == 0)
				continue;
			w = hdafg_widget_lookup(sc, as[i].as_dacs[j]);
			if (w == NULL || w->w_enable == false)
				continue;
			if (w->w_p.aw_cap & COP_AWCAP_DIGITAL) {
				dfmt = hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_GET_DIGITAL_CONVERTER_CONTROL, 0) &
				    0xff;
				dfmt &= ~COP_DIGITAL_CONVCTRL1_DIGEN;
				hdaudio_command(sc->sc_codec, w->w_nid,
				    CORB_SET_DIGITAL_CONVERTER_CONTROL_1, dfmt);
			}
		}
	}

	hdaudio_stream_stop(ad->ad_playback);

	return 0;
}

static int
hdafg_halt_input(void *opaque)
{
	struct hdaudio_audiodev *ad = opaque;

	hdaudio_stream_stop(ad->ad_capture);

	return 0;
}

static int
hdafg_getdev(void *opaque, struct audio_device *audiodev)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdafg_softc *sc = ad->ad_sc;

	hdaudio_findvendor(audiodev->name, sizeof(audiodev->name),
	    sc->sc_vendor);
	hdaudio_findproduct(audiodev->version, sizeof(audiodev->version),
	    sc->sc_vendor, sc->sc_product);
	snprintf(audiodev->config, sizeof(audiodev->config),
	    "%02Xh", sc->sc_nid);

	return 0;
}

static int
hdafg_set_port(void *opaque, mixer_ctrl_t *mc)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdafg_softc *sc = ad->ad_sc;
	struct hdaudio_mixer *mx;
	struct hdaudio_control *ctl;
	int i, divisor;

	if (mc->dev < 0 || mc->dev >= sc->sc_nmixers)
		return EINVAL;
	mx = &sc->sc_mixers[mc->dev];
	ctl = mx->mx_ctl;
	if (ctl == NULL) {
		if (mx->mx_di.type != AUDIO_MIXER_SET)
			return ENXIO;
		if (mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_OUTPUTS &&
		    mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_RECORD)
			return ENXIO;
		for (i = 0; i < sc->sc_nassocs; i++) {
			if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_OUT &&
			    mx->mx_di.mixer_class ==
			    HDAUDIO_MIXER_CLASS_OUTPUTS)
				continue;
			if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_IN &&
			    mx->mx_di.mixer_class ==
			    HDAUDIO_MIXER_CLASS_RECORD)
				continue;
			sc->sc_assocs[i].as_activated =
			    (mc->un.mask & (1 << i)) ? true : false;
		}
		hdafg_stream_connect(ad->ad_sc,
		    mx->mx_di.mixer_class == HDAUDIO_MIXER_CLASS_OUTPUTS ?
		    AUMODE_PLAY : AUMODE_RECORD);
		return 0;
	}

	switch (mx->mx_di.type) {
	case AUDIO_MIXER_VALUE:
		if (ctl->ctl_step == 0)
			divisor = 128; /* ??? - just avoid div by 0 */
		else
			divisor = 255 / ctl->ctl_step;

		hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_NONE,
		  mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] / divisor,
		  mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] / divisor);
		break;
	case AUDIO_MIXER_ENUM:
		hdafg_control_amp_set(ctl,
		    mc->un.ord ? HDAUDIO_AMP_MUTE_ALL : HDAUDIO_AMP_MUTE_NONE,
		    ctl->ctl_left, ctl->ctl_right);
		break;
	default:
		return ENXIO;
	}

	return 0;
}

static int
hdafg_get_port(void *opaque, mixer_ctrl_t *mc)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdafg_softc *sc = ad->ad_sc;
	struct hdaudio_mixer *mx;
	struct hdaudio_control *ctl;
	u_int mask = 0;
	int i, factor;

	if (mc->dev < 0 || mc->dev >= sc->sc_nmixers)
		return EINVAL;
	mx = &sc->sc_mixers[mc->dev];
	ctl = mx->mx_ctl;
	if (ctl == NULL) {
		if (mx->mx_di.type != AUDIO_MIXER_SET)
			return ENXIO;
		if (mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_OUTPUTS &&
		    mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_RECORD)
			return ENXIO;
		for (i = 0; i < sc->sc_nassocs; i++) {
			if (sc->sc_assocs[i].as_enable == false)
				continue;
			if (sc->sc_assocs[i].as_activated == false)
				continue;
			if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_OUT &&
			    mx->mx_di.mixer_class ==
			    HDAUDIO_MIXER_CLASS_OUTPUTS)
				mask |= (1 << i);
			if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_IN &&
			    mx->mx_di.mixer_class ==
			    HDAUDIO_MIXER_CLASS_RECORD)
				mask |= (1 << i);
		}
		mc->un.mask = mask;
		return 0;
	}

	switch (mx->mx_di.type) {
	case AUDIO_MIXER_VALUE:
		if (ctl->ctl_step == 0)
			factor = 128; /* ??? - just avoid div by 0 */
		else
			factor = 255 / ctl->ctl_step;

		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = ctl->ctl_left * factor;
		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = ctl->ctl_right * factor;
		break;
	case AUDIO_MIXER_ENUM:
		mc->un.ord = (ctl->ctl_muted || ctl->ctl_forcemute) ? 1 : 0;
		break;
	default:
		return ENXIO;
	}
	return 0;
}

static int
hdafg_query_devinfo(void *opaque, mixer_devinfo_t *di)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdafg_softc *sc = ad->ad_sc;

	if (di->index < 0 || di->index >= sc->sc_nmixers)
		return ENXIO;

	*di = sc->sc_mixers[di->index].mx_di;

	return 0;
}

static void *
hdafg_allocm(void *opaque, int direction, size_t size)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdaudio_stream *st;
	int err;

	st = (direction == AUMODE_PLAY) ? ad->ad_playback : ad->ad_capture;
	if (st == NULL)
		return NULL;

	if (st->st_data.dma_valid == true)
		hda_error(ad->ad_sc, "WARNING: allocm leak\n");

	st->st_data.dma_size = size;
	err = hdaudio_dma_alloc(st->st_host, &st->st_data,
	    BUS_DMA_COHERENT | BUS_DMA_NOCACHE);
	if (err || st->st_data.dma_valid == false)
		return NULL;

	return DMA_KERNADDR(&st->st_data);
}

static void
hdafg_freem(void *opaque, void *addr, size_t size)
{
	struct hdaudio_audiodev *ad = opaque;
	struct hdaudio_stream *st;

	if (ad == NULL)
		return;

	if (ad->ad_playback != NULL && addr == DMA_KERNADDR(&ad->ad_playback->st_data))
		st = ad->ad_playback;
	else if (ad->ad_capture != NULL && addr == DMA_KERNADDR(&ad->ad_capture->st_data))
		st = ad->ad_capture;
	else
		return;

	hdaudio_dma_free(st->st_host, &st->st_data);
}

static int
hdafg_get_props(void *opaque)
{
	struct hdaudio_audiodev *ad = opaque;
	int props = 0;

	if (ad->ad_playback)
		props |= AUDIO_PROP_PLAYBACK;
	if (ad->ad_capture)
		props |= AUDIO_PROP_CAPTURE;
	if (ad->ad_playback && ad->ad_capture) {
		props |= AUDIO_PROP_FULLDUPLEX;
		props |= AUDIO_PROP_INDEPENDENT;
	}

	return props;
}

static int
hdafg_trigger_output(void *opaque, void *start, void *end, int blksize,
    void (*intr)(void *), void *intrarg, const audio_params_t *param)
{
	struct hdaudio_audiodev *ad = opaque;
	bus_size_t dmasize;

	if (ad->ad_playback == NULL)
		return ENXIO;
	if (ad->ad_playback->st_data.dma_valid == false)
		return ENOMEM;

	ad->ad_playbackintr = intr;
	ad->ad_playbackintrarg = intrarg;

	dmasize = (char *)end - (char *)start;
	hdafg_stream_connect(ad->ad_sc, AUMODE_PLAY);
	hdaudio_stream_start(ad->ad_playback, blksize, dmasize,
	    &ad->ad_sc->sc_pparam);

	return 0;
}

static int
hdafg_trigger_input(void *opaque, void *start, void *end, int blksize,
    void (*intr)(void *), void *intrarg, const audio_params_t *param)
{
	struct hdaudio_audiodev *ad = opaque;
	bus_size_t dmasize;

	if (ad->ad_capture == NULL)
		return ENXIO;
	if (ad->ad_capture->st_data.dma_valid == false)
		return ENOMEM;

	ad->ad_captureintr = intr;
	ad->ad_captureintrarg = intrarg;

	dmasize = (char *)end - (char *)start;
	hdafg_stream_connect(ad->ad_sc, AUMODE_RECORD);
	hdaudio_stream_start(ad->ad_capture, blksize, dmasize,
	    &ad->ad_sc->sc_rparam);

	return 0;
}

static void
hdafg_get_locks(void *opaque, kmutex_t **intr, kmutex_t **thread)
{
	struct hdaudio_audiodev *ad = opaque;

	*intr = &ad->ad_sc->sc_intr_lock;
	*thread = &ad->ad_sc->sc_lock;
}

static int
hdafg_unsol(device_t self, uint8_t tag)
{
	struct hdafg_softc *sc = device_private(self);
	struct hdaudio_assoc *as = sc->sc_assocs;
	int i, j;

	switch (tag) {
	case HDAUDIO_UNSOLTAG_EVENT_DD:
#ifdef HDAFG_HDMI_DEBUG
		hda_print(sc, "unsol: display device hotplug\n");
#endif
		for (i = 0; i < sc->sc_nassocs; i++) {
			if (as[i].as_displaydev == false)
				continue;
			for (j = 0; j < HDAUDIO_MAXPINS; j++) {
				if (as[i].as_pins[j] == 0)
					continue;
				hdafg_assoc_dump_dd(sc, &as[i], j, 0);
			}
		}
		break;
	default:
#ifdef HDAFG_HDMI_DEBUG
		hda_print(sc, "unsol: tag=%u\n", tag);
#endif
		break;
	}

	return 0;
}

static int
hdafg_widget_info(void *opaque, prop_dictionary_t request,
    prop_dictionary_t response)
{
	struct hdafg_softc *sc = opaque;
	struct hdaudio_widget *w;
	prop_array_t connlist;
	uint32_t config, wcap;
	uint16_t index;
	int nid;
	int i;

	if (prop_dictionary_get_uint16(request, "index", &index) == false)
		return EINVAL;

	nid = sc->sc_startnode + index;
	if (nid >= sc->sc_endnode)
		return EINVAL;

	w = hdafg_widget_lookup(sc, nid);
	if (w == NULL)
		return ENXIO;
	wcap = hda_get_wparam(w, PIN_CAPABILITIES);
	config = hdaudio_command(sc->sc_codec, w->w_nid,
	    CORB_GET_CONFIGURATION_DEFAULT, 0);
	prop_dictionary_set_cstring_nocopy(response, "name", w->w_name);
	prop_dictionary_set_bool(response, "enable", w->w_enable);
	prop_dictionary_set_uint8(response, "nid", w->w_nid);
	prop_dictionary_set_uint8(response, "type", w->w_type);
	prop_dictionary_set_uint32(response, "config", config);
	prop_dictionary_set_uint32(response, "cap", wcap);
	if (w->w_nconns == 0)
		return 0;
	connlist = prop_array_create();
	for (i = 0; i < w->w_nconns; i++) {
		if (w->w_conns[i] == 0)
			continue;
		prop_array_add(connlist,
		    prop_number_create_unsigned_integer(w->w_conns[i]));
	}
	prop_dictionary_set(response, "connlist", connlist);
	prop_object_release(connlist);
	return 0;
}

static int
hdafg_codec_info(void *opaque, prop_dictionary_t request,
    prop_dictionary_t response)
{
	struct hdafg_softc *sc = opaque;
	prop_dictionary_set_uint16(response, "vendor-id",
	    sc->sc_vendor);
	prop_dictionary_set_uint16(response, "product-id",
	    sc->sc_product);
	return 0;
}

MODULE(MODULE_CLASS_DRIVER, hdafg, "hdaudio");

#ifdef _MODULE
#include "ioconf.c"
#endif

static int
hdafg_modcmd(modcmd_t cmd, void *opaque)
{
	int error = 0;

	switch (cmd) {
	case MODULE_CMD_INIT:
#ifdef _MODULE
		error = config_init_component(cfdriver_ioconf_hdafg,
		    cfattach_ioconf_hdafg, cfdata_ioconf_hdafg);
#endif
		return error;
	case MODULE_CMD_FINI:
#ifdef _MODULE
		error = config_fini_component(cfdriver_ioconf_hdafg,
		    cfattach_ioconf_hdafg, cfdata_ioconf_hdafg);
#endif
		return error;
	default:
		return ENOTTY;
	}
}

#define HDAFG_GET_ANACTRL		0xfe0
#define HDAFG_SET_ANACTRL		0x7e0
#define HDAFG_ANALOG_BEEP_EN		__BIT(5)
#define HDAFG_ALC231_MONO_OUT_MIXER	0xf
#define HDAFG_STAC9200_AFG		0x1
#define HDAFG_STAC9200_GET_ANACTRL_PAYLOAD	0x0
#define HDAFG_ALC231_INPUT_BOTH_CHANNELS_UNMUTE 0x7100

static void
hdafg_enable_analog_beep(struct hdafg_softc *sc)
{
	int nid;
	uint32_t response;

	switch (sc->sc_vendor) {
	case HDAUDIO_VENDOR_SIGMATEL:
		switch (sc->sc_product) {
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9200:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9200D:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9202:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9202D:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9204:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9204D:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9205:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9205_1:
		case HDAUDIO_PRODUCT_SIGMATEL_STAC9205D:
			nid = HDAFG_STAC9200_AFG;

			response = hdaudio_command(sc->sc_codec, nid,
			    HDAFG_GET_ANACTRL,
			    HDAFG_STAC9200_GET_ANACTRL_PAYLOAD);
			hda_delay(100);

			response |= HDAFG_ANALOG_BEEP_EN;

			hdaudio_command(sc->sc_codec, nid, HDAFG_SET_ANACTRL,
			    response);
			hda_delay(100);
			break;
		default:
			break;
		}
		break;
	case HDAUDIO_VENDOR_REALTEK:
		switch (sc->sc_product) {
		case HDAUDIO_PRODUCT_REALTEK_ALC269:
			/* The Panasonic Toughbook CF19 - Mk 5 uses a Realtek
			 * ALC231 that identifies as an ALC269.
			 * This unmutes the PCBEEP on the speaker.
			 */
			nid = HDAFG_ALC231_MONO_OUT_MIXER;
			response = hdaudio_command(sc->sc_codec, nid,
			    CORB_SET_AMPLIFIER_GAIN_MUTE,
			    HDAFG_ALC231_INPUT_BOTH_CHANNELS_UNMUTE);
			hda_delay(100);
			break;
		default:
			break;
		}
	default:
		break;
	}
}