/*	$OpenBSD: azalia.c,v 1.222 2015/07/29 08:06:29 ratchov Exp $	*/
/*	$NetBSD: azalia.c,v 1.20 2006/05/07 08:31:44 kent Exp $	*/

/*-
 * Copyright (c) 2005 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by TAMURA Kent
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/*
 * High Definition Audio Specification
 *	ftp://download.intel.com/standards/hdaudio/pdf/HDAudio_03.pdf
 *
 *
 * TO DO:
 *  - multiple codecs (needed?)
 *  - multiple streams (needed?)
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/timeout.h>
#include <dev/audio_if.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/pcivar.h>

#include <dev/pci/azalia.h>

typedef struct audio_params audio_params_t;

struct audio_format {
	void *driver_data;
	int32_t mode;
	u_int encoding;
	u_int precision;
	u_int channels;

	/**
	 * 0: frequency[0] is lower limit, and frequency[1] is higher limit.
	 * 1-16: frequency[0] to frequency[frequency_type-1] are valid.
	 */
	u_int frequency_type;

#define	AUFMT_MAX_FREQUENCIES	16
	/**
	 * sampling rates
	 */
	u_int frequency[AUFMT_MAX_FREQUENCIES];
};


#ifdef AZALIA_DEBUG
# define DPRINTFN(n,x)	do { if (az_debug > (n)) printf x; } while (0/*CONSTCOND*/)
int az_debug = 0;
#else
# define DPRINTFN(n,x)	do {} while (0/*CONSTCOND*/)
#endif


/* ----------------------------------------------------------------
 * ICH6/ICH7 constant values
 * ---------------------------------------------------------------- */

/* PCI registers */
#define ICH_PCI_HDBARL	0x10
#define ICH_PCI_HDBARU	0x14
#define ICH_PCI_HDCTL	0x40
#define		ICH_PCI_HDCTL_CLKDETCLR		0x08
#define		ICH_PCI_HDCTL_CLKDETEN		0x04
#define		ICH_PCI_HDCTL_CLKDETINV		0x02
#define		ICH_PCI_HDCTL_SIGNALMODE	0x01
#define ICH_PCI_HDTCSEL	0x44
#define		ICH_PCI_HDTCSEL_MASK	0x7
#define ICH_PCI_MMC	0x62
#define		ICH_PCI_MMC_ME		0x1

/* internal types */

typedef struct {
	bus_dmamap_t map;
	caddr_t addr;		/* kernel virtual address */
	bus_dma_segment_t segments[1];
	size_t size;
} azalia_dma_t;
#define AZALIA_DMA_DMAADDR(p)	((p)->map->dm_segs[0].ds_addr)

typedef struct {
	struct azalia_t *az;
	int regbase;
	int number;
	int dir;		/* AUMODE_PLAY or AUMODE_RECORD */
	uint32_t intr_bit;
	azalia_dma_t bdlist;
	azalia_dma_t buffer;
	void (*intr)(void*);
	void *intr_arg;
	int bufsize;
	uint16_t fmt;
	int blk;
	unsigned int swpos;		/* position in the audio(4) layer */
} stream_t;
#define STR_READ_1(s, r)	\
	bus_space_read_1((s)->az->iot, (s)->az->ioh, (s)->regbase + HDA_SD_##r)
#define STR_READ_2(s, r)	\
	bus_space_read_2((s)->az->iot, (s)->az->ioh, (s)->regbase + HDA_SD_##r)
#define STR_READ_4(s, r)	\
	bus_space_read_4((s)->az->iot, (s)->az->ioh, (s)->regbase + HDA_SD_##r)
#define STR_WRITE_1(s, r, v)	\
	bus_space_write_1((s)->az->iot, (s)->az->ioh, (s)->regbase + HDA_SD_##r, v)
#define STR_WRITE_2(s, r, v)	\
	bus_space_write_2((s)->az->iot, (s)->az->ioh, (s)->regbase + HDA_SD_##r, v)
#define STR_WRITE_4(s, r, v)	\
	bus_space_write_4((s)->az->iot, (s)->az->ioh, (s)->regbase + HDA_SD_##r, v)

typedef struct azalia_t {
	struct device dev;

	pci_chipset_tag_t pc;
	pcitag_t tag;
	void *ih;
	bus_space_tag_t iot;
	bus_space_handle_t ioh;
	bus_size_t map_size;
	bus_dma_tag_t dmat;
	pcireg_t pciid;
	uint32_t subid;

	codec_t *codecs;
	int ncodecs;		/* number of codecs */
	int codecno;		/* index of the using codec */
	int detached;		/* 1 if failed to initialize, 2 if
				 * azalia_pci_detach has run
				 */
	azalia_dma_t corb_dma;
	int corb_entries;
	uint8_t corbsize;
	azalia_dma_t rirb_dma;
	int rirb_entries;
	uint8_t rirbsize;
	int rirb_rp;
#define UNSOLQ_SIZE	256
	rirb_entry_t *unsolq;
	int unsolq_wp;
	int unsolq_rp;
	int unsolq_kick;
	struct timeout unsol_to;

	int ok64;
	int nistreams, nostreams, nbstreams;
	stream_t pstream;
	stream_t rstream;
} azalia_t;
#define XNAME(sc)		((sc)->dev.dv_xname)
#define AZ_READ_1(z, r)		bus_space_read_1((z)->iot, (z)->ioh, HDA_##r)
#define AZ_READ_2(z, r)		bus_space_read_2((z)->iot, (z)->ioh, HDA_##r)
#define AZ_READ_4(z, r)		bus_space_read_4((z)->iot, (z)->ioh, HDA_##r)
#define AZ_WRITE_1(z, r, v)	bus_space_write_1((z)->iot, (z)->ioh, HDA_##r, v)
#define AZ_WRITE_2(z, r, v)	bus_space_write_2((z)->iot, (z)->ioh, HDA_##r, v)
#define AZ_WRITE_4(z, r, v)	bus_space_write_4((z)->iot, (z)->ioh, HDA_##r, v)


/* prototypes */
uint8_t azalia_pci_read(pci_chipset_tag_t, pcitag_t, int);
void	azalia_pci_write(pci_chipset_tag_t, pcitag_t, int, uint8_t);
int	azalia_pci_match(struct device *, void *, void *);
void	azalia_pci_attach(struct device *, struct device *, void *);
int	azalia_pci_activate(struct device *, int);
int	azalia_pci_detach(struct device *, int);
void	azalia_configure_pci(azalia_t *);
int	azalia_intr(void *);
void	azalia_print_codec(codec_t *);
int	azalia_reset(azalia_t *);
int	azalia_get_ctrlr_caps(azalia_t *);
int	azalia_init(azalia_t *, int);
int	azalia_init_codecs(azalia_t *);
int	azalia_init_streams(azalia_t *);
void	azalia_shutdown(void *);
int	azalia_halt_corb(azalia_t *);
int	azalia_init_corb(azalia_t *, int);
int	azalia_halt_rirb(azalia_t *);
int	azalia_init_rirb(azalia_t *, int);
int	azalia_set_command(azalia_t *, nid_t, int, uint32_t, uint32_t);
int	azalia_get_response(azalia_t *, uint32_t *);
void	azalia_rirb_kick_unsol_events(void *);
void	azalia_rirb_intr(azalia_t *);
int	azalia_alloc_dmamem(azalia_t *, size_t, size_t, azalia_dma_t *);
int	azalia_free_dmamem(const azalia_t *, azalia_dma_t*);

int	azalia_codec_init(codec_t *);
int	azalia_codec_delete(codec_t *);
void	azalia_codec_add_bits(codec_t *, int, uint32_t, int);
void	azalia_codec_add_format(codec_t *, int, int, uint32_t, int32_t);
int	azalia_codec_connect_stream(stream_t *);
int	azalia_codec_disconnect_stream(stream_t *);
void	azalia_codec_print_audiofunc(const codec_t *);
void	azalia_codec_print_groups(const codec_t *);
int	azalia_codec_find_defdac(codec_t *, int, int);
int	azalia_codec_find_defadc(codec_t *, int, int);
int	azalia_codec_find_defadc_sub(codec_t *, nid_t, int, int);
int	azalia_codec_init_volgroups(codec_t *);
int	azalia_codec_sort_pins(codec_t *);
int	azalia_codec_select_micadc(codec_t *);
int	azalia_codec_select_dacs(codec_t *);
int	azalia_codec_select_spkrdac(codec_t *);
int	azalia_codec_find_inputmixer(codec_t *);

int	azalia_widget_init(widget_t *, const codec_t *, int);
int	azalia_widget_label_widgets(codec_t *);
int	azalia_widget_init_audio(widget_t *, const codec_t *);
int	azalia_widget_init_pin(widget_t *, const codec_t *);
int	azalia_widget_init_connection(widget_t *, const codec_t *);
int	azalia_widget_check_conn(codec_t *, int, int);
int	azalia_widget_sole_conn(codec_t *, nid_t);
void	azalia_widget_print_widget(const widget_t *, const codec_t *);
void	azalia_widget_print_audio(const widget_t *, const char *);
void	azalia_widget_print_pin(const widget_t *);

int	azalia_stream_init(stream_t *, azalia_t *, int, int, int);
int	azalia_stream_reset(stream_t *);
int	azalia_stream_start(stream_t *);
int	azalia_stream_halt(stream_t *);
int	azalia_stream_intr(stream_t *);

int	azalia_open(void *, int);
void	azalia_close(void *);
int	azalia_query_encoding(void *, audio_encoding_t *);
int	azalia_set_params(void *, int, int, audio_params_t *,
	audio_params_t *);
void	azalia_get_default_params(void *, int, struct audio_params*);
int	azalia_round_blocksize(void *, int);
int	azalia_halt_output(void *);
int	azalia_halt_input(void *);
int	azalia_getdev(void *, struct audio_device *);
int	azalia_set_port(void *, mixer_ctrl_t *);
int	azalia_get_port(void *, mixer_ctrl_t *);
int	azalia_query_devinfo(void *, mixer_devinfo_t *);
void	*azalia_allocm(void *, int, size_t, int, int);
void	azalia_freem(void *, void *, int);
size_t	azalia_round_buffersize(void *, int, size_t);
int	azalia_get_props(void *);
int	azalia_trigger_output(void *, void *, void *, int,
	void (*)(void *), void *, audio_params_t *);
int	azalia_trigger_input(void *, void *, void *, int,
	void (*)(void *), void *, audio_params_t *);

int	azalia_params2fmt(const audio_params_t *, uint16_t *);
int	azalia_create_encodings(codec_t *);

int	azalia_match_format(codec_t *, int, audio_params_t *);
int	azalia_set_params_sub(codec_t *, int, audio_params_t *);

void	azalia_save_mixer(codec_t *);
void	azalia_restore_mixer(codec_t *);

int	azalia_suspend(azalia_t *);
int	azalia_resume(azalia_t *);
int	azalia_resume_codec(codec_t *);

/* variables */
struct cfattach azalia_ca = {
	sizeof(azalia_t), azalia_pci_match, azalia_pci_attach,
	azalia_pci_detach, azalia_pci_activate
};

struct cfdriver azalia_cd = {
	NULL, "azalia", DV_DULL
};

struct audio_hw_if azalia_hw_if = {
	azalia_open,
	azalia_close,
	NULL,			/* drain */
	azalia_query_encoding,
	azalia_set_params,
	azalia_round_blocksize,
	NULL,			/* commit_settings */
	NULL,			/* init_output */
	NULL,			/* init_input */
	NULL,			/* start_output */
	NULL,			/* start_input */
	azalia_halt_output,
	azalia_halt_input,
	NULL,			/* speaker_ctl */
	azalia_getdev,
	NULL,			/* setfd */
	azalia_set_port,
	azalia_get_port,
	azalia_query_devinfo,
	azalia_allocm,
	azalia_freem,
	azalia_round_buffersize,
	NULL,			/* mappage */
	azalia_get_props,
	azalia_trigger_output,
	azalia_trigger_input,
	azalia_get_default_params
};

static const char *pin_devices[16] = {
	AudioNline, AudioNspeaker, AudioNheadphone, AudioNcd,
	"SPDIF", "digital-out", "modem-line", "modem-handset",
	"line-in", AudioNaux, AudioNmicrophone, "telephony",
	"SPDIF-in", "digital-in", "beep", "other"};
static const char *wtypes[16] = {
	"dac", "adc", "mix", "sel", "pin", "pow", "volume",
	"beep", "wid08", "wid09", "wid0a", "wid0b", "wid0c",
	"wid0d", "wid0e", "vendor"};
static const char *line_colors[16] = {
	"unk", "blk", "gry", "blu", "grn", "red", "org", "yel",
	"pur", "pnk", "0xa", "0xb", "0xc", "0xd", "wht", "oth"};

/* ================================================================
 * PCI functions
 * ================================================================ */

#define ATI_PCIE_SNOOP_REG		0x42
#define ATI_PCIE_SNOOP_MASK		0xf8
#define ATI_PCIE_SNOOP_ENABLE		0x02
#define NVIDIA_PCIE_SNOOP_REG		0x4e
#define NVIDIA_PCIE_SNOOP_MASK		0xf0
#define NVIDIA_PCIE_SNOOP_ENABLE	0x0f
#define NVIDIA_HDA_ISTR_COH_REG		0x4d
#define NVIDIA_HDA_OSTR_COH_REG		0x4c
#define NVIDIA_HDA_STR_COH_ENABLE	0x01
#define INTEL_PCIE_NOSNOOP_REG		0x79
#define INTEL_PCIE_NOSNOOP_MASK		0xf7
#define INTEL_PCIE_NOSNOOP_ENABLE	0x08

uint8_t
azalia_pci_read(pci_chipset_tag_t pc, pcitag_t pa, int reg)
{
	return (pci_conf_read(pc, pa, (reg & ~0x03)) >>
	    ((reg & 0x03) * 8) & 0xff);
}

void
azalia_pci_write(pci_chipset_tag_t pc, pcitag_t pa, int reg, uint8_t val)
{
	pcireg_t pcival;

	pcival = pci_conf_read(pc, pa, (reg & ~0x03));
	pcival &= ~(0xff << ((reg & 0x03) * 8));
	pcival |= (val << ((reg & 0x03) * 8));
	pci_conf_write(pc, pa, (reg & ~0x03), pcival);
}

void
azalia_configure_pci(azalia_t *az)
{
	pcireg_t v;
	uint8_t reg;

	/* enable back-to-back */
	v = pci_conf_read(az->pc, az->tag, PCI_COMMAND_STATUS_REG);
	pci_conf_write(az->pc, az->tag, PCI_COMMAND_STATUS_REG,
	    v | PCI_COMMAND_BACKTOBACK_ENABLE);

	/* traffic class select */
	v = pci_conf_read(az->pc, az->tag, ICH_PCI_HDTCSEL);
	pci_conf_write(az->pc, az->tag, ICH_PCI_HDTCSEL,
	    v & ~(ICH_PCI_HDTCSEL_MASK));

	/* enable PCIe snoop */
	switch (PCI_PRODUCT(az->pciid)) {
	case PCI_PRODUCT_ATI_SB450_HDA:
	case PCI_PRODUCT_ATI_SBX00_HDA:
	case PCI_PRODUCT_AMD_HUDSON2_HDA:
		reg = azalia_pci_read(az->pc, az->tag, ATI_PCIE_SNOOP_REG);
		reg &= ATI_PCIE_SNOOP_MASK;
		reg |= ATI_PCIE_SNOOP_ENABLE;
		azalia_pci_write(az->pc, az->tag, ATI_PCIE_SNOOP_REG, reg);
		break;
	case PCI_PRODUCT_NVIDIA_MCP51_HDA:
	case PCI_PRODUCT_NVIDIA_MCP55_HDA:
	case PCI_PRODUCT_NVIDIA_MCP61_HDA_1:
	case PCI_PRODUCT_NVIDIA_MCP61_HDA_2:
	case PCI_PRODUCT_NVIDIA_MCP65_HDA_1:
	case PCI_PRODUCT_NVIDIA_MCP65_HDA_2:
	case PCI_PRODUCT_NVIDIA_MCP67_HDA_1:
	case PCI_PRODUCT_NVIDIA_MCP67_HDA_2:
	case PCI_PRODUCT_NVIDIA_MCP73_HDA_1:
	case PCI_PRODUCT_NVIDIA_MCP73_HDA_2:
	case PCI_PRODUCT_NVIDIA_MCP77_HDA_1:
	case PCI_PRODUCT_NVIDIA_MCP77_HDA_2:
	case PCI_PRODUCT_NVIDIA_MCP77_HDA_3:
	case PCI_PRODUCT_NVIDIA_MCP77_HDA_4:
	case PCI_PRODUCT_NVIDIA_MCP79_HDA_1:
	case PCI_PRODUCT_NVIDIA_MCP79_HDA_2:
	case PCI_PRODUCT_NVIDIA_MCP79_HDA_3:
	case PCI_PRODUCT_NVIDIA_MCP79_HDA_4:
	case PCI_PRODUCT_NVIDIA_MCP89_HDA_1:
	case PCI_PRODUCT_NVIDIA_MCP89_HDA_2:
	case PCI_PRODUCT_NVIDIA_MCP89_HDA_3:
	case PCI_PRODUCT_NVIDIA_MCP89_HDA_4:
		reg = azalia_pci_read(az->pc, az->tag,
		    NVIDIA_HDA_OSTR_COH_REG);
		reg |= NVIDIA_HDA_STR_COH_ENABLE;
		azalia_pci_write(az->pc, az->tag,
		    NVIDIA_HDA_OSTR_COH_REG, reg);

		reg = azalia_pci_read(az->pc, az->tag,
		    NVIDIA_HDA_ISTR_COH_REG);
		reg |= NVIDIA_HDA_STR_COH_ENABLE;
		azalia_pci_write(az->pc, az->tag,
		    NVIDIA_HDA_ISTR_COH_REG, reg);

		reg = azalia_pci_read(az->pc, az->tag,
		    NVIDIA_PCIE_SNOOP_REG);
		reg &= NVIDIA_PCIE_SNOOP_MASK;
		reg |= NVIDIA_PCIE_SNOOP_ENABLE;
		azalia_pci_write(az->pc, az->tag,
		    NVIDIA_PCIE_SNOOP_REG, reg);

		reg = azalia_pci_read(az->pc, az->tag,
		    NVIDIA_PCIE_SNOOP_REG);
		if ((reg & NVIDIA_PCIE_SNOOP_ENABLE) !=
		    NVIDIA_PCIE_SNOOP_ENABLE) {
			printf(": could not enable PCIe cache snooping!\n");
		}
		break;
	case PCI_PRODUCT_INTEL_82801FB_HDA:
	case PCI_PRODUCT_INTEL_82801GB_HDA:
	case PCI_PRODUCT_INTEL_82801H_HDA:
	case PCI_PRODUCT_INTEL_82801I_HDA:
	case PCI_PRODUCT_INTEL_82801JI_HDA:
	case PCI_PRODUCT_INTEL_82801JD_HDA:
	case PCI_PRODUCT_INTEL_6321ESB_HDA:
	case PCI_PRODUCT_INTEL_3400_HDA:
	case PCI_PRODUCT_INTEL_QS57_HDA:
	case PCI_PRODUCT_INTEL_6SERIES_HDA:
	case PCI_PRODUCT_INTEL_7SERIES_HDA:
	case PCI_PRODUCT_INTEL_8SERIES_HDA:
	case PCI_PRODUCT_INTEL_8SERIES_LP_HDA:
	case PCI_PRODUCT_INTEL_9SERIES_HDA:
	case PCI_PRODUCT_INTEL_9SERIES_LP_HDA:
	case PCI_PRODUCT_INTEL_BAYTRAIL_HDA:
		reg = azalia_pci_read(az->pc, az->tag,
		    INTEL_PCIE_NOSNOOP_REG);
		reg &= INTEL_PCIE_NOSNOOP_MASK;
		azalia_pci_write(az->pc, az->tag,
		    INTEL_PCIE_NOSNOOP_REG, reg);
		break;
	}
}

int
azalia_pci_match(struct device *parent, void *match, void *aux)
{
	struct pci_attach_args *pa;

	pa = aux;
	if (PCI_CLASS(pa->pa_class) == PCI_CLASS_MULTIMEDIA
	    && PCI_SUBCLASS(pa->pa_class) == PCI_SUBCLASS_MULTIMEDIA_HDAUDIO)
		return 1;
	return 0;
}

void
azalia_pci_attach(struct device *parent, struct device *self, void *aux)
{
	azalia_t *sc;
	struct pci_attach_args *pa;
	pcireg_t v;
	uint8_t reg;
	pci_intr_handle_t ih;
	const char *interrupt_str;

	sc = (azalia_t*)self;
	pa = aux;

	sc->dmat = pa->pa_dmat;

	pci_set_powerstate(pa->pa_pc, pa->pa_tag, PCI_PMCSR_STATE_D0);

	v = pci_conf_read(pa->pa_pc, pa->pa_tag, ICH_PCI_HDBARL);
	v &= PCI_MAPREG_TYPE_MASK | PCI_MAPREG_MEM_TYPE_MASK;
	if (pci_mapreg_map(pa, ICH_PCI_HDBARL, v, 0,
			   &sc->iot, &sc->ioh, NULL, &sc->map_size, 0)) {
		printf(": can't map device i/o space\n");
		return;
	}

	sc->pc = pa->pa_pc;
	sc->tag = pa->pa_tag;
	sc->pciid = pa->pa_id;
	sc->subid = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG);

	azalia_configure_pci(sc);

	/* disable MSI, use INTx instead */
	if (PCI_VENDOR(sc->pciid) == PCI_VENDOR_INTEL) {
		reg = azalia_pci_read(sc->pc, sc->tag, ICH_PCI_MMC);
		reg &= ~(ICH_PCI_MMC_ME);
		azalia_pci_write(sc->pc, sc->tag, ICH_PCI_MMC, reg);
	}

	/* interrupt */
	if (pci_intr_map_msi(pa, &ih) && pci_intr_map(pa, &ih)) {
		printf(": can't map interrupt\n");
		return;
	}
	interrupt_str = pci_intr_string(pa->pa_pc, ih);
	sc->ih = pci_intr_establish(pa->pa_pc, ih, IPL_AUDIO | IPL_MPSAFE,
	    azalia_intr, sc, sc->dev.dv_xname);
	if (sc->ih == NULL) {
		printf(": can't establish interrupt");
		if (interrupt_str != NULL)
			printf(" at %s", interrupt_str);
		printf("\n");
		return;
	}
	printf(": %s\n", interrupt_str);

	if (azalia_init(sc, 0))
		goto err_exit;

	if (azalia_init_codecs(sc))
		goto err_exit;

	if (azalia_init_streams(sc))
		goto err_exit;

	audio_attach_mi(&azalia_hw_if, sc, &sc->dev);

	return;

err_exit:
	sc->detached = 1;
	azalia_pci_detach(self, 0);
}

int
azalia_pci_activate(struct device *self, int act)
{
	azalia_t *sc = (azalia_t*)self;
	int rv = 0; 

	switch (act) {
	case DVACT_SUSPEND:
		azalia_suspend(sc);
		break;
	case DVACT_POWERDOWN:
		azalia_shutdown(sc);
		break;
	case DVACT_RESUME:
		azalia_resume(sc);
		rv = config_activate_children(self, act);
		break;
	default:
		rv = config_activate_children(self, act);
		break;
	}
	return (rv);
}

int
azalia_pci_detach(struct device *self, int flags)
{
	azalia_t *az = (azalia_t*)self;
	uint32_t gctl;
	int i;

	DPRINTF(("%s\n", __func__));

	/*
	 * this function is called if the device could not be supported,
	 * in which case az->detached == 1.  check if this function has
	 * already cleaned up.
	 */
	if (az->detached > 1)
		return 0;

	config_detach_children(self, flags);

	/* disable unsolicited responses if soft detaching */
	if (az->detached == 1) {
		gctl = AZ_READ_4(az, GCTL);
		AZ_WRITE_4(az, GCTL, gctl &~(HDA_GCTL_UNSOL));
	}

	timeout_del(&az->unsol_to);

	DPRINTF(("%s: delete streams\n", __func__));
	if (az->rstream.bdlist.addr != NULL)
		azalia_free_dmamem(az, &az->rstream.bdlist);
	if (az->pstream.bdlist.addr != NULL)
		azalia_free_dmamem(az, &az->pstream.bdlist);

	DPRINTF(("%s: delete codecs\n", __func__));
	for (i = 0; i < az->ncodecs; i++) {
		azalia_codec_delete(&az->codecs[i]);
	}
	az->ncodecs = 0;
	if (az->codecs != NULL) {
		free(az->codecs, M_DEVBUF, 0);
		az->codecs = NULL;
	}

	DPRINTF(("%s: delete CORB and RIRB\n", __func__));
	if (az->corb_dma.addr != NULL)
		azalia_free_dmamem(az, &az->corb_dma);
	if (az->rirb_dma.addr != NULL)
		azalia_free_dmamem(az, &az->rirb_dma);
	if (az->unsolq != NULL) {
		free(az->unsolq, M_DEVBUF, 0);
		az->unsolq = NULL;
	}

	/* disable interrupts if soft detaching */
	if (az->detached == 1) {
		DPRINTF(("%s: disable interrupts\n", __func__));
		AZ_WRITE_4(az, INTCTL, 0);

		DPRINTF(("%s: clear interrupts\n", __func__));
		AZ_WRITE_4(az, INTSTS, HDA_INTSTS_CIS | HDA_INTSTS_GIS);
		AZ_WRITE_2(az, STATESTS, HDA_STATESTS_SDIWAKE);
		AZ_WRITE_1(az, RIRBSTS, HDA_RIRBSTS_RINTFL | HDA_RIRBSTS_RIRBOIS);
	}

	DPRINTF(("%s: delete PCI resources\n", __func__));
	if (az->ih != NULL) {
		pci_intr_disestablish(az->pc, az->ih);
		az->ih = NULL;
	}
	if (az->map_size != 0) {
		bus_space_unmap(az->iot, az->ioh, az->map_size);
		az->map_size = 0;
	}

	az->detached = 2;
	return 0;
}

int
azalia_intr(void *v)
{
	azalia_t *az = v;
	uint32_t intsts;
	int ret = 0;

	mtx_enter(&audio_lock);
	intsts = AZ_READ_4(az, INTSTS);
	if (intsts == 0 || intsts == 0xffffffff) {
		mtx_leave(&audio_lock);
		return (ret);
	}

	AZ_WRITE_4(az, INTSTS, intsts);

	if (intsts & az->pstream.intr_bit) {
		azalia_stream_intr(&az->pstream);
		ret = 1;
	}

	if (intsts & az->rstream.intr_bit) {
		azalia_stream_intr(&az->rstream);
		ret = 1;
	}

	if ((intsts & HDA_INTSTS_CIS) &&
	    (AZ_READ_1(az, RIRBCTL) & HDA_RIRBCTL_RINTCTL) &&
	    (AZ_READ_1(az, RIRBSTS) & HDA_RIRBSTS_RINTFL)) {
		azalia_rirb_intr(az);
		ret = 1;
	}
	mtx_leave(&audio_lock);
	return (ret);
}

void
azalia_shutdown(void *v)
{
	azalia_t *az = (azalia_t *)v;
	uint32_t gctl;

	/* disable unsolicited response */
	gctl = AZ_READ_4(az, GCTL);
	AZ_WRITE_4(az, GCTL, gctl & ~(HDA_GCTL_UNSOL));

	timeout_del(&az->unsol_to);

	/* halt CORB/RIRB */
	azalia_halt_corb(az);
	azalia_halt_rirb(az);
}

/* ================================================================
 * HDA controller functions
 * ================================================================ */

void
azalia_print_codec(codec_t *codec)
{
	const char *vendor;

	if (codec->name == NULL) {
		vendor = pci_findvendor(codec->vid >> 16);
		if (vendor == NULL)
			printf("0x%04x/0x%04x",
			    codec->vid >> 16, codec->vid & 0xffff);
		else
			printf("%s/0x%04x", vendor, codec->vid & 0xffff);
	} else
		printf("%s", codec->name);
}

int
azalia_reset(azalia_t *az)
{
	uint32_t gctl;
	int i;

	/* 4.2.2 Starting the High Definition Audio Controller */
	DPRINTF(("%s: resetting\n", __func__));
	gctl = AZ_READ_4(az, GCTL);
	AZ_WRITE_4(az, GCTL, gctl & ~HDA_GCTL_CRST);
	for (i = 5000; i > 0; i--) {
		DELAY(10);
		if ((AZ_READ_4(az, GCTL) & HDA_GCTL_CRST) == 0)
			break;
	}
	DPRINTF(("%s: reset counter = %d\n", __func__, i));
	if (i == 0) {
		DPRINTF(("%s: reset failure\n", XNAME(az)));
		return(ETIMEDOUT);
	}
	DELAY(1000);
	gctl = AZ_READ_4(az, GCTL);
	AZ_WRITE_4(az, GCTL, gctl | HDA_GCTL_CRST);
	for (i = 5000; i > 0; i--) {
		DELAY(10);
		if (AZ_READ_4(az, GCTL) & HDA_GCTL_CRST)
			break;
	}
	DPRINTF(("%s: reset counter = %d\n", __func__, i));
	if (i == 0) {
		DPRINTF(("%s: reset-exit failure\n", XNAME(az)));
		return(ETIMEDOUT);
	}
	DELAY(1000);

	return(0);
}

int
azalia_get_ctrlr_caps(azalia_t *az)
{
	int i, n;
	uint16_t gcap;
	uint16_t statests;
	uint8_t cap;

	DPRINTF(("%s: host: High Definition Audio rev. %d.%d\n",
	    XNAME(az), AZ_READ_1(az, VMAJ), AZ_READ_1(az, VMIN)));
	gcap = AZ_READ_2(az, GCAP);
	az->nistreams = HDA_GCAP_ISS(gcap);
	az->nostreams = HDA_GCAP_OSS(gcap);
	az->nbstreams = HDA_GCAP_BSS(gcap);
	az->ok64 = (gcap & HDA_GCAP_64OK) != 0;
	DPRINTF(("%s: host: %d output, %d input, and %d bidi streams\n",
	    XNAME(az), az->nostreams, az->nistreams, az->nbstreams));

	/* 4.3 Codec discovery */
	statests = AZ_READ_2(az, STATESTS);
	for (i = 0, n = 0; i < HDA_MAX_CODECS; i++) {
		if ((statests >> i) & 1) {
			DPRINTF(("%s: found a codec at #%d\n", XNAME(az), i));
			n++;
		}
	}
	az->ncodecs = n;
	if (az->ncodecs < 1) {
		printf("%s: no HD-Audio codecs\n", XNAME(az));
		return -1;
	}
	az->codecs = mallocarray(az->ncodecs, sizeof(codec_t), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (az->codecs == NULL) {
		printf("%s: can't allocate memory for codecs\n", XNAME(az));
		return ENOMEM;
	}
	for (i = 0, n = 0; n < az->ncodecs; i++) {
		if ((statests >> i) & 1) {
			az->codecs[n].address = i;
			az->codecs[n++].az = az;
		}
	}

	/* determine CORB size */
	az->corbsize = AZ_READ_1(az, CORBSIZE);
	cap = az->corbsize & HDA_CORBSIZE_CORBSZCAP_MASK;
	az->corbsize  &= ~HDA_CORBSIZE_CORBSIZE_MASK;
	if (cap & HDA_CORBSIZE_CORBSZCAP_256) {
		az->corb_entries = 256;
		az->corbsize |= HDA_CORBSIZE_CORBSIZE_256;
	} else if (cap & HDA_CORBSIZE_CORBSZCAP_16) {
		az->corb_entries = 16;
		az->corbsize |= HDA_CORBSIZE_CORBSIZE_16;
	} else if (cap & HDA_CORBSIZE_CORBSZCAP_2) {
		az->corb_entries = 2;
		az->corbsize |= HDA_CORBSIZE_CORBSIZE_2;
	} else {
		printf("%s: invalid CORBSZCAP: 0x%2x\n", XNAME(az), cap);
		return(-1);
	}

	/* determine RIRB size */
	az->rirbsize = AZ_READ_1(az, RIRBSIZE);
	cap = az->rirbsize & HDA_RIRBSIZE_RIRBSZCAP_MASK;
	az->rirbsize &= ~HDA_RIRBSIZE_RIRBSIZE_MASK;
	if (cap & HDA_RIRBSIZE_RIRBSZCAP_256) {
		az->rirb_entries = 256;
		az->rirbsize |= HDA_RIRBSIZE_RIRBSIZE_256;
	} else if (cap & HDA_RIRBSIZE_RIRBSZCAP_16) {
		az->rirb_entries = 16;
		az->rirbsize |= HDA_RIRBSIZE_RIRBSIZE_16;
	} else if (cap & HDA_RIRBSIZE_RIRBSZCAP_2) {
		az->rirb_entries = 2;
		az->rirbsize |= HDA_RIRBSIZE_RIRBSIZE_2;
	} else {
		printf("%s: invalid RIRBSZCAP: 0x%2x\n", XNAME(az), cap);
		return(-1);
	}

	return(0);
}

int
azalia_init(azalia_t *az, int resuming)
{
	int err;

	err = azalia_reset(az);
	if (err)
		return(err);

	if (!resuming) {
		err = azalia_get_ctrlr_caps(az);
		if (err)
			return(err);
	}

	/* clear interrupt status */
	AZ_WRITE_2(az, STATESTS, HDA_STATESTS_SDIWAKE);
	AZ_WRITE_1(az, RIRBSTS, HDA_RIRBSTS_RINTFL | HDA_RIRBSTS_RIRBOIS);
	AZ_WRITE_4(az, INTSTS, HDA_INTSTS_CIS | HDA_INTSTS_GIS);
	AZ_WRITE_4(az, DPLBASE, 0);
	AZ_WRITE_4(az, DPUBASE, 0);

	/* 4.4.1 Command Outbound Ring Buffer */
	err = azalia_init_corb(az, resuming);
	if (err)
		return(err);

	/* 4.4.2 Response Inbound Ring Buffer */
	err = azalia_init_rirb(az, resuming);
	if (err)
		return(err);

	AZ_WRITE_4(az, INTCTL,
	    AZ_READ_4(az, INTCTL) | HDA_INTCTL_CIE | HDA_INTCTL_GIE);

	return(0);
}

int
azalia_init_codecs(azalia_t *az)
{
	codec_t *codec;
	int c, i;

	c = 0;
	for (i = 0; i < az->ncodecs; i++) {
		if (!azalia_codec_init(&az->codecs[i]))
			c++;
	}
	if (c == 0) {
		printf("%s: No codecs found\n", XNAME(az));
		return(1);
	}

	/* Use the first codec capable of analog I/O.  If there are none,
	 * use the first codec capable of digital I/O.  Skip HDMI codecs.
	 */
	c = -1;
	for (i = 0; i < az->ncodecs; i++) {
		codec = &az->codecs[i];
		if ((codec->audiofunc < 0) ||
		    (codec->codec_type == AZ_CODEC_TYPE_HDMI))
			continue;
		if (codec->codec_type == AZ_CODEC_TYPE_DIGITAL) {
			if (c < 0)
				c = i;
		} else {
			c = i;
			break;
		}
	}
	az->codecno = c;
	if (az->codecno < 0) {
		printf("%s: no supported codecs\n", XNAME(az));
		return(1);
	}

	printf("%s: codecs: ", XNAME(az));
	for (i = 0; i < az->ncodecs; i++) {
		azalia_print_codec(&az->codecs[i]);
		if (i < az->ncodecs - 1)
			printf(", ");
	}
	if (az->ncodecs > 1) {
		printf(", using ");
		azalia_print_codec(&az->codecs[az->codecno]);
	}
	printf("\n");

	/* All codecs with audio are enabled, but only one will be used. */
	for (i = 0; i < az->ncodecs; i++) {
		codec = &az->codecs[i];
		if (i != az->codecno) {
			if (codec->audiofunc < 0)
				continue;
			azalia_comresp(codec, codec->audiofunc,
			    CORB_SET_POWER_STATE, CORB_PS_D3, NULL);
			DELAY(100);
			azalia_codec_delete(codec);
		}
	}

	/* Enable unsolicited responses now that az->codecno is set. */
	AZ_WRITE_4(az, GCTL, AZ_READ_4(az, GCTL) | HDA_GCTL_UNSOL);

	return(0);
}

int
azalia_init_streams(azalia_t *az)
{
	int err;

	/* Use stream#1 and #2.  Don't use stream#0. */
	err = azalia_stream_init(&az->pstream, az, az->nistreams + 0,
	    1, AUMODE_PLAY);
	if (err)
		return(err);
	err = azalia_stream_init(&az->rstream, az, 0, 2, AUMODE_RECORD);
	if (err)
		return(err);

	return(0);
}

int
azalia_halt_corb(azalia_t *az)
{
	uint8_t corbctl;
	int i;

	corbctl = AZ_READ_1(az, CORBCTL);
	if (corbctl & HDA_CORBCTL_CORBRUN) { /* running? */
		AZ_WRITE_1(az, CORBCTL, corbctl & ~HDA_CORBCTL_CORBRUN);
		for (i = 5000; i > 0; i--) {
			DELAY(10);
			corbctl = AZ_READ_1(az, CORBCTL);
			if ((corbctl & HDA_CORBCTL_CORBRUN) == 0)
				break;
		}
		if (i == 0) {
			DPRINTF(("%s: CORB is running\n", XNAME(az)));
			return EBUSY;
		}
	}
	return(0);
}

int
azalia_init_corb(azalia_t *az, int resuming)
{
	int err, i;
	uint16_t corbrp, corbwp;
	uint8_t corbctl;

	err = azalia_halt_corb(az);
	if (err)
		return(err);

	if (!resuming) {
		err = azalia_alloc_dmamem(az,
		    az->corb_entries * sizeof(corb_entry_t), 128,
		    &az->corb_dma);
		if (err) {
			printf("%s: can't allocate CORB buffer\n", XNAME(az));
			return(err);
		}
		DPRINTF(("%s: CORB allocation succeeded.\n", __func__));
	}
	timeout_set(&az->unsol_to, azalia_rirb_kick_unsol_events, az);

	AZ_WRITE_4(az, CORBLBASE, (uint32_t)AZALIA_DMA_DMAADDR(&az->corb_dma));
	AZ_WRITE_4(az, CORBUBASE, PTR_UPPER32(AZALIA_DMA_DMAADDR(&az->corb_dma)));
	AZ_WRITE_1(az, CORBSIZE, az->corbsize);
 
	/* reset CORBRP */
	corbrp = AZ_READ_2(az, CORBRP);
	AZ_WRITE_2(az, CORBRP, corbrp | HDA_CORBRP_CORBRPRST);
	AZ_WRITE_2(az, CORBRP, corbrp & ~HDA_CORBRP_CORBRPRST);
	for (i = 5000; i > 0; i--) {
		DELAY(10);
		corbrp = AZ_READ_2(az, CORBRP);
		if ((corbrp & HDA_CORBRP_CORBRPRST) == 0)
			break;
	}
	if (i == 0) {
		DPRINTF(("%s: CORBRP reset failure\n", XNAME(az)));
		return -1;
	}
	DPRINTF(("%s: CORBWP=%d; size=%d\n", __func__,
		 AZ_READ_2(az, CORBRP) & HDA_CORBRP_CORBRP, az->corb_entries));

	/* clear CORBWP */
	corbwp = AZ_READ_2(az, CORBWP);
	AZ_WRITE_2(az, CORBWP, corbwp & ~HDA_CORBWP_CORBWP);

	/* Run! */
	corbctl = AZ_READ_1(az, CORBCTL);
	AZ_WRITE_1(az, CORBCTL, corbctl | HDA_CORBCTL_CORBRUN);
	return 0;
}

int
azalia_halt_rirb(azalia_t *az)
{
	int i;
	uint8_t rirbctl;

	rirbctl = AZ_READ_1(az, RIRBCTL);
	if (rirbctl & HDA_RIRBCTL_RIRBDMAEN) { /* running? */
		AZ_WRITE_1(az, RIRBCTL, rirbctl & ~HDA_RIRBCTL_RIRBDMAEN);
		for (i = 5000; i > 0; i--) {
			DELAY(10);
			rirbctl = AZ_READ_1(az, RIRBCTL);
			if ((rirbctl & HDA_RIRBCTL_RIRBDMAEN) == 0)
				break;
		}
		if (i == 0) {
			DPRINTF(("%s: RIRB is running\n", XNAME(az)));
			return(EBUSY);
		}
	}
	return(0);
}

int
azalia_init_rirb(azalia_t *az, int resuming)
{
	int err, i;
	uint16_t rirbwp;
	uint8_t rirbctl;

	err = azalia_halt_rirb(az);
	if (err)
		return(err);

	if (!resuming) {
		err = azalia_alloc_dmamem(az,
		    az->rirb_entries * sizeof(rirb_entry_t), 128,
		    &az->rirb_dma);
		if (err) {
			printf("%s: can't allocate RIRB buffer\n", XNAME(az));
			return err;
		}
		DPRINTF(("%s: RIRB allocation succeeded.\n", __func__));

		/* setup the unsolicited response queue */
		az->unsolq = malloc(sizeof(rirb_entry_t) * UNSOLQ_SIZE,
		    M_DEVBUF, M_NOWAIT | M_ZERO);
		if (az->unsolq == NULL) {
			DPRINTF(("%s: can't allocate unsolicited response queue.\n",
			    XNAME(az)));
			azalia_free_dmamem(az, &az->rirb_dma);
			return ENOMEM;
		}
	}
	AZ_WRITE_4(az, RIRBLBASE, (uint32_t)AZALIA_DMA_DMAADDR(&az->rirb_dma));
	AZ_WRITE_4(az, RIRBUBASE, PTR_UPPER32(AZALIA_DMA_DMAADDR(&az->rirb_dma)));
	AZ_WRITE_1(az, RIRBSIZE, az->rirbsize);

	/* reset the write pointer */
	rirbwp = AZ_READ_2(az, RIRBWP);
	AZ_WRITE_2(az, RIRBWP, rirbwp | HDA_RIRBWP_RIRBWPRST);

	/* clear the read pointer */
	az->rirb_rp = AZ_READ_2(az, RIRBWP) & HDA_RIRBWP_RIRBWP;
	DPRINTF(("%s: RIRBRP=%d, size=%d\n", __func__, az->rirb_rp,
	    az->rirb_entries));

	az->unsolq_rp = 0;
	az->unsolq_wp = 0;
	az->unsolq_kick = 0;

	AZ_WRITE_2(az, RINTCNT, 1);

	/* Run! */
	rirbctl = AZ_READ_1(az, RIRBCTL);
	AZ_WRITE_1(az, RIRBCTL, rirbctl |
	    HDA_RIRBCTL_RIRBDMAEN | HDA_RIRBCTL_RINTCTL);
	for (i = 5000; i > 0; i--) {
		DELAY(10);
		rirbctl = AZ_READ_1(az, RIRBCTL);
		if (rirbctl & HDA_RIRBCTL_RIRBDMAEN)
			break;
	}
	if (i == 0) {
		DPRINTF(("%s: RIRB is not running\n", XNAME(az)));
		return(EBUSY);
	}

	return (0);
}

int
azalia_comresp(const codec_t *codec, nid_t nid, uint32_t control,
    uint32_t param, uint32_t* result)
{
	int err;

	mtx_enter(&audio_lock);
	err = azalia_set_command(codec->az, codec->address, nid, control,
	    param);
	if (err)
		goto exit;
	err = azalia_get_response(codec->az, result);
exit:
	mtx_leave(&audio_lock);
	return(err);
}

int
azalia_set_command(azalia_t *az, int caddr, nid_t nid, uint32_t control,
    uint32_t param)
{
	corb_entry_t *corb;
	int  wp;
	uint32_t verb;
	uint16_t corbwp;

#ifdef DIAGNOSTIC
	if ((AZ_READ_1(az, CORBCTL) & HDA_CORBCTL_CORBRUN) == 0) {
		DPRINTF(("%s: CORB is not running.\n", XNAME(az)));
		return(-1);
	}
#endif
	verb = (caddr << 28) | (nid << 20) | (control << 8) | param;
	corbwp = AZ_READ_2(az, CORBWP);
	wp = corbwp & HDA_CORBWP_CORBWP;
	corb = (corb_entry_t*)az->corb_dma.addr;
	if (++wp >= az->corb_entries)
		wp = 0;
	corb[wp] = verb;

	AZ_WRITE_2(az, CORBWP, (corbwp & ~HDA_CORBWP_CORBWP) | wp);

	return(0);
}

int
azalia_get_response(azalia_t *az, uint32_t *result)
{
	const rirb_entry_t *rirb;
	int i;
	uint16_t wp;

#ifdef DIAGNOSTIC
	if ((AZ_READ_1(az, RIRBCTL) & HDA_RIRBCTL_RIRBDMAEN) == 0) {
		DPRINTF(("%s: RIRB is not running.\n", XNAME(az)));
		return(-1);
	}
#endif

	rirb = (rirb_entry_t*)az->rirb_dma.addr;
	i = 5000;
	for (;;) {
		while (i > 0) {
			wp = AZ_READ_2(az, RIRBWP) & HDA_RIRBWP_RIRBWP;
			if (az->rirb_rp != wp)
				break;
			DELAY(10);
			i--;
		}
		if (i == 0) {
			DPRINTF(("%s: RIRB time out\n", XNAME(az)));
			return(ETIMEDOUT);
		}
		if (++az->rirb_rp >= az->rirb_entries)
			az->rirb_rp = 0;
		if (rirb[az->rirb_rp].resp_ex & RIRB_RESP_UNSOL) {
			az->unsolq[az->unsolq_wp].resp = rirb[az->rirb_rp].resp;
			az->unsolq[az->unsolq_wp++].resp_ex = rirb[az->rirb_rp].resp_ex;
			az->unsolq_wp %= UNSOLQ_SIZE;
		} else
			break;
	}
	if (result != NULL)
		*result = rirb[az->rirb_rp].resp;

	return(0);
}

void
azalia_rirb_kick_unsol_events(void *v)
{
	azalia_t *az = v;
	int addr, tag;

	if (az->unsolq_kick)
		return;
	az->unsolq_kick = 1;
	while (az->unsolq_rp != az->unsolq_wp) {
		addr = RIRB_RESP_CODEC(az->unsolq[az->unsolq_rp].resp_ex);
		tag = RIRB_UNSOL_TAG(az->unsolq[az->unsolq_rp].resp);
		DPRINTF(("%s: codec address=%d tag=%d\n", __func__, addr, tag));

		az->unsolq_rp++;
		az->unsolq_rp %= UNSOLQ_SIZE;

		/* We only care about events on the using codec. */
		if (az->codecs[az->codecno].address == addr)
			azalia_unsol_event(&az->codecs[az->codecno], tag);
	}
	az->unsolq_kick = 0;
}

void
azalia_rirb_intr(azalia_t *az)
{
	const rirb_entry_t *rirb;
	uint16_t wp;
	uint8_t rirbsts;

	rirbsts = AZ_READ_1(az, RIRBSTS);

	wp = AZ_READ_2(az, RIRBWP) & HDA_RIRBWP_RIRBWP;
	rirb = (rirb_entry_t*)az->rirb_dma.addr;
	while (az->rirb_rp != wp) {
		if (++az->rirb_rp >= az->rirb_entries)
			az->rirb_rp = 0;
		if (rirb[az->rirb_rp].resp_ex & RIRB_RESP_UNSOL) {
			az->unsolq[az->unsolq_wp].resp = rirb[az->rirb_rp].resp;
			az->unsolq[az->unsolq_wp++].resp_ex = rirb[az->rirb_rp].resp_ex;
			az->unsolq_wp %= UNSOLQ_SIZE;
		} else {
			DPRINTF(("%s: dropped solicited response\n", __func__));
		}
	}
	timeout_add_msec(&az->unsol_to, 1);

	AZ_WRITE_1(az, RIRBSTS,
	    rirbsts | HDA_RIRBSTS_RIRBOIS | HDA_RIRBSTS_RINTFL);
}

int
azalia_alloc_dmamem(azalia_t *az, size_t size, size_t align, azalia_dma_t *d)
{
	int err;
	int nsegs;

	d->size = size;
	err = bus_dmamem_alloc(az->dmat, size, align, 0, d->segments, 1,
	    &nsegs, BUS_DMA_NOWAIT);
	if (err)
		return err;
	if (nsegs != 1)
		goto free;
	err = bus_dmamem_map(az->dmat, d->segments, 1, size,
	    &d->addr, BUS_DMA_NOWAIT | BUS_DMA_COHERENT);
	if (err)
		goto free;
	err = bus_dmamap_create(az->dmat, size, 1, size, 0,
	    BUS_DMA_NOWAIT, &d->map);
	if (err)
		goto unmap;
	err = bus_dmamap_load(az->dmat, d->map, d->addr, size,
	    NULL, BUS_DMA_NOWAIT);
	if (err)
		goto destroy;

	if (!az->ok64 && PTR_UPPER32(AZALIA_DMA_DMAADDR(d)) != 0) {
		azalia_free_dmamem(az, d);
		return -1;
	}
	return 0;

destroy:
	bus_dmamap_destroy(az->dmat, d->map);
unmap:
	bus_dmamem_unmap(az->dmat, d->addr, size);
free:
	bus_dmamem_free(az->dmat, d->segments, 1);
	d->addr = NULL;
	return err;
}

int
azalia_free_dmamem(const azalia_t *az, azalia_dma_t* d)
{
	if (d->addr == NULL)
		return 0;
	bus_dmamap_unload(az->dmat, d->map);
	bus_dmamap_destroy(az->dmat, d->map);
	bus_dmamem_unmap(az->dmat, d->addr, d->size);
	bus_dmamem_free(az->dmat, d->segments, 1);
	d->addr = NULL;
	return 0;
}

int
azalia_suspend(azalia_t *az)
{
	int err;

	if (az->detached)
		return 0;

	/* disable unsolicited responses */
	AZ_WRITE_4(az, GCTL, AZ_READ_4(az, GCTL) & ~HDA_GCTL_UNSOL);

	timeout_del(&az->unsol_to);

	azalia_save_mixer(&az->codecs[az->codecno]);
	/* azalia_halt_{corb,rirb}() only fail if the {CORB,RIRB} can't
	 * be stopped and azalia_init_{corb,rirb}(), which starts the
	 * {CORB,RIRB}, first calls azalia_halt_{corb,rirb}().  If halt
	 * fails, don't try to restart.
	 */
	err = azalia_halt_corb(az);
	if (err)
		goto corb_fail;

	err = azalia_halt_rirb(az);
	if (err)
		goto rirb_fail;

	/* stop interrupts and clear status registers */
	AZ_WRITE_4(az, INTCTL, 0);
	AZ_WRITE_4(az, INTSTS, HDA_INTSTS_CIS | HDA_INTSTS_GIS);
	AZ_WRITE_2(az, STATESTS, HDA_STATESTS_SDIWAKE);
	AZ_WRITE_1(az, RIRBSTS, HDA_RIRBSTS_RINTFL | HDA_RIRBSTS_RIRBOIS);

	return 0;

rirb_fail:
	azalia_init_corb(az, 1);
corb_fail:
	AZ_WRITE_4(az, GCTL, AZ_READ_4(az, GCTL) | HDA_GCTL_UNSOL);

	return err;
}

int
azalia_resume_codec(codec_t *this)
{
	widget_t *w;
	uint32_t result;
	int i, err;

	err = azalia_comresp(this, this->audiofunc, CORB_SET_POWER_STATE,
 	    CORB_PS_D0, &result);
	if (err) {
		DPRINTF(("%s: power audio func error: result=0x%8.8x\n",
		    __func__, result));
	}
	DELAY(100);

	FOR_EACH_WIDGET(this, i) {
		w = &this->w[i];
		if (w->widgetcap & COP_AWCAP_POWER) {
			azalia_comresp(this, w->nid, CORB_SET_POWER_STATE,
			    CORB_PS_D0, &result);
			DELAY(100);
		}
		if (w->type == COP_AWTYPE_PIN_COMPLEX)
			azalia_widget_init_pin(w, this);
		if (this->qrks & AZ_QRK_WID_MASK)
			azalia_codec_widget_quirks(this, w->nid);
	}

	if (this->qrks & AZ_QRK_GPIO_MASK) {
		err = azalia_codec_gpio_quirks(this);
		if (err)
			return err;
	}

	azalia_restore_mixer(this);

	return(0);
}

int
azalia_resume(azalia_t *az)
{
	int err;

	if (az->detached)
		return 0;

	azalia_configure_pci(az);

	/* is this necessary? */
	pci_conf_write(az->pc, az->tag, PCI_SUBSYS_ID_REG, az->subid);

	err = azalia_init(az, 1);
	if (err)
		return err;

	/* enable unsolicited responses on the controller */
	AZ_WRITE_4(az, GCTL, AZ_READ_4(az, GCTL) | HDA_GCTL_UNSOL);

	err = azalia_resume_codec(&az->codecs[az->codecno]);
	if (err)
		return err;

	err = azalia_codec_enable_unsol(&az->codecs[az->codecno]);
	if (err)
		return err;

	return 0;
}

void
azalia_save_mixer(codec_t *this)
{
	mixer_item_t *m;
	mixer_ctrl_t mc;
	int i;

	for (i = 0; i < this->nmixers; i++) {
		m = &this->mixers[i];
		if (m->nid == this->playvols.master)
			continue;
		mc.dev = i;
		mc.type = m->devinfo.type;
		azalia_mixer_get(this, m->nid, m->target, &mc);
		switch (mc.type) {
		case AUDIO_MIXER_ENUM:
			m->saved.ord = mc.un.ord;
			break;
		case AUDIO_MIXER_SET:
			m->saved.mask = mc.un.mask;
			break;
		case AUDIO_MIXER_VALUE:
			m->saved.value = mc.un.value;
			break;
		case AUDIO_MIXER_CLASS:
			break;
		default:
			DPRINTF(("%s: invalid mixer type in mixer %d\n",
			    __func__, mc.dev));
			break;
		}
	}
}

void
azalia_restore_mixer(codec_t *this)
{
	mixer_item_t *m;
	mixer_ctrl_t mc;
	int i;

	for (i = 0; i < this->nmixers; i++) {
		m = &this->mixers[i];
		if (m->nid == this->playvols.master)
			continue;
		mc.dev = i;
		mc.type = m->devinfo.type;
		switch (mc.type) {
		case AUDIO_MIXER_ENUM:
			mc.un.ord = m->saved.ord;
			break;
		case AUDIO_MIXER_SET:
			mc.un.mask = m->saved.mask; 
			break;
		case AUDIO_MIXER_VALUE:
			mc.un.value = m->saved.value;
			break;
		case AUDIO_MIXER_CLASS:
			break;
		default:
			DPRINTF(("%s: invalid mixer type in mixer %d\n",
			    __func__, mc.dev));
			continue;
		}
		azalia_mixer_set(this, m->nid, m->target, &mc);
	}
}

/* ================================================================
 * HDA codec functions
 * ================================================================ */

int
azalia_codec_init(codec_t *this)
{
	widget_t *w;
	uint32_t rev, id, result;
	int err, addr, n, i, nspdif, nhdmi;

	addr = this->address;
	/* codec vendor/device/revision */
	err = azalia_comresp(this, CORB_NID_ROOT, CORB_GET_PARAMETER,
	    COP_REVISION_ID, &rev);
	if (err)
		return err;
	err = azalia_comresp(this, CORB_NID_ROOT, CORB_GET_PARAMETER,
	    COP_VENDOR_ID, &id);
	if (err)
		return err;
	this->vid = id;
	this->subid = this->az->subid;
	azalia_codec_init_vtbl(this);
	DPRINTF(("%s: codec[%d] vid 0x%8.8x, subid 0x%8.8x, rev. %u.%u,",
	    XNAME(this->az), addr, this->vid, this->subid,
	    COP_RID_REVISION(rev), COP_RID_STEPPING(rev)));
	DPRINTF((" HDA version %u.%u\n",
	    COP_RID_MAJ(rev), COP_RID_MIN(rev)));

	/* identify function nodes */
	err = azalia_comresp(this, CORB_NID_ROOT, CORB_GET_PARAMETER,
	    COP_SUBORDINATE_NODE_COUNT, &result);
	if (err)
		return err;
	this->nfunctions = COP_NSUBNODES(result);
	if (COP_NSUBNODES(result) <= 0) {
		DPRINTF(("%s: codec[%d]: No function groups\n",
		    XNAME(this->az), addr));
		return -1;
	}
	/* iterate function nodes and find an audio function */
	n = COP_START_NID(result);
	DPRINTF(("%s: nidstart=%d #functions=%d\n",
	    XNAME(this->az), n, this->nfunctions));
	this->audiofunc = -1;
	for (i = 0; i < this->nfunctions; i++) {
		err = azalia_comresp(this, n + i, CORB_GET_PARAMETER,
		    COP_FUNCTION_GROUP_TYPE, &result);
		if (err)
			continue;
		DPRINTF(("%s: FTYPE result = 0x%8.8x\n", __func__, result));
		if (COP_FTYPE(result) == COP_FTYPE_AUDIO) {
			this->audiofunc = n + i;
			break;	/* XXX multiple audio functions? */
		}
	}
	if (this->audiofunc < 0) {
		DPRINTF(("%s: codec[%d]: No audio function groups\n",
		    XNAME(this->az), addr));
		return -1;
	}

	/* power the audio function */
	azalia_comresp(this, this->audiofunc, CORB_SET_POWER_STATE,
	    CORB_PS_D0, &result);
	DELAY(100);

	/* check widgets in the audio function */
	err = azalia_comresp(this, this->audiofunc, CORB_GET_PARAMETER,
	    COP_SUBORDINATE_NODE_COUNT, &result);
	if (err)
		return err;
	DPRINTF(("%s: There are %d widgets in the audio function.\n",
	   __func__, COP_NSUBNODES(result)));
	this->wstart = COP_START_NID(result);
	if (this->wstart < 2) {
		printf("%s: invalid node structure\n", XNAME(this->az));
		return -1;
	}
	this->wend = this->wstart + COP_NSUBNODES(result);
	this->w = mallocarray(this->wend, sizeof(widget_t), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (this->w == NULL) {
		printf("%s: out of memory\n", XNAME(this->az));
		return ENOMEM;
	}

	/* query the base parameters */
	azalia_comresp(this, this->audiofunc, CORB_GET_PARAMETER,
	    COP_STREAM_FORMATS, &result);
	this->w[this->audiofunc].d.audio.encodings = result;
	azalia_comresp(this, this->audiofunc, CORB_GET_PARAMETER,
	    COP_PCM, &result);
	this->w[this->audiofunc].d.audio.bits_rates = result;
	azalia_comresp(this, this->audiofunc, CORB_GET_PARAMETER,
	    COP_INPUT_AMPCAP, &result);
	this->w[this->audiofunc].inamp_cap = result;
	azalia_comresp(this, this->audiofunc, CORB_GET_PARAMETER,
	    COP_OUTPUT_AMPCAP, &result);
	this->w[this->audiofunc].outamp_cap = result;

	azalia_codec_print_audiofunc(this);

	strlcpy(this->w[CORB_NID_ROOT].name, "root",
	    sizeof(this->w[CORB_NID_ROOT].name));
	strlcpy(this->w[this->audiofunc].name, "hdaudio",
	    sizeof(this->w[this->audiofunc].name));
	this->w[this->audiofunc].enable = 1;

	FOR_EACH_WIDGET(this, i) {
		w = &this->w[i];
		err = azalia_widget_init(w, this, i);
		if (err)
			return err;
		err = azalia_widget_init_connection(w, this);
		if (err)
			return err;

		azalia_widget_print_widget(w, this);

		if (this->qrks & AZ_QRK_WID_MASK) {
			azalia_codec_widget_quirks(this, i);
		}
	}

	this->na_dacs = this->na_dacs_d = 0;
	this->na_adcs = this->na_adcs_d = 0;
	this->speaker = this->speaker2 = this->spkr_dac =
	    this->fhp = this->fhp_dac =
	    this->mic = this->mic_adc = -1;
	this->nsense_pins = 0;
	this->nout_jacks = 0;
	nspdif = nhdmi = 0;
	FOR_EACH_WIDGET(this, i) {
		w = &this->w[i];

		if (!w->enable)
			continue;

		switch (w->type) {

		case COP_AWTYPE_AUDIO_MIXER:
		case COP_AWTYPE_AUDIO_SELECTOR:
			if (!azalia_widget_check_conn(this, i, 0))
				w->enable = 0;
			break;

		case COP_AWTYPE_AUDIO_OUTPUT:
			if ((w->widgetcap & COP_AWCAP_DIGITAL) == 0) {
				if (this->na_dacs < HDA_MAX_CHANNELS)
					this->a_dacs[this->na_dacs++] = i;
			} else {
				if (this->na_dacs_d < HDA_MAX_CHANNELS)
					this->a_dacs_d[this->na_dacs_d++] = i;
			}
			break;

		case COP_AWTYPE_AUDIO_INPUT:
			if ((w->widgetcap & COP_AWCAP_DIGITAL) == 0) {
				if (this->na_adcs < HDA_MAX_CHANNELS)
					this->a_adcs[this->na_adcs++] = i;
			} else {
				if (this->na_adcs_d < HDA_MAX_CHANNELS)
					this->a_adcs_d[this->na_adcs_d++] = i;
			}
			break;

		case COP_AWTYPE_PIN_COMPLEX:
			switch (CORB_CD_PORT(w->d.pin.config)) {
			case CORB_CD_FIXED:
				switch (w->d.pin.device) {
				case CORB_CD_SPEAKER:
					if (this->speaker == -1) {
						this->speaker = i;
					} else if (w->d.pin.association <
					    this->w[this->speaker].d.pin.association ||
					    (w->d.pin.association ==
					    this->w[this->speaker].d.pin.association &&
					    w->d.pin.sequence <
					    this->w[this->speaker].d.pin.sequence)) {
						this->speaker2 = this->speaker;
						this->speaker = i;
					} else {
						this->speaker2 = i;
					}
					if (this->speaker == i)
						this->spkr_dac =
						    azalia_codec_find_defdac(this, i, 0);
					break;
				case CORB_CD_MICIN:
					this->mic = i;
					this->mic_adc =
					    azalia_codec_find_defadc(this, i, 0);
					break;
				}
				break;
			case CORB_CD_JACK:
				if (w->d.pin.device == CORB_CD_LINEOUT)
					this->nout_jacks++;
				else if (w->d.pin.device == CORB_CD_HEADPHONE &&
				    CORB_CD_LOC_GEO(w->d.pin.config) ==
				    CORB_CD_FRONT) {
					this->fhp = i;
					this->fhp_dac =
					    azalia_codec_find_defdac(this, i, 0);
				}
				if (this->nsense_pins >= HDA_MAX_SENSE_PINS ||
				    !(w->d.pin.cap & COP_PINCAP_PRESENCE))
					break;
				/* check override bit */
				err = azalia_comresp(this, i,
				    CORB_GET_CONFIGURATION_DEFAULT, 0, &result);
				if (err)
					break;
				if (!(CORB_CD_MISC(result) & CORB_CD_PRESENCEOV)) {
					this->sense_pins[this->nsense_pins++] = i;
				}
				break;
			}
			if ((w->d.pin.device == CORB_CD_DIGITALOUT) &&
			    (w->d.pin.cap & COP_PINCAP_HDMI))
				nhdmi++;
			else if (w->d.pin.device == CORB_CD_SPDIFOUT ||
			    w->d.pin.device == CORB_CD_SPDIFIN)
				nspdif++;
			break;
		}
	}
	this->codec_type = AZ_CODEC_TYPE_ANALOG;
	if ((this->na_dacs == 0) && (this->na_adcs == 0)) {
		this->codec_type = AZ_CODEC_TYPE_DIGITAL;
		if (nspdif == 0 && nhdmi > 0)
			this->codec_type = AZ_CODEC_TYPE_HDMI;
	}

	/* make sure built-in mic is connected to an adc */
	if (this->mic != -1 && this->mic_adc == -1) {
		if (azalia_codec_select_micadc(this)) {
			DPRINTF(("%s: cound not select mic adc\n", __func__));
		}
	}

	err = azalia_codec_sort_pins(this);
	if (err)
		return err;

	err = azalia_codec_find_inputmixer(this);
	if (err)
		return err;

	/* If the codec can do multichannel, select different DACs for
	 * the multichannel jack group.  Also be sure to keep track of
	 * which DAC the front headphone is connected to.
	 */
	if (this->na_dacs >= 3 && this->nopins >= 3) {
		err = azalia_codec_select_dacs(this);
		if (err)
			return err;
	}

	err = azalia_codec_select_spkrdac(this);
	if (err)
		return err;

	err = azalia_init_dacgroup(this);
	if (err)
		return err;

	azalia_codec_print_groups(this);

	err = azalia_widget_label_widgets(this);
	if (err)
		return err;

	err = azalia_codec_construct_format(this, 0, 0);
	if (err)
		return err;

	err = azalia_codec_init_volgroups(this);
	if (err)
		return err;

	if (this->qrks & AZ_QRK_GPIO_MASK) {
		err = azalia_codec_gpio_quirks(this);
		if (err)
			return err;
	}

	err = azalia_mixer_init(this);
	if (err)
		return err;

	return 0;
}

int
azalia_codec_find_inputmixer(codec_t *this)
{
	widget_t *w;
	int i, j;

	this->input_mixer = -1;

	FOR_EACH_WIDGET(this, i) {
		w = &this->w[i];
		if (w->type != COP_AWTYPE_AUDIO_MIXER)
			continue;

		/* can input from a pin */
		for (j = 0; j < this->nipins; j++) {
			if (azalia_codec_fnode(this, this->ipins[j].nid,
			    w->nid, 0) != -1)
				break;
		}
		if (j == this->nipins)
			continue;

		/* can output to a pin */
		for (j = 0; j < this->nopins; j++) {
			if (azalia_codec_fnode(this, w->nid,
			    this->opins[j].nid, 0) != -1)
				break;
		}
		if (j == this->nopins)
			continue;

		/* can output to an ADC */
		for (j = 0; j < this->na_adcs; j++) {
			if (azalia_codec_fnode(this, w->nid,
			    this->a_adcs[j], 0) != -1)
				break;
		}
		if (j == this->na_adcs)
			continue;

		this->input_mixer = i;
		break;
	}
	return(0);
}

int
azalia_codec_select_micadc(codec_t *this)
{
	widget_t *w;
	int i, j, conv, err;

	for (i = 0; i < this->na_adcs; i++) {
		if (azalia_codec_fnode(this, this->mic,
		    this->a_adcs[i], 0) >= 0)
			break;
	}
	if (i >= this->na_adcs)
		return(-1);
	conv = this->a_adcs[i];

	w = &this->w[conv];
	for (j = 0; j < 10; j++) {
		for (i = 0; i < w->nconnections; i++) {
			if (!azalia_widget_enabled(this, w->connections[i]))
				continue;
			if (azalia_codec_fnode(this, this->mic,
			    w->connections[i], j + 1) >= 0) {
				break;
			}
		}
		if (i >= w->nconnections)
			return(-1);
		err = azalia_comresp(this, w->nid,
		    CORB_SET_CONNECTION_SELECT_CONTROL, i, 0);
		if (err)
			return(err);
		w->selected = i;
		if (w->connections[i] == this->mic) {
			this->mic_adc = conv;
			return(0);
		}
		w = &this->w[w->connections[i]];
	}
	return(-1);
}

int
azalia_codec_sort_pins(codec_t *this)
{
#define MAX_PINS	16
	const widget_t *w;
	struct io_pin opins[MAX_PINS], opins_d[MAX_PINS];
	struct io_pin ipins[MAX_PINS], ipins_d[MAX_PINS];
	int nopins, nopins_d, nipins, nipins_d;
	int prio, loc, add, nd, conv;
	int i, j, k;

	nopins = nopins_d = nipins = nipins_d = 0;

	FOR_EACH_WIDGET(this, i) {
		w = &this->w[i];
		if (!w->enable || w->type != COP_AWTYPE_PIN_COMPLEX)
			continue;

		loc = 0;
		if (this->na_dacs >= 3 && this->nout_jacks < 3)
			loc = CORB_CD_LOC_GEO(w->d.pin.config);

		prio = w->d.pin.association << 4 | w->d.pin.sequence;
		conv = -1;

		/* analog out */
		if ((w->d.pin.cap & COP_PINCAP_OUTPUT) && 
		    !(w->widgetcap & COP_AWCAP_DIGITAL)) {
			add = nd = 0;
			conv = azalia_codec_find_defdac(this, w->nid, 0);
			switch(w->d.pin.device) {
			/* primary - output by default */
			case CORB_CD_SPEAKER:
				if (w->nid == this->speaker ||
				    w->nid == this->speaker2)
					break;
				/* FALLTHROUGH */
			case CORB_CD_HEADPHONE:
			case CORB_CD_LINEOUT:
				add = 1;
				break;
			/* secondary - input by default */
			case CORB_CD_MICIN:
				if (w->nid == this->mic)
					break;
				/* FALLTHROUGH */
			case CORB_CD_LINEIN:
				add = nd = 1;
				break;
			}
			if (add && nopins < MAX_PINS) {
				opins[nopins].nid = w->nid;
				opins[nopins].conv = conv;
				prio |= (nd << 8) | (loc << 9);
				opins[nopins].prio = prio;
				nopins++;
			}
		}
		/* digital out */
		if ((w->d.pin.cap & COP_PINCAP_OUTPUT) && 
		    (w->widgetcap & COP_AWCAP_DIGITAL)) {
			conv = azalia_codec_find_defdac(this, w->nid, 0);
			switch(w->d.pin.device) {
			case CORB_CD_SPDIFOUT:
			case CORB_CD_DIGITALOUT:
				if (nopins_d < MAX_PINS) {
					opins_d[nopins_d].nid = w->nid;
					opins_d[nopins_d].conv = conv;
					opins_d[nopins_d].prio = prio;
					nopins_d++;
				}
				break;
			}
		}
		/* analog in */
		if ((w->d.pin.cap & COP_PINCAP_INPUT) &&
		    !(w->widgetcap & COP_AWCAP_DIGITAL)) {
			add = nd = 0;
			conv = azalia_codec_find_defadc(this, w->nid, 0);
			switch(w->d.pin.device) {
			/* primary - input by default */
			case CORB_CD_MICIN:
			case CORB_CD_LINEIN:
				add = 1;
				break;
			/* secondary - output by default */
			case CORB_CD_SPEAKER:
				if (w->nid == this->speaker ||
				    w->nid == this->speaker2)
					break;
				/* FALLTHROUGH */
			case CORB_CD_HEADPHONE:
			case CORB_CD_LINEOUT:
				add = nd = 1;
				break;
			}
			if (add && nipins < MAX_PINS) {
				ipins[nipins].nid = w->nid;
				ipins[nipins].prio = prio | (nd << 8);
				ipins[nipins].conv = conv;
				nipins++;
			}
		}
		/* digital in */
		if ((w->d.pin.cap & COP_PINCAP_INPUT) && 
		    (w->widgetcap & COP_AWCAP_DIGITAL)) {
			conv = azalia_codec_find_defadc(this, w->nid, 0);
			switch(w->d.pin.device) {
			case CORB_CD_SPDIFIN:
			case CORB_CD_DIGITALIN:
			case CORB_CD_MICIN:
				if (nipins_d < MAX_PINS) {
					ipins_d[nipins_d].nid = w->nid;
					ipins_d[nipins_d].prio = prio;
					ipins_d[nipins_d].conv = conv;
					nipins_d++;
				}
				break;
			}
		}
	}

	this->opins = mallocarray(nopins, sizeof(struct io_pin), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (this->opins == NULL)
		return(ENOMEM);
	this->nopins = 0;
	for (i = 0; i < nopins; i++) {
		for (j = 0; j < this->nopins; j++)
			if (this->opins[j].prio > opins[i].prio)
				break;
		for (k = this->nopins; k > j; k--)
			this->opins[k] = this->opins[k - 1];
		if (j < nopins)
			this->opins[j] = opins[i];
		this->nopins++;
		if (this->nopins == nopins)
			break;
	}

	this->opins_d = mallocarray(nopins_d, sizeof(struct io_pin), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (this->opins_d == NULL)
		return(ENOMEM);
	this->nopins_d = 0;
	for (i = 0; i < nopins_d; i++) {
		for (j = 0; j < this->nopins_d; j++)
			if (this->opins_d[j].prio > opins_d[i].prio)
				break;
		for (k = this->nopins_d; k > j; k--)
			this->opins_d[k] = this->opins_d[k - 1];
		if (j < nopins_d)
			this->opins_d[j] = opins_d[i];
		this->nopins_d++;
		if (this->nopins_d == nopins_d)
			break;
	}

	this->ipins = mallocarray(nipins, sizeof(struct io_pin), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (this->ipins == NULL)
		return(ENOMEM);
	this->nipins = 0;
	for (i = 0; i < nipins; i++) {
		for (j = 0; j < this->nipins; j++)
			if (this->ipins[j].prio > ipins[i].prio)
				break;
		for (k = this->nipins; k > j; k--)
			this->ipins[k] = this->ipins[k - 1];
		if (j < nipins)
			this->ipins[j] = ipins[i];
		this->nipins++;
		if (this->nipins == nipins)
			break;
	}

	this->ipins_d = mallocarray(nipins_d, sizeof(struct io_pin), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (this->ipins_d == NULL)
		return(ENOMEM);
	this->nipins_d = 0;
	for (i = 0; i < nipins_d; i++) {
		for (j = 0; j < this->nipins_d; j++)
			if (this->ipins_d[j].prio > ipins_d[i].prio)
				break;
		for (k = this->nipins_d; k > j; k--)
			this->ipins_d[k] = this->ipins_d[k - 1];
		if (j < nipins_d)
			this->ipins_d[j] = ipins_d[i];
		this->nipins_d++;
		if (this->nipins_d == nipins_d)
			break;
	}

#ifdef AZALIA_DEBUG
	printf("%s: analog out pins:", __func__);
	for (i = 0; i < this->nopins; i++)
		printf(" 0x%2.2x->0x%2.2x", this->opins[i].nid,
		    this->opins[i].conv);
	printf("\n");
	printf("%s: digital out pins:", __func__);
	for (i = 0; i < this->nopins_d; i++)
		printf(" 0x%2.2x->0x%2.2x", this->opins_d[i].nid,
		    this->opins_d[i].conv);
	printf("\n");
	printf("%s: analog in pins:", __func__);
	for (i = 0; i < this->nipins; i++)
		printf(" 0x%2.2x->0x%2.2x", this->ipins[i].nid,
		    this->ipins[i].conv);
	printf("\n");
	printf("%s: digital in pins:", __func__);
	for (i = 0; i < this->nipins_d; i++)
		printf(" 0x%2.2x->0x%2.2x", this->ipins_d[i].nid,
		    this->ipins_d[i].conv);
	printf("\n");
#endif

	return 0;
#undef MAX_PINS
}

int
azalia_codec_select_dacs(codec_t *this)
{
	widget_t *w;
	nid_t *convs;
	int nconv, conv;
	int i, j, k, err;

	convs = mallocarray(this->na_dacs, sizeof(nid_t), M_DEVBUF,
	    M_NOWAIT | M_ZERO);
	if (convs == NULL)
		return(ENOMEM);

	err = 0;
	nconv = 0;
	for (i = 0; i < this->nopins; i++) {
		w = &this->w[this->opins[i].nid];

		conv = this->opins[i].conv;
		for (j = 0; j < nconv; j++) {
			if (conv == convs[j])
				break;
		}
		if (j == nconv) {
			convs[nconv++] = conv;
			if (w->nid == this->fhp)
				this->fhp_dac = conv;
			if (nconv >= this->na_dacs) {
				break;
			}
		} else {
			/* find a different dac */
			conv = -1;
			for (j = 0; j < w->nconnections; j++) {
				if (!azalia_widget_enabled(this,
				    w->connections[j]))
					continue;
				conv = azalia_codec_find_defdac(this,
				    w->connections[j], 1);
				if (conv == -1)
					continue;
				for (k = 0; k < nconv; k++) {
					if (conv == convs[k])
						break;
				}
				if (k == nconv)
					break;
			}
			if (j < w->nconnections && conv != -1) {
				err = azalia_comresp(this, w->nid,
				    CORB_SET_CONNECTION_SELECT_CONTROL, j, 0);
				if (err)
					break;
				w->selected = j;
				this->opins[i].conv = conv;
				if (w->nid == this->fhp)
					this->fhp_dac = conv;
				convs[nconv++] = conv;
				if (nconv >= this->na_dacs)
					break;
			}
		}
	}

	free(convs, M_DEVBUF, 0);
	return(err);
}

/* Connect the speaker to a DAC that no other output pin is connected
 * to by default.  If that is not possible, connect to a DAC other
 * than the one the first output pin is connected to. 
 */
int
azalia_codec_select_spkrdac(codec_t *this)
{
	widget_t *w;
	nid_t convs[HDA_MAX_CHANNELS];
	int nconv, conv;
	int i, j, err, fspkr, conn;

	nconv = fspkr = 0;
	for (i = 0; i < this->nopins; i++) {
		conv = this->opins[i].conv;
		for (j = 0; j < nconv; j++) {
			if (conv == convs[j])
				break;
		}
		if (j == nconv) {
			if (conv == this->spkr_dac)
				fspkr = 1;
			convs[nconv++] = conv;
			if (nconv == this->na_dacs)
				break;
		}
	}

	if (fspkr) {
		conn = conv = -1;
		w = &this->w[this->speaker];
		for (i = 0; i < w->nconnections; i++) {
			conv = azalia_codec_find_defdac(this,
			    w->connections[i], 1);
			for (j = 0; j < nconv; j++)
				if (conv == convs[j])
					break;
			if (j == nconv)
				break;
		}
		if (i < w->nconnections) {
			conn = i;
		} else {
			/* Couldn't get a unique DAC.  Try to get a diferent
			 * DAC than the first pin's DAC.
			 */
			if (this->spkr_dac == this->opins[0].conv) {
				/* If the speaker connection can't be changed,
				 * change the first pin's connection.
				 */
				if (w->nconnections == 1)
					w = &this->w[this->opins[0].nid];
				for (j = 0; j < w->nconnections; j++) {
					conv = azalia_codec_find_defdac(this,
					    w->connections[j], 1);
					if (conv != this->opins[0].conv) {
						conn = j;
						break;
					}
				}
			}
		}
		if (conn != -1 && conv != -1) {
			err = azalia_comresp(this, w->nid,
			    CORB_SET_CONNECTION_SELECT_CONTROL, conn, 0);
			if (err)
				return(err);
			w->selected = conn;
			if (w->nid == this->speaker)
				this->spkr_dac = conv;
			else
				this->opins[0].conv = conv;
		}
	}

	/* If there is a speaker2, try to connect it to spkr_dac. */
	if (this->speaker2 != -1) {
		conn = conv = -1;
		w = &this->w[this->speaker2];
		for (i = 0; i < w->nconnections; i++) {
			conv = azalia_codec_find_defdac(this,
			    w->connections[i], 1);
			if (conv == this->spkr_dac) {
				conn = i;
				break;
			}
		}
		if (conn != -1) {
			err = azalia_comresp(this, w->nid,
			    CORB_SET_CONNECTION_SELECT_CONTROL, conn, 0);
			if (err)
				return(err);
			w->selected = conn;
		}
	}

	return(0);
}

int
azalia_codec_find_defdac(codec_t *this, int index, int depth)
{
	const widget_t *w;
	int i, ret;

	w = &this->w[index];
	if (w->enable == 0)
		return -1;

	if (w->type == COP_AWTYPE_AUDIO_OUTPUT)
		return index;

	if (depth > 0 &&
	    (w->type == COP_AWTYPE_PIN_COMPLEX ||
	    w->type == COP_AWTYPE_BEEP_GENERATOR ||
	    w->type == COP_AWTYPE_AUDIO_INPUT))
		return -1;
	if (++depth >= 10)
		return -1;

	if (w->nconnections > 0) {
		/* by default, all mixer connections are active */
		if (w->type == COP_AWTYPE_AUDIO_MIXER) {
			for (i = 0; i < w->nconnections; i++) {
				index = w->connections[i];
				if (!azalia_widget_enabled(this, index))
					continue;
				ret = azalia_codec_find_defdac(this, index,
				    depth);
				if (ret >= 0)
					return ret;
			}
		/* 7.3.3.2 Connection Select Control
		 * If an attempt is made to Set an index value greater than
		 * the number of list entries (index is equal to or greater
		 * than the Connection List Length property for the widget)
		 * the behavior is not predictable.
		 */

		/* negative index values are wrong too */
		} else if (w->selected >= 0 &&
			w->selected < sizeof(w->connections)) {
				index = w->connections[w->selected];
				if (VALID_WIDGET_NID(index, this)) {
					ret = azalia_codec_find_defdac(this,
						index, depth);
					if (ret >= 0)
						return ret;
				}
		}
	}

	return -1;
}

int
azalia_codec_find_defadc_sub(codec_t *this, nid_t node, int index, int depth)
{
	const widget_t *w;
	int i, ret;

	w = &this->w[index];
	if (w->nid == node) {
		return index;
	}
	/* back at the beginning or a bad end */
	if (depth > 0 &&
	    (w->type == COP_AWTYPE_PIN_COMPLEX ||
	    w->type == COP_AWTYPE_BEEP_GENERATOR ||
	    w->type == COP_AWTYPE_AUDIO_OUTPUT ||
	    w->type == COP_AWTYPE_AUDIO_INPUT))
		return -1;
	if (++depth >= 10)
		return -1;

	if (w->nconnections > 0) {
		/* by default, all mixer connections are active */
		if (w->type == COP_AWTYPE_AUDIO_MIXER) {
			for (i = 0; i < w->nconnections; i++) {
				if (!azalia_widget_enabled(this, w->connections[i]))
					continue;
				ret = azalia_codec_find_defadc_sub(this, node,
				    w->connections[i], depth);
				if (ret >= 0)
					return ret;
			}
		/* 7.3.3.2 Connection Select Control
		 * If an attempt is made to Set an index value greater than
		 * the number of list entries (index is equal to or greater
		 * than the Connection List Length property for the widget)
		 * the behavior is not predictable.
		 */

		/* negative index values are wrong too */
		} else if (w->selected >= 0 &&
			w->selected < sizeof(w->connections)) {
				index = w->connections[w->selected];
				if (VALID_WIDGET_NID(index, this)) {
					ret = azalia_codec_find_defadc_sub(this,
						node, index, depth);
					if (ret >= 0)
						return ret;
				}
		}
	}
	return -1;
}

int
azalia_codec_find_defadc(codec_t *this, int index, int depth)
{
	int i, j, conv;

	conv = -1;
	for (i = 0; i < this->na_adcs; i++) {
		j = azalia_codec_find_defadc_sub(this, index,
		    this->a_adcs[i], 0);
		if (j >= 0) {
			conv = this->a_adcs[i];
			break;
		}
	}
	return(conv);
}

int
azalia_codec_init_volgroups(codec_t *this)
{
	const widget_t *w;
	uint32_t cap, result;
	int i, j, dac, err;

	j = 0;
	this->playvols.mask = 0;
	FOR_EACH_WIDGET(this, i) {
		w = &this->w[i];
		if (w->enable == 0)
			continue;
		if (w->mixer_class == AZ_CLASS_RECORD)
			continue;
		if (!(w->widgetcap & COP_AWCAP_OUTAMP))
			continue;
		if ((COP_AMPCAP_NUMSTEPS(w->outamp_cap) == 0) &&
		    !(w->outamp_cap & COP_AMPCAP_MUTE))
			continue;
		this->playvols.mask |= (1 << j);
		this->playvols.slaves[j++] = w->nid;
		if (j >= AZ_MAX_VOL_SLAVES)
			break;
	}
	this->playvols.nslaves = j;

	this->playvols.cur = 0;
	for (i = 0; i < this->playvols.nslaves; i++) {
		w = &this->w[this->playvols.slaves[i]];
		if (w->nid == this->input_mixer ||
		    w->parent == this->input_mixer ||
		    WIDGET_CHANNELS(w) < 2)
			continue;
		j = 0;
		/* azalia_codec_find_defdac only goes 10 connections deep.
		 * Start the connection depth at 7 so it doesn't go more
		 * than 3 connections deep.
		 */
		if (w->type == COP_AWTYPE_AUDIO_MIXER ||
		    w->type == COP_AWTYPE_AUDIO_SELECTOR)
			j = 7;
		dac = azalia_codec_find_defdac(this, w->nid, j);
		if (dac == -1)
			continue;
		if (dac != this->dacs.groups[this->dacs.cur].conv[0] &&
		    dac != this->spkr_dac && dac != this->fhp_dac)
			continue;
		cap = w->outamp_cap;
		if ((cap & COP_AMPCAP_MUTE) && COP_AMPCAP_NUMSTEPS(cap)) {
			if (w->type == COP_AWTYPE_BEEP_GENERATOR) {
				continue;
			} else if (w->type == COP_AWTYPE_PIN_COMPLEX) {
				err = azalia_comresp(this, w->nid,
				    CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
				if (!err && (result & CORB_PWC_OUTPUT))
					this->playvols.cur |= (1 << i);
			} else
				this->playvols.cur |= (1 << i);
		}
	}
	if (this->playvols.cur == 0) {
		for (i = 0; i < this->playvols.nslaves; i++) {
			w = &this->w[this->playvols.slaves[i]];
			j = 0;
			if (w->type == COP_AWTYPE_AUDIO_MIXER ||
			    w->type == COP_AWTYPE_AUDIO_SELECTOR)
				j = 7;
			dac = azalia_codec_find_defdac(this, w->nid, j);
			if (dac == -1)
				continue;
			if (dac != this->dacs.groups[this->dacs.cur].conv[0] &&
			    dac != this->spkr_dac && dac != this->fhp_dac)
				continue;
			if (w->type == COP_AWTYPE_BEEP_GENERATOR)
				continue;
			if (w->type == COP_AWTYPE_PIN_COMPLEX) {
				err = azalia_comresp(this, w->nid,
				    CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
				if (!err && (result & CORB_PWC_OUTPUT))
					this->playvols.cur |= (1 << i);
			} else {
				this->playvols.cur |= (1 << i);
			}
		}
	}

	this->playvols.master = this->audiofunc;
	if (this->playvols.nslaves > 0) {
		FOR_EACH_WIDGET(this, i) {
			w = &this->w[i];
			if (w->type != COP_AWTYPE_VOLUME_KNOB)
				continue;
			if (!COP_VKCAP_NUMSTEPS(w->d.volume.cap))
				continue;
			this->playvols.master = w->nid;
			break;
		}
	}

	j = 0;
	this->recvols.mask = 0;
	FOR_EACH_WIDGET(this, i) {
		w = &this->w[i];
		if (w->enable == 0)
			continue;
		if (w->type == COP_AWTYPE_AUDIO_INPUT ||
		    w->type == COP_AWTYPE_PIN_COMPLEX) {
			if (!(w->widgetcap & COP_AWCAP_INAMP))
				continue;
			if ((COP_AMPCAP_NUMSTEPS(w->inamp_cap) == 0) &&
			    !(w->inamp_cap & COP_AMPCAP_MUTE))
				continue;
		} else if (w->type == COP_AWTYPE_AUDIO_MIXER ||
		    w->type == COP_AWTYPE_AUDIO_SELECTOR) {
			if (w->mixer_class != AZ_CLASS_RECORD)
				continue;
			if (!(w->widgetcap & COP_AWCAP_OUTAMP))
				continue;
			if ((COP_AMPCAP_NUMSTEPS(w->outamp_cap) == 0) &&
			    !(w->outamp_cap & COP_AMPCAP_MUTE))
				continue;
		} else {
			continue;
		}
		this->recvols.mask |= (1 << j);
		this->recvols.slaves[j++] = w->nid;
		if (j >= AZ_MAX_VOL_SLAVES)
			break;
	}
	this->recvols.nslaves = j;

	this->recvols.cur = 0;
	for (i = 0; i < this->recvols.nslaves; i++) {
		w = &this->w[this->recvols.slaves[i]];
		cap = w->outamp_cap;
		if (w->type == COP_AWTYPE_AUDIO_INPUT ||
		    w->type != COP_AWTYPE_PIN_COMPLEX)
			cap = w->inamp_cap;
		 else
			if (w->mixer_class != AZ_CLASS_RECORD)
				continue;
		if ((cap & COP_AMPCAP_MUTE) && COP_AMPCAP_NUMSTEPS(cap)) {
			if (w->type == COP_AWTYPE_PIN_COMPLEX) {
				err = azalia_comresp(this, w->nid,
				    CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
				if (!err && !(result & CORB_PWC_OUTPUT))
					this->recvols.cur |= (1 << i);
			} else
				this->recvols.cur |= (1 << i);
		}
	}
	if (this->recvols.cur == 0) {
		for (i = 0; i < this->recvols.nslaves; i++) {
			w = &this->w[this->recvols.slaves[i]];
			cap = w->outamp_cap;
			if (w->type == COP_AWTYPE_AUDIO_INPUT ||
			    w->type != COP_AWTYPE_PIN_COMPLEX)
				cap = w->inamp_cap;
			 else
				if (w->mixer_class != AZ_CLASS_RECORD)
					continue;
			if (w->type == COP_AWTYPE_PIN_COMPLEX) {
				err = azalia_comresp(this, w->nid,
				    CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
				if (!err && !(result & CORB_PWC_OUTPUT))
					this->recvols.cur |= (1 << i);
			} else {
				this->recvols.cur |= (1 << i);
			}
		}
	}

	this->recvols.master = this->audiofunc;

	return 0;
}

int
azalia_codec_delete(codec_t *this)
{
	azalia_mixer_delete(this);

	if (this->formats != NULL) {
		free(this->formats, M_DEVBUF, 0);
		this->formats = NULL;
	}
	this->nformats = 0;

	if (this->encs != NULL) {
		free(this->encs, M_DEVBUF, 0);
		this->encs = NULL;
	}
	this->nencs = 0;

	if (this->opins != NULL) {
		free(this->opins, M_DEVBUF, 0);
		this->opins = NULL;
	}
	this->nopins = 0;

	if (this->opins_d != NULL) {
		free(this->opins_d, M_DEVBUF, 0);
		this->opins_d = NULL;
	}
	this->nopins_d = 0;

	if (this->ipins != NULL) {
		free(this->ipins, M_DEVBUF, 0);
		this->ipins = NULL;
	}
	this->nipins = 0;

	if (this->ipins_d != NULL) {
		free(this->ipins_d, M_DEVBUF, 0);
		this->ipins_d = NULL;
	}
	this->nipins_d = 0;

	if (this->w != NULL) {
		free(this->w, M_DEVBUF, 0);
		this->w = NULL;
	}

	return 0;
}

int
azalia_codec_construct_format(codec_t *this, int newdac, int newadc)
{
	const convgroup_t *group;
	uint32_t bits_rates;
	int variation;
	int nbits, c, chan, i, err;
	nid_t nid;

	variation = 0;

	if (this->dacs.ngroups > 0 && newdac < this->dacs.ngroups &&
	    newdac >= 0) {
		this->dacs.cur = newdac;
		group = &this->dacs.groups[this->dacs.cur];
		bits_rates = this->w[group->conv[0]].d.audio.bits_rates;
		nbits = 0;
		if (bits_rates & COP_PCM_B8)
			nbits++;
		if (bits_rates & COP_PCM_B16)
			nbits++;
		if (bits_rates & COP_PCM_B20)
			nbits++;
		if (bits_rates & COP_PCM_B24)
			nbits++;
		if ((bits_rates & COP_PCM_B32) &&
		    !(this->w[group->conv[0]].widgetcap & COP_AWCAP_DIGITAL))
			nbits++;
		if (nbits == 0) {
			printf("%s: invalid DAC PCM format: 0x%8.8x\n",
			    XNAME(this->az), bits_rates);
			return -1;
		}
		variation += group->nconv * nbits;
	}

	if (this->adcs.ngroups > 0 && newadc < this->adcs.ngroups &&
	    newadc >= 0) {
		this->adcs.cur = newadc;
		group = &this->adcs.groups[this->adcs.cur];
		bits_rates = this->w[group->conv[0]].d.audio.bits_rates;
		nbits = 0;
		if (bits_rates & COP_PCM_B8)
			nbits++;
		if (bits_rates & COP_PCM_B16)
			nbits++;
		if (bits_rates & COP_PCM_B20)
			nbits++;
		if (bits_rates & COP_PCM_B24)
			nbits++;
		if ((bits_rates & COP_PCM_B32) &&
		    !(this->w[group->conv[0]].widgetcap & COP_AWCAP_DIGITAL))
			nbits++;
		if (nbits == 0) {
			printf("%s: invalid ADC PCM format: 0x%8.8x\n",
			    XNAME(this->az), bits_rates);
			return -1;
		}
		variation += group->nconv * nbits;
	}

	if (variation == 0) {
		DPRINTF(("%s: no converter groups\n", XNAME(this->az)));
		return -1;
	}

	if (this->formats != NULL)
		free(this->formats, M_DEVBUF, 0);
	this->nformats = 0;
	this->formats = mallocarray(variation, sizeof(struct audio_format),
	    M_DEVBUF, M_NOWAIT | M_ZERO);
	if (this->formats == NULL) {
		printf("%s: out of memory in %s\n",
		    XNAME(this->az), __func__);
		return ENOMEM;
	}

	/* register formats for playback */
	if (this->dacs.ngroups > 0) {
		group = &this->dacs.groups[this->dacs.cur];
		for (c = 0; c < group->nconv; c++) {
			chan = 0;
			bits_rates = ~0;
			if (this->w[group->conv[0]].widgetcap &
			    COP_AWCAP_DIGITAL)
				bits_rates &= ~(COP_PCM_B32);
			for (i = 0; i <= c; i++) {
				nid = group->conv[i];
				chan += WIDGET_CHANNELS(&this->w[nid]);
				bits_rates &= this->w[nid].d.audio.bits_rates;
			}
			azalia_codec_add_bits(this, chan, bits_rates,
			    AUMODE_PLAY);
		}
	}

	/* register formats for recording */
	if (this->adcs.ngroups > 0) {
		group = &this->adcs.groups[this->adcs.cur];
		for (c = 0; c < group->nconv; c++) {
			chan = 0;
			bits_rates = ~0;
			if (this->w[group->conv[0]].widgetcap &
			    COP_AWCAP_DIGITAL)
				bits_rates &= ~(COP_PCM_B32);
			for (i = 0; i <= c; i++) {
				nid = group->conv[i];
				chan += WIDGET_CHANNELS(&this->w[nid]);
				bits_rates &= this->w[nid].d.audio.bits_rates;
			}
			azalia_codec_add_bits(this, chan, bits_rates,
			    AUMODE_RECORD);
		}
	}

	err = azalia_create_encodings(this);
	if (err)
		return err;
	return 0;
}

void
azalia_codec_add_bits(codec_t *this, int chan, uint32_t bits_rates, int mode)
{
	if (bits_rates & COP_PCM_B8)
		azalia_codec_add_format(this, chan, 8, bits_rates, mode);
	if (bits_rates & COP_PCM_B16)
		azalia_codec_add_format(this, chan, 16, bits_rates, mode);
	if (bits_rates & COP_PCM_B20)
		azalia_codec_add_format(this, chan, 20, bits_rates, mode);
	if (bits_rates & COP_PCM_B24)
		azalia_codec_add_format(this, chan, 24, bits_rates, mode);
	if (bits_rates & COP_PCM_B32)
		azalia_codec_add_format(this, chan, 32, bits_rates, mode);
}

void
azalia_codec_add_format(codec_t *this, int chan, int prec, uint32_t rates,
    int32_t mode)
{
	struct audio_format *f;

	f = &this->formats[this->nformats++];
	f->mode = mode;
	f->encoding = AUDIO_ENCODING_SLINEAR_LE;
	if (prec == 8)
		f->encoding = AUDIO_ENCODING_ULINEAR_LE;
	f->precision = prec;
	f->channels = chan;
	f->frequency_type = 0;
	if (rates & COP_PCM_R80)
		f->frequency[f->frequency_type++] = 8000;
	if (rates & COP_PCM_R110)
		f->frequency[f->frequency_type++] = 11025;
	if (rates & COP_PCM_R160)
		f->frequency[f->frequency_type++] = 16000;
	if (rates & COP_PCM_R220)
		f->frequency[f->frequency_type++] = 22050;
	if (rates & COP_PCM_R320)
		f->frequency[f->frequency_type++] = 32000;
	if (rates & COP_PCM_R441)
		f->frequency[f->frequency_type++] = 44100;
	if (rates & COP_PCM_R480)
		f->frequency[f->frequency_type++] = 48000;
	if (rates & COP_PCM_R882)
		f->frequency[f->frequency_type++] = 88200;
	if (rates & COP_PCM_R960)
		f->frequency[f->frequency_type++] = 96000;
	if (rates & COP_PCM_R1764)
		f->frequency[f->frequency_type++] = 176400;
	if (rates & COP_PCM_R1920)
		f->frequency[f->frequency_type++] = 192000;
	if (rates & COP_PCM_R3840)
		f->frequency[f->frequency_type++] = 384000;
}

int
azalia_codec_connect_stream(stream_t *this)
{
	const codec_t *codec = &this->az->codecs[this->az->codecno];
	const convgroup_t *group;
	widget_t *w;
	uint32_t digital, stream_chan;
	int i, err, curchan, nchan, widchan;

	err = 0;
	nchan = (this->fmt & HDA_SD_FMT_CHAN) + 1;

	if (this->dir == AUMODE_RECORD)
		group = &codec->adcs.groups[codec->adcs.cur];
	else
		group = &codec->dacs.groups[codec->dacs.cur];

	curchan = 0;
	for (i = 0; i < group->nconv; i++) {
		w = &codec->w[group->conv[i]];
		widchan = WIDGET_CHANNELS(w);

		stream_chan = (this->number << 4);
		if (curchan < nchan) {
			stream_chan |= curchan;
		} else if (w->nid == codec->spkr_dac ||
		    w->nid == codec->fhp_dac) {
			stream_chan |= 0;	/* first channel(s) */
		} else
			stream_chan = 0;	/* idle stream */

		if (stream_chan == 0) {
			DPRINTFN(0, ("%s: %2.2x is idle\n", __func__, w->nid));
		} else {
			DPRINTFN(0, ("%s: %2.2x on stream chan %d\n", __func__,
			    w->nid, stream_chan & ~(this->number << 4)));
		}

		err = azalia_comresp(codec, w->nid, CORB_SET_CONVERTER_FORMAT,
		    this->fmt, NULL);
		if (err) {
			DPRINTF(("%s: nid %2.2x fmt %2.2x: %d\n",
			    __func__, w->nid, this->fmt, err));
			break;
		}
		err = azalia_comresp(codec, w->nid,
		    CORB_SET_CONVERTER_STREAM_CHANNEL, stream_chan, NULL);
		if (err) {
			DPRINTF(("%s: nid %2.2x chan %d: %d\n",
			    __func__, w->nid, stream_chan, err));
			break;
		}

		if (w->widgetcap & COP_AWCAP_DIGITAL) {
			err = azalia_comresp(codec, w->nid,
			    CORB_GET_DIGITAL_CONTROL, 0, &digital);
			if (err) {
				DPRINTF(("%s: nid %2.2x get digital: %d\n",
				    __func__, w->nid, err));
				break;
			}
			digital = (digital & 0xff) | CORB_DCC_DIGEN;
			err = azalia_comresp(codec, w->nid,
			    CORB_SET_DIGITAL_CONTROL_L, digital, NULL);
			if (err) {
				DPRINTF(("%s: nid %2.2x set digital: %d\n",
				    __func__, w->nid, err));
				break;
			}
		}
		curchan += widchan;
	}

	return err;
}

int
azalia_codec_disconnect_stream(stream_t *this)
{
	const codec_t *codec = &this->az->codecs[this->az->codecno];
	const convgroup_t *group;
	uint32_t v;
	int i;
	nid_t nid;

	if (this->dir == AUMODE_RECORD)
		group = &codec->adcs.groups[codec->adcs.cur];
	else
		group = &codec->dacs.groups[codec->dacs.cur];
	for (i = 0; i < group->nconv; i++) {
		nid = group->conv[i];
		azalia_comresp(codec, nid, CORB_SET_CONVERTER_STREAM_CHANNEL,
		    0, NULL);	/* stream#0 */
		if (codec->w[nid].widgetcap & COP_AWCAP_DIGITAL) {
			/* disable S/PDIF */
			azalia_comresp(codec, nid, CORB_GET_DIGITAL_CONTROL,
			    0, &v);
			v = (v & ~CORB_DCC_DIGEN) & 0xff;
			azalia_comresp(codec, nid, CORB_SET_DIGITAL_CONTROL_L,
			    v, NULL);
		}
	}
	return 0;
}

/* ================================================================
 * HDA widget functions
 * ================================================================ */

int
azalia_widget_init(widget_t *this, const codec_t *codec, nid_t nid)
{
	uint32_t result;
	int err;

	err = azalia_comresp(codec, nid, CORB_GET_PARAMETER,
	    COP_AUDIO_WIDGET_CAP, &result);
	if (err)
		return err;
	this->nid = nid;
	this->widgetcap = result;
	this->type = COP_AWCAP_TYPE(result);
	if (this->widgetcap & COP_AWCAP_POWER) {
		azalia_comresp(codec, nid, CORB_SET_POWER_STATE, CORB_PS_D0,
		    &result);
		DELAY(100);
	}

	this->enable = 1;
	this->mixer_class = -1;
	this->parent = codec->audiofunc;

	switch (this->type) {
	case COP_AWTYPE_AUDIO_OUTPUT:
		/* FALLTHROUGH */
	case COP_AWTYPE_AUDIO_INPUT:
		azalia_widget_init_audio(this, codec);
		break;
	case COP_AWTYPE_PIN_COMPLEX:
		azalia_widget_init_pin(this, codec);
		break;
	case COP_AWTYPE_VOLUME_KNOB:
		err = azalia_comresp(codec, this->nid, CORB_GET_PARAMETER,
		    COP_VOLUME_KNOB_CAPABILITIES, &result);
		if (err)
			return err;
		this->d.volume.cap = result;
		break;
	case COP_AWTYPE_POWER:
		/* FALLTHROUGH */
	case COP_AWTYPE_VENDOR_DEFINED:
		this->enable = 0;
		break;
	}

	/* amplifier information */
	/* XXX (ab)use bits 24-30 to store the "control offset", which is
	 * the number of steps, starting at 0, that have no effect.  these
	 * bits are reserved in HDA 1.0.
	 */
	if (this->widgetcap & COP_AWCAP_INAMP) {
		if (this->widgetcap & COP_AWCAP_AMPOV)
			azalia_comresp(codec, nid, CORB_GET_PARAMETER,
			    COP_INPUT_AMPCAP, &this->inamp_cap);
		else
			this->inamp_cap = codec->w[codec->audiofunc].inamp_cap;
		this->inamp_cap &= ~(0x7f << 24);
	}
	if (this->widgetcap & COP_AWCAP_OUTAMP) {
		if (this->widgetcap & COP_AWCAP_AMPOV)
			azalia_comresp(codec, nid, CORB_GET_PARAMETER,
			    COP_OUTPUT_AMPCAP, &this->outamp_cap);
		else
			this->outamp_cap = codec->w[codec->audiofunc].outamp_cap;
		this->outamp_cap &= ~(0x7f << 24);
	}
	return 0;
}

int
azalia_widget_sole_conn(codec_t *this, nid_t nid)
{
	int i, j, target, nconn, has_target;

	/* connected to ADC */
	for (i = 0; i < this->adcs.ngroups; i++) {
		for (j = 0; j < this->adcs.groups[i].nconv; j++) {
			target = this->adcs.groups[i].conv[j];
			if (this->w[target].nconnections == 1 &&
			    this->w[target].connections[0] == nid) {
				return target;
			}
		}
	}
	/* connected to DAC */
	for (i = 0; i < this->dacs.ngroups; i++) {
		for (j = 0; j < this->dacs.groups[i].nconv; j++) {
			target = this->dacs.groups[i].conv[j];
			if (this->w[target].nconnections == 1 &&
			    this->w[target].connections[0] == nid) {
				return target;
			}
		}
	}
	/* connected to pin complex */
	target = -1;
	FOR_EACH_WIDGET(this, i) {
		if (this->w[i].type != COP_AWTYPE_PIN_COMPLEX)
			continue;
		if (this->w[i].nconnections == 1 &&
		    this->w[i].connections[0] == nid) {
			if (target != -1)
				return -1;
			target = i;
		} else {
			nconn = 0;
			has_target = 0;
			for (j = 0; j < this->w[i].nconnections; j++) {
				if (!this->w[this->w[i].connections[j]].enable)
					continue;
				nconn++;
				if (this->w[i].connections[j] == nid)
					has_target = 1;
			}
			if (has_target == 1) {
				if (nconn == 1) {
					if (target != -1)
						return -1;
					target = i;
				} else {
					/* not sole connection at least once */
					return -1;
				}
			}
		}
	}
	if (target != -1)
		return target;

	return -1;
}

int
azalia_widget_label_widgets(codec_t *codec)
{
	widget_t *w;
	convgroup_t *group;
	int types[16];
	int pins[16];
	int colors_used, use_colors, schan;
	int i, j;

	bzero(&pins, sizeof(pins));
	bzero(&types, sizeof(types));

	/* If codec has more than one line-out jack, check if the jacks
	 * have unique colors.  If so, use the colors in the mixer names.
	 */
	use_colors = 1;
	colors_used = 0;
	if (codec->nout_jacks < 2)
		use_colors = 0;
	for (i = 0; use_colors && i < codec->nopins; i++) {
		w = &codec->w[codec->opins[i].nid];
		if (w->d.pin.device != CORB_CD_LINEOUT)
			continue;
		if (colors_used & (1 << w->d.pin.color))
			use_colors = 0;
		else
			colors_used |= (1 << w->d.pin.color);
	}

	FOR_EACH_WIDGET(codec, i) {
		w = &codec->w[i];
		/* default for disabled/unused widgets */
		snprintf(w->name, sizeof(w->name), "u-wid%2.2x", w->nid);
		if (w->enable == 0)
			continue;
		switch (w->type) {
		case COP_AWTYPE_PIN_COMPLEX:
			pins[w->d.pin.device]++;
			if (use_colors && w->d.pin.device == CORB_CD_LINEOUT) {
				snprintf(w->name, sizeof(w->name), "%s-%s",
				    pin_devices[w->d.pin.device],
				    line_colors[w->d.pin.color]);
			} else if (pins[w->d.pin.device] > 1) {
				snprintf(w->name, sizeof(w->name), "%s%d",
				    pin_devices[w->d.pin.device],
				    pins[w->d.pin.device]);
			} else {
				snprintf(w->name, sizeof(w->name), "%s",
				    pin_devices[w->d.pin.device]);
			}
			break;
		case COP_AWTYPE_AUDIO_OUTPUT:
			if (codec->dacs.ngroups < 1)
				break;
			group = &codec->dacs.groups[0];
			schan = 0;
			for (j = 0; j < group->nconv; j++) {
				if (w->nid == group->conv[j]) {
					snprintf(w->name, sizeof(w->name),
					    "%s-%d:%d", wtypes[w->type], schan,
					    schan + WIDGET_CHANNELS(w) - 1);
				}
				schan += WIDGET_CHANNELS(w);
			}
			if (codec->dacs.ngroups < 2)
				break;
			group = &codec->dacs.groups[1];
			schan = 0;
			for (j = 0; j < group->nconv; j++) {
				if (w->nid == group->conv[j]) {
					snprintf(w->name, sizeof(w->name),
					    "dig-%s-%d:%d", wtypes[w->type],
					    schan,
					    schan + WIDGET_CHANNELS(w) - 1);
				}
				schan += WIDGET_CHANNELS(w);
			}
			break;
		case COP_AWTYPE_AUDIO_INPUT:
			w->mixer_class = AZ_CLASS_RECORD;
			if (codec->adcs.ngroups < 1)
				break;
			group = &codec->adcs.groups[0];
			schan = 0;
			for (j = 0; j < group->nconv; j++) {
				if (w->nid == group->conv[j]) {
					snprintf(w->name, sizeof(w->name),
					    "%s-%d:%d", wtypes[w->type], schan,
					    schan + WIDGET_CHANNELS(w) - 1);
				}
				schan += WIDGET_CHANNELS(w);
			}
			if (codec->adcs.ngroups < 2)
				break;
			group = &codec->adcs.groups[1];
			schan = 0;
			for (j = 0; j < group->nconv; j++) {
				if (w->nid == group->conv[j]) {
					snprintf(w->name, sizeof(w->name),
					    "dig-%s-%d:%d", wtypes[w->type],
					    schan,
					    schan + WIDGET_CHANNELS(w) - 1);
				}
				schan += WIDGET_CHANNELS(w);
			}
			break;
		default:
			types[w->type]++;
			if (types[w->type] > 1)
				snprintf(w->name, sizeof(w->name), "%s%d",
				    wtypes[w->type], types[w->type]);
			else
				snprintf(w->name, sizeof(w->name), "%s",
				    wtypes[w->type]);
			break;
		}
	}

	/* Mixers and selectors that connect to only one other widget are
	 * functionally part of the widget they are connected to.  Show that
	 * relationship in the name.
	 */
	FOR_EACH_WIDGET(codec, i) {
		if (codec->w[i].type != COP_AWTYPE_AUDIO_MIXER &&
		    codec->w[i].type != COP_AWTYPE_AUDIO_SELECTOR)
			continue;
		if (codec->w[i].enable == 0)
			continue;
		j = azalia_widget_sole_conn(codec, i);
		if (j == -1) {
			/* Special case.  A selector with outamp capabilities
			 * and is connected to a single widget that has either
			 * no input or no output capabilities.  This widget
			 * serves as the input or output amp for the widget
			 * it is connected to.
			 */
			if (codec->w[i].type == COP_AWTYPE_AUDIO_SELECTOR &&
			    (codec->w[i].widgetcap & COP_AWCAP_OUTAMP) &&
			    codec->w[i].nconnections == 1) {
				j = codec->w[i].connections[0];
				if (!azalia_widget_enabled(codec, j))
					continue;
				if (!(codec->w[j].widgetcap & COP_AWCAP_INAMP))
					codec->w[i].mixer_class =
					    AZ_CLASS_INPUT;
				else if (!(codec->w[j].widgetcap & COP_AWCAP_OUTAMP))
					codec->w[i].mixer_class =
					    AZ_CLASS_OUTPUT;
				else
					continue;
			}
		}
		if (j >= 0) {
			/* As part of a disabled widget, this widget
			 * should be disabled as well.
			 */
			if (codec->w[j].enable == 0) {
				codec->w[i].enable = 0;
				snprintf(codec->w[i].name,
				    sizeof(codec->w[i].name),
				    "u-wid%2.2x", i);
				continue;
			}
			snprintf(codec->w[i].name, sizeof(codec->w[i].name),
			    "%s", codec->w[j].name);
			if (codec->w[j].mixer_class == AZ_CLASS_RECORD)
				codec->w[i].mixer_class = AZ_CLASS_RECORD;
			codec->w[i].parent = j;
		}
	}

	return 0;
}

int
azalia_widget_init_audio(widget_t *this, const codec_t *codec)
{
	uint32_t result;
	int err;

	/* check audio format */
	if (this->widgetcap & COP_AWCAP_FORMATOV) {
		err = azalia_comresp(codec, this->nid, CORB_GET_PARAMETER,
		    COP_STREAM_FORMATS, &result);
		if (err)
			return err;
		this->d.audio.encodings = result;
		if (result == 0) { /* quirk for CMI9880.
				    * This must not occur usually... */
			this->d.audio.encodings =
			    codec->w[codec->audiofunc].d.audio.encodings;
			this->d.audio.bits_rates =
			    codec->w[codec->audiofunc].d.audio.bits_rates;
		} else {
			if ((result & COP_STREAM_FORMAT_PCM) == 0) {
				printf("%s: %s: No PCM support: %x\n",
				    XNAME(codec->az), this->name, result);
				return -1;
			}
			err = azalia_comresp(codec, this->nid,
			    CORB_GET_PARAMETER, COP_PCM, &result);
			if (err)
				return err;
			this->d.audio.bits_rates = result;
		}
	} else {
		this->d.audio.encodings =
		    codec->w[codec->audiofunc].d.audio.encodings;
		this->d.audio.bits_rates =
		    codec->w[codec->audiofunc].d.audio.bits_rates;
	}
	return 0;
}

int
azalia_widget_init_pin(widget_t *this, const codec_t *codec)
{
	uint32_t result, dir;
	int err;

	err = azalia_comresp(codec, this->nid, CORB_GET_CONFIGURATION_DEFAULT,
	    0, &result);
	if (err)
		return err;
	this->d.pin.config = result;
	this->d.pin.sequence = CORB_CD_SEQUENCE(result);
	this->d.pin.association = CORB_CD_ASSOCIATION(result);
	this->d.pin.color = CORB_CD_COLOR(result);
	this->d.pin.device = CORB_CD_DEVICE(result);

	err = azalia_comresp(codec, this->nid, CORB_GET_PARAMETER,
	    COP_PINCAP, &result);
	if (err)
		return err;
	this->d.pin.cap = result;

	dir = CORB_PWC_INPUT;
	switch (this->d.pin.device) {
	case CORB_CD_LINEOUT:
	case CORB_CD_SPEAKER:
	case CORB_CD_HEADPHONE:
	case CORB_CD_SPDIFOUT:
	case CORB_CD_DIGITALOUT:
		dir = CORB_PWC_OUTPUT;
		break;
	}

	if (dir == CORB_PWC_INPUT && !(this->d.pin.cap & COP_PINCAP_INPUT))
		dir = CORB_PWC_OUTPUT;
	if (dir == CORB_PWC_OUTPUT && !(this->d.pin.cap & COP_PINCAP_OUTPUT))
		dir = CORB_PWC_INPUT;

	if (dir == CORB_PWC_INPUT && this->d.pin.device == CORB_CD_MICIN) {
		if (COP_PINCAP_VREF(this->d.pin.cap) & (1 << CORB_PWC_VREF_80))
			dir |= CORB_PWC_VREF_80;
		else if (COP_PINCAP_VREF(this->d.pin.cap) &
		    (1 << CORB_PWC_VREF_50))
			dir |= CORB_PWC_VREF_50;
	}

	if ((codec->qrks & AZ_QRK_WID_OVREF50) && (dir == CORB_PWC_OUTPUT))
		dir |= CORB_PWC_VREF_50;

	azalia_comresp(codec, this->nid, CORB_SET_PIN_WIDGET_CONTROL,
	    dir, NULL);

	if (this->d.pin.cap & COP_PINCAP_EAPD) {
		err = azalia_comresp(codec, this->nid,
		    CORB_GET_EAPD_BTL_ENABLE, 0, &result);
		if (err)
			return err;
		result &= 0xff;
		result |= CORB_EAPD_EAPD;
		err = azalia_comresp(codec, this->nid,
		    CORB_SET_EAPD_BTL_ENABLE, result, &result);
		if (err)
			return err;
	}

	/* Disable unconnected pins */
	if (CORB_CD_PORT(this->d.pin.config) == CORB_CD_NONE)
		this->enable = 0;

	return 0;
}

int
azalia_widget_init_connection(widget_t *this, const codec_t *codec)
{
	uint32_t result;
	int err;
	int i, j, k;
	int length, nconn, bits, conn, last;

	this->selected = -1;
	if ((this->widgetcap & COP_AWCAP_CONNLIST) == 0)
		return 0;

	err = azalia_comresp(codec, this->nid, CORB_GET_PARAMETER,
	    COP_CONNECTION_LIST_LENGTH, &result);
	if (err)
		return err;

	bits = 8;
	if (result & COP_CLL_LONG)
		bits = 16;

	length = COP_CLL_LENGTH(result);
	if (length == 0)
		return 0;

	/*
	 * 'length' is the number of entries, not the number of
	 * connections.  Find the number of connections, 'nconn', so
	 * enough space can be allocated for the list of connected
	 * nids.
	 */
	nconn = last = 0;
	for (i = 0; i < length;) {
		err = azalia_comresp(codec, this->nid,
		    CORB_GET_CONNECTION_LIST_ENTRY, i, &result);
		if (err)
			return err;
		for (k = 0; i < length && (k < 32 / bits); k++) {
			conn = (result >> (k * bits)) & ((1 << bits) - 1);
			/* If high bit is set, this is the end of a continuous
			 * list that started with the last connection.
			 */
			if ((nconn > 0) && (conn & (1 << (bits - 1))))
				nconn += (conn & ~(1 << (bits - 1))) - last;
			else
				nconn++;
			last = conn;
			i++;
		}
	}

	this->connections = mallocarray(nconn, sizeof(nid_t), M_DEVBUF, M_NOWAIT);
	if (this->connections == NULL) {
		printf("%s: out of memory\n", XNAME(codec->az));
		return ENOMEM;
	}
	for (i = 0; i < nconn;) {
		err = azalia_comresp(codec, this->nid,
		    CORB_GET_CONNECTION_LIST_ENTRY, i, &result);
		if (err)
			return err;
		for (k = 0; i < nconn && (k < 32 / bits); k++) {
			conn = (result >> (k * bits)) & ((1 << bits) - 1);
			/* If high bit is set, this is the end of a continuous
			 * list that started with the last connection.
			 */
			if ((i > 0) && (conn & (1 << (bits - 1)))) {
				for (j = 1; i < nconn && j <= conn - last; j++)
					this->connections[i++] = last + j;
			} else {
				this->connections[i++] = conn;
			}
			last = conn;
		}
	}
	this->nconnections = nconn;

	if (nconn > 0) {
		err = azalia_comresp(codec, this->nid,
		    CORB_GET_CONNECTION_SELECT_CONTROL, 0, &result);
		if (err)
			return err;
		this->selected = CORB_CSC_INDEX(result);
	}
	return 0;
}

int
azalia_widget_check_conn(codec_t *codec, int index, int depth)
{
	const widget_t *w;
	int i;

	w = &codec->w[index];

	if (w->type == COP_AWTYPE_BEEP_GENERATOR)
		return 0;

	if (depth > 0 &&
	    (w->type == COP_AWTYPE_PIN_COMPLEX ||
	    w->type == COP_AWTYPE_AUDIO_OUTPUT ||
	    w->type == COP_AWTYPE_AUDIO_INPUT)) {
		if (w->enable)
			return 1;
		else
			return 0;
	}
	if (++depth >= 10)
		return 0;
	for (i = 0; i < w->nconnections; i++) {
		if (!azalia_widget_enabled(codec, w->connections[i]))
			continue;
		if (azalia_widget_check_conn(codec, w->connections[i], depth))
			return 1;
	}
	return 0;
}

#ifdef AZALIA_DEBUG

#define	WIDGETCAP_BITS							\
    "\20\014LRSWAP\013POWER\012DIGITAL"					\
    "\011CONNLIST\010UNSOL\07PROC\06STRIPE\05FORMATOV\04AMPOV\03OUTAMP"	\
    "\02INAMP\01STEREO"

#define	PINCAP_BITS	"\20\021EAPD\16VREF100\15VREF80" \
    "\13VREFGND\12VREF50\11VREFHIZ\07BALANCE\06INPUT" \
    "\05OUTPUT\04HEADPHONE\03PRESENCE\02TRIGGER\01IMPEDANCE"

#define	ENCODING_BITS	"\20\3AC3\2FLOAT32\1PCM"

#define	BITSRATES_BITS	"\20\x15""32bit\x14""24bit\x13""20bit"		\
    "\x12""16bit\x11""8bit""\x0c""384kHz\x0b""192kHz\x0a""176.4kHz"	\
    "\x09""96kHz\x08""88.2kHz\x07""48kHz\x06""44.1kHz\x05""32kHz\x04"	\
    "22.05kHz\x03""16kHz\x02""11.025kHz\x01""8kHz"

static const char *pin_colors[16] = {
	"unknown", "black", "gray", "blue",
	"green", "red", "orange", "yellow",
	"purple", "pink", "col0a", "col0b",
	"col0c", "col0d", "white", "other"};
static const char *pin_conn[4] = {
	"jack", "none", "fixed", "combined"};
static const char *pin_conntype[16] = {
	"unknown", "1/8", "1/4", "atapi", "rca", "optical",
	"digital", "analog", "din", "xlr", "rj-11", "combination",
	"con0c", "con0d", "con0e", "other"};
static const char *pin_geo[15] = {
	"n/a", "rear", "front", "left",
	"right", "top", "bottom", "spec0", "spec1", "spec2",
	"loc0a", "loc0b", "loc0c", "loc0d", "loc0f"};
static const char *pin_chass[4] = {
	"external", "internal", "separate", "other"};

void
azalia_codec_print_audiofunc(const codec_t *this)
{
	uint32_t result;

	azalia_widget_print_audio(&this->w[this->audiofunc], "\t");

	result = this->w[this->audiofunc].inamp_cap;
	DPRINTF(("\tinamp: mute=%u size=%u steps=%u offset=%u\n",
	    (result & COP_AMPCAP_MUTE) != 0, COP_AMPCAP_STEPSIZE(result),
	    COP_AMPCAP_NUMSTEPS(result), COP_AMPCAP_OFFSET(result)));
	result = this->w[this->audiofunc].outamp_cap;
	DPRINTF(("\toutamp: mute=%u size=%u steps=%u offset=%u\n",
	    (result & COP_AMPCAP_MUTE) != 0, COP_AMPCAP_STEPSIZE(result),
	    COP_AMPCAP_NUMSTEPS(result), COP_AMPCAP_OFFSET(result)));
	azalia_comresp(this, this->audiofunc, CORB_GET_PARAMETER,
	    COP_GPIO_COUNT, &result);
	DPRINTF(("\tgpio: wake=%u unsol=%u gpis=%u gpos=%u gpios=%u\n",
	    (result & COP_GPIO_WAKE) != 0, (result & COP_GPIO_UNSOL) != 0,
	    COP_GPIO_GPIS(result), COP_GPIO_GPOS(result),
	    COP_GPIO_GPIOS(result)));
}

void
azalia_codec_print_groups(const codec_t *this)
{
	int i, n;

	for (i = 0; i < this->dacs.ngroups; i++) {
		printf("%s: dacgroup[%d]:", XNAME(this->az), i);
		for (n = 0; n < this->dacs.groups[i].nconv; n++) {
			printf(" %2.2x", this->dacs.groups[i].conv[n]);
		}
		printf("\n");
	}
	for (i = 0; i < this->adcs.ngroups; i++) {
		printf("%s: adcgroup[%d]:", XNAME(this->az), i);
		for (n = 0; n < this->adcs.groups[i].nconv; n++) {
			printf(" %2.2x", this->adcs.groups[i].conv[n]);
		}
		printf("\n");
	}
}

void
azalia_widget_print_audio(const widget_t *this, const char *lead)
{
	printf("%sencodings=%b\n", lead, this->d.audio.encodings,
	    ENCODING_BITS);
	printf("%sPCM formats=%b\n", lead, this->d.audio.bits_rates,
	    BITSRATES_BITS);
}

void
azalia_widget_print_widget(const widget_t *w, const codec_t *codec)
{
	int i;

	printf("%s: ", XNAME(codec->az));
	printf("%s%2.2x wcap=%b\n", w->type == COP_AWTYPE_PIN_COMPLEX ?
	    pin_colors[w->d.pin.color] : wtypes[w->type],
	    w->nid, w->widgetcap, WIDGETCAP_BITS);
	if (w->widgetcap & COP_AWCAP_FORMATOV)
		azalia_widget_print_audio(w, "\t");
	if (w->type == COP_AWTYPE_PIN_COMPLEX)
		azalia_widget_print_pin(w);

	if (w->type == COP_AWTYPE_VOLUME_KNOB)
		printf("\tdelta=%d steps=%d\n",
		    !!(w->d.volume.cap & COP_VKCAP_DELTA),
		    COP_VKCAP_NUMSTEPS(w->d.volume.cap));

	if ((w->widgetcap & COP_AWCAP_INAMP) &&
	    (w->widgetcap & COP_AWCAP_AMPOV))
		printf("\tinamp: mute=%u size=%u steps=%u offset=%u\n",
		    (w->inamp_cap & COP_AMPCAP_MUTE) != 0,
		    COP_AMPCAP_STEPSIZE(w->inamp_cap),
		    COP_AMPCAP_NUMSTEPS(w->inamp_cap),
		    COP_AMPCAP_OFFSET(w->inamp_cap));

	if ((w->widgetcap & COP_AWCAP_OUTAMP) &&
	    (w->widgetcap & COP_AWCAP_AMPOV))
		printf("\toutamp: mute=%u size=%u steps=%u offset=%u\n",
		    (w->outamp_cap & COP_AMPCAP_MUTE) != 0,
		    COP_AMPCAP_STEPSIZE(w->outamp_cap),
		    COP_AMPCAP_NUMSTEPS(w->outamp_cap),
		    COP_AMPCAP_OFFSET(w->outamp_cap));

	if (w->nconnections > 0) {
		printf("\tconnections=0x%x", w->connections[0]);
		for (i = 1; i < w->nconnections; i++)
			printf(",0x%x", w->connections[i]);
		printf("; selected=0x%x\n", w->connections[w->selected]);
	}
}

void
azalia_widget_print_pin(const widget_t *this)
{
	printf("\tcap=%b\n", this->d.pin.cap, PINCAP_BITS);
	printf("\t[%2.2d/%2.2d] ", CORB_CD_ASSOCIATION(this->d.pin.config),
	    CORB_CD_SEQUENCE(this->d.pin.config));
	printf("color=%s ", pin_colors[CORB_CD_COLOR(this->d.pin.config)]);
	printf("device=%s ", pin_devices[CORB_CD_DEVICE(this->d.pin.config)]);
	printf("conn=%s ", pin_conn[CORB_CD_PORT(this->d.pin.config)]);
	printf("conntype=%s\n", pin_conntype[CORB_CD_CONNECTION(this->d.pin.config)]);
	printf("\tlocation=%s ", pin_geo[CORB_CD_LOC_GEO(this->d.pin.config)]);
	printf("chassis=%s ", pin_chass[CORB_CD_LOC_CHASS(this->d.pin.config)]);
	printf("special=");
	if (CORB_CD_LOC_GEO(this->d.pin.config) == CORB_CD_LOC_SPEC0) {
		if (CORB_CD_LOC_CHASS(this->d.pin.config) == CORB_CD_EXTERNAL)
			printf("rear-panel");
		else if (CORB_CD_LOC_CHASS(this->d.pin.config) == CORB_CD_INTERNAL)
			printf("riser");
		else if (CORB_CD_LOC_CHASS(this->d.pin.config) == CORB_CD_LOC_OTHER)
			printf("mobile-lid-internal");
	} else if (CORB_CD_LOC_GEO(this->d.pin.config) == CORB_CD_LOC_SPEC1) {
		if (CORB_CD_LOC_CHASS(this->d.pin.config) == CORB_CD_EXTERNAL)
			printf("drive-bay");
		else if (CORB_CD_LOC_CHASS(this->d.pin.config) == CORB_CD_INTERNAL)
			printf("hdmi");
		else if (CORB_CD_LOC_CHASS(this->d.pin.config) == CORB_CD_LOC_OTHER)
			printf("mobile-lid-external");
	} else if (CORB_CD_LOC_GEO(this->d.pin.config) == CORB_CD_LOC_SPEC2) {
		if (CORB_CD_LOC_CHASS(this->d.pin.config) == CORB_CD_INTERNAL)
			printf("atapi");
	} else
		printf("none");
	printf("\n");
}

#else	/* AZALIA_DEBUG */

void
azalia_codec_print_audiofunc(const codec_t *this) {}

void
azalia_codec_print_groups(const codec_t *this) {}

void
azalia_widget_print_audio(const widget_t *this, const char *lead) {}

void
azalia_widget_print_widget(const widget_t *w, const codec_t *codec) {}

void
azalia_widget_print_pin(const widget_t *this) {}

#endif	/* AZALIA_DEBUG */

/* ================================================================
 * Stream functions
 * ================================================================ */

int
azalia_stream_init(stream_t *this, azalia_t *az, int regindex, int strnum,
    int dir)
{
	int err;

	this->az = az;
	this->regbase = HDA_SD_BASE + regindex * HDA_SD_SIZE;
	this->intr_bit = 1 << regindex;
	this->number = strnum;
	this->dir = dir;

	/* setup BDL buffers */
	err = azalia_alloc_dmamem(az, sizeof(bdlist_entry_t) * HDA_BDL_MAX,
				  128, &this->bdlist);
	if (err) {
		printf("%s: can't allocate a BDL buffer\n", XNAME(az));
		return err;
	}
	return 0;
}

int
azalia_stream_reset(stream_t *this)
{
	int i;
	uint16_t ctl;
	uint8_t sts;

	/* Make sure RUN bit is zero before resetting */
	ctl = STR_READ_2(this, CTL);
	ctl &= ~HDA_SD_CTL_RUN;
	STR_WRITE_2(this, CTL, ctl);
	DELAY(40);

	/* Start reset and wait for chip to enter. */
	ctl = STR_READ_2(this, CTL);
	STR_WRITE_2(this, CTL, ctl | HDA_SD_CTL_SRST);
	for (i = 5000; i > 0; i--) {
		DELAY(10);
		ctl = STR_READ_2(this, CTL);
		if (ctl & HDA_SD_CTL_SRST)
			break;
	}
	if (i == 0) {
		DPRINTF(("%s: stream reset failure 1\n", XNAME(this->az)));
		return -1;
	}

	/* Clear reset and wait for chip to finish */
	STR_WRITE_2(this, CTL, ctl & ~HDA_SD_CTL_SRST);
	for (i = 5000; i > 0; i--) {
		DELAY(10);
		ctl = STR_READ_2(this, CTL);
		if ((ctl & HDA_SD_CTL_SRST) == 0)
			break;
	}
	if (i == 0) {
		DPRINTF(("%s: stream reset failure 2\n", XNAME(this->az)));
		return -1;
	}

	sts = STR_READ_1(this, STS);
	sts |= HDA_SD_STS_DESE | HDA_SD_STS_FIFOE | HDA_SD_STS_BCIS;
	STR_WRITE_1(this, STS, sts);

	return (0);
}

int
azalia_stream_start(stream_t *this)
{
	bdlist_entry_t *bdlist;
	bus_addr_t dmaaddr, dmaend;
	int err, index;
	uint32_t intctl;
	uint8_t ctl2;

	err = azalia_stream_reset(this);
	if (err) {
		DPRINTF(("%s: stream reset failed\n", "azalia"));
		return err;
	}

	STR_WRITE_4(this, BDPL, 0);
	STR_WRITE_4(this, BDPU, 0);

	/* setup BDL */
	dmaaddr = AZALIA_DMA_DMAADDR(&this->buffer);
	dmaend = dmaaddr + this->bufsize;
	bdlist = (bdlist_entry_t*)this->bdlist.addr;
	for (index = 0; index < HDA_BDL_MAX; index++) {
		bdlist[index].low = htole32(dmaaddr);
		bdlist[index].high = htole32(PTR_UPPER32(dmaaddr));
		bdlist[index].length = htole32(this->blk);
		bdlist[index].flags = htole32(BDLIST_ENTRY_IOC);
		dmaaddr += this->blk;
		if (dmaaddr >= dmaend) {
			index++;
			break;
		}
	}

	DPRINTFN(1, ("%s: size=%d fmt=0x%4.4x index=%d\n",
	    __func__, this->bufsize, this->fmt, index));

	dmaaddr = AZALIA_DMA_DMAADDR(&this->bdlist);
	STR_WRITE_4(this, BDPL, dmaaddr);
	STR_WRITE_4(this, BDPU, PTR_UPPER32(dmaaddr));
	STR_WRITE_2(this, LVI, (index - 1) & HDA_SD_LVI_LVI);
	ctl2 = STR_READ_1(this, CTL2);
	STR_WRITE_1(this, CTL2,
	    (ctl2 & ~HDA_SD_CTL2_STRM) | (this->number << HDA_SD_CTL2_STRM_SHIFT));
	STR_WRITE_4(this, CBL, this->bufsize);
	STR_WRITE_2(this, FMT, this->fmt);

	err = azalia_codec_connect_stream(this);
	if (err)
		return EINVAL;

	intctl = AZ_READ_4(this->az, INTCTL);
	intctl |= this->intr_bit;
	AZ_WRITE_4(this->az, INTCTL, intctl);

	STR_WRITE_1(this, CTL, STR_READ_1(this, CTL) |
	    HDA_SD_CTL_DEIE | HDA_SD_CTL_FEIE | HDA_SD_CTL_IOCE |
	    HDA_SD_CTL_RUN);
	return (0);
}

int
azalia_stream_halt(stream_t *this)
{
	uint16_t ctl;

	ctl = STR_READ_2(this, CTL);
	ctl &= ~(HDA_SD_CTL_DEIE | HDA_SD_CTL_FEIE | HDA_SD_CTL_IOCE | HDA_SD_CTL_RUN);
	STR_WRITE_2(this, CTL, ctl);
	AZ_WRITE_4(this->az, INTCTL,
	    AZ_READ_4(this->az, INTCTL) & ~this->intr_bit);
	azalia_codec_disconnect_stream(this);

	return (0);
}

#define	HDA_SD_STS_BITS	"\20\3BCIS\4FIFOE\5DESE\6FIFORDY"

int
azalia_stream_intr(stream_t *this)
{
	unsigned int lpib, fifos, hwpos, cnt;
	u_int8_t sts;

	sts = STR_READ_1(this, STS);
	STR_WRITE_1(this, STS, sts |
	    HDA_SD_STS_DESE | HDA_SD_STS_FIFOE | HDA_SD_STS_BCIS);

	if (sts & (HDA_SD_STS_DESE | HDA_SD_STS_FIFOE))
		DPRINTF(("%s: stream %d: sts=%b\n", XNAME(this->az),
		    this->number, sts, HDA_SD_STS_BITS));

	if (sts & HDA_SD_STS_BCIS) {
		lpib = STR_READ_4(this, LPIB);
		fifos = STR_READ_2(this, FIFOS);
		if (fifos & 1)
			fifos++;
		hwpos = lpib;
		if (this->dir == AUMODE_PLAY)
			hwpos += fifos + 1;
		if (hwpos >= this->bufsize)
			hwpos -= this->bufsize;
		DPRINTFN(2, ("%s: stream %d, pos = %d -> %d, "
		    "lpib = %u, fifos = %u\n", __func__,
		    this->number, this->swpos, hwpos, lpib, fifos));
		cnt = 0;
		while (hwpos - this->swpos >= this->blk) {
			this->intr(this->intr_arg);
			this->swpos += this->blk;
			if (this->swpos == this->bufsize)
				this->swpos = 0;
			cnt++;
		}
		if (cnt != 1) {
			DPRINTF(("%s: stream %d: hwpos %u, %u intrs\n",
			    __func__, this->number, this->swpos, cnt));
		}
	}
	return (1);
}

/* ================================================================
 * MI audio entries
 * ================================================================ */

int
azalia_open(void *v, int flags)
{
	azalia_t *az;
	codec_t *codec;

	DPRINTFN(1, ("%s: flags=0x%x\n", __func__, flags));
	az = v;
	codec = &az->codecs[az->codecno];
	codec->running++;
	return 0;
}

void
azalia_close(void *v)
{
	azalia_t *az;
	codec_t *codec;

	DPRINTFN(1, ("%s\n", __func__));
	az = v;
	codec = &az->codecs[az->codecno];
	codec->running--;
}

int
azalia_query_encoding(void *v, audio_encoding_t *enc)
{
	azalia_t *az;
	codec_t *codec;

	az = v;
	codec = &az->codecs[az->codecno];

	if (enc->index < 0 || enc->index >= codec->nencs)
		return (EINVAL);

	*enc = codec->encs[enc->index];

	return (0);
}

void
azalia_get_default_params(void *addr, int mode, struct audio_params *params)
{
	params->sample_rate = 48000;
	params->encoding = AUDIO_ENCODING_SLINEAR_LE;
	params->precision = 16;
	params->bps = 2;
	params->msb = 1;
	params->channels = 2;
}

int
azalia_match_format(codec_t *codec, int mode, audio_params_t *par)
{
	int i;

	DPRINTFN(1, ("%s: mode=%d, want: enc=%d, prec=%d, chans=%d\n", __func__,
	    mode, par->encoding, par->precision, par->channels));

	for (i = 0; i < codec->nformats; i++) {
		if (mode != codec->formats[i].mode)
			continue;
		if (par->encoding != codec->formats[i].encoding)
			continue;
		if (par->precision != codec->formats[i].precision)
			continue;
		if (par->channels != codec->formats[i].channels)
			continue;
		break;
	}

	DPRINTFN(1, ("%s: return: enc=%d, prec=%d, chans=%d\n", __func__,
	    codec->formats[i].encoding, codec->formats[i].precision,
	    codec->formats[i].channels));

	return (i);
}

int
azalia_set_params_sub(codec_t *codec, int mode, audio_params_t *par)
{
	char *cmode;
	int i, j;
	uint ochan, oenc, opre;

	if (mode == AUMODE_PLAY)
		cmode = "play";
	else
		cmode = "record";

	ochan = par->channels;
	oenc = par->encoding;
	opre = par->precision;

	if ((mode == AUMODE_PLAY && codec->dacs.ngroups == 0) ||
	    (mode == AUMODE_RECORD && codec->adcs.ngroups == 0)) {
		azalia_get_default_params(NULL, mode, par);
		return 0;
	}

	i = azalia_match_format(codec, mode, par);
	if (i == codec->nformats && (par->precision != 16 || par->encoding !=
	    AUDIO_ENCODING_SLINEAR_LE)) {
		/* try with default encoding/precision */
		par->encoding = AUDIO_ENCODING_SLINEAR_LE;
		par->precision = 16;
		i = azalia_match_format(codec, mode, par);
	}
	if (i == codec->nformats && par->channels != 2) {
		/* try with default channels */
		par->encoding = oenc;
		par->precision = opre;
		par->channels = 2;
		i = azalia_match_format(codec, mode, par);
	}
	/* try with default everything */
	if (i == codec->nformats) {
		par->encoding = AUDIO_ENCODING_SLINEAR_LE;
		par->precision = 16;
		par->channels = 2;
		i = azalia_match_format(codec, mode, par);
		if (i == codec->nformats) {
			DPRINTF(("%s: can't find %s format %u/%u/%u\n",
			    __func__, cmode, par->encoding,
			    par->precision, par->channels));
			return EINVAL;
		}
	}
	if (codec->formats[i].frequency_type == 0) {
		DPRINTF(("%s: matched %s format %d has 0 frequencies\n",
		    __func__, cmode, i));
		return EINVAL;
	}

	for (j = 0; j < codec->formats[i].frequency_type; j++) {
		if (par->sample_rate != codec->formats[i].frequency[j])
			continue;
		break;
	}
	if (j == codec->formats[i].frequency_type) {
		/* try again with default */
		par->sample_rate = 48000;
		for (j = 0; j < codec->formats[i].frequency_type; j++) {
			if (par->sample_rate != codec->formats[i].frequency[j])
				continue;
			break;
		}
		if (j == codec->formats[i].frequency_type) {
			DPRINTF(("%s: can't find %s rate %lu\n",
			    __func__, cmode, par->sample_rate));
			return EINVAL;
		}
	}
	par->bps = AUDIO_BPS(par->precision);
	par->msb = 1;

	return (0);
}

int
azalia_set_params(void *v, int smode, int umode, audio_params_t *p,
    audio_params_t *r)
{
	azalia_t *az;
	codec_t *codec;
	int ret;

	az = v;
	codec = &az->codecs[az->codecno];
	if (codec->nformats == 0) {
		DPRINTF(("%s: codec has no formats\n", __func__));
		return EINVAL;
	}

	if (smode & AUMODE_RECORD && r != NULL) {
		ret = azalia_set_params_sub(codec, AUMODE_RECORD, r);
		if (ret)
			return (ret);
	}

	if (smode & AUMODE_PLAY && p != NULL) {
		ret = azalia_set_params_sub(codec, AUMODE_PLAY, p);
		if (ret)
			return (ret);
	}

	return (0);
}

int
azalia_round_blocksize(void *v, int blk)
{
	azalia_t *az;
	size_t size;

	blk &= ~0x7f;		/* must be multiple of 128 */
	if (blk <= 0)
		blk = 128;
	/* number of blocks must be <= HDA_BDL_MAX */
	az = v;
	size = az->pstream.buffer.size;
#ifdef DIAGNOSTIC
	if (size <= 0) {
		printf("%s: size is 0", __func__);
		return 256;
	}
#endif
	if (size > HDA_BDL_MAX * blk) {
		blk = size / HDA_BDL_MAX;
		if (blk & 0x7f)
			blk = (blk + 0x7f) & ~0x7f;
	}
	DPRINTFN(1,("%s: resultant block size = %d\n", __func__, blk));
	return blk;
}

int
azalia_halt_output(void *v)
{
	azalia_t *az;

	DPRINTFN(1, ("%s\n", __func__));
	az = v;
	return azalia_stream_halt(&az->pstream);
}

int
azalia_halt_input(void *v)
{
	azalia_t *az;

	DPRINTFN(1, ("%s\n", __func__));
	az = v;
	return azalia_stream_halt(&az->rstream);
}

int
azalia_getdev(void *v, struct audio_device *dev)
{
	azalia_t *az;

	az = v;
	strlcpy(dev->name, "HD-Audio", MAX_AUDIO_DEV_LEN);
	snprintf(dev->version, MAX_AUDIO_DEV_LEN,
	    "%d.%d", AZ_READ_1(az, VMAJ), AZ_READ_1(az, VMIN));
	strlcpy(dev->config, XNAME(az), MAX_AUDIO_DEV_LEN);
	return 0;
}

int
azalia_set_port(void *v, mixer_ctrl_t *mc)
{
	azalia_t *az;
	codec_t *co;
	const mixer_item_t *m;

	az = v;
	co = &az->codecs[az->codecno];
	if (mc->dev < 0 || mc->dev >= co->nmixers)
		return EINVAL;

	m = &co->mixers[mc->dev];
	if (mc->type != m->devinfo.type)
		return EINVAL;

	return azalia_mixer_set(co, m->nid, m->target, mc);
}

int
azalia_get_port(void *v, mixer_ctrl_t *mc)
{
	azalia_t *az;
	codec_t *co;
	const mixer_item_t *m;

	az = v;
	co = &az->codecs[az->codecno];
	if (mc->dev < 0 || mc->dev >= co->nmixers)
		return EINVAL;

	m = &co->mixers[mc->dev];
	mc->type = m->devinfo.type;

	return azalia_mixer_get(co, m->nid, m->target, mc);
}

int
azalia_query_devinfo(void *v, mixer_devinfo_t *mdev)
{
	azalia_t *az;
	const codec_t *co;

	az = v;
	co = &az->codecs[az->codecno];
	if (mdev->index < 0 || mdev->index >= co->nmixers)
		return ENXIO;
	*mdev = co->mixers[mdev->index].devinfo;
	return 0;
}

void *
azalia_allocm(void *v, int dir, size_t size, int pool, int flags)
{
	azalia_t *az;
	stream_t *stream;
	int err;

	az = v;
	stream = dir == AUMODE_PLAY ? &az->pstream : &az->rstream;
	err = azalia_alloc_dmamem(az, size, 128, &stream->buffer);
	if (err) {
		printf("%s: allocm failed\n", az->dev.dv_xname);
		return NULL;
	}
	return stream->buffer.addr;
}

void
azalia_freem(void *v, void *addr, int pool)
{
	azalia_t *az;
	stream_t *stream;

	az = v;
	if (addr == az->pstream.buffer.addr) {
		stream = &az->pstream;
	} else if (addr == az->rstream.buffer.addr) {
		stream = &az->rstream;
	} else {
		return;
	}
	azalia_free_dmamem(az, &stream->buffer);
}

size_t
azalia_round_buffersize(void *v, int dir, size_t size)
{
	size &= ~0x7f;		/* must be multiple of 128 */
	if (size <= 0)
		size = 128;
	return size;
}

int
azalia_get_props(void *v)
{
	return AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX;
}

int
azalia_trigger_output(void *v, void *start, void *end, int blk,
    void (*intr)(void *), void *arg, audio_params_t *param)
{
	azalia_t *az;
	int err;
	uint16_t fmt;

	az = v;

	if (az->codecs[az->codecno].dacs.ngroups == 0) {
		DPRINTF(("%s: can't play without a DAC\n", __func__));
		return ENXIO;
	}

	err = azalia_params2fmt(param, &fmt);
	if (err)
		return(EINVAL);

	az->pstream.bufsize = (caddr_t)end - (caddr_t)start;
	az->pstream.blk = blk;
	az->pstream.fmt = fmt;
	az->pstream.intr = intr;
	az->pstream.intr_arg = arg;
	az->pstream.swpos = 0;

	return azalia_stream_start(&az->pstream);
}

int
azalia_trigger_input(void *v, void *start, void *end, int blk,
    void (*intr)(void *), void *arg, audio_params_t *param)
{
	azalia_t *az;
	int err;
	uint16_t fmt;

	DPRINTFN(1, ("%s: this=%p start=%p end=%p blk=%d {enc=%u %uch %ubit %luHz}\n",
	    __func__, v, start, end, blk, param->encoding, param->channels,
	    param->precision, param->sample_rate));

	az = v;

	if (az->codecs[az->codecno].adcs.ngroups == 0) {
		DPRINTF(("%s: can't record without an ADC\n", __func__));
		return ENXIO;
	}

	err = azalia_params2fmt(param, &fmt);
	if (err)
		return(EINVAL);

	az->rstream.bufsize = (caddr_t)end - (caddr_t)start;
	az->rstream.blk = blk;
	az->rstream.fmt = fmt;
	az->rstream.intr = intr;
	az->rstream.intr_arg = arg;
	az->rstream.swpos = 0;

	return azalia_stream_start(&az->rstream);
}

/* --------------------------------
 * helpers for MI audio functions
 * -------------------------------- */
int
azalia_params2fmt(const audio_params_t *param, uint16_t *fmt)
{
	uint16_t ret;

	ret = 0;
	if (param->channels > HDA_MAX_CHANNELS) {
		printf("%s: too many channels: %u\n", __func__,
		    param->channels);
		return EINVAL;
	}

	DPRINTFN(1, ("%s: prec=%d, chan=%d, rate=%ld\n", __func__,
	    param->precision, param->channels, param->sample_rate));

	/* XXX: can channels be >2 ? */
	ret |= param->channels - 1;

	switch (param->precision) {
	case 8:
		ret |= HDA_SD_FMT_BITS_8_16;
		break;
	case 16:
		ret |= HDA_SD_FMT_BITS_16_16;
		break;
	case 20:
		ret |= HDA_SD_FMT_BITS_20_32;
		break;
	case 24:
		ret |= HDA_SD_FMT_BITS_24_32;
		break;
	case 32:
		ret |= HDA_SD_FMT_BITS_32_32;
		break;
	}

	switch (param->sample_rate) {
	default:
	case 384000:
		printf("%s: invalid sample_rate: %lu\n", __func__,
		    param->sample_rate);
		return EINVAL;
	case 192000:
		ret |= HDA_SD_FMT_BASE_48 | HDA_SD_FMT_MULT_X4 | HDA_SD_FMT_DIV_BY1;
		break;
	case 176400:
		ret |= HDA_SD_FMT_BASE_44 | HDA_SD_FMT_MULT_X4 | HDA_SD_FMT_DIV_BY1;
		break;
	case 96000:
		ret |= HDA_SD_FMT_BASE_48 | HDA_SD_FMT_MULT_X2 | HDA_SD_FMT_DIV_BY1;
		break;
	case 88200:
		ret |= HDA_SD_FMT_BASE_44 | HDA_SD_FMT_MULT_X2 | HDA_SD_FMT_DIV_BY1;
		break;
	case 48000:
		ret |= HDA_SD_FMT_BASE_48 | HDA_SD_FMT_MULT_X1 | HDA_SD_FMT_DIV_BY1;
		break;
	case 44100:
		ret |= HDA_SD_FMT_BASE_44 | HDA_SD_FMT_MULT_X1 | HDA_SD_FMT_DIV_BY1;
		break;
	case 32000:
		ret |= HDA_SD_FMT_BASE_48 | HDA_SD_FMT_MULT_X2 | HDA_SD_FMT_DIV_BY3;
		break;
	case 22050:
		ret |= HDA_SD_FMT_BASE_44 | HDA_SD_FMT_MULT_X1 | HDA_SD_FMT_DIV_BY2;
		break;
	case 16000:
		ret |= HDA_SD_FMT_BASE_48 | HDA_SD_FMT_MULT_X1 | HDA_SD_FMT_DIV_BY3;
		break;
	case 11025:
		ret |= HDA_SD_FMT_BASE_44 | HDA_SD_FMT_MULT_X1 | HDA_SD_FMT_DIV_BY4;
		break;
	case 8000:
		ret |= HDA_SD_FMT_BASE_48 | HDA_SD_FMT_MULT_X1 | HDA_SD_FMT_DIV_BY6;
		break;
	}
	*fmt = ret;
	return 0;
}

int
azalia_create_encodings(codec_t *this)
{
	struct audio_format f;
	int encs[16];
	int enc, nencs;
	int i, j;

	nencs = 0;
	for (i = 0; i < this->nformats && nencs < 16; i++) {
		f = this->formats[i];
		enc = f.precision << 8 | f.encoding;
		for (j = 0; j < nencs; j++) {
			if (encs[j] == enc)
				break;
		}
		if (j < nencs)
			continue;
		encs[j] = enc;
		nencs++;
	}

	if (this->encs != NULL)
		free(this->encs, M_DEVBUF, 0);
	this->nencs = 0;
	this->encs = mallocarray(nencs, sizeof(struct audio_encoding),
	    M_DEVBUF, M_NOWAIT | M_ZERO);
	if (this->encs == NULL) {
		printf("%s: out of memory in %s\n",
		    XNAME(this->az), __func__);
		return ENOMEM;
	}

	this->nencs = nencs;
	for (i = 0; i < this->nencs; i++) {
		this->encs[i].index = i;
		this->encs[i].encoding = encs[i] & 0xff;
		this->encs[i].precision = encs[i] >> 8;
		this->encs[i].bps = AUDIO_BPS(encs[i] >> 8);
		this->encs[i].msb = 1;
		this->encs[i].flags = 0;
		switch (this->encs[i].encoding) {
		case AUDIO_ENCODING_SLINEAR_LE:
			strlcpy(this->encs[i].name,
			    this->encs[i].precision == 8 ?
			    AudioEslinear : AudioEslinear_le,
			    sizeof this->encs[i].name);
			break;
		case AUDIO_ENCODING_ULINEAR_LE:
			strlcpy(this->encs[i].name,
			    this->encs[i].precision == 8 ?
			    AudioEulinear : AudioEulinear_le,
			    sizeof this->encs[i].name);
			break;
		default:
			DPRINTF(("%s: unknown format\n", __func__));
			break;
		}
	}

	return (0);
}
