/* $NetBSD: hdafg.c,v 1.32 2024/01/29 18:58:54 riastradh Exp $ */

/*
* Copyright (c) 2009 Precedence Technologies Ltd <[email protected]>
* Copyright (c) 2009-2011 Jared D. McNeill <[email protected]>
* 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 <[email protected]>
* Copyright (c) 2006 Ariff Abdullah <[email protected]>
* Copyright (c) 2008 Alexander Motin <[email protected]>
* 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.32 2024/01/29 18:58:54 riastradh 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;
               ++nmixers;
               if (ctl->ctl_mute)
                       ++nmixers;
       }

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

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

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

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

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

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

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

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

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

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

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

               ++index;

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

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

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

       sc->sc_mixers = mx;
}

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

       /* Commit controls */
       hdafg_control_commit(sc);

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

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

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

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

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

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

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

       if (sc->sc_disable_dip)
               return;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

       return handled;
}

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

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

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

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

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

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

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

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

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

#undef HDAUDIO_INITFMT

       return true;
}


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

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

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

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

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

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

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

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

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

       hda_print1(sc, "\n");
}

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

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

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

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

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

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

       KASSERT(sc->sc_jack_polling);

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

       kthread_exit(0);
}

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

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

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

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

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

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

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

static void
hdafg_attach(device_t parent, device_t self, void *opaque)
{
       struct hdafg_softc *sc = device_private(self);
       audio_params_t defparams;
       prop_dictionary_t args = opaque;
       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) {
               int error __diagused;

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

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

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

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

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

       pmf_device_deregister(self);

       return 0;
}

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

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

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

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

       return true;
}

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

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

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

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

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

       return true;
}

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

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

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

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

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

static u_int gcd(u_int a, u_int b)
{

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

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

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

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

       if (blksize > 8192)
               blksize = 8192;

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

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

       return blksize;
}

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

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

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

       hdaudio_stream_stop(ad->ad_playback);

       return 0;
}

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

       hdaudio_stream_stop(ad->ad_capture);

       return 0;
}

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

       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:
               if (ctl->ctl_step == 0)
                       divisor = 128; /* ??? - just avoid div by 0 */
               else
                       divisor = 255 / ctl->ctl_step;

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

       return 0;
}

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

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

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

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

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

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

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

       return 0;
}

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

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

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

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

       return DMA_KERNADDR(&st->st_data);
}

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

       if (ad->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;
       }
}