root/sys/dev/hdaudio/hdafg.c
/* $NetBSD: hdafg.c,v 1.34 2025/11/03 22:45:25 buhrow 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.34 2025/11/03 22:45:25 buhrow 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;

        char                            sc_name[MAX_AUDIO_DEV_LEN];
        char                            sc_version[MAX_AUDIO_DEV_LEN];
};

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 = sc->sc_startnode; 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;
                }

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

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;
                if (ctl->ctl_step > 0)
                        ++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) &&
                            sc->sc_ctls[i].ctl_step > 0) {
                                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.prev = index > 0 ? index - 1 : 0;
                mx[index].mx_di.next = index + 1;
                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) {
                if (masterctl->ctl_step > 0) {
                        mx[index].mx_ctl = masterctl;
                        mx[index].mx_di.index = index;
                        mx[index].mx_di.type = AUDIO_MIXER_VALUE;
                        mx[index].mx_di.prev = index > 0 ? index - 1 : 0;
                        mx[index].mx_di.next = index + 1;
                        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;
                        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) {
                        if (masterctl->ctl_step > 0) {
                                mx[index - 1].mx_di.next = index;
                                mx[index] = mx[index - 1];
                                mx[index].mx_di.prev = index;
                        }
                        mx[index].mx_ctl = masterctl;
                        mx[index].mx_di.index = index;
                        mx[index].mx_di.type = AUDIO_MIXER_ENUM;
                        mx[index].mx_di.prev = index > 0 ? index - 1 : 0;
                        mx[index].mx_di.next = index + 1;
                        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;

                switch (audiodev) {
                case HDAUDIO_MIXER_VOLUME:
                case HDAUDIO_MIXER_BASS:
                case HDAUDIO_MIXER_TREBLE:
                case HDAUDIO_MIXER_OGAIN:
                case HDAUDIO_MIXER_SPEAKER:
                case HDAUDIO_MIXER_PHONEOUT:
                        mx[index].mx_di.mixer_class =
                            HDAUDIO_MIXER_CLASS_OUTPUTS;
                        break;
                case HDAUDIO_MIXER_MIC:
                case HDAUDIO_MIXER_MONITOR:
                        mx[index].mx_di.mixer_class =
                            HDAUDIO_MIXER_CLASS_RECORD;
                        break;
                default:
                        mx[index].mx_di.mixer_class =
                            HDAUDIO_MIXER_CLASS_INPUTS;
                        break;
                }

                if (ctl->ctl_step > 0) {
                        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]++;
                        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;
                        strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);
                        hda_trace(sc, "  adding vol control %s idx %d\n",
                            mx[index].mx_di.label.name, index);
                        ++index;
                }

                if (ctl->ctl_mute) {
                        if (ctl->ctl_step > 0) {
                                /* there is a parent vol control */
                                mx[index] = mx[index - 1];
                                snprintf(mx[index].mx_di.label.name,
                                    sizeof(mx[index].mx_di.label.name),
                                    "%s." AudioNmute,
                                    mx[index - 1].mx_di.label.name);
                        } else {
                                if (ctrlcnt[audiodev] > 0)
                                        snprintf(mx[index].mx_di.label.name,
                                            sizeof(mx[index].mx_di.label.name),
                                            "%s%d." AudioNmute,
                                            hdafg_mixer_names[audiodev],
                                            ctrlcnt[audiodev] + 1);
                                else
                                        snprintf(mx[index].mx_di.label.name,
                                            sizeof(mx[index].mx_di.label.name),
                                            "%s." AudioNmute,
                                            hdafg_mixer_names[audiodev]);
                                ctrlcnt[audiodev]++;
                        }
                        mx[index].mx_ctl = ctl;
                        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;
                        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;
                        hda_trace(sc, "  adding mute control %s idx %d\n",
                            mx[index].mx_di.label.name, index);
                        ++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;
        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(sc->sc_name, sizeof(sc->sc_name), sc->sc_vendor);
        hdaudio_findproduct(sc->sc_version, sizeof(sc->sc_version), sc->sc_vendor,
            sc->sc_product);
        hda_print1(sc, ": %s %s%s\n", sc->sc_name, sc->sc_version,
            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, "committing 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);

        if (sc->sc_audiodev.ad_capture == NULL &&
            sc->sc_audiodev.ad_playback == NULL) {
                hda_error(sc, "couldn't find any input or output streams\n");
                return;
        }

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

        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) {
                mutex_enter(&sc->sc_jack_lock);
                sc->sc_jack_dying = true;
                cv_broadcast(&sc->sc_jack_cv);
                mutex_exit(&sc->sc_jack_lock);
        }

        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;

        memcpy(audiodev->name, sc->sc_name, sizeof(audiodev->name));
        memcpy(audiodev->version, sc->sc_version, sizeof(audiodev->version));
        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:
                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:
                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->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
                panic("bad hdafg hwbuf mem: %p (%zu bytes)", addr, size);

        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_string_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_and_rel(connlist,
                    prop_number_create_unsigned(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;
        }
}