xref: /netbsd/sys/dev/hdaudio/hdaudio.c (revision 778f5600)
1*778f5600Sandvar /* $NetBSD: hdaudio.c,v 1.18 2022/04/07 19:33:37 andvar 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*778f5600Sandvar __KERNEL_RCSID(0, "$NetBSD: hdaudio.c,v 1.18 2022/04/07 19:33:37 andvar 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"
48351ab7adSpgoyette #include "hdaudiodevs.h"
497b8ad118Sjmcneill 
507b8ad118Sjmcneill /* #define	HDAUDIO_DEBUG */
517b8ad118Sjmcneill 
527b8ad118Sjmcneill #define	HDAUDIO_RESET_TIMEOUT	5000
537b8ad118Sjmcneill #define HDAUDIO_CORB_TIMEOUT	1000
547b8ad118Sjmcneill #define	HDAUDIO_RIRB_TIMEOUT	5000
557b8ad118Sjmcneill 
567b8ad118Sjmcneill #define	HDAUDIO_CODEC_DELAY	1000	/* spec calls for 250 */
577b8ad118Sjmcneill 
587b8ad118Sjmcneill dev_type_open(hdaudioopen);
597b8ad118Sjmcneill dev_type_close(hdaudioclose);
607b8ad118Sjmcneill dev_type_ioctl(hdaudioioctl);
617b8ad118Sjmcneill 
627b8ad118Sjmcneill const struct cdevsw hdaudio_cdevsw = {
637b8ad118Sjmcneill 	.d_open = hdaudioopen,
647b8ad118Sjmcneill 	.d_close = hdaudioclose,
657b8ad118Sjmcneill 	.d_read = noread,
667b8ad118Sjmcneill 	.d_write = nowrite,
677b8ad118Sjmcneill 	.d_ioctl = hdaudioioctl,
687b8ad118Sjmcneill 	.d_stop = nostop,
697b8ad118Sjmcneill 	.d_tty = notty,
707b8ad118Sjmcneill 	.d_poll = nopoll,
717b8ad118Sjmcneill 	.d_mmap = nommap,
727b8ad118Sjmcneill 	.d_kqfilter = nokqfilter,
737b8ad118Sjmcneill 	.d_discard = nodiscard,
747b8ad118Sjmcneill 	.d_flag = D_OTHER
757b8ad118Sjmcneill };
767b8ad118Sjmcneill 
777b8ad118Sjmcneill extern struct cfdriver hdaudio_cd;
787b8ad118Sjmcneill 
797b8ad118Sjmcneill #define	HDAUDIOUNIT(x)	minor((x))
807b8ad118Sjmcneill 
817b8ad118Sjmcneill static void
hdaudio_stream_init(struct hdaudio_softc * sc,int nis,int nos,int nbidir)827b8ad118Sjmcneill hdaudio_stream_init(struct hdaudio_softc *sc, int nis, int nos, int nbidir)
837b8ad118Sjmcneill {
847b8ad118Sjmcneill 	int i, cnt = 0;
857b8ad118Sjmcneill 
867b8ad118Sjmcneill 	for (i = 0; i < nis && cnt < HDAUDIO_MAX_STREAMS; i++) {
877b8ad118Sjmcneill 		sc->sc_stream[cnt].st_host = sc;
887b8ad118Sjmcneill 		sc->sc_stream[cnt].st_enable = true;
897b8ad118Sjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
907b8ad118Sjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_ISS;
917b8ad118Sjmcneill 	}
927b8ad118Sjmcneill 	for (i = 0; i < nos && cnt < HDAUDIO_MAX_STREAMS; i++) {
937b8ad118Sjmcneill 		sc->sc_stream[cnt].st_host = sc;
947b8ad118Sjmcneill 		sc->sc_stream[cnt].st_enable = true;
957b8ad118Sjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
967b8ad118Sjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_OSS;
977b8ad118Sjmcneill 	}
987b8ad118Sjmcneill 	for (i = 0; i < nbidir && cnt < HDAUDIO_MAX_STREAMS; i++) {
997b8ad118Sjmcneill 		sc->sc_stream[cnt].st_host = sc;
1007b8ad118Sjmcneill 		sc->sc_stream[cnt].st_enable = true;
1017b8ad118Sjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
1027b8ad118Sjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_BSS;
1037b8ad118Sjmcneill 	}
1047b8ad118Sjmcneill 
1057b8ad118Sjmcneill 	for (i = 0; i < cnt; i++)
1067b8ad118Sjmcneill 		hdaudio_stream_stop(&sc->sc_stream[i]);
1077b8ad118Sjmcneill 
1087b8ad118Sjmcneill 	sc->sc_stream_mask = 0;
1097b8ad118Sjmcneill }
1107b8ad118Sjmcneill 
1117b8ad118Sjmcneill static void
hdaudio_codec_init(struct hdaudio_softc * sc)1127b8ad118Sjmcneill hdaudio_codec_init(struct hdaudio_softc *sc)
1137b8ad118Sjmcneill {
1147b8ad118Sjmcneill 	int i;
1157b8ad118Sjmcneill 
1167b8ad118Sjmcneill 	for (i = 0; i < HDAUDIO_MAX_CODECS; i++) {
1177b8ad118Sjmcneill 		sc->sc_codec[i].co_addr = i;
1187b8ad118Sjmcneill 		sc->sc_codec[i].co_host = sc;
1197b8ad118Sjmcneill 	}
1207b8ad118Sjmcneill }
1217b8ad118Sjmcneill 
1227b8ad118Sjmcneill static void
hdaudio_init(struct hdaudio_softc * sc)1237b8ad118Sjmcneill hdaudio_init(struct hdaudio_softc *sc)
1247b8ad118Sjmcneill {
125e1474beeSjmcneill 	const uint8_t vmaj = hda_read1(sc, HDAUDIO_MMIO_VMAJ);
126e1474beeSjmcneill 	const uint8_t vmin = hda_read1(sc, HDAUDIO_MMIO_VMIN);
127e1474beeSjmcneill 	const uint16_t gcap = hda_read2(sc, HDAUDIO_MMIO_GCAP);
128e1474beeSjmcneill 	const int nis = HDAUDIO_GCAP_ISS(gcap);
129e1474beeSjmcneill 	const int nos = HDAUDIO_GCAP_OSS(gcap);
130e1474beeSjmcneill 	const int nbidir = HDAUDIO_GCAP_BSS(gcap);
131e1474beeSjmcneill 	const int nsdo = HDAUDIO_GCAP_NSDO(gcap);
132e1474beeSjmcneill 	const int addr64 = HDAUDIO_GCAP_64OK(gcap);
1337b8ad118Sjmcneill 
134e1474beeSjmcneill 	hda_print(sc, "HDA ver. %d.%d, OSS %d, ISS %d, BSS %d, SDO %d%s\n",
135e1474beeSjmcneill 	    vmaj, vmin, nos, nis, nbidir, nsdo, addr64 ? ", 64-bit" : "");
1367b8ad118Sjmcneill 
1377b8ad118Sjmcneill 	/* Initialize codecs and streams */
1387b8ad118Sjmcneill 	hdaudio_codec_init(sc);
1397b8ad118Sjmcneill 	hdaudio_stream_init(sc, nis, nos, nbidir);
1407b8ad118Sjmcneill }
1417b8ad118Sjmcneill 
1427b8ad118Sjmcneill static int
hdaudio_codec_probe(struct hdaudio_softc * sc)1437b8ad118Sjmcneill hdaudio_codec_probe(struct hdaudio_softc *sc)
1447b8ad118Sjmcneill {
1457b8ad118Sjmcneill 	uint16_t statests;
1467b8ad118Sjmcneill 	int codecid;
1477b8ad118Sjmcneill 
1487b8ad118Sjmcneill 	statests = hda_read2(sc, HDAUDIO_MMIO_STATESTS);
1497b8ad118Sjmcneill 	for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++)
1507b8ad118Sjmcneill 		if (statests & (1 << codecid))
1517b8ad118Sjmcneill 			sc->sc_codec[codecid].co_valid = true;
1527b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, statests);
1537b8ad118Sjmcneill 
1547b8ad118Sjmcneill 	return statests;
1557b8ad118Sjmcneill }
1567b8ad118Sjmcneill 
1577b8ad118Sjmcneill int
hdaudio_dma_alloc(struct hdaudio_softc * sc,struct hdaudio_dma * dma,int flags)1587b8ad118Sjmcneill hdaudio_dma_alloc(struct hdaudio_softc *sc, struct hdaudio_dma *dma,
1597b8ad118Sjmcneill     int flags)
1607b8ad118Sjmcneill {
1617b8ad118Sjmcneill 	int err;
1627b8ad118Sjmcneill 
1637b8ad118Sjmcneill 	KASSERT(dma->dma_size > 0);
1647b8ad118Sjmcneill 
1657b8ad118Sjmcneill 	err = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, 128, 0,
1667b8ad118Sjmcneill 	    dma->dma_segs, sizeof(dma->dma_segs) / sizeof(dma->dma_segs[0]),
1677b8ad118Sjmcneill 	    &dma->dma_nsegs, BUS_DMA_WAITOK);
1687b8ad118Sjmcneill 	if (err)
1697b8ad118Sjmcneill 		return err;
1707b8ad118Sjmcneill 	err = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs,
1717b8ad118Sjmcneill 	    dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | flags);
1727b8ad118Sjmcneill 	if (err)
1737b8ad118Sjmcneill 		goto free;
1747b8ad118Sjmcneill 	err = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs,
1757b8ad118Sjmcneill 	    dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map);
1767b8ad118Sjmcneill 	if (err)
1777b8ad118Sjmcneill 		goto unmap;
1787b8ad118Sjmcneill 	err = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr,
1797b8ad118Sjmcneill 	    dma->dma_size, NULL, BUS_DMA_WAITOK | flags);
1807b8ad118Sjmcneill 	if (err)
1817b8ad118Sjmcneill 		goto destroy;
1827b8ad118Sjmcneill 
18351e195c1Sjmcneill 	memset(dma->dma_addr, 0, dma->dma_size);
18451e195c1Sjmcneill 	bus_dmamap_sync(sc->sc_dmat, dma->dma_map, 0, dma->dma_size,
18551e195c1Sjmcneill 	    BUS_DMASYNC_PREWRITE);
18651e195c1Sjmcneill 
1877b8ad118Sjmcneill 	dma->dma_valid = true;
1887b8ad118Sjmcneill 	return 0;
1897b8ad118Sjmcneill 
1907b8ad118Sjmcneill destroy:
1917b8ad118Sjmcneill 	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
1927b8ad118Sjmcneill unmap:
1937b8ad118Sjmcneill 	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
1947b8ad118Sjmcneill free:
1957b8ad118Sjmcneill 	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
1967b8ad118Sjmcneill 
1977b8ad118Sjmcneill 	dma->dma_valid = false;
1987b8ad118Sjmcneill 	return err;
1997b8ad118Sjmcneill }
2007b8ad118Sjmcneill 
2017b8ad118Sjmcneill void
hdaudio_dma_free(struct hdaudio_softc * sc,struct hdaudio_dma * dma)2027b8ad118Sjmcneill hdaudio_dma_free(struct hdaudio_softc *sc, struct hdaudio_dma *dma)
2037b8ad118Sjmcneill {
2047b8ad118Sjmcneill 	if (dma->dma_valid == false)
2057b8ad118Sjmcneill 		return;
2067b8ad118Sjmcneill 	bus_dmamap_unload(sc->sc_dmat, dma->dma_map);
2077b8ad118Sjmcneill 	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
2087b8ad118Sjmcneill 	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
2097b8ad118Sjmcneill 	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
2107b8ad118Sjmcneill 	dma->dma_valid = false;
2117b8ad118Sjmcneill }
2127b8ad118Sjmcneill 
2137b8ad118Sjmcneill static void
hdaudio_corb_enqueue(struct hdaudio_softc * sc,int addr,int nid,uint32_t control,uint32_t param)2147b8ad118Sjmcneill hdaudio_corb_enqueue(struct hdaudio_softc *sc, int addr, int nid,
2157b8ad118Sjmcneill     uint32_t control, uint32_t param)
2167b8ad118Sjmcneill {
2177b8ad118Sjmcneill 	uint32_t *corb = DMA_KERNADDR(&sc->sc_corb);
2187b8ad118Sjmcneill 	uint32_t verb;
2197b8ad118Sjmcneill 	uint16_t corbrp;
2207b8ad118Sjmcneill 	int wp;
2217b8ad118Sjmcneill 
2227b8ad118Sjmcneill 	/* Build command */
2237b8ad118Sjmcneill 	verb = (addr << 28) | (nid << 20) | (control << 8) | param;
2247b8ad118Sjmcneill 
2257b8ad118Sjmcneill 	/* Fetch and update write pointer */
2267b8ad118Sjmcneill 	corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBWP);
2277b8ad118Sjmcneill 	wp = (corbrp & 0xff) + 1;
2287b8ad118Sjmcneill 	if (wp >= (sc->sc_corb.dma_size / sizeof(*corb)))
2297b8ad118Sjmcneill 		wp = 0;
2307b8ad118Sjmcneill 
2317b8ad118Sjmcneill 	/* Enqueue command */
2327b8ad118Sjmcneill 	bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
2337b8ad118Sjmcneill 	    sc->sc_corb.dma_size, BUS_DMASYNC_POSTWRITE);
2347b8ad118Sjmcneill 	corb[wp] = verb;
2357b8ad118Sjmcneill 	bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
2367b8ad118Sjmcneill 	    sc->sc_corb.dma_size, BUS_DMASYNC_PREWRITE);
2377b8ad118Sjmcneill 
2387b8ad118Sjmcneill 	/* Commit updated write pointer */
2397b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBWP, wp);
2407b8ad118Sjmcneill }
2417b8ad118Sjmcneill 
2427b8ad118Sjmcneill static void
hdaudio_rirb_unsol(struct hdaudio_softc * sc,struct rirb_entry * entry)2437b8ad118Sjmcneill hdaudio_rirb_unsol(struct hdaudio_softc *sc, struct rirb_entry *entry)
2447b8ad118Sjmcneill {
2457b8ad118Sjmcneill 	struct hdaudio_codec *co;
2467b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
2477b8ad118Sjmcneill 	uint8_t codecid = RIRB_CODEC_ID(entry);
2487b8ad118Sjmcneill 	unsigned int i;
2497b8ad118Sjmcneill 
2507b8ad118Sjmcneill 	if (codecid >= HDAUDIO_MAX_CODECS) {
2517b8ad118Sjmcneill 		hda_error(sc, "unsol: codec id 0x%02x out of range\n", codecid);
2527b8ad118Sjmcneill 		return;
2537b8ad118Sjmcneill 	}
2547b8ad118Sjmcneill 	co = &sc->sc_codec[codecid];
2557b8ad118Sjmcneill 	if (sc->sc_codec[codecid].co_valid == false) {
2567b8ad118Sjmcneill 		hda_error(sc, "unsol: codec id 0x%02x not valid\n", codecid);
2577b8ad118Sjmcneill 		return;
2587b8ad118Sjmcneill 	}
2597b8ad118Sjmcneill 
2607b8ad118Sjmcneill 	for (i = 0; i < co->co_nfg; i++) {
2617b8ad118Sjmcneill 		fg = &co->co_fg[i];
2627b8ad118Sjmcneill 		if (fg->fg_device && fg->fg_unsol)
2637b8ad118Sjmcneill 			fg->fg_unsol(fg->fg_device, entry->resp);
2647b8ad118Sjmcneill 	}
2657b8ad118Sjmcneill }
2667b8ad118Sjmcneill 
2677b8ad118Sjmcneill static uint32_t
hdaudio_rirb_dequeue(struct hdaudio_softc * sc,bool unsol)2687b8ad118Sjmcneill hdaudio_rirb_dequeue(struct hdaudio_softc *sc, bool unsol)
2697b8ad118Sjmcneill {
2707b8ad118Sjmcneill 	uint16_t rirbwp;
2717b8ad118Sjmcneill 	uint64_t *rirb = DMA_KERNADDR(&sc->sc_rirb);
2727b8ad118Sjmcneill 	struct rirb_entry entry;
2737b8ad118Sjmcneill 	int retry;
2747b8ad118Sjmcneill 
2757b8ad118Sjmcneill 	for (;;) {
2767b8ad118Sjmcneill 		retry = HDAUDIO_RIRB_TIMEOUT;
2777b8ad118Sjmcneill 
2787b8ad118Sjmcneill 		rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
2797b8ad118Sjmcneill 		while (--retry > 0 && (rirbwp & 0xff) == sc->sc_rirbrp) {
2807b8ad118Sjmcneill 			if (unsol) {
2817b8ad118Sjmcneill 				/* don't wait for more unsol events */
2827b8ad118Sjmcneill 				hda_trace(sc, "unsol: rirb empty\n");
2837b8ad118Sjmcneill 				return 0xffffffff;
2847b8ad118Sjmcneill 			}
2857b8ad118Sjmcneill 			hda_delay(10);
2867b8ad118Sjmcneill 			rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
2877b8ad118Sjmcneill 		}
2887b8ad118Sjmcneill 		if (retry == 0) {
2897b8ad118Sjmcneill 			hda_error(sc, "RIRB timeout\n");
2907b8ad118Sjmcneill 			return 0xffffffff;
2917b8ad118Sjmcneill 		}
2927b8ad118Sjmcneill 
2937b8ad118Sjmcneill 		sc->sc_rirbrp++;
2947b8ad118Sjmcneill 		if (sc->sc_rirbrp >= (sc->sc_rirb.dma_size / sizeof(*rirb)))
2957b8ad118Sjmcneill 			sc->sc_rirbrp = 0;
2967b8ad118Sjmcneill 
2977b8ad118Sjmcneill 		bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
2987b8ad118Sjmcneill 		    sc->sc_rirb.dma_size, BUS_DMASYNC_POSTREAD);
2997b8ad118Sjmcneill 		entry = *(struct rirb_entry *)&rirb[sc->sc_rirbrp];
3007b8ad118Sjmcneill 		bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
3017b8ad118Sjmcneill 		    sc->sc_rirb.dma_size, BUS_DMASYNC_PREREAD);
3027b8ad118Sjmcneill 
3037b8ad118Sjmcneill 		hda_trace(sc, "%s: response %08X %08X\n",
3047b8ad118Sjmcneill 		    unsol ? "unsol" : "cmd  ",
3057b8ad118Sjmcneill 		    entry.resp, entry.resp_ex);
3067b8ad118Sjmcneill 
3077b8ad118Sjmcneill 		if (RIRB_UNSOL(&entry)) {
3087b8ad118Sjmcneill 			hdaudio_rirb_unsol(sc, &entry);
3097b8ad118Sjmcneill 			continue;
3107b8ad118Sjmcneill 		}
3117b8ad118Sjmcneill 
3127b8ad118Sjmcneill 		return entry.resp;
3137b8ad118Sjmcneill 	}
3147b8ad118Sjmcneill }
3157b8ad118Sjmcneill 
3167b8ad118Sjmcneill uint32_t
hdaudio_command(struct hdaudio_codec * co,int nid,uint32_t control,uint32_t param)3177b8ad118Sjmcneill hdaudio_command(struct hdaudio_codec *co, int nid, uint32_t control,
3187b8ad118Sjmcneill     uint32_t param)
3197b8ad118Sjmcneill {
3207b8ad118Sjmcneill 	uint32_t result;
3217b8ad118Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
3227b8ad118Sjmcneill 	mutex_enter(&sc->sc_corb_mtx);
3237b8ad118Sjmcneill 	result = hdaudio_command_unlocked(co, nid, control, param);
3247b8ad118Sjmcneill 	mutex_exit(&sc->sc_corb_mtx);
3257b8ad118Sjmcneill 	return result;
3267b8ad118Sjmcneill }
3277b8ad118Sjmcneill 
3287b8ad118Sjmcneill uint32_t
hdaudio_command_unlocked(struct hdaudio_codec * co,int nid,uint32_t control,uint32_t param)3297b8ad118Sjmcneill hdaudio_command_unlocked(struct hdaudio_codec *co, int nid, uint32_t control,
3307b8ad118Sjmcneill     uint32_t param)
3317b8ad118Sjmcneill {
3327b8ad118Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
3337b8ad118Sjmcneill 	uint32_t result;
3347b8ad118Sjmcneill 
3357b8ad118Sjmcneill 	hda_trace(sc, "cmd  : request %08X %08X (%02X)\n",
3367b8ad118Sjmcneill 	    control, param, nid);
3377b8ad118Sjmcneill 	hdaudio_corb_enqueue(sc, co->co_addr, nid, control, param);
3387b8ad118Sjmcneill 	result = hdaudio_rirb_dequeue(sc, false);
3397b8ad118Sjmcneill 
340fbb8195bSjmcneill 	/* Clear response interrupt status */
341fbb8195bSjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS, hda_read1(sc, HDAUDIO_MMIO_RIRBSTS));
342fbb8195bSjmcneill 
3437b8ad118Sjmcneill 	return result;
3447b8ad118Sjmcneill }
3457b8ad118Sjmcneill 
3467b8ad118Sjmcneill static int
hdaudio_corb_setsize(struct hdaudio_softc * sc)3477b8ad118Sjmcneill hdaudio_corb_setsize(struct hdaudio_softc *sc)
3487b8ad118Sjmcneill {
3497b8ad118Sjmcneill 	uint8_t corbsize;
3507b8ad118Sjmcneill 	bus_size_t bufsize = 0;
3517b8ad118Sjmcneill 
3527b8ad118Sjmcneill 	/*
3537b8ad118Sjmcneill 	 * The size of the CORB is programmable to 2, 16, or 256 entries
3547b8ad118Sjmcneill 	 * by using the CORBSIZE register. Choose a size based on the
3557b8ad118Sjmcneill 	 * controller capabilities, preferring a larger size when possible.
3567b8ad118Sjmcneill 	 */
3577b8ad118Sjmcneill 	corbsize = hda_read1(sc, HDAUDIO_MMIO_CORBSIZE);
3587b8ad118Sjmcneill 	corbsize &= ~0x3;
3597b8ad118Sjmcneill 	if ((corbsize >> 4) & 0x4) {
3607b8ad118Sjmcneill 		corbsize |= 0x2;
3617b8ad118Sjmcneill 		bufsize = 1024;
3627b8ad118Sjmcneill 	} else if ((corbsize >> 4) & 0x2) {
3637b8ad118Sjmcneill 		corbsize |= 0x1;
3647b8ad118Sjmcneill 		bufsize = 64;
3657b8ad118Sjmcneill 	} else if ((corbsize >> 4) & 0x1) {
3667b8ad118Sjmcneill 		corbsize |= 0x0;
3677b8ad118Sjmcneill 		bufsize = 8;
3687b8ad118Sjmcneill 	} else {
3697b8ad118Sjmcneill 		hda_error(sc, "couldn't configure CORB size\n");
3707b8ad118Sjmcneill 		return ENXIO;
3717b8ad118Sjmcneill 	}
3727b8ad118Sjmcneill 
3737b8ad118Sjmcneill #if defined(HDAUDIO_DEBUG)
3747b8ad118Sjmcneill 	hda_print(sc, "using %d byte CORB (cap %X)\n",
3757b8ad118Sjmcneill 	    (int)bufsize, corbsize >> 4);
3767b8ad118Sjmcneill #endif
3777b8ad118Sjmcneill 
3787b8ad118Sjmcneill 	sc->sc_corb.dma_size = bufsize;
3797b8ad118Sjmcneill 	sc->sc_corb.dma_sizereg = corbsize;
3807b8ad118Sjmcneill 
3817b8ad118Sjmcneill 	return 0;
3827b8ad118Sjmcneill }
3837b8ad118Sjmcneill 
3847b8ad118Sjmcneill static int
hdaudio_corb_config(struct hdaudio_softc * sc)3857b8ad118Sjmcneill hdaudio_corb_config(struct hdaudio_softc *sc)
3867b8ad118Sjmcneill {
3877b8ad118Sjmcneill 	uint32_t corbubase, corblbase;
3887b8ad118Sjmcneill 	uint16_t corbrp;
3897b8ad118Sjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
3907b8ad118Sjmcneill 
3917b8ad118Sjmcneill 	/* Program command buffer base address and size */
3927b8ad118Sjmcneill 	corblbase = (uint32_t)DMA_DMAADDR(&sc->sc_corb);
3937b8ad118Sjmcneill 	corbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_corb)) >> 32);
3947b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_CORBLBASE, corblbase);
3957b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_CORBUBASE, corbubase);
3967b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_CORBSIZE, sc->sc_corb.dma_sizereg);
3977b8ad118Sjmcneill 
3987b8ad118Sjmcneill 	/* Clear the read and write pointers */
3997b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBRP, HDAUDIO_CORBRP_RP_RESET);
4007b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBRP, 0);
4017b8ad118Sjmcneill 	do {
4027b8ad118Sjmcneill 		hda_delay(10);
4037b8ad118Sjmcneill 		corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBRP);
4047b8ad118Sjmcneill 	} while (--retry > 0 && (corbrp & HDAUDIO_CORBRP_RP_RESET) != 0);
4057b8ad118Sjmcneill 	if (retry == 0) {
4067b8ad118Sjmcneill 		hda_error(sc, "timeout resetting CORB\n");
4077b8ad118Sjmcneill 		return ETIME;
4087b8ad118Sjmcneill 	}
4097b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBWP, 0);
4107b8ad118Sjmcneill 
4117b8ad118Sjmcneill 	return 0;
4127b8ad118Sjmcneill }
4137b8ad118Sjmcneill 
4147b8ad118Sjmcneill static int
hdaudio_corb_stop(struct hdaudio_softc * sc)4157b8ad118Sjmcneill hdaudio_corb_stop(struct hdaudio_softc *sc)
4167b8ad118Sjmcneill {
4177b8ad118Sjmcneill 	uint8_t corbctl;
4187b8ad118Sjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
4197b8ad118Sjmcneill 
4207b8ad118Sjmcneill 	/* Stop the CORB if necessary */
4217b8ad118Sjmcneill 	corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4227b8ad118Sjmcneill 	if (corbctl & HDAUDIO_CORBCTL_RUN) {
4237b8ad118Sjmcneill 		corbctl &= ~HDAUDIO_CORBCTL_RUN;
42467d13b00Sjmcneill 		hda_write1(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
4257b8ad118Sjmcneill 		do {
4267b8ad118Sjmcneill 			hda_delay(10);
42767d13b00Sjmcneill 			corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4287b8ad118Sjmcneill 		} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) != 0);
4297b8ad118Sjmcneill 		if (retry == 0) {
4307b8ad118Sjmcneill 			hda_error(sc, "timeout stopping CORB\n");
4317b8ad118Sjmcneill 			return ETIME;
4327b8ad118Sjmcneill 		}
4337b8ad118Sjmcneill 	}
4347b8ad118Sjmcneill 
4357b8ad118Sjmcneill 	return 0;
4367b8ad118Sjmcneill }
4377b8ad118Sjmcneill 
4387b8ad118Sjmcneill static int
hdaudio_corb_start(struct hdaudio_softc * sc)4397b8ad118Sjmcneill hdaudio_corb_start(struct hdaudio_softc *sc)
4407b8ad118Sjmcneill {
4417b8ad118Sjmcneill 	uint8_t corbctl;
4427b8ad118Sjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
4437b8ad118Sjmcneill 
4447b8ad118Sjmcneill 	/* Start the CORB if necessary */
4457b8ad118Sjmcneill 	corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4467b8ad118Sjmcneill 	if ((corbctl & HDAUDIO_CORBCTL_RUN) == 0) {
4477b8ad118Sjmcneill 		corbctl |= HDAUDIO_CORBCTL_RUN;
44867d13b00Sjmcneill 		hda_write1(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
4497b8ad118Sjmcneill 		do {
4507b8ad118Sjmcneill 			hda_delay(10);
45167d13b00Sjmcneill 			corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4527b8ad118Sjmcneill 		} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) == 0);
4537b8ad118Sjmcneill 		if (retry == 0) {
4547b8ad118Sjmcneill 			hda_error(sc, "timeout starting CORB\n");
4557b8ad118Sjmcneill 			return ETIME;
4567b8ad118Sjmcneill 		}
4577b8ad118Sjmcneill 	}
4587b8ad118Sjmcneill 
4597b8ad118Sjmcneill 	return 0;
4607b8ad118Sjmcneill }
4617b8ad118Sjmcneill 
4627b8ad118Sjmcneill static int
hdaudio_rirb_stop(struct hdaudio_softc * sc)4637b8ad118Sjmcneill hdaudio_rirb_stop(struct hdaudio_softc *sc)
4647b8ad118Sjmcneill {
4657b8ad118Sjmcneill 	uint8_t rirbctl;
4667b8ad118Sjmcneill 	int retry = HDAUDIO_RIRB_TIMEOUT;
4677b8ad118Sjmcneill 
4687b8ad118Sjmcneill 	/* Stop the RIRB if necessary */
4697b8ad118Sjmcneill 	rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4707b8ad118Sjmcneill 	if (rirbctl & (HDAUDIO_RIRBCTL_RUN|HDAUDIO_RIRBCTL_ROI_EN)) {
4717b8ad118Sjmcneill 		rirbctl &= ~HDAUDIO_RIRBCTL_RUN;
4727b8ad118Sjmcneill 		rirbctl &= ~HDAUDIO_RIRBCTL_ROI_EN;
4737b8ad118Sjmcneill 		hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
4747b8ad118Sjmcneill 		do {
4757b8ad118Sjmcneill 			hda_delay(10);
4767b8ad118Sjmcneill 			rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4777b8ad118Sjmcneill 		} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) != 0);
4787b8ad118Sjmcneill 		if (retry == 0) {
4797b8ad118Sjmcneill 			hda_error(sc, "timeout stopping RIRB\n");
4807b8ad118Sjmcneill 			return ETIME;
4817b8ad118Sjmcneill 		}
4827b8ad118Sjmcneill 	}
4837b8ad118Sjmcneill 
4847b8ad118Sjmcneill 	return 0;
4857b8ad118Sjmcneill }
4867b8ad118Sjmcneill 
4877b8ad118Sjmcneill static int
hdaudio_rirb_start(struct hdaudio_softc * sc)4887b8ad118Sjmcneill hdaudio_rirb_start(struct hdaudio_softc *sc)
4897b8ad118Sjmcneill {
4907b8ad118Sjmcneill 	uint8_t rirbctl;
4917b8ad118Sjmcneill 	int retry = HDAUDIO_RIRB_TIMEOUT;
4927b8ad118Sjmcneill 
493fbb8195bSjmcneill 	/* Set the RIRB interrupt count */
494fbb8195bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_RINTCNT, 1);
495fbb8195bSjmcneill 
496fbb8195bSjmcneill 	/* Start the RIRB */
4977b8ad118Sjmcneill 	rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4987b8ad118Sjmcneill 	rirbctl |= HDAUDIO_RIRBCTL_RUN;
4997b8ad118Sjmcneill 	rirbctl |= HDAUDIO_RIRBCTL_INT_EN;
5007b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
5017b8ad118Sjmcneill 	do {
5027b8ad118Sjmcneill 		hda_delay(10);
5037b8ad118Sjmcneill 		rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
5047b8ad118Sjmcneill 	} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) == 0);
5057b8ad118Sjmcneill 	if (retry == 0) {
5067b8ad118Sjmcneill 		hda_error(sc, "timeout starting RIRB\n");
5077b8ad118Sjmcneill 		return ETIME;
5087b8ad118Sjmcneill 	}
5097b8ad118Sjmcneill 
5107b8ad118Sjmcneill 	return 0;
5117b8ad118Sjmcneill }
5127b8ad118Sjmcneill 
5137b8ad118Sjmcneill static int
hdaudio_rirb_setsize(struct hdaudio_softc * sc)5147b8ad118Sjmcneill hdaudio_rirb_setsize(struct hdaudio_softc *sc)
5157b8ad118Sjmcneill {
5167b8ad118Sjmcneill 	uint8_t rirbsize;
5177b8ad118Sjmcneill 	bus_size_t bufsize = 0;
5187b8ad118Sjmcneill 
5197b8ad118Sjmcneill 	/*
5207b8ad118Sjmcneill 	 * The size of the RIRB is programmable to 2, 16, or 256 entries
5217b8ad118Sjmcneill 	 * by using the RIRBSIZE register. Choose a size based on the
5227b8ad118Sjmcneill 	 * controller capabilities, preferring a larger size when possible.
5237b8ad118Sjmcneill 	 */
5247b8ad118Sjmcneill 	rirbsize = hda_read1(sc, HDAUDIO_MMIO_RIRBSIZE);
5257b8ad118Sjmcneill 	rirbsize &= ~0x3;
5267b8ad118Sjmcneill 	if ((rirbsize >> 4) & 0x4) {
5277b8ad118Sjmcneill 		rirbsize |= 0x2;
5287b8ad118Sjmcneill 		bufsize = 2048;
5297b8ad118Sjmcneill 	} else if ((rirbsize >> 4) & 0x2) {
5307b8ad118Sjmcneill 		rirbsize |= 0x1;
5317b8ad118Sjmcneill 		bufsize = 128;
5327b8ad118Sjmcneill 	} else if ((rirbsize >> 4) & 0x1) {
5337b8ad118Sjmcneill 		rirbsize |= 0x0;
5347b8ad118Sjmcneill 		bufsize = 16;
5357b8ad118Sjmcneill 	} else {
5367b8ad118Sjmcneill 		hda_error(sc, "couldn't configure RIRB size\n");
5377b8ad118Sjmcneill 		return ENXIO;
5387b8ad118Sjmcneill 	}
5397b8ad118Sjmcneill 
5407b8ad118Sjmcneill #if defined(HDAUDIO_DEBUG)
5417b8ad118Sjmcneill 	hda_print(sc, "using %d byte RIRB (cap %X)\n",
5427b8ad118Sjmcneill 	    (int)bufsize, rirbsize >> 4);
5437b8ad118Sjmcneill #endif
5447b8ad118Sjmcneill 
5457b8ad118Sjmcneill 	sc->sc_rirb.dma_size = bufsize;
5467b8ad118Sjmcneill 	sc->sc_rirb.dma_sizereg = rirbsize;
5477b8ad118Sjmcneill 
5487b8ad118Sjmcneill 	return 0;
5497b8ad118Sjmcneill }
5507b8ad118Sjmcneill 
5517b8ad118Sjmcneill static int
hdaudio_rirb_config(struct hdaudio_softc * sc)5527b8ad118Sjmcneill hdaudio_rirb_config(struct hdaudio_softc *sc)
5537b8ad118Sjmcneill {
5547b8ad118Sjmcneill 	uint32_t rirbubase, rirblbase;
5557b8ad118Sjmcneill 
5567b8ad118Sjmcneill 	/* Program command buffer base address and size */
5577b8ad118Sjmcneill 	rirblbase = (uint32_t)DMA_DMAADDR(&sc->sc_rirb);
5587b8ad118Sjmcneill 	rirbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_rirb)) >> 32);
5597b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_RIRBLBASE, rirblbase);
5607b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_RIRBUBASE, rirbubase);
5617b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSIZE, sc->sc_rirb.dma_sizereg);
5627b8ad118Sjmcneill 
5637b8ad118Sjmcneill 	/* Clear the write pointer */
5647b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_RIRBWP, HDAUDIO_RIRBWP_WP_RESET);
5657b8ad118Sjmcneill 	sc->sc_rirbrp = 0;
5667b8ad118Sjmcneill 
5677b8ad118Sjmcneill 	return 0;
5687b8ad118Sjmcneill }
5697b8ad118Sjmcneill 
5707b8ad118Sjmcneill static int
hdaudio_reset(struct hdaudio_softc * sc)5717b8ad118Sjmcneill hdaudio_reset(struct hdaudio_softc *sc)
5727b8ad118Sjmcneill {
5737b8ad118Sjmcneill 	int retry = HDAUDIO_RESET_TIMEOUT;
5747b8ad118Sjmcneill 	uint32_t gctl;
5757b8ad118Sjmcneill 	int err;
5767b8ad118Sjmcneill 
5777b8ad118Sjmcneill 	if ((err = hdaudio_rirb_stop(sc)) != 0) {
5787b8ad118Sjmcneill 		hda_error(sc, "couldn't reset because RIRB is busy\n");
5797b8ad118Sjmcneill 		return err;
5807b8ad118Sjmcneill 	}
5817b8ad118Sjmcneill 	if ((err = hdaudio_corb_stop(sc)) != 0) {
5827b8ad118Sjmcneill 		hda_error(sc, "couldn't reset because CORB is busy\n");
5837b8ad118Sjmcneill 		return err;
5847b8ad118Sjmcneill 	}
5857b8ad118Sjmcneill 
5867b8ad118Sjmcneill 	/* Disable wake events */
5877b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_WAKEEN, 0);
5887b8ad118Sjmcneill 
5897b8ad118Sjmcneill 	/* Disable interrupts */
5907b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
5917b8ad118Sjmcneill 
5927b8ad118Sjmcneill 	/* Clear state change status register */
5937b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS,
5947b8ad118Sjmcneill 	    hda_read2(sc, HDAUDIO_MMIO_STATESTS));
5957b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
5967b8ad118Sjmcneill 	    hda_read1(sc, HDAUDIO_MMIO_RIRBSTS));
5977b8ad118Sjmcneill 
5983935fef1Sjmcneill 	/* Put the controller into reset state */
5997b8ad118Sjmcneill 	gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6007b8ad118Sjmcneill 	gctl &= ~HDAUDIO_GCTL_CRST;
6017b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl);
6027b8ad118Sjmcneill 	do {
6037b8ad118Sjmcneill 		hda_delay(10);
6047b8ad118Sjmcneill 		gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6057b8ad118Sjmcneill 	} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) != 0);
6067b8ad118Sjmcneill 	if (retry == 0) {
6077b8ad118Sjmcneill 		hda_error(sc, "timeout entering reset state\n");
6087b8ad118Sjmcneill 		return ETIME;
6097b8ad118Sjmcneill 	}
6103935fef1Sjmcneill 
6113935fef1Sjmcneill 	hda_delay(1000);
6127b8ad118Sjmcneill 
6137b8ad118Sjmcneill 	/* Now the controller is in reset state, so bring it out */
6147b8ad118Sjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
6157b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_CRST);
6167b8ad118Sjmcneill 	do {
6177b8ad118Sjmcneill 		hda_delay(10);
6187b8ad118Sjmcneill 		gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6197b8ad118Sjmcneill 	} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) == 0);
6207b8ad118Sjmcneill 	if (retry == 0) {
6217b8ad118Sjmcneill 		hda_error(sc, "timeout leaving reset state\n");
6227b8ad118Sjmcneill 		return ETIME;
6237b8ad118Sjmcneill 	}
6247b8ad118Sjmcneill 
6253935fef1Sjmcneill 	hda_delay(2000);
6263935fef1Sjmcneill 
6277b8ad118Sjmcneill 	/* Accept unsolicited responses */
6287b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_UNSOL_EN);
6297b8ad118Sjmcneill 
6307b8ad118Sjmcneill 	return 0;
6317b8ad118Sjmcneill }
6327b8ad118Sjmcneill 
6337b8ad118Sjmcneill static void
hdaudio_intr_enable(struct hdaudio_softc * sc)6347b8ad118Sjmcneill hdaudio_intr_enable(struct hdaudio_softc *sc)
6357b8ad118Sjmcneill {
6367b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
6377b8ad118Sjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
6387b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL,
6397b8ad118Sjmcneill 	    HDAUDIO_INTCTL_GIE | HDAUDIO_INTCTL_CIE);
6407b8ad118Sjmcneill }
6417b8ad118Sjmcneill 
6427b8ad118Sjmcneill static void
hdaudio_intr_disable(struct hdaudio_softc * sc)6437b8ad118Sjmcneill hdaudio_intr_disable(struct hdaudio_softc *sc)
6447b8ad118Sjmcneill {
6457b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
6467b8ad118Sjmcneill }
6477b8ad118Sjmcneill 
6487b8ad118Sjmcneill static int
hdaudio_config_print(void * opaque,const char * pnp)6497b8ad118Sjmcneill hdaudio_config_print(void *opaque, const char *pnp)
6507b8ad118Sjmcneill {
6517b8ad118Sjmcneill 	prop_dictionary_t dict = opaque;
6527b8ad118Sjmcneill 	uint8_t fgtype, nid;
6537b8ad118Sjmcneill 	uint16_t vendor, product;
6547b8ad118Sjmcneill 	const char *type = "unknown";
6557b8ad118Sjmcneill 
6567b8ad118Sjmcneill 	prop_dictionary_get_uint8(dict, "function-group-type", &fgtype);
6577b8ad118Sjmcneill 	prop_dictionary_get_uint8(dict, "node-id", &nid);
6587b8ad118Sjmcneill 	prop_dictionary_get_uint16(dict, "vendor-id", &vendor);
6597b8ad118Sjmcneill 	prop_dictionary_get_uint16(dict, "product-id", &product);
6607b8ad118Sjmcneill 	if (pnp) {
6617b8ad118Sjmcneill 		if (fgtype == HDAUDIO_GROUP_TYPE_AFG)
6627b8ad118Sjmcneill 			type = "hdafg";
6637b8ad118Sjmcneill 		else if (fgtype == HDAUDIO_GROUP_TYPE_VSM_FG)
6647b8ad118Sjmcneill 			type = "hdvsmfg";
6657b8ad118Sjmcneill 
6667b8ad118Sjmcneill 		aprint_normal("%s at %s", type, pnp);
6677b8ad118Sjmcneill 	}
6687b8ad118Sjmcneill 	aprint_debug(" vendor 0x%04X product 0x%04X nid 0x%02X",
6697b8ad118Sjmcneill 	    vendor, product, nid);
6707b8ad118Sjmcneill 
6717b8ad118Sjmcneill 	return UNCONF;
6727b8ad118Sjmcneill }
6737b8ad118Sjmcneill 
6747b8ad118Sjmcneill static void
hdaudio_attach_fg(struct hdaudio_function_group * fg,prop_array_t config)6757b8ad118Sjmcneill hdaudio_attach_fg(struct hdaudio_function_group *fg, prop_array_t config)
6767b8ad118Sjmcneill {
6777b8ad118Sjmcneill 	struct hdaudio_codec *co = fg->fg_codec;
6787b8ad118Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
6797b8ad118Sjmcneill 	prop_dictionary_t args = prop_dictionary_create();
6807b8ad118Sjmcneill 	uint64_t fgptr = (vaddr_t)fg;
6817b8ad118Sjmcneill 	int locs[1];
6827b8ad118Sjmcneill 
6837b8ad118Sjmcneill 	prop_dictionary_set_uint8(args, "function-group-type", fg->fg_type);
6847b8ad118Sjmcneill 	prop_dictionary_set_uint64(args, "function-group", fgptr);
6857b8ad118Sjmcneill 	prop_dictionary_set_uint8(args, "node-id", fg->fg_nid);
6867b8ad118Sjmcneill 	prop_dictionary_set_uint16(args, "vendor-id", fg->fg_vendor);
6877b8ad118Sjmcneill 	prop_dictionary_set_uint16(args, "product-id", fg->fg_product);
6887b8ad118Sjmcneill 	if (config)
6897b8ad118Sjmcneill 		prop_dictionary_set(args, "pin-config", config);
6907b8ad118Sjmcneill 
6917b8ad118Sjmcneill 	locs[0] = fg->fg_nid;
6927b8ad118Sjmcneill 
6933bee0c11Sthorpej 	fg->fg_device = config_found(sc->sc_dev, args, hdaudio_config_print,
694beecddb6Sthorpej 	    CFARGS(.submatch = config_stdsubmatch,
695beecddb6Sthorpej 		   .locators = locs));
6967b8ad118Sjmcneill 
6977b8ad118Sjmcneill 	prop_object_release(args);
6987b8ad118Sjmcneill }
6997b8ad118Sjmcneill 
7007b8ad118Sjmcneill static void
hdaudio_codec_attach(struct hdaudio_codec * co)7017b8ad118Sjmcneill hdaudio_codec_attach(struct hdaudio_codec *co)
7027b8ad118Sjmcneill {
703776dd837Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
7047b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
7057b8ad118Sjmcneill 	uint32_t vid, snc, fgrp;
7067b8ad118Sjmcneill 	int starting_node, num_nodes, nid;
7077b8ad118Sjmcneill 
7087b8ad118Sjmcneill 	if (co->co_valid == false)
7097b8ad118Sjmcneill 		return;
7107b8ad118Sjmcneill 
7117b8ad118Sjmcneill 	vid = hdaudio_command(co, 0, CORB_GET_PARAMETER, COP_VENDOR_ID);
7127b8ad118Sjmcneill 	snc = hdaudio_command(co, 0, CORB_GET_PARAMETER,
7137b8ad118Sjmcneill 	    COP_SUBORDINATE_NODE_COUNT);
7147b8ad118Sjmcneill 
7157b8ad118Sjmcneill 	/* make sure the vendor and product IDs are valid */
7167b8ad118Sjmcneill 	if (vid == 0xffffffff || vid == 0x00000000)
7177b8ad118Sjmcneill 		return;
7187b8ad118Sjmcneill 
7197b8ad118Sjmcneill #ifdef HDAUDIO_DEBUG
7207b8ad118Sjmcneill 	uint32_t rid = hdaudio_command(co, 0, CORB_GET_PARAMETER,
7217b8ad118Sjmcneill 	    COP_REVISION_ID);
7227b8ad118Sjmcneill 	hda_print(sc, "Codec%02X: %04X:%04X HDA %d.%d rev %d stepping %d\n",
7237b8ad118Sjmcneill 	    co->co_addr, vid >> 16, vid & 0xffff,
7247b8ad118Sjmcneill 	    (rid >> 20) & 0xf, (rid >> 16) & 0xf,
7257b8ad118Sjmcneill 	    (rid >> 8) & 0xff, rid & 0xff);
7267b8ad118Sjmcneill #endif
7277b8ad118Sjmcneill 	starting_node = (snc >> 16) & 0xff;
7287b8ad118Sjmcneill 	num_nodes = snc & 0xff;
7297b8ad118Sjmcneill 
730776dd837Sjmcneill 	/*
731776dd837Sjmcneill 	 * If the total number of nodes is 0, there's nothing we can do.
732776dd837Sjmcneill 	 * This shouldn't happen, so complain about it.
733776dd837Sjmcneill 	 */
734776dd837Sjmcneill 	if (num_nodes == 0) {
735776dd837Sjmcneill 		hda_error(sc, "Codec%02X: No subordinate nodes found (%08x)\n",
736776dd837Sjmcneill 		    co->co_addr, snc);
737776dd837Sjmcneill 		return;
738776dd837Sjmcneill 	}
739776dd837Sjmcneill 
7407b8ad118Sjmcneill 	co->co_nfg = num_nodes;
7417b8ad118Sjmcneill 	co->co_fg = kmem_zalloc(co->co_nfg * sizeof(*co->co_fg), KM_SLEEP);
7427b8ad118Sjmcneill 
7437b8ad118Sjmcneill 	for (nid = starting_node; nid < starting_node + num_nodes; nid++) {
7447b8ad118Sjmcneill 		fg = &co->co_fg[nid - starting_node];
7457b8ad118Sjmcneill 		fg->fg_codec = co;
7467b8ad118Sjmcneill 		fg->fg_nid = nid;
7477b8ad118Sjmcneill 		fg->fg_vendor = vid >> 16;
7487b8ad118Sjmcneill 		fg->fg_product = vid & 0xffff;
7497b8ad118Sjmcneill 
7507b8ad118Sjmcneill 		fgrp = hdaudio_command(co, nid, CORB_GET_PARAMETER,
7517b8ad118Sjmcneill 		    COP_FUNCTION_GROUP_TYPE);
7527b8ad118Sjmcneill 		switch (fgrp & 0xff) {
7537b8ad118Sjmcneill 		case 0x01:	/* Audio Function Group */
7547b8ad118Sjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_AFG;
7557b8ad118Sjmcneill 			break;
7567b8ad118Sjmcneill 		case 0x02:	/* Vendor Specific Modem Function Group */
7577b8ad118Sjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_VSM_FG;
7587b8ad118Sjmcneill 			break;
7597b8ad118Sjmcneill 		default:
7607b8ad118Sjmcneill 			/* Function group type not supported */
7617b8ad118Sjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_UNKNOWN;
7627b8ad118Sjmcneill 			break;
7637b8ad118Sjmcneill 		}
7647b8ad118Sjmcneill 		hdaudio_attach_fg(fg, NULL);
7657b8ad118Sjmcneill 	}
7667b8ad118Sjmcneill }
7677b8ad118Sjmcneill 
7687b8ad118Sjmcneill int
hdaudio_stream_tag(struct hdaudio_stream * st)7697b8ad118Sjmcneill hdaudio_stream_tag(struct hdaudio_stream *st)
7707b8ad118Sjmcneill {
7717b8ad118Sjmcneill 	int ret = 0;
7727b8ad118Sjmcneill 
7737b8ad118Sjmcneill 	switch (st->st_type) {
7747b8ad118Sjmcneill 	case HDAUDIO_STREAM_ISS:
7757b8ad118Sjmcneill 		ret = 1;
7767b8ad118Sjmcneill 		break;
7777b8ad118Sjmcneill 	case HDAUDIO_STREAM_OSS:
7787b8ad118Sjmcneill 		ret = 2;
7797b8ad118Sjmcneill 		break;
7807b8ad118Sjmcneill 	case HDAUDIO_STREAM_BSS:
7817b8ad118Sjmcneill 		ret = 3;
7827b8ad118Sjmcneill 		break;
7837b8ad118Sjmcneill 	}
7847b8ad118Sjmcneill 
7857b8ad118Sjmcneill 	return ret;
7867b8ad118Sjmcneill }
7877b8ad118Sjmcneill 
7887b8ad118Sjmcneill int
hdaudio_attach(device_t dev,struct hdaudio_softc * sc)7897b8ad118Sjmcneill hdaudio_attach(device_t dev, struct hdaudio_softc *sc)
7907b8ad118Sjmcneill {
7917b8ad118Sjmcneill 	int err, i;
7927b8ad118Sjmcneill 
7937b8ad118Sjmcneill 	KASSERT(sc->sc_memvalid == true);
7947b8ad118Sjmcneill 
7957b8ad118Sjmcneill 	sc->sc_dev = dev;
7967b8ad118Sjmcneill 	mutex_init(&sc->sc_corb_mtx, MUTEX_DEFAULT, IPL_AUDIO);
7977b8ad118Sjmcneill 	mutex_init(&sc->sc_stream_mtx, MUTEX_DEFAULT, IPL_AUDIO);
7987b8ad118Sjmcneill 
7997b8ad118Sjmcneill 	/*
8007b8ad118Sjmcneill 	 * Put the controller into a known state by entering and leaving
8017b8ad118Sjmcneill 	 * CRST as necessary.
8027b8ad118Sjmcneill 	 */
8037b8ad118Sjmcneill 	if ((err = hdaudio_reset(sc)) != 0)
8047b8ad118Sjmcneill 		goto fail;
8057b8ad118Sjmcneill 
8067b8ad118Sjmcneill 	/*
8077b8ad118Sjmcneill 	 * From the spec:
8087b8ad118Sjmcneill 	 *
8097b8ad118Sjmcneill 	 * Must wait 250us after reading CRST as a 1 before assuming that
8107b8ad118Sjmcneill 	 * codecs have all made status change requests and have been
8117b8ad118Sjmcneill 	 * registered by the controller.
8127b8ad118Sjmcneill 	 *
8137b8ad118Sjmcneill 	 * In reality, we need to wait longer than this.
8147b8ad118Sjmcneill 	 */
8157b8ad118Sjmcneill 	hda_delay(HDAUDIO_CODEC_DELAY);
816e1474beeSjmcneill 
817e1474beeSjmcneill 	/*
818e1474beeSjmcneill 	 * Read device capabilities
819e1474beeSjmcneill 	 */
820e1474beeSjmcneill 	hdaudio_init(sc);
821e1474beeSjmcneill 
822e1474beeSjmcneill 	/*
823e1474beeSjmcneill 	 * Detect codecs
824e1474beeSjmcneill 	 */
8257b8ad118Sjmcneill 	if (hdaudio_codec_probe(sc) == 0) {
8267b8ad118Sjmcneill 		hda_error(sc, "no codecs found\n");
8277b8ad118Sjmcneill 		err = ENODEV;
8287b8ad118Sjmcneill 		goto fail;
8297b8ad118Sjmcneill 	}
8307b8ad118Sjmcneill 
8317b8ad118Sjmcneill 	/*
8327b8ad118Sjmcneill 	 * Ensure that the device is in a known state
8337b8ad118Sjmcneill 	 */
8347b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
8357b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
8367b8ad118Sjmcneill 	    HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
8377b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
8387b8ad118Sjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
8397b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
8407b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
8417b8ad118Sjmcneill 
8427b8ad118Sjmcneill 	/*
8437b8ad118Sjmcneill 	 * Initialize the CORB. First negotiate a command buffer size,
8447b8ad118Sjmcneill 	 * then allocate and configure it.
8457b8ad118Sjmcneill 	 */
8467b8ad118Sjmcneill 	if ((err = hdaudio_corb_setsize(sc)) != 0)
8477b8ad118Sjmcneill 		goto fail;
8487b8ad118Sjmcneill 	if ((err = hdaudio_dma_alloc(sc, &sc->sc_corb, BUS_DMA_WRITE)) != 0)
8497b8ad118Sjmcneill 		goto fail;
8507b8ad118Sjmcneill 	if ((err = hdaudio_corb_config(sc)) != 0)
8517b8ad118Sjmcneill 		goto fail;
8527b8ad118Sjmcneill 
8537b8ad118Sjmcneill 	/*
8547b8ad118Sjmcneill 	 * Initialize the RIRB.
8557b8ad118Sjmcneill 	 */
8567b8ad118Sjmcneill 	if ((err = hdaudio_rirb_setsize(sc)) != 0)
8577b8ad118Sjmcneill 		goto fail;
8587b8ad118Sjmcneill 	if ((err = hdaudio_dma_alloc(sc, &sc->sc_rirb, BUS_DMA_READ)) != 0)
8597b8ad118Sjmcneill 		goto fail;
8607b8ad118Sjmcneill 	if ((err = hdaudio_rirb_config(sc)) != 0)
8617b8ad118Sjmcneill 		goto fail;
8627b8ad118Sjmcneill 
8637b8ad118Sjmcneill 	/*
8647b8ad118Sjmcneill 	 * Start the CORB and RIRB
8657b8ad118Sjmcneill 	 */
8667b8ad118Sjmcneill 	if ((err = hdaudio_corb_start(sc)) != 0)
8677b8ad118Sjmcneill 		goto fail;
8687b8ad118Sjmcneill 	if ((err = hdaudio_rirb_start(sc)) != 0)
8697b8ad118Sjmcneill 		goto fail;
8707b8ad118Sjmcneill 
8717b8ad118Sjmcneill 	/*
8727b8ad118Sjmcneill 	 * Identify and attach discovered codecs
8737b8ad118Sjmcneill 	 */
8747b8ad118Sjmcneill 	for (i = 0; i < HDAUDIO_MAX_CODECS; i++)
8757b8ad118Sjmcneill 		hdaudio_codec_attach(&sc->sc_codec[i]);
8767b8ad118Sjmcneill 
8777b8ad118Sjmcneill 	/*
8787b8ad118Sjmcneill 	 * Enable interrupts
8797b8ad118Sjmcneill 	 */
8807b8ad118Sjmcneill 	hdaudio_intr_enable(sc);
8817b8ad118Sjmcneill 
8827b8ad118Sjmcneill fail:
8837b8ad118Sjmcneill 	if (err)
8847b8ad118Sjmcneill 		hda_error(sc, "device driver failed to attach\n");
8857b8ad118Sjmcneill 	return err;
8867b8ad118Sjmcneill }
8877b8ad118Sjmcneill 
8887b8ad118Sjmcneill int
hdaudio_detach(struct hdaudio_softc * sc,int flags)8897b8ad118Sjmcneill hdaudio_detach(struct hdaudio_softc *sc, int flags)
8907b8ad118Sjmcneill {
8917b8ad118Sjmcneill 	int error;
8927b8ad118Sjmcneill 
8937b8ad118Sjmcneill 	/* Disable interrupts */
8947b8ad118Sjmcneill 	hdaudio_intr_disable(sc);
8957b8ad118Sjmcneill 
8967b8ad118Sjmcneill 	error = config_detach_children(sc->sc_dev, flags);
8977b8ad118Sjmcneill 	if (error != 0) {
8987b8ad118Sjmcneill 		hdaudio_intr_enable(sc);
8997b8ad118Sjmcneill 		return error;
9007b8ad118Sjmcneill 	}
9017b8ad118Sjmcneill 
9027b8ad118Sjmcneill 	mutex_destroy(&sc->sc_corb_mtx);
9037b8ad118Sjmcneill 	mutex_destroy(&sc->sc_stream_mtx);
9047b8ad118Sjmcneill 
9057b8ad118Sjmcneill 	hdaudio_dma_free(sc, &sc->sc_corb);
9067b8ad118Sjmcneill 	hdaudio_dma_free(sc, &sc->sc_rirb);
9077b8ad118Sjmcneill 
9087b8ad118Sjmcneill 	return 0;
9097b8ad118Sjmcneill }
9107b8ad118Sjmcneill 
9117b8ad118Sjmcneill bool
hdaudio_resume(struct hdaudio_softc * sc)9127b8ad118Sjmcneill hdaudio_resume(struct hdaudio_softc *sc)
9137b8ad118Sjmcneill {
9147b8ad118Sjmcneill 	if (hdaudio_reset(sc) != 0)
9157b8ad118Sjmcneill 		return false;
9167b8ad118Sjmcneill 
9177b8ad118Sjmcneill 	hda_delay(HDAUDIO_CODEC_DELAY);
9187b8ad118Sjmcneill 
9197b8ad118Sjmcneill 	/*
9207b8ad118Sjmcneill 	 * Ensure that the device is in a known state
9217b8ad118Sjmcneill 	 */
9227b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
9237b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
9247b8ad118Sjmcneill 	    HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
9257b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
9267b8ad118Sjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
9277b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
9287b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
9297b8ad118Sjmcneill 
9307b8ad118Sjmcneill 	if (hdaudio_corb_config(sc) != 0)
9317b8ad118Sjmcneill 		return false;
9327b8ad118Sjmcneill 	if (hdaudio_rirb_config(sc) != 0)
9337b8ad118Sjmcneill 		return false;
9347b8ad118Sjmcneill 	if (hdaudio_corb_start(sc) != 0)
9357b8ad118Sjmcneill 		return false;
9367b8ad118Sjmcneill 	if (hdaudio_rirb_start(sc) != 0)
9377b8ad118Sjmcneill 		return false;
9387b8ad118Sjmcneill 
9397b8ad118Sjmcneill 	hdaudio_intr_enable(sc);
9407b8ad118Sjmcneill 
9417b8ad118Sjmcneill 	return true;
9427b8ad118Sjmcneill }
9437b8ad118Sjmcneill 
9447b8ad118Sjmcneill int
hdaudio_rescan(struct hdaudio_softc * sc,const char * ifattr,const int * locs)9457b8ad118Sjmcneill hdaudio_rescan(struct hdaudio_softc *sc, const char *ifattr, const int *locs)
9467b8ad118Sjmcneill {
9477b8ad118Sjmcneill 	struct hdaudio_codec *co;
9487b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
9497b8ad118Sjmcneill 	unsigned int codec;
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
hdaudio_childdet(struct hdaudio_softc * sc,device_t child)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
hdaudio_intr(struct hdaudio_softc * sc)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 *
hdaudio_stream_establish(struct hdaudio_softc * sc,enum hdaudio_stream_type type,int (* intr)(struct hdaudio_stream *),void * cookie)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
hdaudio_stream_disestablish(struct hdaudio_stream * st)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 /*
1082*778f5600Sandvar  * Convert most of audio_params_t to stream fmt descriptor; noticeably 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
hdaudio_stream_param(struct hdaudio_stream * st,const audio_params_t * param)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
hdaudio_stream_reset(struct hdaudio_stream * st)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 
11817b8ad118Sjmcneill 	ctl0 &= ~HDAUDIO_CTL_SRST;
11827b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
11837b8ad118Sjmcneill 
11847b8ad118Sjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
11857b8ad118Sjmcneill 	do {
11867b8ad118Sjmcneill 		ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
11877b8ad118Sjmcneill 		if (!(ctl0 & HDAUDIO_CTL_SRST))
11887b8ad118Sjmcneill 			break;
11897b8ad118Sjmcneill 		hda_delay(10);
11907b8ad118Sjmcneill 	} while (--retry > 0);
11917b8ad118Sjmcneill 	if (retry == 0) {
11927b8ad118Sjmcneill 		hda_error(sc, "timeout leaving stream reset state\n");
11937b8ad118Sjmcneill 		return;
11947b8ad118Sjmcneill 	}
11957b8ad118Sjmcneill }
11967b8ad118Sjmcneill 
11977b8ad118Sjmcneill void
hdaudio_stream_start(struct hdaudio_stream * st,int blksize,bus_size_t dmasize,const audio_params_t * params)11987b8ad118Sjmcneill hdaudio_stream_start(struct hdaudio_stream *st, int blksize,
11997b8ad118Sjmcneill     bus_size_t dmasize, const audio_params_t *params)
12007b8ad118Sjmcneill {
12017b8ad118Sjmcneill 	struct hdaudio_softc *sc = st->st_host;
12027b8ad118Sjmcneill 	struct hdaudio_bdl_entry *bdl;
12037b8ad118Sjmcneill 	uint64_t dmaaddr;
12047b8ad118Sjmcneill 	uint32_t intctl;
12057b8ad118Sjmcneill 	uint16_t fmt;
12067b8ad118Sjmcneill 	uint8_t ctl0, ctl2;
12077b8ad118Sjmcneill 	int cnt, snum = st->st_shift;
12087b8ad118Sjmcneill 
12097b8ad118Sjmcneill 	KASSERT(sc->sc_stream_mask & (1 << st->st_shift));
12107b8ad118Sjmcneill 	KASSERT(st->st_data.dma_valid == true);
12117b8ad118Sjmcneill 	KASSERT(st->st_bdl.dma_valid == true);
12127b8ad118Sjmcneill 
12137b8ad118Sjmcneill 	hdaudio_stream_stop(st);
12147b8ad118Sjmcneill 	hdaudio_stream_reset(st);
12157b8ad118Sjmcneill 
12167b8ad118Sjmcneill 	/*
12177b8ad118Sjmcneill 	 * Configure buffer descriptor list
12187b8ad118Sjmcneill 	 */
12197b8ad118Sjmcneill 	dmaaddr = DMA_DMAADDR(&st->st_data);
12207b8ad118Sjmcneill 	bdl = DMA_KERNADDR(&st->st_bdl);
12217b8ad118Sjmcneill 	for (cnt = 0; cnt < HDAUDIO_BDL_MAX; cnt++) {
12227b8ad118Sjmcneill 		bdl[cnt].address_lo = (uint32_t)dmaaddr;
12237b8ad118Sjmcneill 		bdl[cnt].address_hi = dmaaddr >> 32;
12247b8ad118Sjmcneill 		bdl[cnt].length = blksize;
12257b8ad118Sjmcneill 		bdl[cnt].flags = HDAUDIO_BDL_ENTRY_IOC;
12267b8ad118Sjmcneill 		dmaaddr += blksize;
12277b8ad118Sjmcneill 		if (dmaaddr >= DMA_DMAADDR(&st->st_data) + dmasize) {
12287b8ad118Sjmcneill 			cnt++;
12297b8ad118Sjmcneill 			break;
12307b8ad118Sjmcneill 		}
12317b8ad118Sjmcneill 	}
12327b8ad118Sjmcneill 
12337b8ad118Sjmcneill 	/*
12347b8ad118Sjmcneill 	 * Program buffer descriptor list
12357b8ad118Sjmcneill 	 */
12367b8ad118Sjmcneill 	dmaaddr = DMA_DMAADDR(&st->st_bdl);
12377b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_SD_BDPL(snum), (uint32_t)dmaaddr);
12387b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_SD_BDPU(snum), (uint32_t)(dmaaddr >> 32));
12397b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_SD_LVI(snum), (cnt - 1) & 0xff);
12407b8ad118Sjmcneill 
12417b8ad118Sjmcneill 	/*
12427b8ad118Sjmcneill 	 * Program cyclic buffer length
12437b8ad118Sjmcneill 	 */
12447b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_SD_CBL(snum), dmasize);
12457b8ad118Sjmcneill 
12467b8ad118Sjmcneill 	/*
12477b8ad118Sjmcneill 	 * Program stream number (tag). Although controller hardware is
12487b8ad118Sjmcneill 	 * capable of transmitting any stream number (0-15), by convention
12497b8ad118Sjmcneill 	 * stream 0 is reserved as unused by software, so that converters
12507b8ad118Sjmcneill 	 * whose stream numbers have been reset to 0 do not unintentionally
12517b8ad118Sjmcneill 	 * decode data not intended for them.
12527b8ad118Sjmcneill 	 */
12537b8ad118Sjmcneill 	ctl2 = hda_read1(sc, HDAUDIO_SD_CTL2(snum));
12547b8ad118Sjmcneill 	ctl2 &= ~0xf0;
12557b8ad118Sjmcneill 	ctl2 |= hdaudio_stream_tag(st) << 4;
12567b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL2(snum), ctl2);
12577b8ad118Sjmcneill 
12587b8ad118Sjmcneill 	/*
12597b8ad118Sjmcneill 	 * Program stream format
12607b8ad118Sjmcneill 	 */
12617b8ad118Sjmcneill 	fmt = hdaudio_stream_param(st, params) |
12627b8ad118Sjmcneill 	    HDAUDIO_FMT_CHAN(params->channels);
12637b8ad118Sjmcneill 	hda_write2(sc, HDAUDIO_SD_FMT(snum), fmt);
12647b8ad118Sjmcneill 
12657b8ad118Sjmcneill 	/*
12667b8ad118Sjmcneill 	 * Switch on interrupts for this stream
12677b8ad118Sjmcneill 	 */
12687b8ad118Sjmcneill 	intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
12697b8ad118Sjmcneill 	intctl |= (1 << st->st_shift);
12707b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
12717b8ad118Sjmcneill 
12727b8ad118Sjmcneill 	/*
12737b8ad118Sjmcneill 	 * Start running the stream
12747b8ad118Sjmcneill 	 */
12757b8ad118Sjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
12767b8ad118Sjmcneill 	ctl0 |= HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
12777b8ad118Sjmcneill 	    HDAUDIO_CTL_RUN;
12787b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
12797b8ad118Sjmcneill }
12807b8ad118Sjmcneill 
12817b8ad118Sjmcneill void
hdaudio_stream_stop(struct hdaudio_stream * st)12827b8ad118Sjmcneill hdaudio_stream_stop(struct hdaudio_stream *st)
12837b8ad118Sjmcneill {
12847b8ad118Sjmcneill 	struct hdaudio_softc *sc = st->st_host;
12857b8ad118Sjmcneill 	uint32_t intctl;
12867b8ad118Sjmcneill 	uint8_t ctl0;
12877b8ad118Sjmcneill 	int snum = st->st_shift;
12887b8ad118Sjmcneill 
12897b8ad118Sjmcneill 	/*
12907b8ad118Sjmcneill 	 * Stop running the stream
12917b8ad118Sjmcneill 	 */
12927b8ad118Sjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
12937b8ad118Sjmcneill 	ctl0 &= ~(HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
12947b8ad118Sjmcneill 	    HDAUDIO_CTL_RUN);
12957b8ad118Sjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
12967b8ad118Sjmcneill 
12977b8ad118Sjmcneill 	/*
12987b8ad118Sjmcneill 	 * Switch off interrupts for this stream
12997b8ad118Sjmcneill 	 */
13007b8ad118Sjmcneill 	intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
13017b8ad118Sjmcneill 	intctl &= ~(1 << st->st_shift);
13027b8ad118Sjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
13037b8ad118Sjmcneill }
13047b8ad118Sjmcneill 
13057b8ad118Sjmcneill /*
13067b8ad118Sjmcneill  * /dev/hdaudioN interface
13077b8ad118Sjmcneill  */
13087b8ad118Sjmcneill 
13097b8ad118Sjmcneill static const char *
hdaudioioctl_fgrp_to_cstr(enum function_group_type type)13107b8ad118Sjmcneill hdaudioioctl_fgrp_to_cstr(enum function_group_type type)
13117b8ad118Sjmcneill {
13127b8ad118Sjmcneill 	switch (type) {
13137b8ad118Sjmcneill 	case HDAUDIO_GROUP_TYPE_AFG:
13147b8ad118Sjmcneill 		return "afg";
13157b8ad118Sjmcneill 	case HDAUDIO_GROUP_TYPE_VSM_FG:
13167b8ad118Sjmcneill 		return "vsmfg";
13177b8ad118Sjmcneill 	default:
13187b8ad118Sjmcneill 		return "unknown";
13197b8ad118Sjmcneill 	}
13207b8ad118Sjmcneill }
13217b8ad118Sjmcneill 
13227b8ad118Sjmcneill static struct hdaudio_function_group *
hdaudioioctl_fgrp_lookup(struct hdaudio_softc * sc,int codecid,int nid)13237b8ad118Sjmcneill hdaudioioctl_fgrp_lookup(struct hdaudio_softc *sc, int codecid, int nid)
13247b8ad118Sjmcneill {
13257b8ad118Sjmcneill 	struct hdaudio_codec *co;
13267b8ad118Sjmcneill 	struct hdaudio_function_group *fg = NULL;
13277b8ad118Sjmcneill 	int i;
13287b8ad118Sjmcneill 
13297b8ad118Sjmcneill 	if (codecid < 0 || codecid >= HDAUDIO_MAX_CODECS)
13307b8ad118Sjmcneill 		return NULL;
13317b8ad118Sjmcneill 	co = &sc->sc_codec[codecid];
13327b8ad118Sjmcneill 	if (co->co_valid == false)
13337b8ad118Sjmcneill 		return NULL;
13347b8ad118Sjmcneill 
13357b8ad118Sjmcneill 	for (i = 0; i < co->co_nfg; i++)
13367b8ad118Sjmcneill 		if (co->co_fg[i].fg_nid == nid) {
13377b8ad118Sjmcneill 			fg = &co->co_fg[i];
13387b8ad118Sjmcneill 			break;
13397b8ad118Sjmcneill 		}
13407b8ad118Sjmcneill 
13417b8ad118Sjmcneill 	return fg;
13427b8ad118Sjmcneill }
13437b8ad118Sjmcneill 
13447b8ad118Sjmcneill static int
hdaudioioctl_fgrp_info(struct hdaudio_softc * sc,prop_dictionary_t request,prop_dictionary_t response)13457b8ad118Sjmcneill hdaudioioctl_fgrp_info(struct hdaudio_softc *sc, prop_dictionary_t request,
13467b8ad118Sjmcneill     prop_dictionary_t response)
13477b8ad118Sjmcneill {
13487b8ad118Sjmcneill 	struct hdaudio_codec *co;
13497b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
13507b8ad118Sjmcneill 	prop_array_t array;
13517b8ad118Sjmcneill 	prop_dictionary_t dict;
13527b8ad118Sjmcneill 	int codecid, fgid;
13537b8ad118Sjmcneill 
13547b8ad118Sjmcneill 	array = prop_array_create();
13557b8ad118Sjmcneill 	if (array == NULL)
13567b8ad118Sjmcneill 		return ENOMEM;
13577b8ad118Sjmcneill 
13587b8ad118Sjmcneill 	for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++) {
13597b8ad118Sjmcneill 		co = &sc->sc_codec[codecid];
13607b8ad118Sjmcneill 		if (co->co_valid == false)
13617b8ad118Sjmcneill 			continue;
13627b8ad118Sjmcneill 		for (fgid = 0; fgid < co->co_nfg; fgid++) {
13637b8ad118Sjmcneill 			fg = &co->co_fg[fgid];
13647b8ad118Sjmcneill 			dict = prop_dictionary_create();
13657b8ad118Sjmcneill 			if (dict == NULL)
13667b8ad118Sjmcneill 				return ENOMEM;
1367c4fd8ae4Sthorpej 			prop_dictionary_set_string_nocopy(dict,
13687b8ad118Sjmcneill 			    "type", hdaudioioctl_fgrp_to_cstr(fg->fg_type));
13697b8ad118Sjmcneill 			prop_dictionary_set_int16(dict, "nid", fg->fg_nid);
13707b8ad118Sjmcneill 			prop_dictionary_set_int16(dict, "codecid", codecid);
13717b8ad118Sjmcneill 			prop_dictionary_set_uint16(dict, "vendor-id",
13727b8ad118Sjmcneill 			    fg->fg_vendor);
13737b8ad118Sjmcneill 			prop_dictionary_set_uint16(dict, "product-id",
13747b8ad118Sjmcneill 			    fg->fg_product);
13757b8ad118Sjmcneill 			prop_dictionary_set_uint32(dict, "subsystem-id",
13767b8ad118Sjmcneill 			    sc->sc_subsystem);
13777b8ad118Sjmcneill 			if (fg->fg_device)
1378c4fd8ae4Sthorpej 				prop_dictionary_set_string(dict, "device",
13797b8ad118Sjmcneill 				    device_xname(fg->fg_device));
13807b8ad118Sjmcneill 			else
1381c4fd8ae4Sthorpej 				prop_dictionary_set_string_nocopy(dict,
13827b8ad118Sjmcneill 				    "device", "<none>");
13837b8ad118Sjmcneill 			prop_array_add(array, dict);
13847b8ad118Sjmcneill 		}
13857b8ad118Sjmcneill 	}
13867b8ad118Sjmcneill 
13877b8ad118Sjmcneill 	prop_dictionary_set(response, "function-group-info", array);
13887b8ad118Sjmcneill 	return 0;
13897b8ad118Sjmcneill }
13907b8ad118Sjmcneill 
13917b8ad118Sjmcneill static int
hdaudioioctl_fgrp_getconfig(struct hdaudio_softc * sc,prop_dictionary_t request,prop_dictionary_t response)13927b8ad118Sjmcneill hdaudioioctl_fgrp_getconfig(struct hdaudio_softc *sc,
13937b8ad118Sjmcneill     prop_dictionary_t request, prop_dictionary_t response)
13947b8ad118Sjmcneill {
13957b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
13967b8ad118Sjmcneill 	prop_dictionary_t dict;
13977b8ad118Sjmcneill 	prop_array_t array;
13987b8ad118Sjmcneill 	uint32_t nodecnt, wcap, config;
13997b8ad118Sjmcneill 	int16_t codecid, nid, i;
14007b8ad118Sjmcneill 	int startnode, endnode;
14017b8ad118Sjmcneill 
14027b8ad118Sjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14037b8ad118Sjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14047b8ad118Sjmcneill 		return EINVAL;
14057b8ad118Sjmcneill 
14067b8ad118Sjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14077b8ad118Sjmcneill 	if (fg == NULL)
14087b8ad118Sjmcneill 		return ENODEV;
14097b8ad118Sjmcneill 
14107b8ad118Sjmcneill 	array = prop_array_create();
14117b8ad118Sjmcneill 	if (array == NULL)
14127b8ad118Sjmcneill 		return ENOMEM;
14137b8ad118Sjmcneill 
14147b8ad118Sjmcneill 	nodecnt = hdaudio_command(fg->fg_codec, fg->fg_nid,
14157b8ad118Sjmcneill 	    CORB_GET_PARAMETER, COP_SUBORDINATE_NODE_COUNT);
14167b8ad118Sjmcneill 	startnode = COP_NODECNT_STARTNODE(nodecnt);
14177b8ad118Sjmcneill 	endnode = startnode + COP_NODECNT_NUMNODES(nodecnt);
14187b8ad118Sjmcneill 
14197b8ad118Sjmcneill 	for (i = startnode; i < endnode; i++) {
14207b8ad118Sjmcneill 		wcap = hdaudio_command(fg->fg_codec, i,
14217b8ad118Sjmcneill 		    CORB_GET_PARAMETER, COP_AUDIO_WIDGET_CAPABILITIES);
14227b8ad118Sjmcneill 		if (COP_AWCAP_TYPE(wcap) != COP_AWCAP_TYPE_PIN_COMPLEX)
14237b8ad118Sjmcneill 			continue;
14247b8ad118Sjmcneill 		config = hdaudio_command(fg->fg_codec, i,
14257b8ad118Sjmcneill 		    CORB_GET_CONFIGURATION_DEFAULT, 0);
14267b8ad118Sjmcneill 		dict = prop_dictionary_create();
14277b8ad118Sjmcneill 		if (dict == NULL)
14287b8ad118Sjmcneill 			return ENOMEM;
14297b8ad118Sjmcneill 		prop_dictionary_set_int16(dict, "nid", i);
14307b8ad118Sjmcneill 		prop_dictionary_set_uint32(dict, "config", config);
14317b8ad118Sjmcneill 		prop_array_add(array, dict);
14327b8ad118Sjmcneill 	}
14337b8ad118Sjmcneill 
14347b8ad118Sjmcneill 	prop_dictionary_set(response, "pin-config", array);
14357b8ad118Sjmcneill 
14367b8ad118Sjmcneill 	return 0;
14377b8ad118Sjmcneill }
14387b8ad118Sjmcneill 
14397b8ad118Sjmcneill static int
hdaudioioctl_fgrp_setconfig(struct hdaudio_softc * sc,prop_dictionary_t request,prop_dictionary_t response)14407b8ad118Sjmcneill hdaudioioctl_fgrp_setconfig(struct hdaudio_softc *sc,
14417b8ad118Sjmcneill     prop_dictionary_t request, prop_dictionary_t response)
14427b8ad118Sjmcneill {
14437b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
14447b8ad118Sjmcneill 	prop_array_t config;
14457b8ad118Sjmcneill 	int16_t codecid, nid;
14467b8ad118Sjmcneill 	int err;
14477b8ad118Sjmcneill 
14487b8ad118Sjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14497b8ad118Sjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14507b8ad118Sjmcneill 		return EINVAL;
14517b8ad118Sjmcneill 
14527b8ad118Sjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14537b8ad118Sjmcneill 	if (fg == NULL)
14547b8ad118Sjmcneill 		return ENODEV;
14557b8ad118Sjmcneill 
14567b8ad118Sjmcneill 	if (fg->fg_device) {
14577b8ad118Sjmcneill 		err = config_detach(fg->fg_device, 0);
14587b8ad118Sjmcneill 		if (err)
14597b8ad118Sjmcneill 			return err;
14607b8ad118Sjmcneill 		fg->fg_device = NULL;
14617b8ad118Sjmcneill 	}
14627b8ad118Sjmcneill 
14637b8ad118Sjmcneill 	/* "pin-config" may be NULL, this means "use BIOS configuration" */
14647b8ad118Sjmcneill 	config = prop_dictionary_get(request, "pin-config");
14657b8ad118Sjmcneill 	if (config && prop_object_type(config) != PROP_TYPE_ARRAY) {
14667b8ad118Sjmcneill 		prop_object_release(config);
14677b8ad118Sjmcneill 		return EINVAL;
14687b8ad118Sjmcneill 	}
14697b8ad118Sjmcneill 	hdaudio_attach_fg(fg, config);
14707b8ad118Sjmcneill 	if (config)
14717b8ad118Sjmcneill 		prop_object_release(config);
14727b8ad118Sjmcneill 
14737b8ad118Sjmcneill 	return 0;
14747b8ad118Sjmcneill }
14757b8ad118Sjmcneill 
14767b8ad118Sjmcneill static int
hdaudio_dispatch_fgrp_ioctl(struct hdaudio_softc * sc,u_long cmd,prop_dictionary_t request,prop_dictionary_t response)14777b8ad118Sjmcneill hdaudio_dispatch_fgrp_ioctl(struct hdaudio_softc *sc, u_long cmd,
14787b8ad118Sjmcneill     prop_dictionary_t request, prop_dictionary_t response)
14797b8ad118Sjmcneill {
14807b8ad118Sjmcneill 	struct hdaudio_function_group *fg;
14817b8ad118Sjmcneill 	int (*infocb)(void *, prop_dictionary_t, prop_dictionary_t);
14827b8ad118Sjmcneill 	prop_dictionary_t fgrp_dict;
14837b8ad118Sjmcneill 	uint64_t info_fn;
14847b8ad118Sjmcneill 	int16_t codecid, nid;
14857b8ad118Sjmcneill 	void *fgrp_sc;
14867b8ad118Sjmcneill 	bool rv;
14877b8ad118Sjmcneill 	int err;
14887b8ad118Sjmcneill 
14897b8ad118Sjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14907b8ad118Sjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14917b8ad118Sjmcneill 		return EINVAL;
14927b8ad118Sjmcneill 
14937b8ad118Sjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14947b8ad118Sjmcneill 	if (fg == NULL)
14957b8ad118Sjmcneill 		return ENODEV;
14967b8ad118Sjmcneill 	if (fg->fg_device == NULL)
14977b8ad118Sjmcneill 		return ENXIO;
14987b8ad118Sjmcneill 	fgrp_sc = device_private(fg->fg_device);
14997b8ad118Sjmcneill 	fgrp_dict = device_properties(fg->fg_device);
15007b8ad118Sjmcneill 
15017b8ad118Sjmcneill 	switch (fg->fg_type) {
15027b8ad118Sjmcneill 	case HDAUDIO_GROUP_TYPE_AFG:
15037b8ad118Sjmcneill 		switch (cmd) {
15047b8ad118Sjmcneill 		case HDAUDIO_FGRP_CODEC_INFO:
15057b8ad118Sjmcneill 			rv = prop_dictionary_get_uint64(fgrp_dict,
15067b8ad118Sjmcneill 			    "codecinfo-callback", &info_fn);
15077b8ad118Sjmcneill 			if (!rv)
15087b8ad118Sjmcneill 				return ENXIO;
15097b8ad118Sjmcneill 			infocb = (void *)(uintptr_t)info_fn;
15107b8ad118Sjmcneill 			err = infocb(fgrp_sc, request, response);
15117b8ad118Sjmcneill 			break;
15127b8ad118Sjmcneill 		case HDAUDIO_FGRP_WIDGET_INFO:
15137b8ad118Sjmcneill 			rv = prop_dictionary_get_uint64(fgrp_dict,
15147b8ad118Sjmcneill 			    "widgetinfo-callback", &info_fn);
15157b8ad118Sjmcneill 			if (!rv)
15167b8ad118Sjmcneill 				return ENXIO;
15177b8ad118Sjmcneill 			infocb = (void *)(uintptr_t)info_fn;
15187b8ad118Sjmcneill 			err = infocb(fgrp_sc, request, response);
15197b8ad118Sjmcneill 			break;
15207b8ad118Sjmcneill 		default:
15217b8ad118Sjmcneill 			err = EINVAL;
15227b8ad118Sjmcneill 			break;
15237b8ad118Sjmcneill 		}
15247b8ad118Sjmcneill 		break;
15257b8ad118Sjmcneill 
15267b8ad118Sjmcneill 	default:
15277b8ad118Sjmcneill 		err = EINVAL;
15287b8ad118Sjmcneill 		break;
15297b8ad118Sjmcneill 	}
15307b8ad118Sjmcneill 	return err;
15317b8ad118Sjmcneill }
15327b8ad118Sjmcneill 
15337b8ad118Sjmcneill int
hdaudioopen(dev_t dev,int flag,int mode,struct lwp * l)15347b8ad118Sjmcneill hdaudioopen(dev_t dev, int flag, int mode, struct lwp *l)
15357b8ad118Sjmcneill {
15367b8ad118Sjmcneill 	device_t self;
15377b8ad118Sjmcneill 
15387b8ad118Sjmcneill 	self = device_lookup(&hdaudio_cd, HDAUDIOUNIT(dev));
15397b8ad118Sjmcneill 	if (self == NULL)
15407b8ad118Sjmcneill 		return ENXIO;
15417b8ad118Sjmcneill 
15427b8ad118Sjmcneill 	return 0;
15437b8ad118Sjmcneill }
15447b8ad118Sjmcneill 
15457b8ad118Sjmcneill int
hdaudioclose(dev_t dev,int flag,int mode,struct lwp * l)15467b8ad118Sjmcneill hdaudioclose(dev_t dev, int flag, int mode, struct lwp *l)
15477b8ad118Sjmcneill {
15487b8ad118Sjmcneill 	return 0;
15497b8ad118Sjmcneill }
15507b8ad118Sjmcneill 
15517b8ad118Sjmcneill int
hdaudioioctl(dev_t dev,u_long cmd,void * addr,int flag,struct lwp * l)15527b8ad118Sjmcneill hdaudioioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
15537b8ad118Sjmcneill {
15547b8ad118Sjmcneill 	struct hdaudio_softc *sc;
15557b8ad118Sjmcneill 	struct plistref *pref = addr;
15567b8ad118Sjmcneill 	prop_dictionary_t request, response;
15577b8ad118Sjmcneill 	int err;
15587b8ad118Sjmcneill 
15597b8ad118Sjmcneill 	sc = device_lookup_private(&hdaudio_cd, HDAUDIOUNIT(dev));
15607b8ad118Sjmcneill 	if (sc == NULL)
15617b8ad118Sjmcneill 		return ENXIO;
15627b8ad118Sjmcneill 
15637b8ad118Sjmcneill 	response = prop_dictionary_create();
15647b8ad118Sjmcneill 	if (response == NULL)
15657b8ad118Sjmcneill 		return ENOMEM;
15667b8ad118Sjmcneill 
15677b8ad118Sjmcneill 	err = prop_dictionary_copyin_ioctl(pref, cmd, &request);
15687b8ad118Sjmcneill 	if (err) {
15697b8ad118Sjmcneill 		prop_object_release(response);
15707b8ad118Sjmcneill 		return err;
15717b8ad118Sjmcneill 	}
15727b8ad118Sjmcneill 
15737b8ad118Sjmcneill 	switch (cmd) {
15747b8ad118Sjmcneill 	case HDAUDIO_FGRP_INFO:
15757b8ad118Sjmcneill 		err = hdaudioioctl_fgrp_info(sc, request, response);
15767b8ad118Sjmcneill 		break;
15777b8ad118Sjmcneill 	case HDAUDIO_FGRP_GETCONFIG:
15787b8ad118Sjmcneill 		err = hdaudioioctl_fgrp_getconfig(sc, request, response);
15797b8ad118Sjmcneill 		break;
15807b8ad118Sjmcneill 	case HDAUDIO_FGRP_SETCONFIG:
15817b8ad118Sjmcneill 		err = hdaudioioctl_fgrp_setconfig(sc, request, response);
15827b8ad118Sjmcneill 		break;
15837b8ad118Sjmcneill 	case HDAUDIO_FGRP_CODEC_INFO:
15847b8ad118Sjmcneill 	case HDAUDIO_FGRP_WIDGET_INFO:
15857b8ad118Sjmcneill 		err = hdaudio_dispatch_fgrp_ioctl(sc, cmd, request, response);
15867b8ad118Sjmcneill 		break;
15877b8ad118Sjmcneill 	default:
15887b8ad118Sjmcneill 		err = EINVAL;
15897b8ad118Sjmcneill 		break;
15907b8ad118Sjmcneill 	}
15917b8ad118Sjmcneill 
15927b8ad118Sjmcneill 	if (!err)
15937b8ad118Sjmcneill 		err = prop_dictionary_copyout_ioctl(pref, cmd, response);
15947b8ad118Sjmcneill 
15957b8ad118Sjmcneill 	if (response)
15967b8ad118Sjmcneill 		prop_object_release(response);
15977b8ad118Sjmcneill 	prop_object_release(request);
15987b8ad118Sjmcneill 	return err;
15997b8ad118Sjmcneill }
16007b8ad118Sjmcneill 
16015b6ebe47Spgoyette MODULE(MODULE_CLASS_DRIVER, hdaudio, "audio");
16025b6ebe47Spgoyette #ifdef _MODULE
16035b6ebe47Spgoyette static const struct cfiattrdata hdaudiobuscf_iattrdata = {
16045b6ebe47Spgoyette         "hdaudiobus", 1, {
16055b6ebe47Spgoyette                 { "nid", "-1", -1 },
16065b6ebe47Spgoyette         }
16075b6ebe47Spgoyette };
16085b6ebe47Spgoyette static const struct cfiattrdata * const hdaudio_attrs[] = {
16095b6ebe47Spgoyette 	&hdaudiobuscf_iattrdata, NULL
16105b6ebe47Spgoyette };
16115b6ebe47Spgoyette CFDRIVER_DECL(hdaudio, DV_AUDIODEV, hdaudio_attrs);
16125b6ebe47Spgoyette #endif
16137b8ad118Sjmcneill 
16147b8ad118Sjmcneill static int
hdaudio_modcmd(modcmd_t cmd,void * opaque)16157b8ad118Sjmcneill hdaudio_modcmd(modcmd_t cmd, void *opaque)
16167b8ad118Sjmcneill {
16177b8ad118Sjmcneill 	int error = 0;
16187b8ad118Sjmcneill #ifdef _MODULE
16197b8ad118Sjmcneill 	int bmaj = -1, cmaj = -1;
16207b8ad118Sjmcneill #endif
16217b8ad118Sjmcneill 
16227b8ad118Sjmcneill 	switch (cmd) {
16237b8ad118Sjmcneill 	case MODULE_CMD_INIT:
16247b8ad118Sjmcneill #ifdef _MODULE
16257b8ad118Sjmcneill 		error = devsw_attach("hdaudio", NULL, &bmaj,
16267b8ad118Sjmcneill 		    &hdaudio_cdevsw, &cmaj);
16275b6ebe47Spgoyette 		if (error)
16285b6ebe47Spgoyette 			break;
16295b6ebe47Spgoyette 		error = config_cfdriver_attach(&hdaudio_cd);
16305b6ebe47Spgoyette 		if (error)
16317b8ad118Sjmcneill 			devsw_detach(NULL, &hdaudio_cdevsw);
16327b8ad118Sjmcneill #endif
16335b6ebe47Spgoyette 		break;
16345b6ebe47Spgoyette 	case MODULE_CMD_FINI:
16355b6ebe47Spgoyette #ifdef _MODULE
16365b6ebe47Spgoyette 		error = config_cfdriver_detach(&hdaudio_cd);
16375b6ebe47Spgoyette 		if (error)
16385b6ebe47Spgoyette 			break;
1639ad9f1588Sriastradh 		devsw_detach(NULL, &hdaudio_cdevsw);
16405b6ebe47Spgoyette #endif
16415b6ebe47Spgoyette 		break;
16425b6ebe47Spgoyette 	default:
16435b6ebe47Spgoyette 		error = ENOTTY;
16445b6ebe47Spgoyette 		break;
16455b6ebe47Spgoyette 	}
16465b6ebe47Spgoyette 	return error;
16477b8ad118Sjmcneill }
16487b8ad118Sjmcneill 
16497b8ad118Sjmcneill DEV_VERBOSE_DEFINE(hdaudio);
1650