Index: uspace/drv/audio/hdaudio/codec.c
===================================================================
--- uspace/drv/audio/hdaudio/codec.c	(revision 9e9d9bc60978e9063a54b74c9301b5001d74660f)
+++ uspace/drv/audio/hdaudio/codec.c	(revision 46b305ad06b46ff90c280b198a0339a0ea00df22)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -512,4 +512,5 @@
 	codec->address = address;
 	codec->in_aw = -1;
+	codec->out_aw = -1;
 
 	rc = hda_get_subnc(codec, 0, &sfg, &nfg);
@@ -584,6 +585,4 @@
 					goto error;
 			} else if (awtype == awt_audio_output) {
-				codec->out_aw_list[codec->out_aw_num++] = aw;
-
 				rc = hda_get_supp_rates(codec, aw, &rates);
 				if (rc != EOK)
@@ -594,6 +593,18 @@
 					goto error;
 
-				ddf_msg(LVL_DEBUG, "Output widget %d: rates=0x%x formats=0x%x",
+				if (rates != 0 && formats != 0 &&
+				    codec->out_aw < 0) {
+					ddf_msg(LVL_DEBUG, "Selected output "
+					    "widget %d\n", aw);
+					codec->out_aw = aw;
+				} else {
+					ddf_msg(LVL_DEBUG, "Ignoring output "
+					    "widget %d\n", aw);
+				}
+
+				ddf_msg(LVL_NOTE, "Output widget %d: rates=0x%x formats=0x%x",
 				    aw, rates, formats);
+				codec->out_aw_rates = rates;
+				codec->out_aw_formats = formats;
 			} else if (awtype == awt_audio_input) {
 				if (codec->in_aw < 0) {
@@ -616,4 +627,6 @@
 				ddf_msg(LVL_DEBUG, "Input widget %d: rates=0x%x formats=0x%x",
 				    aw, rates, formats);
+				codec->in_aw_rates = rates;
+				codec->in_aw_formats = formats;
 			}
 
@@ -644,22 +657,16 @@
 {
 	errno_t rc;
-	int out_aw;
-	int i;
-
-	for (i = 0; i < codec->out_aw_num; i++) {
-		out_aw = codec->out_aw_list[i];
-
-		/* Configure converter */
-
-		ddf_msg(LVL_DEBUG, "Configure output converter format");
-		rc = hda_set_converter_fmt(codec, out_aw, stream->fmt);
-		if (rc != EOK)
-			goto error;
-
-		ddf_msg(LVL_DEBUG, "Configure output converter stream, channel");
-		rc = hda_set_converter_ctl(codec, out_aw, stream->sid, 0);
-		if (rc != EOK)
-			goto error;
-	}
+
+	/* Configure converter */
+	ddf_msg(LVL_DEBUG, "Configure output converter format / %d",
+	    codec->out_aw);
+	rc = hda_set_converter_fmt(codec, codec->out_aw, stream->fmt);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_DEBUG, "Configure output converter stream, channel");
+	rc = hda_set_converter_ctl(codec, codec->out_aw, stream->sid, 0);
+	if (rc != EOK)
+		goto error;
 
 	return EOK;
Index: uspace/drv/audio/hdaudio/codec.h
===================================================================
--- uspace/drv/audio/hdaudio/codec.h	(revision 9e9d9bc60978e9063a54b74c9301b5001d74660f)
+++ uspace/drv/audio/hdaudio/codec.h	(revision 46b305ad06b46ff90c280b198a0339a0ea00df22)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,13 +39,15 @@
 #include "stream.h"
 
-#define MAX_OUT_AW 256
-
 typedef struct hda_codec {
 	hda_t *hda;
 	uint8_t address;
-	uint8_t out_aw_list[MAX_OUT_AW];
+	int out_aw;
+	uint32_t out_aw_rates;
+	uint32_t out_aw_formats;
 	int out_aw_num;
 	int out_aw_sel;
 	int in_aw;
+	uint32_t in_aw_rates;
+	uint32_t in_aw_formats;
 } hda_codec_t;
 
Index: uspace/drv/audio/hdaudio/hdactl.c
===================================================================
--- uspace/drv/audio/hdaudio/hdactl.c	(revision 9e9d9bc60978e9063a54b74c9301b5001d74660f)
+++ uspace/drv/audio/hdaudio/hdactl.c	(revision 46b305ad06b46ff90c280b198a0339a0ea00df22)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -670,6 +670,28 @@
 }
 
+static void hda_ctl_check_fifo_error(hda_ctl_t *ctl)
+{
+	int i;
+	uint8_t sts;
+
+	/*
+	 * XXX Ideally the interrupt handler would tell us which stream
+	 * has the error.
+	 */
+
+	for (i = 0; i < 30; i++) {
+		sts = hda_reg8_read(&ctl->hda->regs->sdesc[i].sts);
+		if ((sts & BIT_V(uint8_t, sdsts_fifoe)) != 0 && (sts & 0x80) == 0) {
+			ddf_msg(LVL_WARN, "sts[%d] = 0x%hhx\n", i, sts);
+			hda_reg8_write(&ctl->hda->regs->sdesc[i].sts,
+			    BIT_V(uint8_t, sdsts_fifoe));
+		}
+	}
+}
+
 void hda_ctl_interrupt(hda_ctl_t *ctl)
 {
+	ddf_msg(LVL_DEBUG, "hda_ctl_interrupt");
+	hda_ctl_check_fifo_error(ctl);
 	hda_ctl_process_rirb(ctl);
 }
Index: uspace/drv/audio/hdaudio/hdaudio.c
===================================================================
--- uspace/drv/audio/hdaudio/hdaudio.c	(revision 9e9d9bc60978e9063a54b74c9301b5001d74660f)
+++ uspace/drv/audio/hdaudio/hdaudio.c	(revision 46b305ad06b46ff90c280b198a0339a0ea00df22)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -140,5 +140,5 @@
 		.cmd = CMD_PIO_WRITE_8,
 		.addr = NULL, /* sdesc[x].sts */
-		.value = 0x4 /* XXX sdesc.sts.BCIS */
+		.value = BIT_V(uint8_t, sdsts_bcis)
 	},
 	/* 4 */
Index: uspace/drv/audio/hdaudio/pcm_iface.c
===================================================================
--- uspace/drv/audio/hdaudio/pcm_iface.c	(revision 9e9d9bc60978e9063a54b74c9301b5001d74660f)
+++ uspace/drv/audio/hdaudio/pcm_iface.c	(revision 46b305ad06b46ff90c280b198a0339a0ea00df22)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -132,5 +132,5 @@
 	    *channels, *rate, *format);
 
-	if (*channels != 1) {
+	if (*channels != 2) {
 		*channels = 2;
 		rc = ELIMIT;
Index: uspace/drv/audio/hdaudio/spec/regs.h
===================================================================
--- uspace/drv/audio/hdaudio/spec/regs.h	(revision 9e9d9bc60978e9063a54b74c9301b5001d74660f)
+++ uspace/drv/audio/hdaudio/spec/regs.h	(revision 46b305ad06b46ff90c280b198a0339a0ea00df22)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -165,4 +165,5 @@
 } hda_regs_t;
 
+/** Stream Descriptor Control bits */
 typedef enum {
 	/** Descriptor Error Interrupt Enable */
@@ -177,4 +178,16 @@
 	sdctl1_srst = 0
 } hda_sdesc_ctl1_bits;
+
+/** Stream Descriptor Status bits */
+typedef enum {
+	/** FIFO Ready */
+	sdctl1_fifordy = 3,
+	/** Descriptor Error */
+	sdsts_dese = 2,
+	/** FIFO Error */
+	sdsts_fifoe = 1,
+	/** Buffer Completion Interrupt Status */
+	sdsts_bcis = 2
+} hda_sdesc_sts_bits;
 
 typedef enum {
Index: uspace/drv/audio/hdaudio/stream.c
===================================================================
--- uspace/drv/audio/hdaudio/stream.c	(revision 9e9d9bc60978e9063a54b74c9301b5001d74660f)
+++ uspace/drv/audio/hdaudio/stream.c	(revision 46b305ad06b46ff90c280b198a0339a0ea00df22)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -166,6 +166,9 @@
 	uint8_t ctl3;
 
+	/* Stream ID */
 	ctl3 = (stream->sid << 4);
-	ctl1 = 0x4;
+
+	/* Interrupt on buffer completion */
+	ctl1 = BIT_V(uint8_t, sdctl1_ioce);
 
 	sdregs = &stream->hda->regs->sdesc[stream->sdid];
