xref: /netbsd/sys/dev/hdaudio/hdaudio.c (revision 5b6ebe47)
1*5b6ebe47Spgoyette /* $NetBSD: hdaudio.c,v 1.5 2017/06/04 23:34:55 pgoyette Exp $ */
27b8ad118Sjmcneill 
37b8ad118Sjmcneill /*
47b8ad118Sjmcneill  * Copyright (c) 2009 Precedence Technologies Ltd <support@precedence.co.uk>
57b8ad118Sjmcneill  * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
67b8ad118Sjmcneill  * All rights reserved.
77b8ad118Sjmcneill  *
87b8ad118Sjmcneill  * This code is derived from software contributed to The NetBSD Foundation
97b8ad118Sjmcneill  * by Precedence Technologies Ltd
107b8ad118Sjmcneill  *
117b8ad118Sjmcneill  * Redistribution and use in source and binary forms, with or without
127b8ad118Sjmcneill  * modification, are permitted provided that the following conditions
137b8ad118Sjmcneill  * are met:
147b8ad118Sjmcneill  * 1. Redistributions of source code must retain the above copyright
157b8ad118Sjmcneill  *    notice, this list of conditions and the following disclaimer.
167b8ad118Sjmcneill  * 2. The name of the author may not be used to endorse or promote products
177b8ad118Sjmcneill  *    derived from this software without specific prior written permission.
187b8ad118Sjmcneill  *
197b8ad118Sjmcneill  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
207b8ad118Sjmcneill  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
217b8ad118Sjmcneill  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
227b8ad118Sjmcneill  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
237b8ad118Sjmcneill  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
247b8ad118Sjmcneill  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
257b8ad118Sjmcneill  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
267b8ad118Sjmcneill  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
277b8ad118Sjmcneill  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
287b8ad118Sjmcneill  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
297b8ad118Sjmcneill  * SUCH DAMAGE.
307b8ad118Sjmcneill  */
317b8ad118Sjmcneill 
327b8ad118Sjmcneill #include <sys/cdefs.h>
33*5b6ebe47Spgoyette __KERNEL_RCSID(0, "$NetBSD: hdaudio.c,v 1.5 2017/06/04 23:34:55 pgoyette Exp $");
347b8ad118Sjmcneill 
357b8ad118Sjmcneill #include <sys/types.h>
367b8ad118Sjmcneill #include <sys/param.h>
377b8ad118Sjmcneill #include <sys/systm.h>
387b8ad118Sjmcneill #include <sys/device.h>
397b8ad118Sjmcneill #include <sys/conf.h>
407b8ad118Sjmcneill #include <sys/bus.h>
417b8ad118Sjmcneill #include <sys/kmem.h>
427b8ad118Sjmcneill #include <sys/module.h>
437b8ad118Sjmcneill 
447b8ad118Sjmcneill #include "hdaudiovar.h"
457b8ad118Sjmcneill #include "hdaudioreg.h"
467b8ad118Sjmcneill #include "hdaudioio.h"
477b8ad118Sjmcneill #include "hdaudio_verbose.h"
487b8ad118Sjmcneill 
497b8ad118Sjmcneill /* #define	HDAUDIO_DEBUG */
507b8ad118Sjmcneill 
517b8ad118Sjmcneill #define	HDAUDIO_RESET_TIMEOUT	5000
527b8ad118Sjmcneill #define HDAUDIO_CORB_TIMEOUT	1000
537b8ad118Sjmcneill #define	HDAUDIO_RIRB_TIMEOUT	5000
547b8ad118Sjmcneill 
557b8ad118Sjmcneill #define	HDAUDIO_CODEC_DELAY	1000	/* spec calls for 250 */
567b8ad118Sjmcneill 
577b8ad118Sjmcneill dev_type_open(hdaudioopen);
587b8ad118Sjmcneill dev_type_close(hdaudioclose);
597b8ad118Sjmcneill dev_type_ioctl(hdaudioioctl);
607b8ad118Sjmcneill 
617b8ad118Sjmcneill const struct cdevsw hdaudio_cdevsw = {
627b8ad118Sjmcneill 	.d_open = hdaudioopen,
637b8ad118Sjmcneill 	.d_close = hdaudioclose,
647b8ad118Sjmcneill 	.d_read = noread,
657b8ad118Sjmcneill 	.d_write = nowrite,
667b8ad118Sjmcneill 	.d_ioctl = hdaudioioctl,
677b8ad118Sjmcneill 	.d_stop = nostop,
687b8ad118Sjmcneill 	.d_tty = notty,
697b8ad118Sjmcneill 	.d_poll = nopoll,
707b8ad118Sjmcneill 	.d_mmap = nommap,
717b8ad118Sjmcneill 	.d_kqfilter = nokqfilter,
727b8ad118Sjmcneill 	.d_discard = nodiscard,
737b8ad118Sjmcneill 	.d_flag = D_OTHER
747b8ad118Sjmcneill };
757b8ad118Sjmcneill 
767b8ad118Sjmcneill extern struct cfdriver hdaudio_cd;
777b8ad118Sjmcneill 
787b8ad118Sjmcneill #define	HDAUDIOUNIT(x)	minor((x))
797b8ad118Sjmcneill 
807b8ad118Sjmcneill static void
817b8ad118Sjmcneill hdaudio_stream_init(struct hdaudio_softc *sc, int nis, int nos, int nbidir)
827b8ad118Sjmcneill {
837b8ad118Sjmcneill 	int i, cnt = 0;
847b8ad118Sjmcneill 
857b8ad118Sjmcneill 	for (i = 0; i < nis && cnt < HDAUDIO_MAX_STREAMS; i++) {
867b8ad118Sjmcneill 		sc->sc_stream[cnt].st_host = sc;
877b8ad118Sjmcneill 		sc->sc_stream[cnt].st_enable = true;
887b8ad118Sjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
897b8ad118Sjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_ISS;
907b8ad118Sjmcneill 	}
917b8ad118Sjmcneill 	for (i = 0; i < nos && cnt < HDAUDIO_MAX_STREAMS; i++) {
927b8ad118Sjmcneill 		sc->sc_stream[cnt].st_host = sc;
937b8ad118Sjmcneill 		sc->sc_stream[cnt].st_enable = true;
947b8ad118Sjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
957b8ad118Sjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_OSS;
967b8ad118Sjmcneill 	}
977b8ad118Sjmcneill 	for (i = 0; i < nbidir && cnt < HDAUDIO_MAX_STREAMS; i++) {
987b8ad118Sjmcneill 		sc->sc_stream[cnt].st_host = sc;
997b8ad118Sjmcneill 		sc->sc_stream[cnt].st_enable = true;
1007b8ad118Sjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
1017b8ad118Sjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_BSS;
1027b8ad118Sjmcneill 	}
1037b8ad118Sjmcneill 
1047b8ad118Sjmcneill 	for (i = 0; i < cnt; i++)
1057b8ad118Sjmcneill 		hdaudio_stream_stop(&sc->sc_stream[i]);
1067b8ad118Sjmcneill 
1077b8ad118Sjmcneill 	sc->sc_stream_mask = 0;
1087b8ad118Sjmcneill }
1097b8ad118Sjmcneill 
1107b8ad118Sjmcneill static void
1117b8ad118Sjmcneill hdaudio_codec_init(struct hdaudio_softc *sc)
1127b8ad118Sjmcneill {
1137b8ad118Sjmcneill 	int i;
1147b8ad118Sjmcneill 
1157b8ad118Sjmcneill 	for (i = 0; i < HDAUDIO_MAX_CODECS; i++) {
1167b8ad118Sjmcneill 		sc->sc_codec[i].co_addr = i;
1177b8ad118Sjmcneill 		sc->sc_codec[i].co_host = sc;
1187b8ad118Sjmcneill 	}
1197b8ad118Sjmcneill }
1207b8ad118Sjmcneill 
1217b8ad118Sjmcneill static void
1227b8ad118Sjmcneill hdaudio_init(struct hdaudio_softc *sc)
1237b8ad118Sjmcneill {
1247b8ad118Sjmcneill 	uint16_t gcap;
1257b8ad118Sjmcneill 	int nos, nis, nbidir;
1267b8ad118Sjmcneill #if defined(HDAUDIO_DEBUG)
1277b8ad118Sjmcneill 	uint8_t vmin, vmaj;
1287b8ad118Sjmcneill 	int nsdo, addr64;
1297b8ad118Sjmcneill #endif
1307b8ad118Sjmcneill 
1317b8ad118Sjmcneill #if defined(HDAUDIO_DEBUG)
1327b8ad118Sjmcneill 	vmaj = hda_read1(sc, HDAUDIO_MMIO_VMAJ);
1337b8ad118Sjmcneill 	vmin = hda_read1(sc, HDAUDIO_MMIO_VMIN);
1347b8ad118Sjmcneill 
1357b8ad118Sjmcneill 	hda_print(sc, "High Definition Audio version %d.%d\n", vmaj, vmin);
1367b8ad118Sjmcneill #endif
1377b8ad118Sjmcneill 
1387b8ad118Sjmcneill 	gcap = hda_read2(sc, HDAUDIO_MMIO_GCAP);
1397b8ad118Sjmcneill 	nis = HDAUDIO_GCAP_ISS(gcap);
1407b8ad118Sjmcneill 	nos = HDAUDIO_GCAP_OSS(gcap);
1417b8ad118Sjmcneill 	nbidir = HDAUDIO_GCAP_BSS(gcap);
1427b8ad118Sjmcneill 
1437b8ad118Sjmcneill 	/* Initialize codecs and streams */
1447b8ad118Sjmcneill 	hdaudio_codec_init(sc);
1457b8ad118Sjmcneill 	hdaudio_stream_init(sc, nis, nos, nbidir);
1467b8ad118Sjmcneill 
1477b8ad118Sjmcneill #if defined(HDAUDIO_DEBUG)
1487b8ad118Sjmcneill 	nsdo = HDAUDIO_GCAP_NSDO(gcap);
1497b8ad118Sjmcneill 	addr64 = HDAUDIO_GCAP_64OK(gcap);
1507b8ad118Sjmcneill 
1517b8ad118Sjmcneill 	hda_print(sc, "OSS %d ISS %d BSS %d SDO %d%s\n",
1527b8ad118Sjmcneill 	    nos, nis, nbidir, nsdo, addr64 ? " 64-bit" : "");
1537b8ad118Sjmcneill #endif
1547b8ad118Sjmcneill }
1557b8ad118Sjmcneill 
1567b8ad118Sjmcneill static int
1577b8ad118Sjmcneill hdaudio_codec_probe(struct hdaudio_softc *sc)
1587b8ad118Sjmcneill {
1597b8ad118Sjmcneill 	uint16_t statests;
1607b8ad118Sjmcneill 	int codecid;
1617b8ad118Sjmcneill 
1627b8ad118Sjmcneill 	statests = hda_read2(sc, HDAUDIO_MMIO_STATESTS);
1637b8ad118Sjmcneill 	for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++)
1647b8ad118Sjmcneill 		if (statests & (1 << codecid))
1657b8ad118Sjmcneill 			sc->sc_codec[codecid].co_valid = true;
1667b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, statests);
1677b8ad118Sjmcneill 
1687b8ad118Sjmcneill 	return statests;
1697b8ad118Sjmcneill }
1707b8ad118Sjmcneill 
1717b8ad118Sjmcneill int
1727b8ad118Sjmcneill hdaudio_dma_alloc(struct hdaudio_softc *sc, struct hdaudio_dma *dma,
1737b8ad118Sjmcneill     int flags)
1747b8ad118Sjmcneill {
1757b8ad118Sjmcneill 	int err;
1767b8ad118Sjmcneill 
1777b8ad118Sjmcneill 	KASSERT(dma->dma_size > 0);
1787b8ad118Sjmcneill 
1797b8ad118Sjmcneill 	err = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, 128, 0,
1807b8ad118Sjmcneill 	    dma->dma_segs, sizeof(dma->dma_segs) / sizeof(dma->dma_segs[0]),
1817b8ad118Sjmcneill 	    &dma->dma_nsegs, BUS_DMA_WAITOK);
1827b8ad118Sjmcneill 	if (err)
1837b8ad118Sjmcneill 		return err;
1847b8ad118Sjmcneill 	err = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs,
1857b8ad118Sjmcneill 	    dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | flags);
1867b8ad118Sjmcneill 	if (err)
1877b8ad118Sjmcneill 		goto free;
1887b8ad118Sjmcneill 	err = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs,
1897b8ad118Sjmcneill 	    dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map);
1907b8ad118Sjmcneill 	if (err)
1917b8ad118Sjmcneill 		goto unmap;
1927b8ad118Sjmcneill 	err = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr,
1937b8ad118Sjmcneill 	    dma->dma_size, NULL, BUS_DMA_WAITOK | flags);
1947b8ad118Sjmcneill 	if (err)
1957b8ad118Sjmcneill 		goto destroy;
1967b8ad118Sjmcneill 
1977b8ad118Sjmcneill 	dma->dma_valid = true;
1987b8ad118Sjmcneill 	return 0;
1997b8ad118Sjmcneill 
2007b8ad118Sjmcneill destroy:
2017b8ad118Sjmcneill 	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
2027b8ad118Sjmcneill unmap:
2037b8ad118Sjmcneill 	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
2047b8ad118Sjmcneill free:
2057b8ad118Sjmcneill 	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
2067b8ad118Sjmcneill 
2077b8ad118Sjmcneill 	dma->dma_valid = false;
2087b8ad118Sjmcneill 	return err;
2097b8ad118Sjmcneill }
2107b8ad118Sjmcneill 
2117b8ad118Sjmcneill void
2127b8ad118Sjmcneill hdaudio_dma_free(struct hdaudio_softc *sc, struct hdaudio_dma *dma)
2137b8ad118Sjmcneill {
2147b8ad118Sjmcneill 	if (dma->dma_valid == false)
2157b8ad118Sjmcneill 		return;
2167b8ad118Sjmcneill 	bus_dmamap_unload(sc->sc_dmat, dma->dma_map);
2177b8ad118Sjmcneill 	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
2187b8ad118Sjmcneill 	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
2197b8ad118Sjmcneill 	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
2207b8ad118Sjmcneill 	dma->dma_valid = false;
2217b8ad118Sjmcneill }
2227b8ad118Sjmcneill 
2237b8ad118Sjmcneill static void
2247b8ad118Sjmcneill hdaudio_corb_enqueue(struct hdaudio_softc *sc, int addr, int nid,
2257b8ad118Sjmcneill     uint32_t control, uint32_t param)
2267b8ad118Sjmcneill {
2277b8ad118Sjmcneill 	uint32_t *corb = DMA_KERNADDR(&sc->sc_corb);
2287b8ad118Sjmcneill 	uint32_t verb;
2297b8ad118Sjmcneill 	uint16_t corbrp;
2307b8ad118Sjmcneill 	int wp;
2317b8ad118Sjmcneill 
2327b8ad118Sjmcneill 	/* Build command */
2337b8ad118Sjmcneill 	verb = (addr << 28) | (nid << 20) | (control << 8) | param;
2347b8ad118Sjmcneill 
2357b8ad118Sjmcneill 	/* Fetch and update write pointer */
2367b8ad118Sjmcneill 	corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBWP);
2377b8ad118Sjmcneill 	wp = (corbrp & 0xff) + 1;
2387b8ad118Sjmcneill 	if (wp >= (sc->sc_corb.dma_size / sizeof(*corb)))
2397b8ad118Sjmcneill 		wp = 0;
2407b8ad118Sjmcneill 
2417b8ad118Sjmcneill 	/* Enqueue command */
2427b8ad118Sjmcneill 	bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
2437b8ad118Sjmcneill 	    sc->sc_corb.dma_size, BUS_DMASYNC_POSTWRITE);
2447b8ad118Sjmcneill 	corb[wp] = verb;
2457b8ad118Sjmcneill 	bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
2467b8ad118Sjmcneill 	    sc->sc_corb.dma_size, BUS_DMASYNC_PREWRITE);
2477b8ad118Sjmcneill 
2487b8ad118Sjmcneill 	/* Commit updated write pointer */
2497b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBWP, wp);
2507b8ad118Sjmcneill }
2517b8ad118Sjmcneill 
2527b8ad118Sjmcneill static void
2537b8ad118Sjmcneill hdaudio_rirb_unsol(struct hdaudio_softc *sc, struct rirb_entry *entry)
2547b8ad118Sjmcneill {
2557b8ad118Sjmcneill 	struct hdaudio_codec *co;
2567b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
2577b8ad118Sjmcneill 	uint8_t codecid = RIRB_CODEC_ID(entry);
2587b8ad118Sjmcneill 	unsigned int i;
2597b8ad118Sjmcneill 
2607b8ad118Sjmcneill 	if (codecid >= HDAUDIO_MAX_CODECS) {
2617b8ad118Sjmcneill 		hda_error(sc, "unsol: codec id 0x%02x out of range\n", codecid);
2627b8ad118Sjmcneill 		return;
2637b8ad118Sjmcneill 	}
2647b8ad118Sjmcneill 	co = &sc->sc_codec[codecid];
2657b8ad118Sjmcneill 	if (sc->sc_codec[codecid].co_valid == false) {
2667b8ad118Sjmcneill 		hda_error(sc, "unsol: codec id 0x%02x not valid\n", codecid);
2677b8ad118Sjmcneill 		return;
2687b8ad118Sjmcneill 	}
2697b8ad118Sjmcneill 
2707b8ad118Sjmcneill 	for (i = 0; i < co->co_nfg; i++) {
2717b8ad118Sjmcneill 		fg = &co->co_fg[i];
2727b8ad118Sjmcneill 		if (fg->fg_device && fg->fg_unsol)
2737b8ad118Sjmcneill 			fg->fg_unsol(fg->fg_device, entry->resp);
2747b8ad118Sjmcneill 	}
2757b8ad118Sjmcneill }
2767b8ad118Sjmcneill 
2777b8ad118Sjmcneill static uint32_t
2787b8ad118Sjmcneill hdaudio_rirb_dequeue(struct hdaudio_softc *sc, bool unsol)
2797b8ad118Sjmcneill {
2807b8ad118Sjmcneill 	uint16_t rirbwp;
2817b8ad118Sjmcneill 	uint64_t *rirb = DMA_KERNADDR(&sc->sc_rirb);
2827b8ad118Sjmcneill 	struct rirb_entry entry;
2837b8ad118Sjmcneill 	int retry;
2847b8ad118Sjmcneill 
2857b8ad118Sjmcneill 	for (;;) {
2867b8ad118Sjmcneill 		retry = HDAUDIO_RIRB_TIMEOUT;
2877b8ad118Sjmcneill 
2887b8ad118Sjmcneill 		rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
2897b8ad118Sjmcneill 		while (--retry > 0 && (rirbwp & 0xff) == sc->sc_rirbrp) {
2907b8ad118Sjmcneill 			if (unsol) {
2917b8ad118Sjmcneill 				/* don't wait for more unsol events */
2927b8ad118Sjmcneill 				hda_trace(sc, "unsol: rirb empty\n");
2937b8ad118Sjmcneill 				return 0xffffffff;
2947b8ad118Sjmcneill 			}
2957b8ad118Sjmcneill 			hda_delay(10);
2967b8ad118Sjmcneill 			rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
2977b8ad118Sjmcneill 		}
2987b8ad118Sjmcneill 		if (retry == 0) {
2997b8ad118Sjmcneill 			hda_error(sc, "RIRB timeout\n");
3007b8ad118Sjmcneill 			return 0xffffffff;
3017b8ad118Sjmcneill 		}
3027b8ad118Sjmcneill 
3037b8ad118Sjmcneill 		sc->sc_rirbrp++;
3047b8ad118Sjmcneill 		if (sc->sc_rirbrp >= (sc->sc_rirb.dma_size / sizeof(*rirb)))
3057b8ad118Sjmcneill 			sc->sc_rirbrp = 0;
3067b8ad118Sjmcneill 
3077b8ad118Sjmcneill 		bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
3087b8ad118Sjmcneill 		    sc->sc_rirb.dma_size, BUS_DMASYNC_POSTREAD);
3097b8ad118Sjmcneill 		entry = *(struct rirb_entry *)&rirb[sc->sc_rirbrp];
3107b8ad118Sjmcneill 		bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
3117b8ad118Sjmcneill 		    sc->sc_rirb.dma_size, BUS_DMASYNC_PREREAD);
3127b8ad118Sjmcneill 
3137b8ad118Sjmcneill 		hda_trace(sc, "%s: response %08X %08X\n",
3147b8ad118Sjmcneill 		    unsol ? "unsol" : "cmd  ",
3157b8ad118Sjmcneill 		    entry.resp, entry.resp_ex);
3167b8ad118Sjmcneill 
3177b8ad118Sjmcneill 		if (RIRB_UNSOL(&entry)) {
3187b8ad118Sjmcneill 			hdaudio_rirb_unsol(sc, &entry);
3197b8ad118Sjmcneill 			continue;
3207b8ad118Sjmcneill 		}
3217b8ad118Sjmcneill 
3227b8ad118Sjmcneill 		return entry.resp;
3237b8ad118Sjmcneill 	}
3247b8ad118Sjmcneill }
3257b8ad118Sjmcneill 
3267b8ad118Sjmcneill uint32_t
3277b8ad118Sjmcneill hdaudio_command(struct hdaudio_codec *co, int nid, uint32_t control,
3287b8ad118Sjmcneill     uint32_t param)
3297b8ad118Sjmcneill {
3307b8ad118Sjmcneill 	uint32_t result;
3317b8ad118Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
3327b8ad118Sjmcneill 	mutex_enter(&sc->sc_corb_mtx);
3337b8ad118Sjmcneill 	result = hdaudio_command_unlocked(co, nid, control, param);
3347b8ad118Sjmcneill 	mutex_exit(&sc->sc_corb_mtx);
3357b8ad118Sjmcneill 	return result;
3367b8ad118Sjmcneill }
3377b8ad118Sjmcneill 
3387b8ad118Sjmcneill uint32_t
3397b8ad118Sjmcneill hdaudio_command_unlocked(struct hdaudio_codec *co, int nid, uint32_t control,
3407b8ad118Sjmcneill     uint32_t param)
3417b8ad118Sjmcneill {
3427b8ad118Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
3437b8ad118Sjmcneill 	uint32_t result;
3447b8ad118Sjmcneill 
3457b8ad118Sjmcneill 	hda_trace(sc, "cmd  : request %08X %08X (%02X)\n",
3467b8ad118Sjmcneill 	    control, param, nid);
3477b8ad118Sjmcneill 	hdaudio_corb_enqueue(sc, co->co_addr, nid, control, param);
3487b8ad118Sjmcneill 	result = hdaudio_rirb_dequeue(sc, false);
3497b8ad118Sjmcneill 
3507b8ad118Sjmcneill 	return result;
3517b8ad118Sjmcneill }
3527b8ad118Sjmcneill 
3537b8ad118Sjmcneill static int
3547b8ad118Sjmcneill hdaudio_corb_setsize(struct hdaudio_softc *sc)
3557b8ad118Sjmcneill {
3567b8ad118Sjmcneill 	uint8_t corbsize;
3577b8ad118Sjmcneill 	bus_size_t bufsize = 0;
3587b8ad118Sjmcneill 
3597b8ad118Sjmcneill 	/*
3607b8ad118Sjmcneill 	 * The size of the CORB is programmable to 2, 16, or 256 entries
3617b8ad118Sjmcneill 	 * by using the CORBSIZE register. Choose a size based on the
3627b8ad118Sjmcneill 	 * controller capabilities, preferring a larger size when possible.
3637b8ad118Sjmcneill 	 */
3647b8ad118Sjmcneill 	corbsize = hda_read1(sc, HDAUDIO_MMIO_CORBSIZE);
3657b8ad118Sjmcneill 	corbsize &= ~0x3;
3667b8ad118Sjmcneill 	if ((corbsize >> 4) & 0x4) {
3677b8ad118Sjmcneill 		corbsize |= 0x2;
3687b8ad118Sjmcneill 		bufsize = 1024;
3697b8ad118Sjmcneill 	} else if ((corbsize >> 4) & 0x2) {
3707b8ad118Sjmcneill 		corbsize |= 0x1;
3717b8ad118Sjmcneill 		bufsize = 64;
3727b8ad118Sjmcneill 	} else if ((corbsize >> 4) & 0x1) {
3737b8ad118Sjmcneill 		corbsize |= 0x0;
3747b8ad118Sjmcneill 		bufsize = 8;
3757b8ad118Sjmcneill 	} else {
3767b8ad118Sjmcneill 		hda_error(sc, "couldn't configure CORB size\n");
3777b8ad118Sjmcneill 		return ENXIO;
3787b8ad118Sjmcneill 	}
3797b8ad118Sjmcneill 
3807b8ad118Sjmcneill #if defined(HDAUDIO_DEBUG)
3817b8ad118Sjmcneill 	hda_print(sc, "using %d byte CORB (cap %X)\n",
3827b8ad118Sjmcneill 	    (int)bufsize, corbsize >> 4);
3837b8ad118Sjmcneill #endif
3847b8ad118Sjmcneill 
3857b8ad118Sjmcneill 	sc->sc_corb.dma_size = bufsize;
3867b8ad118Sjmcneill 	sc->sc_corb.dma_sizereg = corbsize;
3877b8ad118Sjmcneill 
3887b8ad118Sjmcneill 	return 0;
3897b8ad118Sjmcneill }
3907b8ad118Sjmcneill 
3917b8ad118Sjmcneill static int
3927b8ad118Sjmcneill hdaudio_corb_config(struct hdaudio_softc *sc)
3937b8ad118Sjmcneill {
3947b8ad118Sjmcneill 	uint32_t corbubase, corblbase;
3957b8ad118Sjmcneill 	uint16_t corbrp;
3967b8ad118Sjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
3977b8ad118Sjmcneill 
3987b8ad118Sjmcneill 	/* Program command buffer base address and size */
3997b8ad118Sjmcneill 	corblbase = (uint32_t)DMA_DMAADDR(&sc->sc_corb);
4007b8ad118Sjmcneill 	corbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_corb)) >> 32);
4017b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_CORBLBASE, corblbase);
4027b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_CORBUBASE, corbubase);
4037b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_CORBSIZE, sc->sc_corb.dma_sizereg);
4047b8ad118Sjmcneill 
4057b8ad118Sjmcneill 	/* Clear the read and write pointers */
4067b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBRP, HDAUDIO_CORBRP_RP_RESET);
4077b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBRP, 0);
4087b8ad118Sjmcneill 	do {
4097b8ad118Sjmcneill 		hda_delay(10);
4107b8ad118Sjmcneill 		corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBRP);
4117b8ad118Sjmcneill 	} while (--retry > 0 && (corbrp & HDAUDIO_CORBRP_RP_RESET) != 0);
4127b8ad118Sjmcneill 	if (retry == 0) {
4137b8ad118Sjmcneill 		hda_error(sc, "timeout resetting CORB\n");
4147b8ad118Sjmcneill 		return ETIME;
4157b8ad118Sjmcneill 	}
4167b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBWP, 0);
4177b8ad118Sjmcneill 
4187b8ad118Sjmcneill 	return 0;
4197b8ad118Sjmcneill }
4207b8ad118Sjmcneill 
4217b8ad118Sjmcneill static int
4227b8ad118Sjmcneill hdaudio_corb_stop(struct hdaudio_softc *sc)
4237b8ad118Sjmcneill {
4247b8ad118Sjmcneill 	uint8_t corbctl;
4257b8ad118Sjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
4267b8ad118Sjmcneill 
4277b8ad118Sjmcneill 	/* Stop the CORB if necessary */
4287b8ad118Sjmcneill 	corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4297b8ad118Sjmcneill 	if (corbctl & HDAUDIO_CORBCTL_RUN) {
4307b8ad118Sjmcneill 		corbctl &= ~HDAUDIO_CORBCTL_RUN;
4317b8ad118Sjmcneill 		hda_write4(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
4327b8ad118Sjmcneill 		do {
4337b8ad118Sjmcneill 			hda_delay(10);
4347b8ad118Sjmcneill 			corbctl = hda_read4(sc, HDAUDIO_MMIO_CORBCTL);
4357b8ad118Sjmcneill 		} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) != 0);
4367b8ad118Sjmcneill 		if (retry == 0) {
4377b8ad118Sjmcneill 			hda_error(sc, "timeout stopping CORB\n");
4387b8ad118Sjmcneill 			return ETIME;
4397b8ad118Sjmcneill 		}
4407b8ad118Sjmcneill 	}
4417b8ad118Sjmcneill 
4427b8ad118Sjmcneill 	return 0;
4437b8ad118Sjmcneill }
4447b8ad118Sjmcneill 
4457b8ad118Sjmcneill static int
4467b8ad118Sjmcneill hdaudio_corb_start(struct hdaudio_softc *sc)
4477b8ad118Sjmcneill {
4487b8ad118Sjmcneill 	uint8_t corbctl;
4497b8ad118Sjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
4507b8ad118Sjmcneill 
4517b8ad118Sjmcneill 	/* Start the CORB if necessary */
4527b8ad118Sjmcneill 	corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4537b8ad118Sjmcneill 	if ((corbctl & HDAUDIO_CORBCTL_RUN) == 0) {
4547b8ad118Sjmcneill 		corbctl |= HDAUDIO_CORBCTL_RUN;
4557b8ad118Sjmcneill 		hda_write4(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
4567b8ad118Sjmcneill 		do {
4577b8ad118Sjmcneill 			hda_delay(10);
4587b8ad118Sjmcneill 			corbctl = hda_read4(sc, HDAUDIO_MMIO_CORBCTL);
4597b8ad118Sjmcneill 		} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) == 0);
4607b8ad118Sjmcneill 		if (retry == 0) {
4617b8ad118Sjmcneill 			hda_error(sc, "timeout starting CORB\n");
4627b8ad118Sjmcneill 			return ETIME;
4637b8ad118Sjmcneill 		}
4647b8ad118Sjmcneill 	}
4657b8ad118Sjmcneill 
4667b8ad118Sjmcneill 	return 0;
4677b8ad118Sjmcneill }
4687b8ad118Sjmcneill 
4697b8ad118Sjmcneill static int
4707b8ad118Sjmcneill hdaudio_rirb_stop(struct hdaudio_softc *sc)
4717b8ad118Sjmcneill {
4727b8ad118Sjmcneill 	uint8_t rirbctl;
4737b8ad118Sjmcneill 	int retry = HDAUDIO_RIRB_TIMEOUT;
4747b8ad118Sjmcneill 
4757b8ad118Sjmcneill 	/* Stop the RIRB if necessary */
4767b8ad118Sjmcneill 	rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4777b8ad118Sjmcneill 	if (rirbctl & (HDAUDIO_RIRBCTL_RUN|HDAUDIO_RIRBCTL_ROI_EN)) {
4787b8ad118Sjmcneill 		rirbctl &= ~HDAUDIO_RIRBCTL_RUN;
4797b8ad118Sjmcneill 		rirbctl &= ~HDAUDIO_RIRBCTL_ROI_EN;
4807b8ad118Sjmcneill 		hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
4817b8ad118Sjmcneill 		do {
4827b8ad118Sjmcneill 			hda_delay(10);
4837b8ad118Sjmcneill 			rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4847b8ad118Sjmcneill 		} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) != 0);
4857b8ad118Sjmcneill 		if (retry == 0) {
4867b8ad118Sjmcneill 			hda_error(sc, "timeout stopping RIRB\n");
4877b8ad118Sjmcneill 			return ETIME;
4887b8ad118Sjmcneill 		}
4897b8ad118Sjmcneill 	}
4907b8ad118Sjmcneill 
4917b8ad118Sjmcneill 	return 0;
4927b8ad118Sjmcneill }
4937b8ad118Sjmcneill 
4947b8ad118Sjmcneill static int
4957b8ad118Sjmcneill hdaudio_rirb_start(struct hdaudio_softc *sc)
4967b8ad118Sjmcneill {
4977b8ad118Sjmcneill 	uint8_t rirbctl;
4987b8ad118Sjmcneill 	int retry = HDAUDIO_RIRB_TIMEOUT;
4997b8ad118Sjmcneill 
5007b8ad118Sjmcneill 	/* Start the RIRB if necessary */
5017b8ad118Sjmcneill 	rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
5027b8ad118Sjmcneill 	if ((rirbctl & (HDAUDIO_RIRBCTL_RUN|HDAUDIO_RIRBCTL_INT_EN)) == 0) {
5037b8ad118Sjmcneill 		rirbctl |= HDAUDIO_RIRBCTL_RUN;
5047b8ad118Sjmcneill 		rirbctl |= HDAUDIO_RIRBCTL_INT_EN;
5057b8ad118Sjmcneill 		hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
5067b8ad118Sjmcneill 		do {
5077b8ad118Sjmcneill 			hda_delay(10);
5087b8ad118Sjmcneill 			rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
5097b8ad118Sjmcneill 		} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) == 0);
5107b8ad118Sjmcneill 		if (retry == 0) {
5117b8ad118Sjmcneill 			hda_error(sc, "timeout starting RIRB\n");
5127b8ad118Sjmcneill 			return ETIME;
5137b8ad118Sjmcneill 		}
5147b8ad118Sjmcneill 	}
5157b8ad118Sjmcneill 
5167b8ad118Sjmcneill 	return 0;
5177b8ad118Sjmcneill }
5187b8ad118Sjmcneill 
5197b8ad118Sjmcneill static int
5207b8ad118Sjmcneill hdaudio_rirb_setsize(struct hdaudio_softc *sc)
5217b8ad118Sjmcneill {
5227b8ad118Sjmcneill 	uint8_t rirbsize;
5237b8ad118Sjmcneill 	bus_size_t bufsize = 0;
5247b8ad118Sjmcneill 
5257b8ad118Sjmcneill 	/*
5267b8ad118Sjmcneill 	 * The size of the RIRB is programmable to 2, 16, or 256 entries
5277b8ad118Sjmcneill 	 * by using the RIRBSIZE register. Choose a size based on the
5287b8ad118Sjmcneill 	 * controller capabilities, preferring a larger size when possible.
5297b8ad118Sjmcneill 	 */
5307b8ad118Sjmcneill 	rirbsize = hda_read1(sc, HDAUDIO_MMIO_RIRBSIZE);
5317b8ad118Sjmcneill 	rirbsize &= ~0x3;
5327b8ad118Sjmcneill 	if ((rirbsize >> 4) & 0x4) {
5337b8ad118Sjmcneill 		rirbsize |= 0x2;
5347b8ad118Sjmcneill 		bufsize = 2048;
5357b8ad118Sjmcneill 	} else if ((rirbsize >> 4) & 0x2) {
5367b8ad118Sjmcneill 		rirbsize |= 0x1;
5377b8ad118Sjmcneill 		bufsize = 128;
5387b8ad118Sjmcneill 	} else if ((rirbsize >> 4) & 0x1) {
5397b8ad118Sjmcneill 		rirbsize |= 0x0;
5407b8ad118Sjmcneill 		bufsize = 16;
5417b8ad118Sjmcneill 	} else {
5427b8ad118Sjmcneill 		hda_error(sc, "couldn't configure RIRB size\n");
5437b8ad118Sjmcneill 		return ENXIO;
5447b8ad118Sjmcneill 	}
5457b8ad118Sjmcneill 
5467b8ad118Sjmcneill #if defined(HDAUDIO_DEBUG)
5477b8ad118Sjmcneill 	hda_print(sc, "using %d byte RIRB (cap %X)\n",
5487b8ad118Sjmcneill 	    (int)bufsize, rirbsize >> 4);
5497b8ad118Sjmcneill #endif
5507b8ad118Sjmcneill 
5517b8ad118Sjmcneill 	sc->sc_rirb.dma_size = bufsize;
5527b8ad118Sjmcneill 	sc->sc_rirb.dma_sizereg = rirbsize;
5537b8ad118Sjmcneill 
5547b8ad118Sjmcneill 	return 0;
5557b8ad118Sjmcneill }
5567b8ad118Sjmcneill 
5577b8ad118Sjmcneill static int
5587b8ad118Sjmcneill hdaudio_rirb_config(struct hdaudio_softc *sc)
5597b8ad118Sjmcneill {
5607b8ad118Sjmcneill 	uint32_t rirbubase, rirblbase;
5617b8ad118Sjmcneill 	uint32_t rirbwp;
5627b8ad118Sjmcneill 	int retry = HDAUDIO_RIRB_TIMEOUT;
5637b8ad118Sjmcneill 
5647b8ad118Sjmcneill 	/* Program command buffer base address and size */
5657b8ad118Sjmcneill 	rirblbase = (uint32_t)DMA_DMAADDR(&sc->sc_rirb);
5667b8ad118Sjmcneill 	rirbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_rirb)) >> 32);
5677b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_RIRBLBASE, rirblbase);
5687b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_RIRBUBASE, rirbubase);
5697b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSIZE, sc->sc_rirb.dma_sizereg);
5707b8ad118Sjmcneill 
5717b8ad118Sjmcneill 	/* Clear the write pointer */
5727b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_RIRBWP, HDAUDIO_RIRBWP_WP_RESET);
5737b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_RIRBWP, 0);
5747b8ad118Sjmcneill 	do {
5757b8ad118Sjmcneill 		hda_delay(10);
5767b8ad118Sjmcneill 		rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
5777b8ad118Sjmcneill 	} while (--retry > 0 && (rirbwp & HDAUDIO_RIRBWP_WP_RESET) != 0);
5787b8ad118Sjmcneill 	if (retry == 0) {
5797b8ad118Sjmcneill 		hda_error(sc, "timeout resetting RIRB\n");
5807b8ad118Sjmcneill 		return ETIME;
5817b8ad118Sjmcneill 	}
5827b8ad118Sjmcneill 	sc->sc_rirbrp = 0;
5837b8ad118Sjmcneill 
5847b8ad118Sjmcneill 	return 0;
5857b8ad118Sjmcneill }
5867b8ad118Sjmcneill 
5877b8ad118Sjmcneill static int
5887b8ad118Sjmcneill hdaudio_reset(struct hdaudio_softc *sc)
5897b8ad118Sjmcneill {
5907b8ad118Sjmcneill 	int retry = HDAUDIO_RESET_TIMEOUT;
5917b8ad118Sjmcneill 	uint32_t gctl;
5927b8ad118Sjmcneill 	int err;
5937b8ad118Sjmcneill 
5947b8ad118Sjmcneill 	if ((err = hdaudio_rirb_stop(sc)) != 0) {
5957b8ad118Sjmcneill 		hda_error(sc, "couldn't reset because RIRB is busy\n");
5967b8ad118Sjmcneill 		return err;
5977b8ad118Sjmcneill 	}
5987b8ad118Sjmcneill 	if ((err = hdaudio_corb_stop(sc)) != 0) {
5997b8ad118Sjmcneill 		hda_error(sc, "couldn't reset because CORB is busy\n");
6007b8ad118Sjmcneill 		return err;
6017b8ad118Sjmcneill 	}
6027b8ad118Sjmcneill 
6037b8ad118Sjmcneill 	/* Disable wake events */
6047b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_WAKEEN, 0);
6057b8ad118Sjmcneill 
6067b8ad118Sjmcneill 	/* Disable interrupts */
6077b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
6087b8ad118Sjmcneill 
6097b8ad118Sjmcneill 	/* Clear state change status register */
6107b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS,
6117b8ad118Sjmcneill 	    hda_read2(sc, HDAUDIO_MMIO_STATESTS));
6127b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
6137b8ad118Sjmcneill 	    hda_read1(sc, HDAUDIO_MMIO_RIRBSTS));
6147b8ad118Sjmcneill 
6157b8ad118Sjmcneill 	/* If the controller isn't in reset state, initiate the transition */
6167b8ad118Sjmcneill 	gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6177b8ad118Sjmcneill 	if (gctl & HDAUDIO_GCTL_CRST) {
6187b8ad118Sjmcneill 		gctl &= ~HDAUDIO_GCTL_CRST;
6197b8ad118Sjmcneill 		hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl);
6207b8ad118Sjmcneill 		do {
6217b8ad118Sjmcneill 			hda_delay(10);
6227b8ad118Sjmcneill 			gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6237b8ad118Sjmcneill 		} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) != 0);
6247b8ad118Sjmcneill 		if (retry == 0) {
6257b8ad118Sjmcneill 			hda_error(sc, "timeout entering reset state\n");
6267b8ad118Sjmcneill 			return ETIME;
6277b8ad118Sjmcneill 		}
6287b8ad118Sjmcneill 	}
6297b8ad118Sjmcneill 
6307b8ad118Sjmcneill 	/* Now the controller is in reset state, so bring it out */
6317b8ad118Sjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
6327b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_CRST);
6337b8ad118Sjmcneill 	do {
6347b8ad118Sjmcneill 		hda_delay(10);
6357b8ad118Sjmcneill 		gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6367b8ad118Sjmcneill 	} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) == 0);
6377b8ad118Sjmcneill 	if (retry == 0) {
6387b8ad118Sjmcneill 		hda_error(sc, "timeout leaving reset state\n");
6397b8ad118Sjmcneill 		return ETIME;
6407b8ad118Sjmcneill 	}
6417b8ad118Sjmcneill 
6427b8ad118Sjmcneill 	/* Accept unsolicited responses */
6437b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_UNSOL_EN);
6447b8ad118Sjmcneill 
6457b8ad118Sjmcneill 	return 0;
6467b8ad118Sjmcneill }
6477b8ad118Sjmcneill 
6487b8ad118Sjmcneill static void
6497b8ad118Sjmcneill hdaudio_intr_enable(struct hdaudio_softc *sc)
6507b8ad118Sjmcneill {
6517b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
6527b8ad118Sjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
6537b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL,
6547b8ad118Sjmcneill 	    HDAUDIO_INTCTL_GIE | HDAUDIO_INTCTL_CIE);
6557b8ad118Sjmcneill }
6567b8ad118Sjmcneill 
6577b8ad118Sjmcneill static void
6587b8ad118Sjmcneill hdaudio_intr_disable(struct hdaudio_softc *sc)
6597b8ad118Sjmcneill {
6607b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
6617b8ad118Sjmcneill }
6627b8ad118Sjmcneill 
6637b8ad118Sjmcneill static int
6647b8ad118Sjmcneill hdaudio_config_print(void *opaque, const char *pnp)
6657b8ad118Sjmcneill {
6667b8ad118Sjmcneill 	prop_dictionary_t dict = opaque;
6677b8ad118Sjmcneill 	uint8_t fgtype, nid;
6687b8ad118Sjmcneill 	uint16_t vendor, product;
6697b8ad118Sjmcneill 	const char *type = "unknown";
6707b8ad118Sjmcneill 
6717b8ad118Sjmcneill 	prop_dictionary_get_uint8(dict, "function-group-type", &fgtype);
6727b8ad118Sjmcneill 	prop_dictionary_get_uint8(dict, "node-id", &nid);
6737b8ad118Sjmcneill 	prop_dictionary_get_uint16(dict, "vendor-id", &vendor);
6747b8ad118Sjmcneill 	prop_dictionary_get_uint16(dict, "product-id", &product);
6757b8ad118Sjmcneill 	if (pnp) {
6767b8ad118Sjmcneill 		if (fgtype == HDAUDIO_GROUP_TYPE_AFG)
6777b8ad118Sjmcneill 			type = "hdafg";
6787b8ad118Sjmcneill 		else if (fgtype == HDAUDIO_GROUP_TYPE_VSM_FG)
6797b8ad118Sjmcneill 			type = "hdvsmfg";
6807b8ad118Sjmcneill 
6817b8ad118Sjmcneill 		aprint_normal("%s at %s", type, pnp);
6827b8ad118Sjmcneill 	}
6837b8ad118Sjmcneill 	aprint_debug(" vendor 0x%04X product 0x%04X nid 0x%02X",
6847b8ad118Sjmcneill 	    vendor, product, nid);
6857b8ad118Sjmcneill 
6867b8ad118Sjmcneill 	return UNCONF;
6877b8ad118Sjmcneill }
6887b8ad118Sjmcneill 
6897b8ad118Sjmcneill static void
6907b8ad118Sjmcneill hdaudio_attach_fg(struct hdaudio_function_group *fg, prop_array_t config)
6917b8ad118Sjmcneill {
6927b8ad118Sjmcneill 	struct hdaudio_codec *co = fg->fg_codec;
6937b8ad118Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
6947b8ad118Sjmcneill 	prop_dictionary_t args = prop_dictionary_create();
6957b8ad118Sjmcneill 	uint64_t fgptr = (vaddr_t)fg;
6967b8ad118Sjmcneill 	int locs[1];
6977b8ad118Sjmcneill 
6987b8ad118Sjmcneill 	prop_dictionary_set_uint8(args, "function-group-type", fg->fg_type);
6997b8ad118Sjmcneill 	prop_dictionary_set_uint64(args, "function-group", fgptr);
7007b8ad118Sjmcneill 	prop_dictionary_set_uint8(args, "node-id", fg->fg_nid);
7017b8ad118Sjmcneill 	prop_dictionary_set_uint16(args, "vendor-id", fg->fg_vendor);
7027b8ad118Sjmcneill 	prop_dictionary_set_uint16(args, "product-id", fg->fg_product);
7037b8ad118Sjmcneill 	if (config)
7047b8ad118Sjmcneill 		prop_dictionary_set(args, "pin-config", config);
7057b8ad118Sjmcneill 
7067b8ad118Sjmcneill 	locs[0] = fg->fg_nid;
7077b8ad118Sjmcneill 
7087b8ad118Sjmcneill 	fg->fg_device = config_found_sm_loc(sc->sc_dev, "hdaudiobus",
7097b8ad118Sjmcneill 	    locs, args, hdaudio_config_print, config_stdsubmatch);
7107b8ad118Sjmcneill 
7117b8ad118Sjmcneill 	prop_object_release(args);
7127b8ad118Sjmcneill }
7137b8ad118Sjmcneill 
7147b8ad118Sjmcneill static void
7157b8ad118Sjmcneill hdaudio_codec_attach(struct hdaudio_codec *co)
7167b8ad118Sjmcneill {
7177b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
7187b8ad118Sjmcneill 	uint32_t vid, snc, fgrp;
7197b8ad118Sjmcneill 	int starting_node, num_nodes, nid;
7207b8ad118Sjmcneill 
7217b8ad118Sjmcneill 	if (co->co_valid == false)
7227b8ad118Sjmcneill 		return;
7237b8ad118Sjmcneill 
7247b8ad118Sjmcneill 	vid = hdaudio_command(co, 0, CORB_GET_PARAMETER, COP_VENDOR_ID);
7257b8ad118Sjmcneill 	snc = hdaudio_command(co, 0, CORB_GET_PARAMETER,
7267b8ad118Sjmcneill 	    COP_SUBORDINATE_NODE_COUNT);
7277b8ad118Sjmcneill 
7287b8ad118Sjmcneill 	/* make sure the vendor and product IDs are valid */
7297b8ad118Sjmcneill 	if (vid == 0xffffffff || vid == 0x00000000)
7307b8ad118Sjmcneill 		return;
7317b8ad118Sjmcneill 
7327b8ad118Sjmcneill #ifdef HDAUDIO_DEBUG
7337b8ad118Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
7347b8ad118Sjmcneill 	uint32_t rid = hdaudio_command(co, 0, CORB_GET_PARAMETER,
7357b8ad118Sjmcneill 	    COP_REVISION_ID);
7367b8ad118Sjmcneill 	hda_print(sc, "Codec%02X: %04X:%04X HDA %d.%d rev %d stepping %d\n",
7377b8ad118Sjmcneill 	    co->co_addr, vid >> 16, vid & 0xffff,
7387b8ad118Sjmcneill 	    (rid >> 20) & 0xf, (rid >> 16) & 0xf,
7397b8ad118Sjmcneill 	    (rid >> 8) & 0xff, rid & 0xff);
7407b8ad118Sjmcneill #endif
7417b8ad118Sjmcneill 	starting_node = (snc >> 16) & 0xff;
7427b8ad118Sjmcneill 	num_nodes = snc & 0xff;
7437b8ad118Sjmcneill 
7447b8ad118Sjmcneill 	co->co_nfg = num_nodes;
7457b8ad118Sjmcneill 	co->co_fg = kmem_zalloc(co->co_nfg * sizeof(*co->co_fg), KM_SLEEP);
7467b8ad118Sjmcneill 
7477b8ad118Sjmcneill 	for (nid = starting_node; nid < starting_node + num_nodes; nid++) {
7487b8ad118Sjmcneill 		fg = &co->co_fg[nid - starting_node];
7497b8ad118Sjmcneill 		fg->fg_codec = co;
7507b8ad118Sjmcneill 		fg->fg_nid = nid;
7517b8ad118Sjmcneill 		fg->fg_vendor = vid >> 16;
7527b8ad118Sjmcneill 		fg->fg_product = vid & 0xffff;
7537b8ad118Sjmcneill 
7547b8ad118Sjmcneill 		fgrp = hdaudio_command(co, nid, CORB_GET_PARAMETER,
7557b8ad118Sjmcneill 		    COP_FUNCTION_GROUP_TYPE);
7567b8ad118Sjmcneill 		switch (fgrp & 0xff) {
7577b8ad118Sjmcneill 		case 0x01:	/* Audio Function Group */
7587b8ad118Sjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_AFG;
7597b8ad118Sjmcneill 			break;
7607b8ad118Sjmcneill 		case 0x02:	/* Vendor Specific Modem Function Group */
7617b8ad118Sjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_VSM_FG;
7627b8ad118Sjmcneill 			break;
7637b8ad118Sjmcneill 		default:
7647b8ad118Sjmcneill 			/* Function group type not supported */
7657b8ad118Sjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_UNKNOWN;
7667b8ad118Sjmcneill 			break;
7677b8ad118Sjmcneill 		}
7687b8ad118Sjmcneill 		hdaudio_attach_fg(fg, NULL);
7697b8ad118Sjmcneill 	}
7707b8ad118Sjmcneill }
7717b8ad118Sjmcneill 
7727b8ad118Sjmcneill int
7737b8ad118Sjmcneill hdaudio_stream_tag(struct hdaudio_stream *st)
7747b8ad118Sjmcneill {
7757b8ad118Sjmcneill 	int ret = 0;
7767b8ad118Sjmcneill 
7777b8ad118Sjmcneill 	switch (st->st_type) {
7787b8ad118Sjmcneill 	case HDAUDIO_STREAM_ISS:
7797b8ad118Sjmcneill 		ret = 1;
7807b8ad118Sjmcneill 		break;
7817b8ad118Sjmcneill 	case HDAUDIO_STREAM_OSS:
7827b8ad118Sjmcneill 		ret = 2;
7837b8ad118Sjmcneill 		break;
7847b8ad118Sjmcneill 	case HDAUDIO_STREAM_BSS:
7857b8ad118Sjmcneill 		ret = 3;
7867b8ad118Sjmcneill 		break;
7877b8ad118Sjmcneill 	}
7887b8ad118Sjmcneill 
7897b8ad118Sjmcneill 	return ret;
7907b8ad118Sjmcneill }
7917b8ad118Sjmcneill 
7927b8ad118Sjmcneill int
7937b8ad118Sjmcneill hdaudio_attach(device_t dev, struct hdaudio_softc *sc)
7947b8ad118Sjmcneill {
7957b8ad118Sjmcneill 	int err, i;
7967b8ad118Sjmcneill 
7977b8ad118Sjmcneill 	KASSERT(sc->sc_memvalid == true);
7987b8ad118Sjmcneill 
7997b8ad118Sjmcneill 	sc->sc_dev = dev;
8007b8ad118Sjmcneill 	mutex_init(&sc->sc_corb_mtx, MUTEX_DEFAULT, IPL_AUDIO);
8017b8ad118Sjmcneill 	mutex_init(&sc->sc_stream_mtx, MUTEX_DEFAULT, IPL_AUDIO);
8027b8ad118Sjmcneill 
8037b8ad118Sjmcneill 	hdaudio_init(sc);
8047b8ad118Sjmcneill 
8057b8ad118Sjmcneill 	/*
8067b8ad118Sjmcneill 	 * Put the controller into a known state by entering and leaving
8077b8ad118Sjmcneill 	 * CRST as necessary.
8087b8ad118Sjmcneill 	 */
8097b8ad118Sjmcneill 	if ((err = hdaudio_reset(sc)) != 0)
8107b8ad118Sjmcneill 		goto fail;
8117b8ad118Sjmcneill 
8127b8ad118Sjmcneill 	/*
8137b8ad118Sjmcneill 	 * From the spec:
8147b8ad118Sjmcneill 	 *
8157b8ad118Sjmcneill 	 * Must wait 250us after reading CRST as a 1 before assuming that
8167b8ad118Sjmcneill 	 * codecs have all made status change requests and have been
8177b8ad118Sjmcneill 	 * registered by the controller.
8187b8ad118Sjmcneill 	 *
8197b8ad118Sjmcneill 	 * In reality, we need to wait longer than this.
8207b8ad118Sjmcneill 	 */
8217b8ad118Sjmcneill 	hda_delay(HDAUDIO_CODEC_DELAY);
8227b8ad118Sjmcneill 	if (hdaudio_codec_probe(sc) == 0) {
8237b8ad118Sjmcneill 		hda_error(sc, "no codecs found\n");
8247b8ad118Sjmcneill 		err = ENODEV;
8257b8ad118Sjmcneill 		goto fail;
8267b8ad118Sjmcneill 	}
8277b8ad118Sjmcneill 
8287b8ad118Sjmcneill 	/*
8297b8ad118Sjmcneill 	 * Ensure that the device is in a known state
8307b8ad118Sjmcneill 	 */
8317b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
8327b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
8337b8ad118Sjmcneill 	    HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
8347b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
8357b8ad118Sjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
8367b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
8377b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
8387b8ad118Sjmcneill 
8397b8ad118Sjmcneill 	/*
8407b8ad118Sjmcneill 	 * Initialize the CORB. First negotiate a command buffer size,
8417b8ad118Sjmcneill 	 * then allocate and configure it.
8427b8ad118Sjmcneill 	 */
8437b8ad118Sjmcneill 	if ((err = hdaudio_corb_setsize(sc)) != 0)
8447b8ad118Sjmcneill 		goto fail;
8457b8ad118Sjmcneill 	if ((err = hdaudio_dma_alloc(sc, &sc->sc_corb, BUS_DMA_WRITE)) != 0)
8467b8ad118Sjmcneill 		goto fail;
8477b8ad118Sjmcneill 	if ((err = hdaudio_corb_config(sc)) != 0)
8487b8ad118Sjmcneill 		goto fail;
8497b8ad118Sjmcneill 
8507b8ad118Sjmcneill 	/*
8517b8ad118Sjmcneill 	 * Initialize the RIRB.
8527b8ad118Sjmcneill 	 */
8537b8ad118Sjmcneill 	if ((err = hdaudio_rirb_setsize(sc)) != 0)
8547b8ad118Sjmcneill 		goto fail;
8557b8ad118Sjmcneill 	if ((err = hdaudio_dma_alloc(sc, &sc->sc_rirb, BUS_DMA_READ)) != 0)
8567b8ad118Sjmcneill 		goto fail;
8577b8ad118Sjmcneill 	if ((err = hdaudio_rirb_config(sc)) != 0)
8587b8ad118Sjmcneill 		goto fail;
8597b8ad118Sjmcneill 
8607b8ad118Sjmcneill 	/*
8617b8ad118Sjmcneill 	 * Start the CORB and RIRB
8627b8ad118Sjmcneill 	 */
8637b8ad118Sjmcneill 	if ((err = hdaudio_corb_start(sc)) != 0)
8647b8ad118Sjmcneill 		goto fail;
8657b8ad118Sjmcneill 	if ((err = hdaudio_rirb_start(sc)) != 0)
8667b8ad118Sjmcneill 		goto fail;
8677b8ad118Sjmcneill 
8687b8ad118Sjmcneill 	/*
8697b8ad118Sjmcneill 	 * Identify and attach discovered codecs
8707b8ad118Sjmcneill 	 */
8717b8ad118Sjmcneill 	for (i = 0; i < HDAUDIO_MAX_CODECS; i++)
8727b8ad118Sjmcneill 		hdaudio_codec_attach(&sc->sc_codec[i]);
8737b8ad118Sjmcneill 
8747b8ad118Sjmcneill 	/*
8757b8ad118Sjmcneill 	 * Enable interrupts
8767b8ad118Sjmcneill 	 */
8777b8ad118Sjmcneill 	hdaudio_intr_enable(sc);
8787b8ad118Sjmcneill 
8797b8ad118Sjmcneill fail:
8807b8ad118Sjmcneill 	if (err)
8817b8ad118Sjmcneill 		hda_error(sc, "device driver failed to attach\n");
8827b8ad118Sjmcneill 	return err;
8837b8ad118Sjmcneill }
8847b8ad118Sjmcneill 
8857b8ad118Sjmcneill int
8867b8ad118Sjmcneill hdaudio_detach(struct hdaudio_softc *sc, int flags)
8877b8ad118Sjmcneill {
8887b8ad118Sjmcneill 	int error;
8897b8ad118Sjmcneill 
8907b8ad118Sjmcneill 	/* Disable interrupts */
8917b8ad118Sjmcneill 	hdaudio_intr_disable(sc);
8927b8ad118Sjmcneill 
8937b8ad118Sjmcneill 	error = config_detach_children(sc->sc_dev, flags);
8947b8ad118Sjmcneill 	if (error != 0) {
8957b8ad118Sjmcneill 		hdaudio_intr_enable(sc);
8967b8ad118Sjmcneill 		return error;
8977b8ad118Sjmcneill 	}
8987b8ad118Sjmcneill 
8997b8ad118Sjmcneill 	mutex_destroy(&sc->sc_corb_mtx);
9007b8ad118Sjmcneill 	mutex_destroy(&sc->sc_stream_mtx);
9017b8ad118Sjmcneill 
9027b8ad118Sjmcneill 	hdaudio_dma_free(sc, &sc->sc_corb);
9037b8ad118Sjmcneill 	hdaudio_dma_free(sc, &sc->sc_rirb);
9047b8ad118Sjmcneill 
9057b8ad118Sjmcneill 	return 0;
9067b8ad118Sjmcneill }
9077b8ad118Sjmcneill 
9087b8ad118Sjmcneill bool
9097b8ad118Sjmcneill hdaudio_resume(struct hdaudio_softc *sc)
9107b8ad118Sjmcneill {
9117b8ad118Sjmcneill 	if (hdaudio_reset(sc) != 0)
9127b8ad118Sjmcneill 		return false;
9137b8ad118Sjmcneill 
9147b8ad118Sjmcneill 	hda_delay(HDAUDIO_CODEC_DELAY);
9157b8ad118Sjmcneill 
9167b8ad118Sjmcneill 	/*
9177b8ad118Sjmcneill 	 * Ensure that the device is in a known state
9187b8ad118Sjmcneill 	 */
9197b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
9207b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
9217b8ad118Sjmcneill 	    HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
9227b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
9237b8ad118Sjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
9247b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
9257b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
9267b8ad118Sjmcneill 
9277b8ad118Sjmcneill 	if (hdaudio_corb_config(sc) != 0)
9287b8ad118Sjmcneill 		return false;
9297b8ad118Sjmcneill 	if (hdaudio_rirb_config(sc) != 0)
9307b8ad118Sjmcneill 		return false;
9317b8ad118Sjmcneill 	if (hdaudio_corb_start(sc) != 0)
9327b8ad118Sjmcneill 		return false;
9337b8ad118Sjmcneill 	if (hdaudio_rirb_start(sc) != 0)
9347b8ad118Sjmcneill 		return false;
9357b8ad118Sjmcneill 
9367b8ad118Sjmcneill 	hdaudio_intr_enable(sc);
9377b8ad118Sjmcneill 
9387b8ad118Sjmcneill 	return true;
9397b8ad118Sjmcneill }
9407b8ad118Sjmcneill 
9417b8ad118Sjmcneill int
9427b8ad118Sjmcneill hdaudio_rescan(struct hdaudio_softc *sc, const char *ifattr, const int *locs)
9437b8ad118Sjmcneill {
9447b8ad118Sjmcneill 	struct hdaudio_codec *co;
9457b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
9467b8ad118Sjmcneill 	unsigned int codec;
9477b8ad118Sjmcneill 
9487b8ad118Sjmcneill 	if (!ifattr_match(ifattr, "hdaudiobus"))
9497b8ad118Sjmcneill 		return 0;
9507b8ad118Sjmcneill 
9517b8ad118Sjmcneill 	for (codec = 0; codec < HDAUDIO_MAX_CODECS; codec++) {
9527b8ad118Sjmcneill 		co = &sc->sc_codec[codec];
9537b8ad118Sjmcneill 		fg = co->co_fg;
9547b8ad118Sjmcneill 		if (!co->co_valid || fg == NULL)
9557b8ad118Sjmcneill 			continue;
9567b8ad118Sjmcneill 		if (fg->fg_device)
9577b8ad118Sjmcneill 			continue;
9587b8ad118Sjmcneill 		hdaudio_attach_fg(fg, NULL);
9597b8ad118Sjmcneill 	}
9607b8ad118Sjmcneill 
9617b8ad118Sjmcneill 	return 0;
9627b8ad118Sjmcneill }
9637b8ad118Sjmcneill 
9647b8ad118Sjmcneill void
9657b8ad118Sjmcneill hdaudio_childdet(struct hdaudio_softc *sc, device_t child)
9667b8ad118Sjmcneill {
9677b8ad118Sjmcneill 	struct hdaudio_codec *co;
9687b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
9697b8ad118Sjmcneill 	unsigned int codec;
9707b8ad118Sjmcneill 
9717b8ad118Sjmcneill 	for (codec = 0; codec < HDAUDIO_MAX_CODECS; codec++) {
9727b8ad118Sjmcneill 		co = &sc->sc_codec[codec];
9737b8ad118Sjmcneill 		fg = co->co_fg;
9747b8ad118Sjmcneill 		if (!co->co_valid || fg == NULL)
9757b8ad118Sjmcneill 			continue;
9767b8ad118Sjmcneill 		if (fg->fg_device == child)
9777b8ad118Sjmcneill 			fg->fg_device = NULL;
9787b8ad118Sjmcneill 	}
9797b8ad118Sjmcneill }
9807b8ad118Sjmcneill 
9817b8ad118Sjmcneill int
9827b8ad118Sjmcneill hdaudio_intr(struct hdaudio_softc *sc)
9837b8ad118Sjmcneill {
9847b8ad118Sjmcneill 	struct hdaudio_stream *st;
9857b8ad118Sjmcneill 	uint32_t intsts, stream_mask;
9867b8ad118Sjmcneill 	int streamid = 0;
9877b8ad118Sjmcneill 	uint8_t rirbsts;
9887b8ad118Sjmcneill 
9897b8ad118Sjmcneill 	intsts = hda_read4(sc, HDAUDIO_MMIO_INTSTS);
9907b8ad118Sjmcneill 	if (!(intsts & HDAUDIO_INTSTS_GIS))
9917b8ad118Sjmcneill 		return 0;
9927b8ad118Sjmcneill 
9937b8ad118Sjmcneill 	if (intsts & HDAUDIO_INTSTS_CIS) {
9947b8ad118Sjmcneill 		rirbsts = hda_read1(sc, HDAUDIO_MMIO_RIRBSTS);
9957b8ad118Sjmcneill 		if (rirbsts & HDAUDIO_RIRBSTS_RINTFL) {
9967b8ad118Sjmcneill 			mutex_enter(&sc->sc_corb_mtx);
9977b8ad118Sjmcneill 			hdaudio_rirb_dequeue(sc, true);
9987b8ad118Sjmcneill 			mutex_exit(&sc->sc_corb_mtx);
9997b8ad118Sjmcneill 		}
10007b8ad118Sjmcneill 		if (rirbsts & (HDAUDIO_RIRBSTS_RIRBOIS|HDAUDIO_RIRBSTS_RINTFL))
10017b8ad118Sjmcneill 			hda_write1(sc, HDAUDIO_MMIO_RIRBSTS, rirbsts);
10027b8ad118Sjmcneill 		hda_write4(sc, HDAUDIO_MMIO_INTSTS, HDAUDIO_INTSTS_CIS);
10037b8ad118Sjmcneill 	}
10047b8ad118Sjmcneill 	if (intsts & HDAUDIO_INTSTS_SIS_MASK) {
10057b8ad118Sjmcneill 		mutex_enter(&sc->sc_stream_mtx);
10067b8ad118Sjmcneill 		stream_mask = intsts & sc->sc_stream_mask;
10077b8ad118Sjmcneill 		while (streamid < HDAUDIO_MAX_STREAMS && stream_mask != 0) {
10087b8ad118Sjmcneill 			st = &sc->sc_stream[streamid++];
10097b8ad118Sjmcneill 			if ((stream_mask & 1) != 0 && st->st_intr) {
10107b8ad118Sjmcneill 				st->st_intr(st);
10117b8ad118Sjmcneill 			}
10127b8ad118Sjmcneill 			stream_mask >>= 1;
10137b8ad118Sjmcneill 		}
10147b8ad118Sjmcneill 		mutex_exit(&sc->sc_stream_mtx);
10157b8ad118Sjmcneill 		hda_write4(sc, HDAUDIO_MMIO_INTSTS, HDAUDIO_INTSTS_SIS_MASK);
10167b8ad118Sjmcneill 	}
10177b8ad118Sjmcneill 
10187b8ad118Sjmcneill 	return 1;
10197b8ad118Sjmcneill }
10207b8ad118Sjmcneill 
10217b8ad118Sjmcneill struct hdaudio_stream *
10227b8ad118Sjmcneill hdaudio_stream_establish(struct hdaudio_softc *sc,
10237b8ad118Sjmcneill     enum hdaudio_stream_type type, int (*intr)(struct hdaudio_stream *),
10247b8ad118Sjmcneill     void *cookie)
10257b8ad118Sjmcneill {
10267b8ad118Sjmcneill 	struct hdaudio_stream *st;
10277b8ad118Sjmcneill 	struct hdaudio_dma dma;
10287b8ad118Sjmcneill 	int i, err;
10297b8ad118Sjmcneill 
10307b8ad118Sjmcneill 	dma.dma_size = sizeof(struct hdaudio_bdl_entry) * HDAUDIO_BDL_MAX;
1031ea2b5803Sriastradh 	dma.dma_sizereg = 0;
10327b8ad118Sjmcneill 	err = hdaudio_dma_alloc(sc, &dma, BUS_DMA_COHERENT | BUS_DMA_NOCACHE);
10337b8ad118Sjmcneill 	if (err)
10347b8ad118Sjmcneill 		return NULL;
10357b8ad118Sjmcneill 
10367b8ad118Sjmcneill 	mutex_enter(&sc->sc_stream_mtx);
10377b8ad118Sjmcneill 	for (i = 0; i < HDAUDIO_MAX_STREAMS; i++) {
10387b8ad118Sjmcneill 		st = &sc->sc_stream[i];
10397b8ad118Sjmcneill 		if (st->st_enable == false)
10407b8ad118Sjmcneill 			break;
10417b8ad118Sjmcneill 		if (st->st_type != type)
10427b8ad118Sjmcneill 			continue;
10437b8ad118Sjmcneill 		if (sc->sc_stream_mask & (1 << i))
10447b8ad118Sjmcneill 			continue;
10457b8ad118Sjmcneill 
10467b8ad118Sjmcneill 		/* Allocate stream */
10477b8ad118Sjmcneill 		st->st_bdl = dma;
10487b8ad118Sjmcneill 		st->st_intr = intr;
10497b8ad118Sjmcneill 		st->st_cookie = cookie;
10507b8ad118Sjmcneill 		sc->sc_stream_mask |= (1 << i);
10517b8ad118Sjmcneill 		mutex_exit(&sc->sc_stream_mtx);
10527b8ad118Sjmcneill 		return st;
10537b8ad118Sjmcneill 	}
10547b8ad118Sjmcneill 	mutex_exit(&sc->sc_stream_mtx);
10557b8ad118Sjmcneill 
10567b8ad118Sjmcneill 	/* No streams of requested type available */
10577b8ad118Sjmcneill 	hdaudio_dma_free(sc, &dma);
10587b8ad118Sjmcneill 	return NULL;
10597b8ad118Sjmcneill }
10607b8ad118Sjmcneill 
10617b8ad118Sjmcneill void
10627b8ad118Sjmcneill hdaudio_stream_disestablish(struct hdaudio_stream *st)
10637b8ad118Sjmcneill {
10647b8ad118Sjmcneill 	struct hdaudio_softc *sc = st->st_host;
10657b8ad118Sjmcneill 	struct hdaudio_dma dma;
10667b8ad118Sjmcneill 
10677b8ad118Sjmcneill 	KASSERT(sc->sc_stream_mask & (1 << st->st_shift));
10687b8ad118Sjmcneill 
10697b8ad118Sjmcneill 	mutex_enter(&sc->sc_stream_mtx);
10707b8ad118Sjmcneill 	sc->sc_stream_mask &= ~(1 << st->st_shift);
10717b8ad118Sjmcneill 	st->st_intr = NULL;
10727b8ad118Sjmcneill 	st->st_cookie = NULL;
10737b8ad118Sjmcneill 	dma = st->st_bdl;
10747b8ad118Sjmcneill 	st->st_bdl.dma_valid = false;
10757b8ad118Sjmcneill 	mutex_exit(&sc->sc_stream_mtx);
10767b8ad118Sjmcneill 
10777b8ad118Sjmcneill 	/* Can't bus_dmamem_unmap while holding a mutex.  */
10787b8ad118Sjmcneill 	hdaudio_dma_free(sc, &dma);
10797b8ad118Sjmcneill }
10807b8ad118Sjmcneill 
10817b8ad118Sjmcneill /*
10827b8ad118Sjmcneill  * Convert most of audio_params_t to stream fmt descriptor; noticably missing
10837b8ad118Sjmcneill  * is the # channels bits, as this is encoded differently in codec and
10847b8ad118Sjmcneill  * stream descriptors.
10857b8ad118Sjmcneill  *
10867b8ad118Sjmcneill  * TODO: validate that the stream and selected codecs can handle the fmt
10877b8ad118Sjmcneill  */
10887b8ad118Sjmcneill uint16_t
10897b8ad118Sjmcneill hdaudio_stream_param(struct hdaudio_stream *st, const audio_params_t *param)
10907b8ad118Sjmcneill {
10917b8ad118Sjmcneill 	uint16_t fmt = 0;
10927b8ad118Sjmcneill 
10937b8ad118Sjmcneill 	switch (param->encoding) {
10947b8ad118Sjmcneill 	case AUDIO_ENCODING_AC3:
10957b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_TYPE_NONPCM;
10967b8ad118Sjmcneill 		break;
10977b8ad118Sjmcneill 	default:
10987b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_TYPE_PCM;
10997b8ad118Sjmcneill 		break;
11007b8ad118Sjmcneill 	}
11017b8ad118Sjmcneill 
11027b8ad118Sjmcneill 	switch (param->sample_rate) {
11037b8ad118Sjmcneill 	case 8000:
11047b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1) |
11057b8ad118Sjmcneill 		    HDAUDIO_FMT_DIV(6);
11067b8ad118Sjmcneill 		break;
11077b8ad118Sjmcneill 	case 11025:
11087b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1) |
11097b8ad118Sjmcneill 		    HDAUDIO_FMT_DIV(4);
11107b8ad118Sjmcneill 		break;
11117b8ad118Sjmcneill 	case 16000:
11127b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1) |
11137b8ad118Sjmcneill 		    HDAUDIO_FMT_DIV(3);
11147b8ad118Sjmcneill 		break;
11157b8ad118Sjmcneill 	case 22050:
11167b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1) |
11177b8ad118Sjmcneill 		    HDAUDIO_FMT_DIV(2);
11187b8ad118Sjmcneill 		break;
11197b8ad118Sjmcneill 	case 32000:
11207b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(2) |
11217b8ad118Sjmcneill 		    HDAUDIO_FMT_DIV(3);
11227b8ad118Sjmcneill 		break;
11237b8ad118Sjmcneill 	case 44100:
11247b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1);
11257b8ad118Sjmcneill 		break;
11267b8ad118Sjmcneill 	case 48000:
11277b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1);
11287b8ad118Sjmcneill 		break;
11297b8ad118Sjmcneill 	case 88200:
11307b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(2);
11317b8ad118Sjmcneill 		break;
11327b8ad118Sjmcneill 	case 96000:
11337b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(2);
11347b8ad118Sjmcneill 		break;
11357b8ad118Sjmcneill 	case 176400:
11367b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(4);
11377b8ad118Sjmcneill 		break;
11387b8ad118Sjmcneill 	case 192000:
11397b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(4);
11407b8ad118Sjmcneill 		break;
11417b8ad118Sjmcneill 	default:
11427b8ad118Sjmcneill 		return 0;
11437b8ad118Sjmcneill 	}
11447b8ad118Sjmcneill 
11457b8ad118Sjmcneill 	if (param->precision == 16 && param->validbits == 8)
11467b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BITS_8_16;
11477b8ad118Sjmcneill 	else if (param->precision == 16 && param->validbits == 16)
11487b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BITS_16_16;
11497b8ad118Sjmcneill 	else if (param->precision == 32 && param->validbits == 20)
11507b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BITS_20_32;
11517b8ad118Sjmcneill 	else if (param->precision == 32 && param->validbits == 24)
11527b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BITS_24_32;
11537b8ad118Sjmcneill 	else if (param->precision == 32 && param->validbits == 32)
11547b8ad118Sjmcneill 		fmt |= HDAUDIO_FMT_BITS_32_32;
11557b8ad118Sjmcneill 	else
11567b8ad118Sjmcneill 		return 0;
11577b8ad118Sjmcneill 
11587b8ad118Sjmcneill 	return fmt;
11597b8ad118Sjmcneill }
11607b8ad118Sjmcneill 
11617b8ad118Sjmcneill void
11627b8ad118Sjmcneill hdaudio_stream_reset(struct hdaudio_stream *st)
11637b8ad118Sjmcneill {
11647b8ad118Sjmcneill 	struct hdaudio_softc *sc = st->st_host;
11657b8ad118Sjmcneill 	int snum = st->st_shift;
11667b8ad118Sjmcneill 	int retry;
11677b8ad118Sjmcneill 	uint8_t ctl0;
11687b8ad118Sjmcneill 
11697b8ad118Sjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
11707b8ad118Sjmcneill 	ctl0 |= HDAUDIO_CTL_SRST;
11717b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
11727b8ad118Sjmcneill 
11737b8ad118Sjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
11747b8ad118Sjmcneill 	do {
11757b8ad118Sjmcneill 		ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
11767b8ad118Sjmcneill 		if (ctl0 & HDAUDIO_CTL_SRST)
11777b8ad118Sjmcneill 			break;
11787b8ad118Sjmcneill 		hda_delay(10);
11797b8ad118Sjmcneill 	} while (--retry > 0);
11807b8ad118Sjmcneill 	if (retry == 0) {
11817b8ad118Sjmcneill 		hda_error(sc, "timeout entering stream reset state\n");
11827b8ad118Sjmcneill 		return;
11837b8ad118Sjmcneill 	}
11847b8ad118Sjmcneill 
11857b8ad118Sjmcneill 	ctl0 &= ~HDAUDIO_CTL_SRST;
11867b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
11877b8ad118Sjmcneill 
11887b8ad118Sjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
11897b8ad118Sjmcneill 	do {
11907b8ad118Sjmcneill 		ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
11917b8ad118Sjmcneill 		if (!(ctl0 & HDAUDIO_CTL_SRST))
11927b8ad118Sjmcneill 			break;
11937b8ad118Sjmcneill 		hda_delay(10);
11947b8ad118Sjmcneill 	} while (--retry > 0);
11957b8ad118Sjmcneill 	if (retry == 0) {
11967b8ad118Sjmcneill 		hda_error(sc, "timeout leaving stream reset state\n");
11977b8ad118Sjmcneill 		return;
11987b8ad118Sjmcneill 	}
11997b8ad118Sjmcneill }
12007b8ad118Sjmcneill 
12017b8ad118Sjmcneill void
12027b8ad118Sjmcneill hdaudio_stream_start(struct hdaudio_stream *st, int blksize,
12037b8ad118Sjmcneill     bus_size_t dmasize, const audio_params_t *params)
12047b8ad118Sjmcneill {
12057b8ad118Sjmcneill 	struct hdaudio_softc *sc = st->st_host;
12067b8ad118Sjmcneill 	struct hdaudio_bdl_entry *bdl;
12077b8ad118Sjmcneill 	uint64_t dmaaddr;
12087b8ad118Sjmcneill 	uint32_t intctl;
12097b8ad118Sjmcneill 	uint16_t fmt;
12107b8ad118Sjmcneill 	uint8_t ctl0, ctl2;
12117b8ad118Sjmcneill 	int cnt, snum = st->st_shift;
12127b8ad118Sjmcneill 
12137b8ad118Sjmcneill 	KASSERT(sc->sc_stream_mask & (1 << st->st_shift));
12147b8ad118Sjmcneill 	KASSERT(st->st_data.dma_valid == true);
12157b8ad118Sjmcneill 	KASSERT(st->st_bdl.dma_valid == true);
12167b8ad118Sjmcneill 
12177b8ad118Sjmcneill 	hdaudio_stream_stop(st);
12187b8ad118Sjmcneill 	hdaudio_stream_reset(st);
12197b8ad118Sjmcneill 
12207b8ad118Sjmcneill 	/*
12217b8ad118Sjmcneill 	 * Configure buffer descriptor list
12227b8ad118Sjmcneill 	 */
12237b8ad118Sjmcneill 	dmaaddr = DMA_DMAADDR(&st->st_data);
12247b8ad118Sjmcneill 	bdl = DMA_KERNADDR(&st->st_bdl);
12257b8ad118Sjmcneill 	for (cnt = 0; cnt < HDAUDIO_BDL_MAX; cnt++) {
12267b8ad118Sjmcneill 		bdl[cnt].address_lo = (uint32_t)dmaaddr;
12277b8ad118Sjmcneill 		bdl[cnt].address_hi = dmaaddr >> 32;
12287b8ad118Sjmcneill 		bdl[cnt].length = blksize;
12297b8ad118Sjmcneill 		bdl[cnt].flags = HDAUDIO_BDL_ENTRY_IOC;
12307b8ad118Sjmcneill 		dmaaddr += blksize;
12317b8ad118Sjmcneill 		if (dmaaddr >= DMA_DMAADDR(&st->st_data) + dmasize) {
12327b8ad118Sjmcneill 			cnt++;
12337b8ad118Sjmcneill 			break;
12347b8ad118Sjmcneill 		}
12357b8ad118Sjmcneill 	}
12367b8ad118Sjmcneill 
12377b8ad118Sjmcneill 	/*
12387b8ad118Sjmcneill 	 * Program buffer descriptor list
12397b8ad118Sjmcneill 	 */
12407b8ad118Sjmcneill 	dmaaddr = DMA_DMAADDR(&st->st_bdl);
12417b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_SD_BDPL(snum), (uint32_t)dmaaddr);
12427b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_SD_BDPU(snum), (uint32_t)(dmaaddr >> 32));
12437b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_SD_LVI(snum), (cnt - 1) & 0xff);
12447b8ad118Sjmcneill 
12457b8ad118Sjmcneill 	/*
12467b8ad118Sjmcneill 	 * Program cyclic buffer length
12477b8ad118Sjmcneill 	 */
12487b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_SD_CBL(snum), dmasize);
12497b8ad118Sjmcneill 
12507b8ad118Sjmcneill 	/*
12517b8ad118Sjmcneill 	 * Program stream number (tag). Although controller hardware is
12527b8ad118Sjmcneill 	 * capable of transmitting any stream number (0-15), by convention
12537b8ad118Sjmcneill 	 * stream 0 is reserved as unused by software, so that converters
12547b8ad118Sjmcneill 	 * whose stream numbers have been reset to 0 do not unintentionally
12557b8ad118Sjmcneill 	 * decode data not intended for them.
12567b8ad118Sjmcneill 	 */
12577b8ad118Sjmcneill 	ctl2 = hda_read1(sc, HDAUDIO_SD_CTL2(snum));
12587b8ad118Sjmcneill 	ctl2 &= ~0xf0;
12597b8ad118Sjmcneill 	ctl2 |= hdaudio_stream_tag(st) << 4;
12607b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL2(snum), ctl2);
12617b8ad118Sjmcneill 
12627b8ad118Sjmcneill 	/*
12637b8ad118Sjmcneill 	 * Program stream format
12647b8ad118Sjmcneill 	 */
12657b8ad118Sjmcneill 	fmt = hdaudio_stream_param(st, params) |
12667b8ad118Sjmcneill 	    HDAUDIO_FMT_CHAN(params->channels);
12677b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_SD_FMT(snum), fmt);
12687b8ad118Sjmcneill 
12697b8ad118Sjmcneill 	/*
12707b8ad118Sjmcneill 	 * Switch on interrupts for this stream
12717b8ad118Sjmcneill 	 */
12727b8ad118Sjmcneill 	intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
12737b8ad118Sjmcneill 	intctl |= (1 << st->st_shift);
12747b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
12757b8ad118Sjmcneill 
12767b8ad118Sjmcneill 	/*
12777b8ad118Sjmcneill 	 * Start running the stream
12787b8ad118Sjmcneill 	 */
12797b8ad118Sjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
12807b8ad118Sjmcneill 	ctl0 |= HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
12817b8ad118Sjmcneill 	    HDAUDIO_CTL_RUN;
12827b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
12837b8ad118Sjmcneill }
12847b8ad118Sjmcneill 
12857b8ad118Sjmcneill void
12867b8ad118Sjmcneill hdaudio_stream_stop(struct hdaudio_stream *st)
12877b8ad118Sjmcneill {
12887b8ad118Sjmcneill 	struct hdaudio_softc *sc = st->st_host;
12897b8ad118Sjmcneill 	uint32_t intctl;
12907b8ad118Sjmcneill 	uint8_t ctl0;
12917b8ad118Sjmcneill 	int snum = st->st_shift;
12927b8ad118Sjmcneill 
12937b8ad118Sjmcneill 	/*
12947b8ad118Sjmcneill 	 * Stop running the stream
12957b8ad118Sjmcneill 	 */
12967b8ad118Sjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
12977b8ad118Sjmcneill 	ctl0 &= ~(HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
12987b8ad118Sjmcneill 	    HDAUDIO_CTL_RUN);
12997b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
13007b8ad118Sjmcneill 
13017b8ad118Sjmcneill 	/*
13027b8ad118Sjmcneill 	 * Switch off interrupts for this stream
13037b8ad118Sjmcneill 	 */
13047b8ad118Sjmcneill 	intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
13057b8ad118Sjmcneill 	intctl &= ~(1 << st->st_shift);
13067b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
13077b8ad118Sjmcneill }
13087b8ad118Sjmcneill 
13097b8ad118Sjmcneill /*
13107b8ad118Sjmcneill  * /dev/hdaudioN interface
13117b8ad118Sjmcneill  */
13127b8ad118Sjmcneill 
13137b8ad118Sjmcneill static const char *
13147b8ad118Sjmcneill hdaudioioctl_fgrp_to_cstr(enum function_group_type type)
13157b8ad118Sjmcneill {
13167b8ad118Sjmcneill 	switch (type) {
13177b8ad118Sjmcneill 	case HDAUDIO_GROUP_TYPE_AFG:
13187b8ad118Sjmcneill 		return "afg";
13197b8ad118Sjmcneill 	case HDAUDIO_GROUP_TYPE_VSM_FG:
13207b8ad118Sjmcneill 		return "vsmfg";
13217b8ad118Sjmcneill 	default:
13227b8ad118Sjmcneill 		return "unknown";
13237b8ad118Sjmcneill 	}
13247b8ad118Sjmcneill }
13257b8ad118Sjmcneill 
13267b8ad118Sjmcneill static struct hdaudio_function_group *
13277b8ad118Sjmcneill hdaudioioctl_fgrp_lookup(struct hdaudio_softc *sc, int codecid, int nid)
13287b8ad118Sjmcneill {
13297b8ad118Sjmcneill 	struct hdaudio_codec *co;
13307b8ad118Sjmcneill 	struct hdaudio_function_group *fg = NULL;
13317b8ad118Sjmcneill 	int i;
13327b8ad118Sjmcneill 
13337b8ad118Sjmcneill 	if (codecid < 0 || codecid >= HDAUDIO_MAX_CODECS)
13347b8ad118Sjmcneill 		return NULL;
13357b8ad118Sjmcneill 	co = &sc->sc_codec[codecid];
13367b8ad118Sjmcneill 	if (co->co_valid == false)
13377b8ad118Sjmcneill 		return NULL;
13387b8ad118Sjmcneill 
13397b8ad118Sjmcneill 	for (i = 0; i < co->co_nfg; i++)
13407b8ad118Sjmcneill 		if (co->co_fg[i].fg_nid == nid) {
13417b8ad118Sjmcneill 			fg = &co->co_fg[i];
13427b8ad118Sjmcneill 			break;
13437b8ad118Sjmcneill 		}
13447b8ad118Sjmcneill 
13457b8ad118Sjmcneill 	return fg;
13467b8ad118Sjmcneill }
13477b8ad118Sjmcneill 
13487b8ad118Sjmcneill static int
13497b8ad118Sjmcneill hdaudioioctl_fgrp_info(struct hdaudio_softc *sc, prop_dictionary_t request,
13507b8ad118Sjmcneill     prop_dictionary_t response)
13517b8ad118Sjmcneill {
13527b8ad118Sjmcneill 	struct hdaudio_codec *co;
13537b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
13547b8ad118Sjmcneill 	prop_array_t array;
13557b8ad118Sjmcneill 	prop_dictionary_t dict;
13567b8ad118Sjmcneill 	int codecid, fgid;
13577b8ad118Sjmcneill 
13587b8ad118Sjmcneill 	array = prop_array_create();
13597b8ad118Sjmcneill 	if (array == NULL)
13607b8ad118Sjmcneill 		return ENOMEM;
13617b8ad118Sjmcneill 
13627b8ad118Sjmcneill 	for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++) {
13637b8ad118Sjmcneill 		co = &sc->sc_codec[codecid];
13647b8ad118Sjmcneill 		if (co->co_valid == false)
13657b8ad118Sjmcneill 			continue;
13667b8ad118Sjmcneill 		for (fgid = 0; fgid < co->co_nfg; fgid++) {
13677b8ad118Sjmcneill 			fg = &co->co_fg[fgid];
13687b8ad118Sjmcneill 			dict = prop_dictionary_create();
13697b8ad118Sjmcneill 			if (dict == NULL)
13707b8ad118Sjmcneill 				return ENOMEM;
13717b8ad118Sjmcneill 			prop_dictionary_set_cstring_nocopy(dict,
13727b8ad118Sjmcneill 			    "type", hdaudioioctl_fgrp_to_cstr(fg->fg_type));
13737b8ad118Sjmcneill 			prop_dictionary_set_int16(dict, "nid", fg->fg_nid);
13747b8ad118Sjmcneill 			prop_dictionary_set_int16(dict, "codecid", codecid);
13757b8ad118Sjmcneill 			prop_dictionary_set_uint16(dict, "vendor-id",
13767b8ad118Sjmcneill 			    fg->fg_vendor);
13777b8ad118Sjmcneill 			prop_dictionary_set_uint16(dict, "product-id",
13787b8ad118Sjmcneill 			    fg->fg_product);
13797b8ad118Sjmcneill 			prop_dictionary_set_uint32(dict, "subsystem-id",
13807b8ad118Sjmcneill 			    sc->sc_subsystem);
13817b8ad118Sjmcneill 			if (fg->fg_device)
13827b8ad118Sjmcneill 				prop_dictionary_set_cstring(dict, "device",
13837b8ad118Sjmcneill 				    device_xname(fg->fg_device));
13847b8ad118Sjmcneill 			else
13857b8ad118Sjmcneill 				prop_dictionary_set_cstring_nocopy(dict,
13867b8ad118Sjmcneill 				    "device", "<none>");
13877b8ad118Sjmcneill 			prop_array_add(array, dict);
13887b8ad118Sjmcneill 		}
13897b8ad118Sjmcneill 	}
13907b8ad118Sjmcneill 
13917b8ad118Sjmcneill 	prop_dictionary_set(response, "function-group-info", array);
13927b8ad118Sjmcneill 	return 0;
13937b8ad118Sjmcneill }
13947b8ad118Sjmcneill 
13957b8ad118Sjmcneill static int
13967b8ad118Sjmcneill hdaudioioctl_fgrp_getconfig(struct hdaudio_softc *sc,
13977b8ad118Sjmcneill     prop_dictionary_t request, prop_dictionary_t response)
13987b8ad118Sjmcneill {
13997b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
14007b8ad118Sjmcneill 	prop_dictionary_t dict;
14017b8ad118Sjmcneill 	prop_array_t array;
14027b8ad118Sjmcneill 	uint32_t nodecnt, wcap, config;
14037b8ad118Sjmcneill 	int16_t codecid, nid, i;
14047b8ad118Sjmcneill 	int startnode, endnode;
14057b8ad118Sjmcneill 
14067b8ad118Sjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14077b8ad118Sjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14087b8ad118Sjmcneill 		return EINVAL;
14097b8ad118Sjmcneill 
14107b8ad118Sjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14117b8ad118Sjmcneill 	if (fg == NULL)
14127b8ad118Sjmcneill 		return ENODEV;
14137b8ad118Sjmcneill 
14147b8ad118Sjmcneill 	array = prop_array_create();
14157b8ad118Sjmcneill 	if (array == NULL)
14167b8ad118Sjmcneill 		return ENOMEM;
14177b8ad118Sjmcneill 
14187b8ad118Sjmcneill 	nodecnt = hdaudio_command(fg->fg_codec, fg->fg_nid,
14197b8ad118Sjmcneill 	    CORB_GET_PARAMETER, COP_SUBORDINATE_NODE_COUNT);
14207b8ad118Sjmcneill 	startnode = COP_NODECNT_STARTNODE(nodecnt);
14217b8ad118Sjmcneill 	endnode = startnode + COP_NODECNT_NUMNODES(nodecnt);
14227b8ad118Sjmcneill 
14237b8ad118Sjmcneill 	for (i = startnode; i < endnode; i++) {
14247b8ad118Sjmcneill 		wcap = hdaudio_command(fg->fg_codec, i,
14257b8ad118Sjmcneill 		    CORB_GET_PARAMETER, COP_AUDIO_WIDGET_CAPABILITIES);
14267b8ad118Sjmcneill 		if (COP_AWCAP_TYPE(wcap) != COP_AWCAP_TYPE_PIN_COMPLEX)
14277b8ad118Sjmcneill 			continue;
14287b8ad118Sjmcneill 		config = hdaudio_command(fg->fg_codec, i,
14297b8ad118Sjmcneill 		    CORB_GET_CONFIGURATION_DEFAULT, 0);
14307b8ad118Sjmcneill 		dict = prop_dictionary_create();
14317b8ad118Sjmcneill 		if (dict == NULL)
14327b8ad118Sjmcneill 			return ENOMEM;
14337b8ad118Sjmcneill 		prop_dictionary_set_int16(dict, "nid", i);
14347b8ad118Sjmcneill 		prop_dictionary_set_uint32(dict, "config", config);
14357b8ad118Sjmcneill 		prop_array_add(array, dict);
14367b8ad118Sjmcneill 	}
14377b8ad118Sjmcneill 
14387b8ad118Sjmcneill 	prop_dictionary_set(response, "pin-config", array);
14397b8ad118Sjmcneill 
14407b8ad118Sjmcneill 	return 0;
14417b8ad118Sjmcneill }
14427b8ad118Sjmcneill 
14437b8ad118Sjmcneill static int
14447b8ad118Sjmcneill hdaudioioctl_fgrp_setconfig(struct hdaudio_softc *sc,
14457b8ad118Sjmcneill     prop_dictionary_t request, prop_dictionary_t response)
14467b8ad118Sjmcneill {
14477b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
14487b8ad118Sjmcneill 	prop_array_t config;
14497b8ad118Sjmcneill 	int16_t codecid, nid;
14507b8ad118Sjmcneill 	int err;
14517b8ad118Sjmcneill 
14527b8ad118Sjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14537b8ad118Sjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14547b8ad118Sjmcneill 		return EINVAL;
14557b8ad118Sjmcneill 
14567b8ad118Sjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14577b8ad118Sjmcneill 	if (fg == NULL)
14587b8ad118Sjmcneill 		return ENODEV;
14597b8ad118Sjmcneill 
14607b8ad118Sjmcneill 	if (fg->fg_device) {
14617b8ad118Sjmcneill 		err = config_detach(fg->fg_device, 0);
14627b8ad118Sjmcneill 		if (err)
14637b8ad118Sjmcneill 			return err;
14647b8ad118Sjmcneill 		fg->fg_device = NULL;
14657b8ad118Sjmcneill 	}
14667b8ad118Sjmcneill 
14677b8ad118Sjmcneill 	/* "pin-config" may be NULL, this means "use BIOS configuration" */
14687b8ad118Sjmcneill 	config = prop_dictionary_get(request, "pin-config");
14697b8ad118Sjmcneill 	if (config && prop_object_type(config) != PROP_TYPE_ARRAY) {
14707b8ad118Sjmcneill 		prop_object_release(config);
14717b8ad118Sjmcneill 		return EINVAL;
14727b8ad118Sjmcneill 	}
14737b8ad118Sjmcneill 	hdaudio_attach_fg(fg, config);
14747b8ad118Sjmcneill 	if (config)
14757b8ad118Sjmcneill 		prop_object_release(config);
14767b8ad118Sjmcneill 
14777b8ad118Sjmcneill 	return 0;
14787b8ad118Sjmcneill }
14797b8ad118Sjmcneill 
14807b8ad118Sjmcneill static int
14817b8ad118Sjmcneill hdaudio_dispatch_fgrp_ioctl(struct hdaudio_softc *sc, u_long cmd,
14827b8ad118Sjmcneill     prop_dictionary_t request, prop_dictionary_t response)
14837b8ad118Sjmcneill {
14847b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
14857b8ad118Sjmcneill 	int (*infocb)(void *, prop_dictionary_t, prop_dictionary_t);
14867b8ad118Sjmcneill 	prop_dictionary_t fgrp_dict;
14877b8ad118Sjmcneill 	uint64_t info_fn;
14887b8ad118Sjmcneill 	int16_t codecid, nid;
14897b8ad118Sjmcneill 	void *fgrp_sc;
14907b8ad118Sjmcneill 	bool rv;
14917b8ad118Sjmcneill 	int err;
14927b8ad118Sjmcneill 
14937b8ad118Sjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14947b8ad118Sjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14957b8ad118Sjmcneill 		return EINVAL;
14967b8ad118Sjmcneill 
14977b8ad118Sjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14987b8ad118Sjmcneill 	if (fg == NULL)
14997b8ad118Sjmcneill 		return ENODEV;
15007b8ad118Sjmcneill 	if (fg->fg_device == NULL)
15017b8ad118Sjmcneill 		return ENXIO;
15027b8ad118Sjmcneill 	fgrp_sc = device_private(fg->fg_device);
15037b8ad118Sjmcneill 	fgrp_dict = device_properties(fg->fg_device);
15047b8ad118Sjmcneill 
15057b8ad118Sjmcneill 	switch (fg->fg_type) {
15067b8ad118Sjmcneill 	case HDAUDIO_GROUP_TYPE_AFG:
15077b8ad118Sjmcneill 		switch (cmd) {
15087b8ad118Sjmcneill 		case HDAUDIO_FGRP_CODEC_INFO:
15097b8ad118Sjmcneill 			rv = prop_dictionary_get_uint64(fgrp_dict,
15107b8ad118Sjmcneill 			    "codecinfo-callback", &info_fn);
15117b8ad118Sjmcneill 			if (!rv)
15127b8ad118Sjmcneill 				return ENXIO;
15137b8ad118Sjmcneill 			infocb = (void *)(uintptr_t)info_fn;
15147b8ad118Sjmcneill 			err = infocb(fgrp_sc, request, response);
15157b8ad118Sjmcneill 			break;
15167b8ad118Sjmcneill 		case HDAUDIO_FGRP_WIDGET_INFO:
15177b8ad118Sjmcneill 			rv = prop_dictionary_get_uint64(fgrp_dict,
15187b8ad118Sjmcneill 			    "widgetinfo-callback", &info_fn);
15197b8ad118Sjmcneill 			if (!rv)
15207b8ad118Sjmcneill 				return ENXIO;
15217b8ad118Sjmcneill 			infocb = (void *)(uintptr_t)info_fn;
15227b8ad118Sjmcneill 			err = infocb(fgrp_sc, request, response);
15237b8ad118Sjmcneill 			break;
15247b8ad118Sjmcneill 		default:
15257b8ad118Sjmcneill 			err = EINVAL;
15267b8ad118Sjmcneill 			break;
15277b8ad118Sjmcneill 		}
15287b8ad118Sjmcneill 		break;
15297b8ad118Sjmcneill 
15307b8ad118Sjmcneill 	default:
15317b8ad118Sjmcneill 		err = EINVAL;
15327b8ad118Sjmcneill 		break;
15337b8ad118Sjmcneill 	}
15347b8ad118Sjmcneill 	return err;
15357b8ad118Sjmcneill }
15367b8ad118Sjmcneill 
15377b8ad118Sjmcneill int
15387b8ad118Sjmcneill hdaudioopen(dev_t dev, int flag, int mode, struct lwp *l)
15397b8ad118Sjmcneill {
15407b8ad118Sjmcneill 	device_t self;
15417b8ad118Sjmcneill 
15427b8ad118Sjmcneill 	self = device_lookup(&hdaudio_cd, HDAUDIOUNIT(dev));
15437b8ad118Sjmcneill 	if (self == NULL)
15447b8ad118Sjmcneill 		return ENXIO;
15457b8ad118Sjmcneill 
15467b8ad118Sjmcneill 	return 0;
15477b8ad118Sjmcneill }
15487b8ad118Sjmcneill 
15497b8ad118Sjmcneill int
15507b8ad118Sjmcneill hdaudioclose(dev_t dev, int flag, int mode, struct lwp *l)
15517b8ad118Sjmcneill {
15527b8ad118Sjmcneill 	return 0;
15537b8ad118Sjmcneill }
15547b8ad118Sjmcneill 
15557b8ad118Sjmcneill int
15567b8ad118Sjmcneill hdaudioioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
15577b8ad118Sjmcneill {
15587b8ad118Sjmcneill 	struct hdaudio_softc *sc;
15597b8ad118Sjmcneill 	struct plistref *pref = addr;
15607b8ad118Sjmcneill 	prop_dictionary_t request, response;
15617b8ad118Sjmcneill 	int err;
15627b8ad118Sjmcneill 
15637b8ad118Sjmcneill 	sc = device_lookup_private(&hdaudio_cd, HDAUDIOUNIT(dev));
15647b8ad118Sjmcneill 	if (sc == NULL)
15657b8ad118Sjmcneill 		return ENXIO;
15667b8ad118Sjmcneill 
15677b8ad118Sjmcneill 	response = prop_dictionary_create();
15687b8ad118Sjmcneill 	if (response == NULL)
15697b8ad118Sjmcneill 		return ENOMEM;
15707b8ad118Sjmcneill 
15717b8ad118Sjmcneill 	err = prop_dictionary_copyin_ioctl(pref, cmd, &request);
15727b8ad118Sjmcneill 	if (err) {
15737b8ad118Sjmcneill 		prop_object_release(response);
15747b8ad118Sjmcneill 		return err;
15757b8ad118Sjmcneill 	}
15767b8ad118Sjmcneill 
15777b8ad118Sjmcneill 	switch (cmd) {
15787b8ad118Sjmcneill 	case HDAUDIO_FGRP_INFO:
15797b8ad118Sjmcneill 		err = hdaudioioctl_fgrp_info(sc, request, response);
15807b8ad118Sjmcneill 		break;
15817b8ad118Sjmcneill 	case HDAUDIO_FGRP_GETCONFIG:
15827b8ad118Sjmcneill 		err = hdaudioioctl_fgrp_getconfig(sc, request, response);
15837b8ad118Sjmcneill 		break;
15847b8ad118Sjmcneill 	case HDAUDIO_FGRP_SETCONFIG:
15857b8ad118Sjmcneill 		err = hdaudioioctl_fgrp_setconfig(sc, request, response);
15867b8ad118Sjmcneill 		break;
15877b8ad118Sjmcneill 	case HDAUDIO_FGRP_CODEC_INFO:
15887b8ad118Sjmcneill 	case HDAUDIO_FGRP_WIDGET_INFO:
15897b8ad118Sjmcneill 		err = hdaudio_dispatch_fgrp_ioctl(sc, cmd, request, response);
15907b8ad118Sjmcneill 		break;
15917b8ad118Sjmcneill 	default:
15927b8ad118Sjmcneill 		err = EINVAL;
15937b8ad118Sjmcneill 		break;
15947b8ad118Sjmcneill 	}
15957b8ad118Sjmcneill 
15967b8ad118Sjmcneill 	if (!err)
15977b8ad118Sjmcneill 		err = prop_dictionary_copyout_ioctl(pref, cmd, response);
15987b8ad118Sjmcneill 
15997b8ad118Sjmcneill 	if (response)
16007b8ad118Sjmcneill 		prop_object_release(response);
16017b8ad118Sjmcneill 	prop_object_release(request);
16027b8ad118Sjmcneill 	return err;
16037b8ad118Sjmcneill }
16047b8ad118Sjmcneill 
1605*5b6ebe47Spgoyette MODULE(MODULE_CLASS_DRIVER, hdaudio, "audio");
1606*5b6ebe47Spgoyette #ifdef _MODULE
1607*5b6ebe47Spgoyette static const struct cfiattrdata hdaudiobuscf_iattrdata = {
1608*5b6ebe47Spgoyette         "hdaudiobus", 1, {
1609*5b6ebe47Spgoyette                 { "nid", "-1", -1 },
1610*5b6ebe47Spgoyette         }
1611*5b6ebe47Spgoyette };
1612*5b6ebe47Spgoyette static const struct cfiattrdata * const hdaudio_attrs[] = {
1613*5b6ebe47Spgoyette 	&hdaudiobuscf_iattrdata, NULL
1614*5b6ebe47Spgoyette };
1615*5b6ebe47Spgoyette CFDRIVER_DECL(hdaudio, DV_AUDIODEV, hdaudio_attrs);
1616*5b6ebe47Spgoyette #endif
16177b8ad118Sjmcneill 
16187b8ad118Sjmcneill static int
16197b8ad118Sjmcneill hdaudio_modcmd(modcmd_t cmd, void *opaque)
16207b8ad118Sjmcneill {
16217b8ad118Sjmcneill 	int error = 0;
16227b8ad118Sjmcneill #ifdef _MODULE
16237b8ad118Sjmcneill 	int bmaj = -1, cmaj = -1;
16247b8ad118Sjmcneill #endif
16257b8ad118Sjmcneill 
16267b8ad118Sjmcneill 	switch (cmd) {
16277b8ad118Sjmcneill 	case MODULE_CMD_INIT:
16287b8ad118Sjmcneill #ifdef _MODULE
16297b8ad118Sjmcneill 		error = devsw_attach("hdaudio", NULL, &bmaj,
16307b8ad118Sjmcneill 		    &hdaudio_cdevsw, &cmaj);
1631*5b6ebe47Spgoyette 		if (error)
1632*5b6ebe47Spgoyette 			break;
1633*5b6ebe47Spgoyette 		error = config_cfdriver_attach(&hdaudio_cd);
1634*5b6ebe47Spgoyette 		if (error)
16357b8ad118Sjmcneill 			devsw_detach(NULL, &hdaudio_cdevsw);
16367b8ad118Sjmcneill #endif
1637*5b6ebe47Spgoyette 		break;
1638*5b6ebe47Spgoyette 	case MODULE_CMD_FINI:
1639*5b6ebe47Spgoyette #ifdef _MODULE
1640*5b6ebe47Spgoyette 		error = config_cfdriver_detach(&hdaudio_cd);
1641*5b6ebe47Spgoyette 		if (error)
1642*5b6ebe47Spgoyette 			break;
1643*5b6ebe47Spgoyette 		error = devsw_detach(NULL, &hdaudio_cdevsw);
1644*5b6ebe47Spgoyette 		if (error) {
1645*5b6ebe47Spgoyette 			config_cfdriver_attach(&hdaudio_cd);
1646*5b6ebe47Spgoyette 			break;
16477b8ad118Sjmcneill 		}
1648*5b6ebe47Spgoyette #endif
1649*5b6ebe47Spgoyette 		break;
1650*5b6ebe47Spgoyette 	default:
1651*5b6ebe47Spgoyette 		error = ENOTTY;
1652*5b6ebe47Spgoyette 		break;
1653*5b6ebe47Spgoyette 	}
1654*5b6ebe47Spgoyette 	return error;
16557b8ad118Sjmcneill }
16567b8ad118Sjmcneill 
16577b8ad118Sjmcneill DEV_VERBOSE_DEFINE(hdaudio);
1658