xref: /netbsd-src/sys/dev/hdaudio/hdaudio.c (revision 2e9df72ec394f443f02e2e3b5922bfdfe348dab9)
1*2e9df72eSandvar /* $NetBSD: hdaudio.c,v 1.18 2022/04/07 19:33:37 andvar Exp $ */
28a9ff04bSjmcneill 
38a9ff04bSjmcneill /*
48a9ff04bSjmcneill  * Copyright (c) 2009 Precedence Technologies Ltd <support@precedence.co.uk>
58a9ff04bSjmcneill  * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
68a9ff04bSjmcneill  * All rights reserved.
78a9ff04bSjmcneill  *
88a9ff04bSjmcneill  * This code is derived from software contributed to The NetBSD Foundation
98a9ff04bSjmcneill  * by Precedence Technologies Ltd
108a9ff04bSjmcneill  *
118a9ff04bSjmcneill  * Redistribution and use in source and binary forms, with or without
128a9ff04bSjmcneill  * modification, are permitted provided that the following conditions
138a9ff04bSjmcneill  * are met:
148a9ff04bSjmcneill  * 1. Redistributions of source code must retain the above copyright
158a9ff04bSjmcneill  *    notice, this list of conditions and the following disclaimer.
168a9ff04bSjmcneill  * 2. The name of the author may not be used to endorse or promote products
178a9ff04bSjmcneill  *    derived from this software without specific prior written permission.
188a9ff04bSjmcneill  *
198a9ff04bSjmcneill  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
208a9ff04bSjmcneill  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
218a9ff04bSjmcneill  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
228a9ff04bSjmcneill  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
238a9ff04bSjmcneill  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
248a9ff04bSjmcneill  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
258a9ff04bSjmcneill  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
268a9ff04bSjmcneill  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
278a9ff04bSjmcneill  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
288a9ff04bSjmcneill  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
298a9ff04bSjmcneill  * SUCH DAMAGE.
308a9ff04bSjmcneill  */
318a9ff04bSjmcneill 
328a9ff04bSjmcneill #include <sys/cdefs.h>
33*2e9df72eSandvar __KERNEL_RCSID(0, "$NetBSD: hdaudio.c,v 1.18 2022/04/07 19:33:37 andvar Exp $");
348a9ff04bSjmcneill 
358a9ff04bSjmcneill #include <sys/types.h>
368a9ff04bSjmcneill #include <sys/param.h>
378a9ff04bSjmcneill #include <sys/systm.h>
388a9ff04bSjmcneill #include <sys/device.h>
398a9ff04bSjmcneill #include <sys/conf.h>
408a9ff04bSjmcneill #include <sys/bus.h>
418a9ff04bSjmcneill #include <sys/kmem.h>
428a9ff04bSjmcneill #include <sys/module.h>
438a9ff04bSjmcneill 
448a9ff04bSjmcneill #include "hdaudiovar.h"
458a9ff04bSjmcneill #include "hdaudioreg.h"
468a9ff04bSjmcneill #include "hdaudioio.h"
478a9ff04bSjmcneill #include "hdaudio_verbose.h"
4874007e33Spgoyette #include "hdaudiodevs.h"
498a9ff04bSjmcneill 
508a9ff04bSjmcneill /* #define	HDAUDIO_DEBUG */
518a9ff04bSjmcneill 
528a9ff04bSjmcneill #define	HDAUDIO_RESET_TIMEOUT	5000
538a9ff04bSjmcneill #define HDAUDIO_CORB_TIMEOUT	1000
548a9ff04bSjmcneill #define	HDAUDIO_RIRB_TIMEOUT	5000
558a9ff04bSjmcneill 
568a9ff04bSjmcneill #define	HDAUDIO_CODEC_DELAY	1000	/* spec calls for 250 */
578a9ff04bSjmcneill 
588a9ff04bSjmcneill dev_type_open(hdaudioopen);
598a9ff04bSjmcneill dev_type_close(hdaudioclose);
608a9ff04bSjmcneill dev_type_ioctl(hdaudioioctl);
618a9ff04bSjmcneill 
628a9ff04bSjmcneill const struct cdevsw hdaudio_cdevsw = {
638a9ff04bSjmcneill 	.d_open = hdaudioopen,
648a9ff04bSjmcneill 	.d_close = hdaudioclose,
658a9ff04bSjmcneill 	.d_read = noread,
668a9ff04bSjmcneill 	.d_write = nowrite,
678a9ff04bSjmcneill 	.d_ioctl = hdaudioioctl,
688a9ff04bSjmcneill 	.d_stop = nostop,
698a9ff04bSjmcneill 	.d_tty = notty,
708a9ff04bSjmcneill 	.d_poll = nopoll,
718a9ff04bSjmcneill 	.d_mmap = nommap,
728a9ff04bSjmcneill 	.d_kqfilter = nokqfilter,
738a9ff04bSjmcneill 	.d_discard = nodiscard,
748a9ff04bSjmcneill 	.d_flag = D_OTHER
758a9ff04bSjmcneill };
768a9ff04bSjmcneill 
778a9ff04bSjmcneill extern struct cfdriver hdaudio_cd;
788a9ff04bSjmcneill 
798a9ff04bSjmcneill #define	HDAUDIOUNIT(x)	minor((x))
808a9ff04bSjmcneill 
818a9ff04bSjmcneill static void
hdaudio_stream_init(struct hdaudio_softc * sc,int nis,int nos,int nbidir)828a9ff04bSjmcneill hdaudio_stream_init(struct hdaudio_softc *sc, int nis, int nos, int nbidir)
838a9ff04bSjmcneill {
848a9ff04bSjmcneill 	int i, cnt = 0;
858a9ff04bSjmcneill 
868a9ff04bSjmcneill 	for (i = 0; i < nis && cnt < HDAUDIO_MAX_STREAMS; i++) {
878a9ff04bSjmcneill 		sc->sc_stream[cnt].st_host = sc;
888a9ff04bSjmcneill 		sc->sc_stream[cnt].st_enable = true;
898a9ff04bSjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
908a9ff04bSjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_ISS;
918a9ff04bSjmcneill 	}
928a9ff04bSjmcneill 	for (i = 0; i < nos && cnt < HDAUDIO_MAX_STREAMS; i++) {
938a9ff04bSjmcneill 		sc->sc_stream[cnt].st_host = sc;
948a9ff04bSjmcneill 		sc->sc_stream[cnt].st_enable = true;
958a9ff04bSjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
968a9ff04bSjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_OSS;
978a9ff04bSjmcneill 	}
988a9ff04bSjmcneill 	for (i = 0; i < nbidir && cnt < HDAUDIO_MAX_STREAMS; i++) {
998a9ff04bSjmcneill 		sc->sc_stream[cnt].st_host = sc;
1008a9ff04bSjmcneill 		sc->sc_stream[cnt].st_enable = true;
1018a9ff04bSjmcneill 		sc->sc_stream[cnt].st_shift = cnt;
1028a9ff04bSjmcneill 		sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_BSS;
1038a9ff04bSjmcneill 	}
1048a9ff04bSjmcneill 
1058a9ff04bSjmcneill 	for (i = 0; i < cnt; i++)
1068a9ff04bSjmcneill 		hdaudio_stream_stop(&sc->sc_stream[i]);
1078a9ff04bSjmcneill 
1088a9ff04bSjmcneill 	sc->sc_stream_mask = 0;
1098a9ff04bSjmcneill }
1108a9ff04bSjmcneill 
1118a9ff04bSjmcneill static void
hdaudio_codec_init(struct hdaudio_softc * sc)1128a9ff04bSjmcneill hdaudio_codec_init(struct hdaudio_softc *sc)
1138a9ff04bSjmcneill {
1148a9ff04bSjmcneill 	int i;
1158a9ff04bSjmcneill 
1168a9ff04bSjmcneill 	for (i = 0; i < HDAUDIO_MAX_CODECS; i++) {
1178a9ff04bSjmcneill 		sc->sc_codec[i].co_addr = i;
1188a9ff04bSjmcneill 		sc->sc_codec[i].co_host = sc;
1198a9ff04bSjmcneill 	}
1208a9ff04bSjmcneill }
1218a9ff04bSjmcneill 
1228a9ff04bSjmcneill static void
hdaudio_init(struct hdaudio_softc * sc)1238a9ff04bSjmcneill hdaudio_init(struct hdaudio_softc *sc)
1248a9ff04bSjmcneill {
125e83803dfSjmcneill 	const uint8_t vmaj = hda_read1(sc, HDAUDIO_MMIO_VMAJ);
126e83803dfSjmcneill 	const uint8_t vmin = hda_read1(sc, HDAUDIO_MMIO_VMIN);
127e83803dfSjmcneill 	const uint16_t gcap = hda_read2(sc, HDAUDIO_MMIO_GCAP);
128e83803dfSjmcneill 	const int nis = HDAUDIO_GCAP_ISS(gcap);
129e83803dfSjmcneill 	const int nos = HDAUDIO_GCAP_OSS(gcap);
130e83803dfSjmcneill 	const int nbidir = HDAUDIO_GCAP_BSS(gcap);
131e83803dfSjmcneill 	const int nsdo = HDAUDIO_GCAP_NSDO(gcap);
132e83803dfSjmcneill 	const int addr64 = HDAUDIO_GCAP_64OK(gcap);
1338a9ff04bSjmcneill 
134e83803dfSjmcneill 	hda_print(sc, "HDA ver. %d.%d, OSS %d, ISS %d, BSS %d, SDO %d%s\n",
135e83803dfSjmcneill 	    vmaj, vmin, nos, nis, nbidir, nsdo, addr64 ? ", 64-bit" : "");
1368a9ff04bSjmcneill 
1378a9ff04bSjmcneill 	/* Initialize codecs and streams */
1388a9ff04bSjmcneill 	hdaudio_codec_init(sc);
1398a9ff04bSjmcneill 	hdaudio_stream_init(sc, nis, nos, nbidir);
1408a9ff04bSjmcneill }
1418a9ff04bSjmcneill 
1428a9ff04bSjmcneill static int
hdaudio_codec_probe(struct hdaudio_softc * sc)1438a9ff04bSjmcneill hdaudio_codec_probe(struct hdaudio_softc *sc)
1448a9ff04bSjmcneill {
1458a9ff04bSjmcneill 	uint16_t statests;
1468a9ff04bSjmcneill 	int codecid;
1478a9ff04bSjmcneill 
1488a9ff04bSjmcneill 	statests = hda_read2(sc, HDAUDIO_MMIO_STATESTS);
1498a9ff04bSjmcneill 	for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++)
1508a9ff04bSjmcneill 		if (statests & (1 << codecid))
1518a9ff04bSjmcneill 			sc->sc_codec[codecid].co_valid = true;
1528a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, statests);
1538a9ff04bSjmcneill 
1548a9ff04bSjmcneill 	return statests;
1558a9ff04bSjmcneill }
1568a9ff04bSjmcneill 
1578a9ff04bSjmcneill int
hdaudio_dma_alloc(struct hdaudio_softc * sc,struct hdaudio_dma * dma,int flags)1588a9ff04bSjmcneill hdaudio_dma_alloc(struct hdaudio_softc *sc, struct hdaudio_dma *dma,
1598a9ff04bSjmcneill     int flags)
1608a9ff04bSjmcneill {
1618a9ff04bSjmcneill 	int err;
1628a9ff04bSjmcneill 
1638a9ff04bSjmcneill 	KASSERT(dma->dma_size > 0);
1648a9ff04bSjmcneill 
1658a9ff04bSjmcneill 	err = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, 128, 0,
1668a9ff04bSjmcneill 	    dma->dma_segs, sizeof(dma->dma_segs) / sizeof(dma->dma_segs[0]),
1678a9ff04bSjmcneill 	    &dma->dma_nsegs, BUS_DMA_WAITOK);
1688a9ff04bSjmcneill 	if (err)
1698a9ff04bSjmcneill 		return err;
1708a9ff04bSjmcneill 	err = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs,
1718a9ff04bSjmcneill 	    dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | flags);
1728a9ff04bSjmcneill 	if (err)
1738a9ff04bSjmcneill 		goto free;
1748a9ff04bSjmcneill 	err = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs,
1758a9ff04bSjmcneill 	    dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map);
1768a9ff04bSjmcneill 	if (err)
1778a9ff04bSjmcneill 		goto unmap;
1788a9ff04bSjmcneill 	err = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr,
1798a9ff04bSjmcneill 	    dma->dma_size, NULL, BUS_DMA_WAITOK | flags);
1808a9ff04bSjmcneill 	if (err)
1818a9ff04bSjmcneill 		goto destroy;
1828a9ff04bSjmcneill 
18341f3aaf4Sjmcneill 	memset(dma->dma_addr, 0, dma->dma_size);
18441f3aaf4Sjmcneill 	bus_dmamap_sync(sc->sc_dmat, dma->dma_map, 0, dma->dma_size,
18541f3aaf4Sjmcneill 	    BUS_DMASYNC_PREWRITE);
18641f3aaf4Sjmcneill 
1878a9ff04bSjmcneill 	dma->dma_valid = true;
1888a9ff04bSjmcneill 	return 0;
1898a9ff04bSjmcneill 
1908a9ff04bSjmcneill destroy:
1918a9ff04bSjmcneill 	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
1928a9ff04bSjmcneill unmap:
1938a9ff04bSjmcneill 	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
1948a9ff04bSjmcneill free:
1958a9ff04bSjmcneill 	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
1968a9ff04bSjmcneill 
1978a9ff04bSjmcneill 	dma->dma_valid = false;
1988a9ff04bSjmcneill 	return err;
1998a9ff04bSjmcneill }
2008a9ff04bSjmcneill 
2018a9ff04bSjmcneill void
hdaudio_dma_free(struct hdaudio_softc * sc,struct hdaudio_dma * dma)2028a9ff04bSjmcneill hdaudio_dma_free(struct hdaudio_softc *sc, struct hdaudio_dma *dma)
2038a9ff04bSjmcneill {
2048a9ff04bSjmcneill 	if (dma->dma_valid == false)
2058a9ff04bSjmcneill 		return;
2068a9ff04bSjmcneill 	bus_dmamap_unload(sc->sc_dmat, dma->dma_map);
2078a9ff04bSjmcneill 	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
2088a9ff04bSjmcneill 	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
2098a9ff04bSjmcneill 	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
2108a9ff04bSjmcneill 	dma->dma_valid = false;
2118a9ff04bSjmcneill }
2128a9ff04bSjmcneill 
2138a9ff04bSjmcneill static void
hdaudio_corb_enqueue(struct hdaudio_softc * sc,int addr,int nid,uint32_t control,uint32_t param)2148a9ff04bSjmcneill hdaudio_corb_enqueue(struct hdaudio_softc *sc, int addr, int nid,
2158a9ff04bSjmcneill     uint32_t control, uint32_t param)
2168a9ff04bSjmcneill {
2178a9ff04bSjmcneill 	uint32_t *corb = DMA_KERNADDR(&sc->sc_corb);
2188a9ff04bSjmcneill 	uint32_t verb;
2198a9ff04bSjmcneill 	uint16_t corbrp;
2208a9ff04bSjmcneill 	int wp;
2218a9ff04bSjmcneill 
2228a9ff04bSjmcneill 	/* Build command */
2238a9ff04bSjmcneill 	verb = (addr << 28) | (nid << 20) | (control << 8) | param;
2248a9ff04bSjmcneill 
2258a9ff04bSjmcneill 	/* Fetch and update write pointer */
2268a9ff04bSjmcneill 	corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBWP);
2278a9ff04bSjmcneill 	wp = (corbrp & 0xff) + 1;
2288a9ff04bSjmcneill 	if (wp >= (sc->sc_corb.dma_size / sizeof(*corb)))
2298a9ff04bSjmcneill 		wp = 0;
2308a9ff04bSjmcneill 
2318a9ff04bSjmcneill 	/* Enqueue command */
2328a9ff04bSjmcneill 	bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
2338a9ff04bSjmcneill 	    sc->sc_corb.dma_size, BUS_DMASYNC_POSTWRITE);
2348a9ff04bSjmcneill 	corb[wp] = verb;
2358a9ff04bSjmcneill 	bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
2368a9ff04bSjmcneill 	    sc->sc_corb.dma_size, BUS_DMASYNC_PREWRITE);
2378a9ff04bSjmcneill 
2388a9ff04bSjmcneill 	/* Commit updated write pointer */
2398a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBWP, wp);
2408a9ff04bSjmcneill }
2418a9ff04bSjmcneill 
2428a9ff04bSjmcneill static void
hdaudio_rirb_unsol(struct hdaudio_softc * sc,struct rirb_entry * entry)2438a9ff04bSjmcneill hdaudio_rirb_unsol(struct hdaudio_softc *sc, struct rirb_entry *entry)
2448a9ff04bSjmcneill {
2458a9ff04bSjmcneill 	struct hdaudio_codec *co;
2468a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
2478a9ff04bSjmcneill 	uint8_t codecid = RIRB_CODEC_ID(entry);
2488a9ff04bSjmcneill 	unsigned int i;
2498a9ff04bSjmcneill 
2508a9ff04bSjmcneill 	if (codecid >= HDAUDIO_MAX_CODECS) {
2518a9ff04bSjmcneill 		hda_error(sc, "unsol: codec id 0x%02x out of range\n", codecid);
2528a9ff04bSjmcneill 		return;
2538a9ff04bSjmcneill 	}
2548a9ff04bSjmcneill 	co = &sc->sc_codec[codecid];
2558a9ff04bSjmcneill 	if (sc->sc_codec[codecid].co_valid == false) {
2568a9ff04bSjmcneill 		hda_error(sc, "unsol: codec id 0x%02x not valid\n", codecid);
2578a9ff04bSjmcneill 		return;
2588a9ff04bSjmcneill 	}
2598a9ff04bSjmcneill 
2608a9ff04bSjmcneill 	for (i = 0; i < co->co_nfg; i++) {
2618a9ff04bSjmcneill 		fg = &co->co_fg[i];
2628a9ff04bSjmcneill 		if (fg->fg_device && fg->fg_unsol)
2638a9ff04bSjmcneill 			fg->fg_unsol(fg->fg_device, entry->resp);
2648a9ff04bSjmcneill 	}
2658a9ff04bSjmcneill }
2668a9ff04bSjmcneill 
2678a9ff04bSjmcneill static uint32_t
hdaudio_rirb_dequeue(struct hdaudio_softc * sc,bool unsol)2688a9ff04bSjmcneill hdaudio_rirb_dequeue(struct hdaudio_softc *sc, bool unsol)
2698a9ff04bSjmcneill {
2708a9ff04bSjmcneill 	uint16_t rirbwp;
2718a9ff04bSjmcneill 	uint64_t *rirb = DMA_KERNADDR(&sc->sc_rirb);
2728a9ff04bSjmcneill 	struct rirb_entry entry;
2738a9ff04bSjmcneill 	int retry;
2748a9ff04bSjmcneill 
2758a9ff04bSjmcneill 	for (;;) {
2768a9ff04bSjmcneill 		retry = HDAUDIO_RIRB_TIMEOUT;
2778a9ff04bSjmcneill 
2788a9ff04bSjmcneill 		rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
2798a9ff04bSjmcneill 		while (--retry > 0 && (rirbwp & 0xff) == sc->sc_rirbrp) {
2808a9ff04bSjmcneill 			if (unsol) {
2818a9ff04bSjmcneill 				/* don't wait for more unsol events */
2828a9ff04bSjmcneill 				hda_trace(sc, "unsol: rirb empty\n");
2838a9ff04bSjmcneill 				return 0xffffffff;
2848a9ff04bSjmcneill 			}
2858a9ff04bSjmcneill 			hda_delay(10);
2868a9ff04bSjmcneill 			rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
2878a9ff04bSjmcneill 		}
2888a9ff04bSjmcneill 		if (retry == 0) {
2898a9ff04bSjmcneill 			hda_error(sc, "RIRB timeout\n");
2908a9ff04bSjmcneill 			return 0xffffffff;
2918a9ff04bSjmcneill 		}
2928a9ff04bSjmcneill 
2938a9ff04bSjmcneill 		sc->sc_rirbrp++;
2948a9ff04bSjmcneill 		if (sc->sc_rirbrp >= (sc->sc_rirb.dma_size / sizeof(*rirb)))
2958a9ff04bSjmcneill 			sc->sc_rirbrp = 0;
2968a9ff04bSjmcneill 
2978a9ff04bSjmcneill 		bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
2988a9ff04bSjmcneill 		    sc->sc_rirb.dma_size, BUS_DMASYNC_POSTREAD);
2998a9ff04bSjmcneill 		entry = *(struct rirb_entry *)&rirb[sc->sc_rirbrp];
3008a9ff04bSjmcneill 		bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
3018a9ff04bSjmcneill 		    sc->sc_rirb.dma_size, BUS_DMASYNC_PREREAD);
3028a9ff04bSjmcneill 
3038a9ff04bSjmcneill 		hda_trace(sc, "%s: response %08X %08X\n",
3048a9ff04bSjmcneill 		    unsol ? "unsol" : "cmd  ",
3058a9ff04bSjmcneill 		    entry.resp, entry.resp_ex);
3068a9ff04bSjmcneill 
3078a9ff04bSjmcneill 		if (RIRB_UNSOL(&entry)) {
3088a9ff04bSjmcneill 			hdaudio_rirb_unsol(sc, &entry);
3098a9ff04bSjmcneill 			continue;
3108a9ff04bSjmcneill 		}
3118a9ff04bSjmcneill 
3128a9ff04bSjmcneill 		return entry.resp;
3138a9ff04bSjmcneill 	}
3148a9ff04bSjmcneill }
3158a9ff04bSjmcneill 
3168a9ff04bSjmcneill uint32_t
hdaudio_command(struct hdaudio_codec * co,int nid,uint32_t control,uint32_t param)3178a9ff04bSjmcneill hdaudio_command(struct hdaudio_codec *co, int nid, uint32_t control,
3188a9ff04bSjmcneill     uint32_t param)
3198a9ff04bSjmcneill {
3208a9ff04bSjmcneill 	uint32_t result;
3218a9ff04bSjmcneill 	struct hdaudio_softc *sc = co->co_host;
3228a9ff04bSjmcneill 	mutex_enter(&sc->sc_corb_mtx);
3238a9ff04bSjmcneill 	result = hdaudio_command_unlocked(co, nid, control, param);
3248a9ff04bSjmcneill 	mutex_exit(&sc->sc_corb_mtx);
3258a9ff04bSjmcneill 	return result;
3268a9ff04bSjmcneill }
3278a9ff04bSjmcneill 
3288a9ff04bSjmcneill uint32_t
hdaudio_command_unlocked(struct hdaudio_codec * co,int nid,uint32_t control,uint32_t param)3298a9ff04bSjmcneill hdaudio_command_unlocked(struct hdaudio_codec *co, int nid, uint32_t control,
3308a9ff04bSjmcneill     uint32_t param)
3318a9ff04bSjmcneill {
3328a9ff04bSjmcneill 	struct hdaudio_softc *sc = co->co_host;
3338a9ff04bSjmcneill 	uint32_t result;
3348a9ff04bSjmcneill 
3358a9ff04bSjmcneill 	hda_trace(sc, "cmd  : request %08X %08X (%02X)\n",
3368a9ff04bSjmcneill 	    control, param, nid);
3378a9ff04bSjmcneill 	hdaudio_corb_enqueue(sc, co->co_addr, nid, control, param);
3388a9ff04bSjmcneill 	result = hdaudio_rirb_dequeue(sc, false);
3398a9ff04bSjmcneill 
340ccc79c82Sjmcneill 	/* Clear response interrupt status */
341ccc79c82Sjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS, hda_read1(sc, HDAUDIO_MMIO_RIRBSTS));
342ccc79c82Sjmcneill 
3438a9ff04bSjmcneill 	return result;
3448a9ff04bSjmcneill }
3458a9ff04bSjmcneill 
3468a9ff04bSjmcneill static int
hdaudio_corb_setsize(struct hdaudio_softc * sc)3478a9ff04bSjmcneill hdaudio_corb_setsize(struct hdaudio_softc *sc)
3488a9ff04bSjmcneill {
3498a9ff04bSjmcneill 	uint8_t corbsize;
3508a9ff04bSjmcneill 	bus_size_t bufsize = 0;
3518a9ff04bSjmcneill 
3528a9ff04bSjmcneill 	/*
3538a9ff04bSjmcneill 	 * The size of the CORB is programmable to 2, 16, or 256 entries
3548a9ff04bSjmcneill 	 * by using the CORBSIZE register. Choose a size based on the
3558a9ff04bSjmcneill 	 * controller capabilities, preferring a larger size when possible.
3568a9ff04bSjmcneill 	 */
3578a9ff04bSjmcneill 	corbsize = hda_read1(sc, HDAUDIO_MMIO_CORBSIZE);
3588a9ff04bSjmcneill 	corbsize &= ~0x3;
3598a9ff04bSjmcneill 	if ((corbsize >> 4) & 0x4) {
3608a9ff04bSjmcneill 		corbsize |= 0x2;
3618a9ff04bSjmcneill 		bufsize = 1024;
3628a9ff04bSjmcneill 	} else if ((corbsize >> 4) & 0x2) {
3638a9ff04bSjmcneill 		corbsize |= 0x1;
3648a9ff04bSjmcneill 		bufsize = 64;
3658a9ff04bSjmcneill 	} else if ((corbsize >> 4) & 0x1) {
3668a9ff04bSjmcneill 		corbsize |= 0x0;
3678a9ff04bSjmcneill 		bufsize = 8;
3688a9ff04bSjmcneill 	} else {
3698a9ff04bSjmcneill 		hda_error(sc, "couldn't configure CORB size\n");
3708a9ff04bSjmcneill 		return ENXIO;
3718a9ff04bSjmcneill 	}
3728a9ff04bSjmcneill 
3738a9ff04bSjmcneill #if defined(HDAUDIO_DEBUG)
3748a9ff04bSjmcneill 	hda_print(sc, "using %d byte CORB (cap %X)\n",
3758a9ff04bSjmcneill 	    (int)bufsize, corbsize >> 4);
3768a9ff04bSjmcneill #endif
3778a9ff04bSjmcneill 
3788a9ff04bSjmcneill 	sc->sc_corb.dma_size = bufsize;
3798a9ff04bSjmcneill 	sc->sc_corb.dma_sizereg = corbsize;
3808a9ff04bSjmcneill 
3818a9ff04bSjmcneill 	return 0;
3828a9ff04bSjmcneill }
3838a9ff04bSjmcneill 
3848a9ff04bSjmcneill static int
hdaudio_corb_config(struct hdaudio_softc * sc)3858a9ff04bSjmcneill hdaudio_corb_config(struct hdaudio_softc *sc)
3868a9ff04bSjmcneill {
3878a9ff04bSjmcneill 	uint32_t corbubase, corblbase;
3888a9ff04bSjmcneill 	uint16_t corbrp;
3898a9ff04bSjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
3908a9ff04bSjmcneill 
3918a9ff04bSjmcneill 	/* Program command buffer base address and size */
3928a9ff04bSjmcneill 	corblbase = (uint32_t)DMA_DMAADDR(&sc->sc_corb);
3938a9ff04bSjmcneill 	corbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_corb)) >> 32);
3948a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_CORBLBASE, corblbase);
3958a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_CORBUBASE, corbubase);
3968a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_MMIO_CORBSIZE, sc->sc_corb.dma_sizereg);
3978a9ff04bSjmcneill 
3988a9ff04bSjmcneill 	/* Clear the read and write pointers */
3998a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBRP, HDAUDIO_CORBRP_RP_RESET);
4008a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBRP, 0);
4018a9ff04bSjmcneill 	do {
4028a9ff04bSjmcneill 		hda_delay(10);
4038a9ff04bSjmcneill 		corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBRP);
4048a9ff04bSjmcneill 	} while (--retry > 0 && (corbrp & HDAUDIO_CORBRP_RP_RESET) != 0);
4058a9ff04bSjmcneill 	if (retry == 0) {
4068a9ff04bSjmcneill 		hda_error(sc, "timeout resetting CORB\n");
4078a9ff04bSjmcneill 		return ETIME;
4088a9ff04bSjmcneill 	}
4098a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_CORBWP, 0);
4108a9ff04bSjmcneill 
4118a9ff04bSjmcneill 	return 0;
4128a9ff04bSjmcneill }
4138a9ff04bSjmcneill 
4148a9ff04bSjmcneill static int
hdaudio_corb_stop(struct hdaudio_softc * sc)4158a9ff04bSjmcneill hdaudio_corb_stop(struct hdaudio_softc *sc)
4168a9ff04bSjmcneill {
4178a9ff04bSjmcneill 	uint8_t corbctl;
4188a9ff04bSjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
4198a9ff04bSjmcneill 
4208a9ff04bSjmcneill 	/* Stop the CORB if necessary */
4218a9ff04bSjmcneill 	corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4228a9ff04bSjmcneill 	if (corbctl & HDAUDIO_CORBCTL_RUN) {
4238a9ff04bSjmcneill 		corbctl &= ~HDAUDIO_CORBCTL_RUN;
42452e61030Sjmcneill 		hda_write1(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
4258a9ff04bSjmcneill 		do {
4268a9ff04bSjmcneill 			hda_delay(10);
42752e61030Sjmcneill 			corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4288a9ff04bSjmcneill 		} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) != 0);
4298a9ff04bSjmcneill 		if (retry == 0) {
4308a9ff04bSjmcneill 			hda_error(sc, "timeout stopping CORB\n");
4318a9ff04bSjmcneill 			return ETIME;
4328a9ff04bSjmcneill 		}
4338a9ff04bSjmcneill 	}
4348a9ff04bSjmcneill 
4358a9ff04bSjmcneill 	return 0;
4368a9ff04bSjmcneill }
4378a9ff04bSjmcneill 
4388a9ff04bSjmcneill static int
hdaudio_corb_start(struct hdaudio_softc * sc)4398a9ff04bSjmcneill hdaudio_corb_start(struct hdaudio_softc *sc)
4408a9ff04bSjmcneill {
4418a9ff04bSjmcneill 	uint8_t corbctl;
4428a9ff04bSjmcneill 	int retry = HDAUDIO_CORB_TIMEOUT;
4438a9ff04bSjmcneill 
4448a9ff04bSjmcneill 	/* Start the CORB if necessary */
4458a9ff04bSjmcneill 	corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4468a9ff04bSjmcneill 	if ((corbctl & HDAUDIO_CORBCTL_RUN) == 0) {
4478a9ff04bSjmcneill 		corbctl |= HDAUDIO_CORBCTL_RUN;
44852e61030Sjmcneill 		hda_write1(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
4498a9ff04bSjmcneill 		do {
4508a9ff04bSjmcneill 			hda_delay(10);
45152e61030Sjmcneill 			corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
4528a9ff04bSjmcneill 		} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) == 0);
4538a9ff04bSjmcneill 		if (retry == 0) {
4548a9ff04bSjmcneill 			hda_error(sc, "timeout starting CORB\n");
4558a9ff04bSjmcneill 			return ETIME;
4568a9ff04bSjmcneill 		}
4578a9ff04bSjmcneill 	}
4588a9ff04bSjmcneill 
4598a9ff04bSjmcneill 	return 0;
4608a9ff04bSjmcneill }
4618a9ff04bSjmcneill 
4628a9ff04bSjmcneill static int
hdaudio_rirb_stop(struct hdaudio_softc * sc)4638a9ff04bSjmcneill hdaudio_rirb_stop(struct hdaudio_softc *sc)
4648a9ff04bSjmcneill {
4658a9ff04bSjmcneill 	uint8_t rirbctl;
4668a9ff04bSjmcneill 	int retry = HDAUDIO_RIRB_TIMEOUT;
4678a9ff04bSjmcneill 
4688a9ff04bSjmcneill 	/* Stop the RIRB if necessary */
4698a9ff04bSjmcneill 	rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4708a9ff04bSjmcneill 	if (rirbctl & (HDAUDIO_RIRBCTL_RUN|HDAUDIO_RIRBCTL_ROI_EN)) {
4718a9ff04bSjmcneill 		rirbctl &= ~HDAUDIO_RIRBCTL_RUN;
4728a9ff04bSjmcneill 		rirbctl &= ~HDAUDIO_RIRBCTL_ROI_EN;
4738a9ff04bSjmcneill 		hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
4748a9ff04bSjmcneill 		do {
4758a9ff04bSjmcneill 			hda_delay(10);
4768a9ff04bSjmcneill 			rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4778a9ff04bSjmcneill 		} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) != 0);
4788a9ff04bSjmcneill 		if (retry == 0) {
4798a9ff04bSjmcneill 			hda_error(sc, "timeout stopping RIRB\n");
4808a9ff04bSjmcneill 			return ETIME;
4818a9ff04bSjmcneill 		}
4828a9ff04bSjmcneill 	}
4838a9ff04bSjmcneill 
4848a9ff04bSjmcneill 	return 0;
4858a9ff04bSjmcneill }
4868a9ff04bSjmcneill 
4878a9ff04bSjmcneill static int
hdaudio_rirb_start(struct hdaudio_softc * sc)4888a9ff04bSjmcneill hdaudio_rirb_start(struct hdaudio_softc *sc)
4898a9ff04bSjmcneill {
4908a9ff04bSjmcneill 	uint8_t rirbctl;
4918a9ff04bSjmcneill 	int retry = HDAUDIO_RIRB_TIMEOUT;
4928a9ff04bSjmcneill 
493ccc79c82Sjmcneill 	/* Set the RIRB interrupt count */
494ccc79c82Sjmcneill 	hda_write2(sc, HDAUDIO_MMIO_RINTCNT, 1);
495ccc79c82Sjmcneill 
496ccc79c82Sjmcneill 	/* Start the RIRB */
4978a9ff04bSjmcneill 	rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
4988a9ff04bSjmcneill 	rirbctl |= HDAUDIO_RIRBCTL_RUN;
4998a9ff04bSjmcneill 	rirbctl |= HDAUDIO_RIRBCTL_INT_EN;
5008a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
5018a9ff04bSjmcneill 	do {
5028a9ff04bSjmcneill 		hda_delay(10);
5038a9ff04bSjmcneill 		rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
5048a9ff04bSjmcneill 	} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) == 0);
5058a9ff04bSjmcneill 	if (retry == 0) {
5068a9ff04bSjmcneill 		hda_error(sc, "timeout starting RIRB\n");
5078a9ff04bSjmcneill 		return ETIME;
5088a9ff04bSjmcneill 	}
5098a9ff04bSjmcneill 
5108a9ff04bSjmcneill 	return 0;
5118a9ff04bSjmcneill }
5128a9ff04bSjmcneill 
5138a9ff04bSjmcneill static int
hdaudio_rirb_setsize(struct hdaudio_softc * sc)5148a9ff04bSjmcneill hdaudio_rirb_setsize(struct hdaudio_softc *sc)
5158a9ff04bSjmcneill {
5168a9ff04bSjmcneill 	uint8_t rirbsize;
5178a9ff04bSjmcneill 	bus_size_t bufsize = 0;
5188a9ff04bSjmcneill 
5198a9ff04bSjmcneill 	/*
5208a9ff04bSjmcneill 	 * The size of the RIRB is programmable to 2, 16, or 256 entries
5218a9ff04bSjmcneill 	 * by using the RIRBSIZE register. Choose a size based on the
5228a9ff04bSjmcneill 	 * controller capabilities, preferring a larger size when possible.
5238a9ff04bSjmcneill 	 */
5248a9ff04bSjmcneill 	rirbsize = hda_read1(sc, HDAUDIO_MMIO_RIRBSIZE);
5258a9ff04bSjmcneill 	rirbsize &= ~0x3;
5268a9ff04bSjmcneill 	if ((rirbsize >> 4) & 0x4) {
5278a9ff04bSjmcneill 		rirbsize |= 0x2;
5288a9ff04bSjmcneill 		bufsize = 2048;
5298a9ff04bSjmcneill 	} else if ((rirbsize >> 4) & 0x2) {
5308a9ff04bSjmcneill 		rirbsize |= 0x1;
5318a9ff04bSjmcneill 		bufsize = 128;
5328a9ff04bSjmcneill 	} else if ((rirbsize >> 4) & 0x1) {
5338a9ff04bSjmcneill 		rirbsize |= 0x0;
5348a9ff04bSjmcneill 		bufsize = 16;
5358a9ff04bSjmcneill 	} else {
5368a9ff04bSjmcneill 		hda_error(sc, "couldn't configure RIRB size\n");
5378a9ff04bSjmcneill 		return ENXIO;
5388a9ff04bSjmcneill 	}
5398a9ff04bSjmcneill 
5408a9ff04bSjmcneill #if defined(HDAUDIO_DEBUG)
5418a9ff04bSjmcneill 	hda_print(sc, "using %d byte RIRB (cap %X)\n",
5428a9ff04bSjmcneill 	    (int)bufsize, rirbsize >> 4);
5438a9ff04bSjmcneill #endif
5448a9ff04bSjmcneill 
5458a9ff04bSjmcneill 	sc->sc_rirb.dma_size = bufsize;
5468a9ff04bSjmcneill 	sc->sc_rirb.dma_sizereg = rirbsize;
5478a9ff04bSjmcneill 
5488a9ff04bSjmcneill 	return 0;
5498a9ff04bSjmcneill }
5508a9ff04bSjmcneill 
5518a9ff04bSjmcneill static int
hdaudio_rirb_config(struct hdaudio_softc * sc)5528a9ff04bSjmcneill hdaudio_rirb_config(struct hdaudio_softc *sc)
5538a9ff04bSjmcneill {
5548a9ff04bSjmcneill 	uint32_t rirbubase, rirblbase;
5558a9ff04bSjmcneill 
5568a9ff04bSjmcneill 	/* Program command buffer base address and size */
5578a9ff04bSjmcneill 	rirblbase = (uint32_t)DMA_DMAADDR(&sc->sc_rirb);
5588a9ff04bSjmcneill 	rirbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_rirb)) >> 32);
5598a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_RIRBLBASE, rirblbase);
5608a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_RIRBUBASE, rirbubase);
5618a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSIZE, sc->sc_rirb.dma_sizereg);
5628a9ff04bSjmcneill 
5638a9ff04bSjmcneill 	/* Clear the write pointer */
5648a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_RIRBWP, HDAUDIO_RIRBWP_WP_RESET);
5658a9ff04bSjmcneill 	sc->sc_rirbrp = 0;
5668a9ff04bSjmcneill 
5678a9ff04bSjmcneill 	return 0;
5688a9ff04bSjmcneill }
5698a9ff04bSjmcneill 
5708a9ff04bSjmcneill static int
hdaudio_reset(struct hdaudio_softc * sc)5718a9ff04bSjmcneill hdaudio_reset(struct hdaudio_softc *sc)
5728a9ff04bSjmcneill {
5738a9ff04bSjmcneill 	int retry = HDAUDIO_RESET_TIMEOUT;
5748a9ff04bSjmcneill 	uint32_t gctl;
5758a9ff04bSjmcneill 	int err;
5768a9ff04bSjmcneill 
5778a9ff04bSjmcneill 	if ((err = hdaudio_rirb_stop(sc)) != 0) {
5788a9ff04bSjmcneill 		hda_error(sc, "couldn't reset because RIRB is busy\n");
5798a9ff04bSjmcneill 		return err;
5808a9ff04bSjmcneill 	}
5818a9ff04bSjmcneill 	if ((err = hdaudio_corb_stop(sc)) != 0) {
5828a9ff04bSjmcneill 		hda_error(sc, "couldn't reset because CORB is busy\n");
5838a9ff04bSjmcneill 		return err;
5848a9ff04bSjmcneill 	}
5858a9ff04bSjmcneill 
5868a9ff04bSjmcneill 	/* Disable wake events */
5878a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_WAKEEN, 0);
5888a9ff04bSjmcneill 
5898a9ff04bSjmcneill 	/* Disable interrupts */
5908a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
5918a9ff04bSjmcneill 
5928a9ff04bSjmcneill 	/* Clear state change status register */
5938a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS,
5948a9ff04bSjmcneill 	    hda_read2(sc, HDAUDIO_MMIO_STATESTS));
5958a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
5968a9ff04bSjmcneill 	    hda_read1(sc, HDAUDIO_MMIO_RIRBSTS));
5978a9ff04bSjmcneill 
59827292242Sjmcneill 	/* Put the controller into reset state */
5998a9ff04bSjmcneill 	gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6008a9ff04bSjmcneill 	gctl &= ~HDAUDIO_GCTL_CRST;
6018a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl);
6028a9ff04bSjmcneill 	do {
6038a9ff04bSjmcneill 		hda_delay(10);
6048a9ff04bSjmcneill 		gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6058a9ff04bSjmcneill 	} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) != 0);
6068a9ff04bSjmcneill 	if (retry == 0) {
6078a9ff04bSjmcneill 		hda_error(sc, "timeout entering reset state\n");
6088a9ff04bSjmcneill 		return ETIME;
6098a9ff04bSjmcneill 	}
61027292242Sjmcneill 
61127292242Sjmcneill 	hda_delay(1000);
6128a9ff04bSjmcneill 
6138a9ff04bSjmcneill 	/* Now the controller is in reset state, so bring it out */
6148a9ff04bSjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
6158a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_CRST);
6168a9ff04bSjmcneill 	do {
6178a9ff04bSjmcneill 		hda_delay(10);
6188a9ff04bSjmcneill 		gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
6198a9ff04bSjmcneill 	} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) == 0);
6208a9ff04bSjmcneill 	if (retry == 0) {
6218a9ff04bSjmcneill 		hda_error(sc, "timeout leaving reset state\n");
6228a9ff04bSjmcneill 		return ETIME;
6238a9ff04bSjmcneill 	}
6248a9ff04bSjmcneill 
62527292242Sjmcneill 	hda_delay(2000);
62627292242Sjmcneill 
6278a9ff04bSjmcneill 	/* Accept unsolicited responses */
6288a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_UNSOL_EN);
6298a9ff04bSjmcneill 
6308a9ff04bSjmcneill 	return 0;
6318a9ff04bSjmcneill }
6328a9ff04bSjmcneill 
6338a9ff04bSjmcneill static void
hdaudio_intr_enable(struct hdaudio_softc * sc)6348a9ff04bSjmcneill hdaudio_intr_enable(struct hdaudio_softc *sc)
6358a9ff04bSjmcneill {
6368a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
6378a9ff04bSjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
6388a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL,
6398a9ff04bSjmcneill 	    HDAUDIO_INTCTL_GIE | HDAUDIO_INTCTL_CIE);
6408a9ff04bSjmcneill }
6418a9ff04bSjmcneill 
6428a9ff04bSjmcneill static void
hdaudio_intr_disable(struct hdaudio_softc * sc)6438a9ff04bSjmcneill hdaudio_intr_disable(struct hdaudio_softc *sc)
6448a9ff04bSjmcneill {
6458a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
6468a9ff04bSjmcneill }
6478a9ff04bSjmcneill 
6488a9ff04bSjmcneill static int
hdaudio_config_print(void * opaque,const char * pnp)6498a9ff04bSjmcneill hdaudio_config_print(void *opaque, const char *pnp)
6508a9ff04bSjmcneill {
6518a9ff04bSjmcneill 	prop_dictionary_t dict = opaque;
6528a9ff04bSjmcneill 	uint8_t fgtype, nid;
6538a9ff04bSjmcneill 	uint16_t vendor, product;
6548a9ff04bSjmcneill 	const char *type = "unknown";
6558a9ff04bSjmcneill 
6568a9ff04bSjmcneill 	prop_dictionary_get_uint8(dict, "function-group-type", &fgtype);
6578a9ff04bSjmcneill 	prop_dictionary_get_uint8(dict, "node-id", &nid);
6588a9ff04bSjmcneill 	prop_dictionary_get_uint16(dict, "vendor-id", &vendor);
6598a9ff04bSjmcneill 	prop_dictionary_get_uint16(dict, "product-id", &product);
6608a9ff04bSjmcneill 	if (pnp) {
6618a9ff04bSjmcneill 		if (fgtype == HDAUDIO_GROUP_TYPE_AFG)
6628a9ff04bSjmcneill 			type = "hdafg";
6638a9ff04bSjmcneill 		else if (fgtype == HDAUDIO_GROUP_TYPE_VSM_FG)
6648a9ff04bSjmcneill 			type = "hdvsmfg";
6658a9ff04bSjmcneill 
6668a9ff04bSjmcneill 		aprint_normal("%s at %s", type, pnp);
6678a9ff04bSjmcneill 	}
6688a9ff04bSjmcneill 	aprint_debug(" vendor 0x%04X product 0x%04X nid 0x%02X",
6698a9ff04bSjmcneill 	    vendor, product, nid);
6708a9ff04bSjmcneill 
6718a9ff04bSjmcneill 	return UNCONF;
6728a9ff04bSjmcneill }
6738a9ff04bSjmcneill 
6748a9ff04bSjmcneill static void
hdaudio_attach_fg(struct hdaudio_function_group * fg,prop_array_t config)6758a9ff04bSjmcneill hdaudio_attach_fg(struct hdaudio_function_group *fg, prop_array_t config)
6768a9ff04bSjmcneill {
6778a9ff04bSjmcneill 	struct hdaudio_codec *co = fg->fg_codec;
6788a9ff04bSjmcneill 	struct hdaudio_softc *sc = co->co_host;
6798a9ff04bSjmcneill 	prop_dictionary_t args = prop_dictionary_create();
6808a9ff04bSjmcneill 	uint64_t fgptr = (vaddr_t)fg;
6818a9ff04bSjmcneill 	int locs[1];
6828a9ff04bSjmcneill 
6838a9ff04bSjmcneill 	prop_dictionary_set_uint8(args, "function-group-type", fg->fg_type);
6848a9ff04bSjmcneill 	prop_dictionary_set_uint64(args, "function-group", fgptr);
6858a9ff04bSjmcneill 	prop_dictionary_set_uint8(args, "node-id", fg->fg_nid);
6868a9ff04bSjmcneill 	prop_dictionary_set_uint16(args, "vendor-id", fg->fg_vendor);
6878a9ff04bSjmcneill 	prop_dictionary_set_uint16(args, "product-id", fg->fg_product);
6888a9ff04bSjmcneill 	if (config)
6898a9ff04bSjmcneill 		prop_dictionary_set(args, "pin-config", config);
6908a9ff04bSjmcneill 
6918a9ff04bSjmcneill 	locs[0] = fg->fg_nid;
6928a9ff04bSjmcneill 
6932685996bSthorpej 	fg->fg_device = config_found(sc->sc_dev, args, hdaudio_config_print,
694c7fb772bSthorpej 	    CFARGS(.submatch = config_stdsubmatch,
695c7fb772bSthorpej 		   .locators = locs));
6968a9ff04bSjmcneill 
6978a9ff04bSjmcneill 	prop_object_release(args);
6988a9ff04bSjmcneill }
6998a9ff04bSjmcneill 
7008a9ff04bSjmcneill static void
hdaudio_codec_attach(struct hdaudio_codec * co)7018a9ff04bSjmcneill hdaudio_codec_attach(struct hdaudio_codec *co)
7028a9ff04bSjmcneill {
703b547d839Sjmcneill 	struct hdaudio_softc *sc = co->co_host;
7048a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
7058a9ff04bSjmcneill 	uint32_t vid, snc, fgrp;
7068a9ff04bSjmcneill 	int starting_node, num_nodes, nid;
7078a9ff04bSjmcneill 
7088a9ff04bSjmcneill 	if (co->co_valid == false)
7098a9ff04bSjmcneill 		return;
7108a9ff04bSjmcneill 
7118a9ff04bSjmcneill 	vid = hdaudio_command(co, 0, CORB_GET_PARAMETER, COP_VENDOR_ID);
7128a9ff04bSjmcneill 	snc = hdaudio_command(co, 0, CORB_GET_PARAMETER,
7138a9ff04bSjmcneill 	    COP_SUBORDINATE_NODE_COUNT);
7148a9ff04bSjmcneill 
7158a9ff04bSjmcneill 	/* make sure the vendor and product IDs are valid */
7168a9ff04bSjmcneill 	if (vid == 0xffffffff || vid == 0x00000000)
7178a9ff04bSjmcneill 		return;
7188a9ff04bSjmcneill 
7198a9ff04bSjmcneill #ifdef HDAUDIO_DEBUG
7208a9ff04bSjmcneill 	uint32_t rid = hdaudio_command(co, 0, CORB_GET_PARAMETER,
7218a9ff04bSjmcneill 	    COP_REVISION_ID);
7228a9ff04bSjmcneill 	hda_print(sc, "Codec%02X: %04X:%04X HDA %d.%d rev %d stepping %d\n",
7238a9ff04bSjmcneill 	    co->co_addr, vid >> 16, vid & 0xffff,
7248a9ff04bSjmcneill 	    (rid >> 20) & 0xf, (rid >> 16) & 0xf,
7258a9ff04bSjmcneill 	    (rid >> 8) & 0xff, rid & 0xff);
7268a9ff04bSjmcneill #endif
7278a9ff04bSjmcneill 	starting_node = (snc >> 16) & 0xff;
7288a9ff04bSjmcneill 	num_nodes = snc & 0xff;
7298a9ff04bSjmcneill 
730b547d839Sjmcneill 	/*
731b547d839Sjmcneill 	 * If the total number of nodes is 0, there's nothing we can do.
732b547d839Sjmcneill 	 * This shouldn't happen, so complain about it.
733b547d839Sjmcneill 	 */
734b547d839Sjmcneill 	if (num_nodes == 0) {
735b547d839Sjmcneill 		hda_error(sc, "Codec%02X: No subordinate nodes found (%08x)\n",
736b547d839Sjmcneill 		    co->co_addr, snc);
737b547d839Sjmcneill 		return;
738b547d839Sjmcneill 	}
739b547d839Sjmcneill 
7408a9ff04bSjmcneill 	co->co_nfg = num_nodes;
7418a9ff04bSjmcneill 	co->co_fg = kmem_zalloc(co->co_nfg * sizeof(*co->co_fg), KM_SLEEP);
7428a9ff04bSjmcneill 
7438a9ff04bSjmcneill 	for (nid = starting_node; nid < starting_node + num_nodes; nid++) {
7448a9ff04bSjmcneill 		fg = &co->co_fg[nid - starting_node];
7458a9ff04bSjmcneill 		fg->fg_codec = co;
7468a9ff04bSjmcneill 		fg->fg_nid = nid;
7478a9ff04bSjmcneill 		fg->fg_vendor = vid >> 16;
7488a9ff04bSjmcneill 		fg->fg_product = vid & 0xffff;
7498a9ff04bSjmcneill 
7508a9ff04bSjmcneill 		fgrp = hdaudio_command(co, nid, CORB_GET_PARAMETER,
7518a9ff04bSjmcneill 		    COP_FUNCTION_GROUP_TYPE);
7528a9ff04bSjmcneill 		switch (fgrp & 0xff) {
7538a9ff04bSjmcneill 		case 0x01:	/* Audio Function Group */
7548a9ff04bSjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_AFG;
7558a9ff04bSjmcneill 			break;
7568a9ff04bSjmcneill 		case 0x02:	/* Vendor Specific Modem Function Group */
7578a9ff04bSjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_VSM_FG;
7588a9ff04bSjmcneill 			break;
7598a9ff04bSjmcneill 		default:
7608a9ff04bSjmcneill 			/* Function group type not supported */
7618a9ff04bSjmcneill 			fg->fg_type = HDAUDIO_GROUP_TYPE_UNKNOWN;
7628a9ff04bSjmcneill 			break;
7638a9ff04bSjmcneill 		}
7648a9ff04bSjmcneill 		hdaudio_attach_fg(fg, NULL);
7658a9ff04bSjmcneill 	}
7668a9ff04bSjmcneill }
7678a9ff04bSjmcneill 
7688a9ff04bSjmcneill int
hdaudio_stream_tag(struct hdaudio_stream * st)7698a9ff04bSjmcneill hdaudio_stream_tag(struct hdaudio_stream *st)
7708a9ff04bSjmcneill {
7718a9ff04bSjmcneill 	int ret = 0;
7728a9ff04bSjmcneill 
7738a9ff04bSjmcneill 	switch (st->st_type) {
7748a9ff04bSjmcneill 	case HDAUDIO_STREAM_ISS:
7758a9ff04bSjmcneill 		ret = 1;
7768a9ff04bSjmcneill 		break;
7778a9ff04bSjmcneill 	case HDAUDIO_STREAM_OSS:
7788a9ff04bSjmcneill 		ret = 2;
7798a9ff04bSjmcneill 		break;
7808a9ff04bSjmcneill 	case HDAUDIO_STREAM_BSS:
7818a9ff04bSjmcneill 		ret = 3;
7828a9ff04bSjmcneill 		break;
7838a9ff04bSjmcneill 	}
7848a9ff04bSjmcneill 
7858a9ff04bSjmcneill 	return ret;
7868a9ff04bSjmcneill }
7878a9ff04bSjmcneill 
7888a9ff04bSjmcneill int
hdaudio_attach(device_t dev,struct hdaudio_softc * sc)7898a9ff04bSjmcneill hdaudio_attach(device_t dev, struct hdaudio_softc *sc)
7908a9ff04bSjmcneill {
7918a9ff04bSjmcneill 	int err, i;
7928a9ff04bSjmcneill 
7938a9ff04bSjmcneill 	KASSERT(sc->sc_memvalid == true);
7948a9ff04bSjmcneill 
7958a9ff04bSjmcneill 	sc->sc_dev = dev;
7968a9ff04bSjmcneill 	mutex_init(&sc->sc_corb_mtx, MUTEX_DEFAULT, IPL_AUDIO);
7978a9ff04bSjmcneill 	mutex_init(&sc->sc_stream_mtx, MUTEX_DEFAULT, IPL_AUDIO);
7988a9ff04bSjmcneill 
7998a9ff04bSjmcneill 	/*
8008a9ff04bSjmcneill 	 * Put the controller into a known state by entering and leaving
8018a9ff04bSjmcneill 	 * CRST as necessary.
8028a9ff04bSjmcneill 	 */
8038a9ff04bSjmcneill 	if ((err = hdaudio_reset(sc)) != 0)
8048a9ff04bSjmcneill 		goto fail;
8058a9ff04bSjmcneill 
8068a9ff04bSjmcneill 	/*
8078a9ff04bSjmcneill 	 * From the spec:
8088a9ff04bSjmcneill 	 *
8098a9ff04bSjmcneill 	 * Must wait 250us after reading CRST as a 1 before assuming that
8108a9ff04bSjmcneill 	 * codecs have all made status change requests and have been
8118a9ff04bSjmcneill 	 * registered by the controller.
8128a9ff04bSjmcneill 	 *
8138a9ff04bSjmcneill 	 * In reality, we need to wait longer than this.
8148a9ff04bSjmcneill 	 */
8158a9ff04bSjmcneill 	hda_delay(HDAUDIO_CODEC_DELAY);
816e83803dfSjmcneill 
817e83803dfSjmcneill 	/*
818e83803dfSjmcneill 	 * Read device capabilities
819e83803dfSjmcneill 	 */
820e83803dfSjmcneill 	hdaudio_init(sc);
821e83803dfSjmcneill 
822e83803dfSjmcneill 	/*
823e83803dfSjmcneill 	 * Detect codecs
824e83803dfSjmcneill 	 */
8258a9ff04bSjmcneill 	if (hdaudio_codec_probe(sc) == 0) {
8268a9ff04bSjmcneill 		hda_error(sc, "no codecs found\n");
8278a9ff04bSjmcneill 		err = ENODEV;
8288a9ff04bSjmcneill 		goto fail;
8298a9ff04bSjmcneill 	}
8308a9ff04bSjmcneill 
8318a9ff04bSjmcneill 	/*
8328a9ff04bSjmcneill 	 * Ensure that the device is in a known state
8338a9ff04bSjmcneill 	 */
8348a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
8358a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
8368a9ff04bSjmcneill 	    HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
8378a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
8388a9ff04bSjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
8398a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
8408a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
8418a9ff04bSjmcneill 
8428a9ff04bSjmcneill 	/*
8438a9ff04bSjmcneill 	 * Initialize the CORB. First negotiate a command buffer size,
8448a9ff04bSjmcneill 	 * then allocate and configure it.
8458a9ff04bSjmcneill 	 */
8468a9ff04bSjmcneill 	if ((err = hdaudio_corb_setsize(sc)) != 0)
8478a9ff04bSjmcneill 		goto fail;
8488a9ff04bSjmcneill 	if ((err = hdaudio_dma_alloc(sc, &sc->sc_corb, BUS_DMA_WRITE)) != 0)
8498a9ff04bSjmcneill 		goto fail;
8508a9ff04bSjmcneill 	if ((err = hdaudio_corb_config(sc)) != 0)
8518a9ff04bSjmcneill 		goto fail;
8528a9ff04bSjmcneill 
8538a9ff04bSjmcneill 	/*
8548a9ff04bSjmcneill 	 * Initialize the RIRB.
8558a9ff04bSjmcneill 	 */
8568a9ff04bSjmcneill 	if ((err = hdaudio_rirb_setsize(sc)) != 0)
8578a9ff04bSjmcneill 		goto fail;
8588a9ff04bSjmcneill 	if ((err = hdaudio_dma_alloc(sc, &sc->sc_rirb, BUS_DMA_READ)) != 0)
8598a9ff04bSjmcneill 		goto fail;
8608a9ff04bSjmcneill 	if ((err = hdaudio_rirb_config(sc)) != 0)
8618a9ff04bSjmcneill 		goto fail;
8628a9ff04bSjmcneill 
8638a9ff04bSjmcneill 	/*
8648a9ff04bSjmcneill 	 * Start the CORB and RIRB
8658a9ff04bSjmcneill 	 */
8668a9ff04bSjmcneill 	if ((err = hdaudio_corb_start(sc)) != 0)
8678a9ff04bSjmcneill 		goto fail;
8688a9ff04bSjmcneill 	if ((err = hdaudio_rirb_start(sc)) != 0)
8698a9ff04bSjmcneill 		goto fail;
8708a9ff04bSjmcneill 
8718a9ff04bSjmcneill 	/*
8728a9ff04bSjmcneill 	 * Identify and attach discovered codecs
8738a9ff04bSjmcneill 	 */
8748a9ff04bSjmcneill 	for (i = 0; i < HDAUDIO_MAX_CODECS; i++)
8758a9ff04bSjmcneill 		hdaudio_codec_attach(&sc->sc_codec[i]);
8768a9ff04bSjmcneill 
8778a9ff04bSjmcneill 	/*
8788a9ff04bSjmcneill 	 * Enable interrupts
8798a9ff04bSjmcneill 	 */
8808a9ff04bSjmcneill 	hdaudio_intr_enable(sc);
8818a9ff04bSjmcneill 
8828a9ff04bSjmcneill fail:
8838a9ff04bSjmcneill 	if (err)
8848a9ff04bSjmcneill 		hda_error(sc, "device driver failed to attach\n");
8858a9ff04bSjmcneill 	return err;
8868a9ff04bSjmcneill }
8878a9ff04bSjmcneill 
8888a9ff04bSjmcneill int
hdaudio_detach(struct hdaudio_softc * sc,int flags)8898a9ff04bSjmcneill hdaudio_detach(struct hdaudio_softc *sc, int flags)
8908a9ff04bSjmcneill {
8918a9ff04bSjmcneill 	int error;
8928a9ff04bSjmcneill 
8938a9ff04bSjmcneill 	/* Disable interrupts */
8948a9ff04bSjmcneill 	hdaudio_intr_disable(sc);
8958a9ff04bSjmcneill 
8968a9ff04bSjmcneill 	error = config_detach_children(sc->sc_dev, flags);
8978a9ff04bSjmcneill 	if (error != 0) {
8988a9ff04bSjmcneill 		hdaudio_intr_enable(sc);
8998a9ff04bSjmcneill 		return error;
9008a9ff04bSjmcneill 	}
9018a9ff04bSjmcneill 
9028a9ff04bSjmcneill 	mutex_destroy(&sc->sc_corb_mtx);
9038a9ff04bSjmcneill 	mutex_destroy(&sc->sc_stream_mtx);
9048a9ff04bSjmcneill 
9058a9ff04bSjmcneill 	hdaudio_dma_free(sc, &sc->sc_corb);
9068a9ff04bSjmcneill 	hdaudio_dma_free(sc, &sc->sc_rirb);
9078a9ff04bSjmcneill 
9088a9ff04bSjmcneill 	return 0;
9098a9ff04bSjmcneill }
9108a9ff04bSjmcneill 
9118a9ff04bSjmcneill bool
hdaudio_resume(struct hdaudio_softc * sc)9128a9ff04bSjmcneill hdaudio_resume(struct hdaudio_softc *sc)
9138a9ff04bSjmcneill {
9148a9ff04bSjmcneill 	if (hdaudio_reset(sc) != 0)
9158a9ff04bSjmcneill 		return false;
9168a9ff04bSjmcneill 
9178a9ff04bSjmcneill 	hda_delay(HDAUDIO_CODEC_DELAY);
9188a9ff04bSjmcneill 
9198a9ff04bSjmcneill 	/*
9208a9ff04bSjmcneill 	 * Ensure that the device is in a known state
9218a9ff04bSjmcneill 	 */
9228a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
9238a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
9248a9ff04bSjmcneill 	    HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
9258a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTSTS,
9268a9ff04bSjmcneill 	    hda_read4(sc, HDAUDIO_MMIO_INTSTS));
9278a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
9288a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
9298a9ff04bSjmcneill 
9308a9ff04bSjmcneill 	if (hdaudio_corb_config(sc) != 0)
9318a9ff04bSjmcneill 		return false;
9328a9ff04bSjmcneill 	if (hdaudio_rirb_config(sc) != 0)
9338a9ff04bSjmcneill 		return false;
9348a9ff04bSjmcneill 	if (hdaudio_corb_start(sc) != 0)
9358a9ff04bSjmcneill 		return false;
9368a9ff04bSjmcneill 	if (hdaudio_rirb_start(sc) != 0)
9378a9ff04bSjmcneill 		return false;
9388a9ff04bSjmcneill 
9398a9ff04bSjmcneill 	hdaudio_intr_enable(sc);
9408a9ff04bSjmcneill 
9418a9ff04bSjmcneill 	return true;
9428a9ff04bSjmcneill }
9438a9ff04bSjmcneill 
9448a9ff04bSjmcneill int
hdaudio_rescan(struct hdaudio_softc * sc,const char * ifattr,const int * locs)9458a9ff04bSjmcneill hdaudio_rescan(struct hdaudio_softc *sc, const char *ifattr, const int *locs)
9468a9ff04bSjmcneill {
9478a9ff04bSjmcneill 	struct hdaudio_codec *co;
9488a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
9498a9ff04bSjmcneill 	unsigned int codec;
9508a9ff04bSjmcneill 
9518a9ff04bSjmcneill 	for (codec = 0; codec < HDAUDIO_MAX_CODECS; codec++) {
9528a9ff04bSjmcneill 		co = &sc->sc_codec[codec];
9538a9ff04bSjmcneill 		fg = co->co_fg;
9548a9ff04bSjmcneill 		if (!co->co_valid || fg == NULL)
9558a9ff04bSjmcneill 			continue;
9568a9ff04bSjmcneill 		if (fg->fg_device)
9578a9ff04bSjmcneill 			continue;
9588a9ff04bSjmcneill 		hdaudio_attach_fg(fg, NULL);
9598a9ff04bSjmcneill 	}
9608a9ff04bSjmcneill 
9618a9ff04bSjmcneill 	return 0;
9628a9ff04bSjmcneill }
9638a9ff04bSjmcneill 
9648a9ff04bSjmcneill void
hdaudio_childdet(struct hdaudio_softc * sc,device_t child)9658a9ff04bSjmcneill hdaudio_childdet(struct hdaudio_softc *sc, device_t child)
9668a9ff04bSjmcneill {
9678a9ff04bSjmcneill 	struct hdaudio_codec *co;
9688a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
9698a9ff04bSjmcneill 	unsigned int codec;
9708a9ff04bSjmcneill 
9718a9ff04bSjmcneill 	for (codec = 0; codec < HDAUDIO_MAX_CODECS; codec++) {
9728a9ff04bSjmcneill 		co = &sc->sc_codec[codec];
9738a9ff04bSjmcneill 		fg = co->co_fg;
9748a9ff04bSjmcneill 		if (!co->co_valid || fg == NULL)
9758a9ff04bSjmcneill 			continue;
9768a9ff04bSjmcneill 		if (fg->fg_device == child)
9778a9ff04bSjmcneill 			fg->fg_device = NULL;
9788a9ff04bSjmcneill 	}
9798a9ff04bSjmcneill }
9808a9ff04bSjmcneill 
9818a9ff04bSjmcneill int
hdaudio_intr(struct hdaudio_softc * sc)9828a9ff04bSjmcneill hdaudio_intr(struct hdaudio_softc *sc)
9838a9ff04bSjmcneill {
9848a9ff04bSjmcneill 	struct hdaudio_stream *st;
9858a9ff04bSjmcneill 	uint32_t intsts, stream_mask;
9868a9ff04bSjmcneill 	int streamid = 0;
9878a9ff04bSjmcneill 	uint8_t rirbsts;
9888a9ff04bSjmcneill 
9898a9ff04bSjmcneill 	intsts = hda_read4(sc, HDAUDIO_MMIO_INTSTS);
9908a9ff04bSjmcneill 	if (!(intsts & HDAUDIO_INTSTS_GIS))
9918a9ff04bSjmcneill 		return 0;
9928a9ff04bSjmcneill 
9938a9ff04bSjmcneill 	if (intsts & HDAUDIO_INTSTS_CIS) {
9948a9ff04bSjmcneill 		rirbsts = hda_read1(sc, HDAUDIO_MMIO_RIRBSTS);
9958a9ff04bSjmcneill 		if (rirbsts & HDAUDIO_RIRBSTS_RINTFL) {
9968a9ff04bSjmcneill 			mutex_enter(&sc->sc_corb_mtx);
9978a9ff04bSjmcneill 			hdaudio_rirb_dequeue(sc, true);
9988a9ff04bSjmcneill 			mutex_exit(&sc->sc_corb_mtx);
9998a9ff04bSjmcneill 		}
10008a9ff04bSjmcneill 		if (rirbsts & (HDAUDIO_RIRBSTS_RIRBOIS|HDAUDIO_RIRBSTS_RINTFL))
10018a9ff04bSjmcneill 			hda_write1(sc, HDAUDIO_MMIO_RIRBSTS, rirbsts);
10028a9ff04bSjmcneill 		hda_write4(sc, HDAUDIO_MMIO_INTSTS, HDAUDIO_INTSTS_CIS);
10038a9ff04bSjmcneill 	}
10048a9ff04bSjmcneill 	if (intsts & HDAUDIO_INTSTS_SIS_MASK) {
10058a9ff04bSjmcneill 		mutex_enter(&sc->sc_stream_mtx);
10068a9ff04bSjmcneill 		stream_mask = intsts & sc->sc_stream_mask;
10078a9ff04bSjmcneill 		while (streamid < HDAUDIO_MAX_STREAMS && stream_mask != 0) {
10088a9ff04bSjmcneill 			st = &sc->sc_stream[streamid++];
10098a9ff04bSjmcneill 			if ((stream_mask & 1) != 0 && st->st_intr) {
10108a9ff04bSjmcneill 				st->st_intr(st);
10118a9ff04bSjmcneill 			}
10128a9ff04bSjmcneill 			stream_mask >>= 1;
10138a9ff04bSjmcneill 		}
10148a9ff04bSjmcneill 		mutex_exit(&sc->sc_stream_mtx);
10158a9ff04bSjmcneill 		hda_write4(sc, HDAUDIO_MMIO_INTSTS, HDAUDIO_INTSTS_SIS_MASK);
10168a9ff04bSjmcneill 	}
10178a9ff04bSjmcneill 
10188a9ff04bSjmcneill 	return 1;
10198a9ff04bSjmcneill }
10208a9ff04bSjmcneill 
10218a9ff04bSjmcneill struct hdaudio_stream *
hdaudio_stream_establish(struct hdaudio_softc * sc,enum hdaudio_stream_type type,int (* intr)(struct hdaudio_stream *),void * cookie)10228a9ff04bSjmcneill hdaudio_stream_establish(struct hdaudio_softc *sc,
10238a9ff04bSjmcneill     enum hdaudio_stream_type type, int (*intr)(struct hdaudio_stream *),
10248a9ff04bSjmcneill     void *cookie)
10258a9ff04bSjmcneill {
10268a9ff04bSjmcneill 	struct hdaudio_stream *st;
10278a9ff04bSjmcneill 	struct hdaudio_dma dma;
10288a9ff04bSjmcneill 	int i, err;
10298a9ff04bSjmcneill 
10308a9ff04bSjmcneill 	dma.dma_size = sizeof(struct hdaudio_bdl_entry) * HDAUDIO_BDL_MAX;
103116adabdcSriastradh 	dma.dma_sizereg = 0;
10328a9ff04bSjmcneill 	err = hdaudio_dma_alloc(sc, &dma, BUS_DMA_COHERENT | BUS_DMA_NOCACHE);
10338a9ff04bSjmcneill 	if (err)
10348a9ff04bSjmcneill 		return NULL;
10358a9ff04bSjmcneill 
10368a9ff04bSjmcneill 	mutex_enter(&sc->sc_stream_mtx);
10378a9ff04bSjmcneill 	for (i = 0; i < HDAUDIO_MAX_STREAMS; i++) {
10388a9ff04bSjmcneill 		st = &sc->sc_stream[i];
10398a9ff04bSjmcneill 		if (st->st_enable == false)
10408a9ff04bSjmcneill 			break;
10418a9ff04bSjmcneill 		if (st->st_type != type)
10428a9ff04bSjmcneill 			continue;
10438a9ff04bSjmcneill 		if (sc->sc_stream_mask & (1 << i))
10448a9ff04bSjmcneill 			continue;
10458a9ff04bSjmcneill 
10468a9ff04bSjmcneill 		/* Allocate stream */
10478a9ff04bSjmcneill 		st->st_bdl = dma;
10488a9ff04bSjmcneill 		st->st_intr = intr;
10498a9ff04bSjmcneill 		st->st_cookie = cookie;
10508a9ff04bSjmcneill 		sc->sc_stream_mask |= (1 << i);
10518a9ff04bSjmcneill 		mutex_exit(&sc->sc_stream_mtx);
10528a9ff04bSjmcneill 		return st;
10538a9ff04bSjmcneill 	}
10548a9ff04bSjmcneill 	mutex_exit(&sc->sc_stream_mtx);
10558a9ff04bSjmcneill 
10568a9ff04bSjmcneill 	/* No streams of requested type available */
10578a9ff04bSjmcneill 	hdaudio_dma_free(sc, &dma);
10588a9ff04bSjmcneill 	return NULL;
10598a9ff04bSjmcneill }
10608a9ff04bSjmcneill 
10618a9ff04bSjmcneill void
hdaudio_stream_disestablish(struct hdaudio_stream * st)10628a9ff04bSjmcneill hdaudio_stream_disestablish(struct hdaudio_stream *st)
10638a9ff04bSjmcneill {
10648a9ff04bSjmcneill 	struct hdaudio_softc *sc = st->st_host;
10658a9ff04bSjmcneill 	struct hdaudio_dma dma;
10668a9ff04bSjmcneill 
10678a9ff04bSjmcneill 	KASSERT(sc->sc_stream_mask & (1 << st->st_shift));
10688a9ff04bSjmcneill 
10698a9ff04bSjmcneill 	mutex_enter(&sc->sc_stream_mtx);
10708a9ff04bSjmcneill 	sc->sc_stream_mask &= ~(1 << st->st_shift);
10718a9ff04bSjmcneill 	st->st_intr = NULL;
10728a9ff04bSjmcneill 	st->st_cookie = NULL;
10738a9ff04bSjmcneill 	dma = st->st_bdl;
10748a9ff04bSjmcneill 	st->st_bdl.dma_valid = false;
10758a9ff04bSjmcneill 	mutex_exit(&sc->sc_stream_mtx);
10768a9ff04bSjmcneill 
10778a9ff04bSjmcneill 	/* Can't bus_dmamem_unmap while holding a mutex.  */
10788a9ff04bSjmcneill 	hdaudio_dma_free(sc, &dma);
10798a9ff04bSjmcneill }
10808a9ff04bSjmcneill 
10818a9ff04bSjmcneill /*
1082*2e9df72eSandvar  * Convert most of audio_params_t to stream fmt descriptor; noticeably missing
10838a9ff04bSjmcneill  * is the # channels bits, as this is encoded differently in codec and
10848a9ff04bSjmcneill  * stream descriptors.
10858a9ff04bSjmcneill  *
10868a9ff04bSjmcneill  * TODO: validate that the stream and selected codecs can handle the fmt
10878a9ff04bSjmcneill  */
10888a9ff04bSjmcneill uint16_t
hdaudio_stream_param(struct hdaudio_stream * st,const audio_params_t * param)10898a9ff04bSjmcneill hdaudio_stream_param(struct hdaudio_stream *st, const audio_params_t *param)
10908a9ff04bSjmcneill {
10918a9ff04bSjmcneill 	uint16_t fmt = 0;
10928a9ff04bSjmcneill 
10938a9ff04bSjmcneill 	switch (param->encoding) {
10948a9ff04bSjmcneill 	case AUDIO_ENCODING_AC3:
10958a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_TYPE_NONPCM;
10968a9ff04bSjmcneill 		break;
10978a9ff04bSjmcneill 	default:
10988a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_TYPE_PCM;
10998a9ff04bSjmcneill 		break;
11008a9ff04bSjmcneill 	}
11018a9ff04bSjmcneill 
11028a9ff04bSjmcneill 	switch (param->sample_rate) {
11038a9ff04bSjmcneill 	case 8000:
11048a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1) |
11058a9ff04bSjmcneill 		    HDAUDIO_FMT_DIV(6);
11068a9ff04bSjmcneill 		break;
11078a9ff04bSjmcneill 	case 11025:
11088a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1) |
11098a9ff04bSjmcneill 		    HDAUDIO_FMT_DIV(4);
11108a9ff04bSjmcneill 		break;
11118a9ff04bSjmcneill 	case 16000:
11128a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1) |
11138a9ff04bSjmcneill 		    HDAUDIO_FMT_DIV(3);
11148a9ff04bSjmcneill 		break;
11158a9ff04bSjmcneill 	case 22050:
11168a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1) |
11178a9ff04bSjmcneill 		    HDAUDIO_FMT_DIV(2);
11188a9ff04bSjmcneill 		break;
11198a9ff04bSjmcneill 	case 32000:
11208a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(2) |
11218a9ff04bSjmcneill 		    HDAUDIO_FMT_DIV(3);
11228a9ff04bSjmcneill 		break;
11238a9ff04bSjmcneill 	case 44100:
11248a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1);
11258a9ff04bSjmcneill 		break;
11268a9ff04bSjmcneill 	case 48000:
11278a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1);
11288a9ff04bSjmcneill 		break;
11298a9ff04bSjmcneill 	case 88200:
11308a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(2);
11318a9ff04bSjmcneill 		break;
11328a9ff04bSjmcneill 	case 96000:
11338a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(2);
11348a9ff04bSjmcneill 		break;
11358a9ff04bSjmcneill 	case 176400:
11368a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(4);
11378a9ff04bSjmcneill 		break;
11388a9ff04bSjmcneill 	case 192000:
11398a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(4);
11408a9ff04bSjmcneill 		break;
11418a9ff04bSjmcneill 	default:
11428a9ff04bSjmcneill 		return 0;
11438a9ff04bSjmcneill 	}
11448a9ff04bSjmcneill 
11458a9ff04bSjmcneill 	if (param->precision == 16 && param->validbits == 8)
11468a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BITS_8_16;
11478a9ff04bSjmcneill 	else if (param->precision == 16 && param->validbits == 16)
11488a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BITS_16_16;
11498a9ff04bSjmcneill 	else if (param->precision == 32 && param->validbits == 20)
11508a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BITS_20_32;
11518a9ff04bSjmcneill 	else if (param->precision == 32 && param->validbits == 24)
11528a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BITS_24_32;
11538a9ff04bSjmcneill 	else if (param->precision == 32 && param->validbits == 32)
11548a9ff04bSjmcneill 		fmt |= HDAUDIO_FMT_BITS_32_32;
11558a9ff04bSjmcneill 	else
11568a9ff04bSjmcneill 		return 0;
11578a9ff04bSjmcneill 
11588a9ff04bSjmcneill 	return fmt;
11598a9ff04bSjmcneill }
11608a9ff04bSjmcneill 
11618a9ff04bSjmcneill void
hdaudio_stream_reset(struct hdaudio_stream * st)11628a9ff04bSjmcneill hdaudio_stream_reset(struct hdaudio_stream *st)
11638a9ff04bSjmcneill {
11648a9ff04bSjmcneill 	struct hdaudio_softc *sc = st->st_host;
11658a9ff04bSjmcneill 	int snum = st->st_shift;
11668a9ff04bSjmcneill 	int retry;
11678a9ff04bSjmcneill 	uint8_t ctl0;
11688a9ff04bSjmcneill 
11698a9ff04bSjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
11708a9ff04bSjmcneill 	ctl0 |= HDAUDIO_CTL_SRST;
11718a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
11728a9ff04bSjmcneill 
11738a9ff04bSjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
11748a9ff04bSjmcneill 	do {
11758a9ff04bSjmcneill 		ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
11768a9ff04bSjmcneill 		if (ctl0 & HDAUDIO_CTL_SRST)
11778a9ff04bSjmcneill 			break;
11788a9ff04bSjmcneill 		hda_delay(10);
11798a9ff04bSjmcneill 	} while (--retry > 0);
11808a9ff04bSjmcneill 
11818a9ff04bSjmcneill 	ctl0 &= ~HDAUDIO_CTL_SRST;
11828a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
11838a9ff04bSjmcneill 
11848a9ff04bSjmcneill 	retry = HDAUDIO_RESET_TIMEOUT;
11858a9ff04bSjmcneill 	do {
11868a9ff04bSjmcneill 		ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
11878a9ff04bSjmcneill 		if (!(ctl0 & HDAUDIO_CTL_SRST))
11888a9ff04bSjmcneill 			break;
11898a9ff04bSjmcneill 		hda_delay(10);
11908a9ff04bSjmcneill 	} while (--retry > 0);
11918a9ff04bSjmcneill 	if (retry == 0) {
11928a9ff04bSjmcneill 		hda_error(sc, "timeout leaving stream reset state\n");
11938a9ff04bSjmcneill 		return;
11948a9ff04bSjmcneill 	}
11958a9ff04bSjmcneill }
11968a9ff04bSjmcneill 
11978a9ff04bSjmcneill void
hdaudio_stream_start(struct hdaudio_stream * st,int blksize,bus_size_t dmasize,const audio_params_t * params)11988a9ff04bSjmcneill hdaudio_stream_start(struct hdaudio_stream *st, int blksize,
11998a9ff04bSjmcneill     bus_size_t dmasize, const audio_params_t *params)
12008a9ff04bSjmcneill {
12018a9ff04bSjmcneill 	struct hdaudio_softc *sc = st->st_host;
12028a9ff04bSjmcneill 	struct hdaudio_bdl_entry *bdl;
12038a9ff04bSjmcneill 	uint64_t dmaaddr;
12048a9ff04bSjmcneill 	uint32_t intctl;
12058a9ff04bSjmcneill 	uint16_t fmt;
12068a9ff04bSjmcneill 	uint8_t ctl0, ctl2;
12078a9ff04bSjmcneill 	int cnt, snum = st->st_shift;
12088a9ff04bSjmcneill 
12098a9ff04bSjmcneill 	KASSERT(sc->sc_stream_mask & (1 << st->st_shift));
12108a9ff04bSjmcneill 	KASSERT(st->st_data.dma_valid == true);
12118a9ff04bSjmcneill 	KASSERT(st->st_bdl.dma_valid == true);
12128a9ff04bSjmcneill 
12138a9ff04bSjmcneill 	hdaudio_stream_stop(st);
12148a9ff04bSjmcneill 	hdaudio_stream_reset(st);
12158a9ff04bSjmcneill 
12168a9ff04bSjmcneill 	/*
12178a9ff04bSjmcneill 	 * Configure buffer descriptor list
12188a9ff04bSjmcneill 	 */
12198a9ff04bSjmcneill 	dmaaddr = DMA_DMAADDR(&st->st_data);
12208a9ff04bSjmcneill 	bdl = DMA_KERNADDR(&st->st_bdl);
12218a9ff04bSjmcneill 	for (cnt = 0; cnt < HDAUDIO_BDL_MAX; cnt++) {
12228a9ff04bSjmcneill 		bdl[cnt].address_lo = (uint32_t)dmaaddr;
12238a9ff04bSjmcneill 		bdl[cnt].address_hi = dmaaddr >> 32;
12248a9ff04bSjmcneill 		bdl[cnt].length = blksize;
12258a9ff04bSjmcneill 		bdl[cnt].flags = HDAUDIO_BDL_ENTRY_IOC;
12268a9ff04bSjmcneill 		dmaaddr += blksize;
12278a9ff04bSjmcneill 		if (dmaaddr >= DMA_DMAADDR(&st->st_data) + dmasize) {
12288a9ff04bSjmcneill 			cnt++;
12298a9ff04bSjmcneill 			break;
12308a9ff04bSjmcneill 		}
12318a9ff04bSjmcneill 	}
12328a9ff04bSjmcneill 
12338a9ff04bSjmcneill 	/*
12348a9ff04bSjmcneill 	 * Program buffer descriptor list
12358a9ff04bSjmcneill 	 */
12368a9ff04bSjmcneill 	dmaaddr = DMA_DMAADDR(&st->st_bdl);
12378a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_SD_BDPL(snum), (uint32_t)dmaaddr);
12388a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_SD_BDPU(snum), (uint32_t)(dmaaddr >> 32));
12398a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_SD_LVI(snum), (cnt - 1) & 0xff);
12408a9ff04bSjmcneill 
12418a9ff04bSjmcneill 	/*
12428a9ff04bSjmcneill 	 * Program cyclic buffer length
12438a9ff04bSjmcneill 	 */
12448a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_SD_CBL(snum), dmasize);
12458a9ff04bSjmcneill 
12468a9ff04bSjmcneill 	/*
12478a9ff04bSjmcneill 	 * Program stream number (tag). Although controller hardware is
12488a9ff04bSjmcneill 	 * capable of transmitting any stream number (0-15), by convention
12498a9ff04bSjmcneill 	 * stream 0 is reserved as unused by software, so that converters
12508a9ff04bSjmcneill 	 * whose stream numbers have been reset to 0 do not unintentionally
12518a9ff04bSjmcneill 	 * decode data not intended for them.
12528a9ff04bSjmcneill 	 */
12538a9ff04bSjmcneill 	ctl2 = hda_read1(sc, HDAUDIO_SD_CTL2(snum));
12548a9ff04bSjmcneill 	ctl2 &= ~0xf0;
12558a9ff04bSjmcneill 	ctl2 |= hdaudio_stream_tag(st) << 4;
12568a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL2(snum), ctl2);
12578a9ff04bSjmcneill 
12588a9ff04bSjmcneill 	/*
12598a9ff04bSjmcneill 	 * Program stream format
12608a9ff04bSjmcneill 	 */
12618a9ff04bSjmcneill 	fmt = hdaudio_stream_param(st, params) |
12628a9ff04bSjmcneill 	    HDAUDIO_FMT_CHAN(params->channels);
12638a9ff04bSjmcneill 	hda_write2(sc, HDAUDIO_SD_FMT(snum), fmt);
12648a9ff04bSjmcneill 
12658a9ff04bSjmcneill 	/*
12668a9ff04bSjmcneill 	 * Switch on interrupts for this stream
12678a9ff04bSjmcneill 	 */
12688a9ff04bSjmcneill 	intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
12698a9ff04bSjmcneill 	intctl |= (1 << st->st_shift);
12708a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
12718a9ff04bSjmcneill 
12728a9ff04bSjmcneill 	/*
12738a9ff04bSjmcneill 	 * Start running the stream
12748a9ff04bSjmcneill 	 */
12758a9ff04bSjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
12768a9ff04bSjmcneill 	ctl0 |= HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
12778a9ff04bSjmcneill 	    HDAUDIO_CTL_RUN;
12788a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
12798a9ff04bSjmcneill }
12808a9ff04bSjmcneill 
12818a9ff04bSjmcneill void
hdaudio_stream_stop(struct hdaudio_stream * st)12828a9ff04bSjmcneill hdaudio_stream_stop(struct hdaudio_stream *st)
12838a9ff04bSjmcneill {
12848a9ff04bSjmcneill 	struct hdaudio_softc *sc = st->st_host;
12858a9ff04bSjmcneill 	uint32_t intctl;
12868a9ff04bSjmcneill 	uint8_t ctl0;
12878a9ff04bSjmcneill 	int snum = st->st_shift;
12888a9ff04bSjmcneill 
12898a9ff04bSjmcneill 	/*
12908a9ff04bSjmcneill 	 * Stop running the stream
12918a9ff04bSjmcneill 	 */
12928a9ff04bSjmcneill 	ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
12938a9ff04bSjmcneill 	ctl0 &= ~(HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
12948a9ff04bSjmcneill 	    HDAUDIO_CTL_RUN);
12958a9ff04bSjmcneill 	hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
12968a9ff04bSjmcneill 
12978a9ff04bSjmcneill 	/*
12988a9ff04bSjmcneill 	 * Switch off interrupts for this stream
12998a9ff04bSjmcneill 	 */
13008a9ff04bSjmcneill 	intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
13018a9ff04bSjmcneill 	intctl &= ~(1 << st->st_shift);
13028a9ff04bSjmcneill 	hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
13038a9ff04bSjmcneill }
13048a9ff04bSjmcneill 
13058a9ff04bSjmcneill /*
13068a9ff04bSjmcneill  * /dev/hdaudioN interface
13078a9ff04bSjmcneill  */
13088a9ff04bSjmcneill 
13098a9ff04bSjmcneill static const char *
hdaudioioctl_fgrp_to_cstr(enum function_group_type type)13108a9ff04bSjmcneill hdaudioioctl_fgrp_to_cstr(enum function_group_type type)
13118a9ff04bSjmcneill {
13128a9ff04bSjmcneill 	switch (type) {
13138a9ff04bSjmcneill 	case HDAUDIO_GROUP_TYPE_AFG:
13148a9ff04bSjmcneill 		return "afg";
13158a9ff04bSjmcneill 	case HDAUDIO_GROUP_TYPE_VSM_FG:
13168a9ff04bSjmcneill 		return "vsmfg";
13178a9ff04bSjmcneill 	default:
13188a9ff04bSjmcneill 		return "unknown";
13198a9ff04bSjmcneill 	}
13208a9ff04bSjmcneill }
13218a9ff04bSjmcneill 
13228a9ff04bSjmcneill static struct hdaudio_function_group *
hdaudioioctl_fgrp_lookup(struct hdaudio_softc * sc,int codecid,int nid)13238a9ff04bSjmcneill hdaudioioctl_fgrp_lookup(struct hdaudio_softc *sc, int codecid, int nid)
13248a9ff04bSjmcneill {
13258a9ff04bSjmcneill 	struct hdaudio_codec *co;
13268a9ff04bSjmcneill 	struct hdaudio_function_group *fg = NULL;
13278a9ff04bSjmcneill 	int i;
13288a9ff04bSjmcneill 
13298a9ff04bSjmcneill 	if (codecid < 0 || codecid >= HDAUDIO_MAX_CODECS)
13308a9ff04bSjmcneill 		return NULL;
13318a9ff04bSjmcneill 	co = &sc->sc_codec[codecid];
13328a9ff04bSjmcneill 	if (co->co_valid == false)
13338a9ff04bSjmcneill 		return NULL;
13348a9ff04bSjmcneill 
13358a9ff04bSjmcneill 	for (i = 0; i < co->co_nfg; i++)
13368a9ff04bSjmcneill 		if (co->co_fg[i].fg_nid == nid) {
13378a9ff04bSjmcneill 			fg = &co->co_fg[i];
13388a9ff04bSjmcneill 			break;
13398a9ff04bSjmcneill 		}
13408a9ff04bSjmcneill 
13418a9ff04bSjmcneill 	return fg;
13428a9ff04bSjmcneill }
13438a9ff04bSjmcneill 
13448a9ff04bSjmcneill static int
hdaudioioctl_fgrp_info(struct hdaudio_softc * sc,prop_dictionary_t request,prop_dictionary_t response)13458a9ff04bSjmcneill hdaudioioctl_fgrp_info(struct hdaudio_softc *sc, prop_dictionary_t request,
13468a9ff04bSjmcneill     prop_dictionary_t response)
13478a9ff04bSjmcneill {
13488a9ff04bSjmcneill 	struct hdaudio_codec *co;
13498a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
13508a9ff04bSjmcneill 	prop_array_t array;
13518a9ff04bSjmcneill 	prop_dictionary_t dict;
13528a9ff04bSjmcneill 	int codecid, fgid;
13538a9ff04bSjmcneill 
13548a9ff04bSjmcneill 	array = prop_array_create();
13558a9ff04bSjmcneill 	if (array == NULL)
13568a9ff04bSjmcneill 		return ENOMEM;
13578a9ff04bSjmcneill 
13588a9ff04bSjmcneill 	for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++) {
13598a9ff04bSjmcneill 		co = &sc->sc_codec[codecid];
13608a9ff04bSjmcneill 		if (co->co_valid == false)
13618a9ff04bSjmcneill 			continue;
13628a9ff04bSjmcneill 		for (fgid = 0; fgid < co->co_nfg; fgid++) {
13638a9ff04bSjmcneill 			fg = &co->co_fg[fgid];
13648a9ff04bSjmcneill 			dict = prop_dictionary_create();
13658a9ff04bSjmcneill 			if (dict == NULL)
13668a9ff04bSjmcneill 				return ENOMEM;
1367814a7798Sthorpej 			prop_dictionary_set_string_nocopy(dict,
13688a9ff04bSjmcneill 			    "type", hdaudioioctl_fgrp_to_cstr(fg->fg_type));
13698a9ff04bSjmcneill 			prop_dictionary_set_int16(dict, "nid", fg->fg_nid);
13708a9ff04bSjmcneill 			prop_dictionary_set_int16(dict, "codecid", codecid);
13718a9ff04bSjmcneill 			prop_dictionary_set_uint16(dict, "vendor-id",
13728a9ff04bSjmcneill 			    fg->fg_vendor);
13738a9ff04bSjmcneill 			prop_dictionary_set_uint16(dict, "product-id",
13748a9ff04bSjmcneill 			    fg->fg_product);
13758a9ff04bSjmcneill 			prop_dictionary_set_uint32(dict, "subsystem-id",
13768a9ff04bSjmcneill 			    sc->sc_subsystem);
13778a9ff04bSjmcneill 			if (fg->fg_device)
1378814a7798Sthorpej 				prop_dictionary_set_string(dict, "device",
13798a9ff04bSjmcneill 				    device_xname(fg->fg_device));
13808a9ff04bSjmcneill 			else
1381814a7798Sthorpej 				prop_dictionary_set_string_nocopy(dict,
13828a9ff04bSjmcneill 				    "device", "<none>");
13838a9ff04bSjmcneill 			prop_array_add(array, dict);
13848a9ff04bSjmcneill 		}
13858a9ff04bSjmcneill 	}
13868a9ff04bSjmcneill 
13878a9ff04bSjmcneill 	prop_dictionary_set(response, "function-group-info", array);
13888a9ff04bSjmcneill 	return 0;
13898a9ff04bSjmcneill }
13908a9ff04bSjmcneill 
13918a9ff04bSjmcneill static int
hdaudioioctl_fgrp_getconfig(struct hdaudio_softc * sc,prop_dictionary_t request,prop_dictionary_t response)13928a9ff04bSjmcneill hdaudioioctl_fgrp_getconfig(struct hdaudio_softc *sc,
13938a9ff04bSjmcneill     prop_dictionary_t request, prop_dictionary_t response)
13948a9ff04bSjmcneill {
13958a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
13968a9ff04bSjmcneill 	prop_dictionary_t dict;
13978a9ff04bSjmcneill 	prop_array_t array;
13988a9ff04bSjmcneill 	uint32_t nodecnt, wcap, config;
13998a9ff04bSjmcneill 	int16_t codecid, nid, i;
14008a9ff04bSjmcneill 	int startnode, endnode;
14018a9ff04bSjmcneill 
14028a9ff04bSjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14038a9ff04bSjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14048a9ff04bSjmcneill 		return EINVAL;
14058a9ff04bSjmcneill 
14068a9ff04bSjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14078a9ff04bSjmcneill 	if (fg == NULL)
14088a9ff04bSjmcneill 		return ENODEV;
14098a9ff04bSjmcneill 
14108a9ff04bSjmcneill 	array = prop_array_create();
14118a9ff04bSjmcneill 	if (array == NULL)
14128a9ff04bSjmcneill 		return ENOMEM;
14138a9ff04bSjmcneill 
14148a9ff04bSjmcneill 	nodecnt = hdaudio_command(fg->fg_codec, fg->fg_nid,
14158a9ff04bSjmcneill 	    CORB_GET_PARAMETER, COP_SUBORDINATE_NODE_COUNT);
14168a9ff04bSjmcneill 	startnode = COP_NODECNT_STARTNODE(nodecnt);
14178a9ff04bSjmcneill 	endnode = startnode + COP_NODECNT_NUMNODES(nodecnt);
14188a9ff04bSjmcneill 
14198a9ff04bSjmcneill 	for (i = startnode; i < endnode; i++) {
14208a9ff04bSjmcneill 		wcap = hdaudio_command(fg->fg_codec, i,
14218a9ff04bSjmcneill 		    CORB_GET_PARAMETER, COP_AUDIO_WIDGET_CAPABILITIES);
14228a9ff04bSjmcneill 		if (COP_AWCAP_TYPE(wcap) != COP_AWCAP_TYPE_PIN_COMPLEX)
14238a9ff04bSjmcneill 			continue;
14248a9ff04bSjmcneill 		config = hdaudio_command(fg->fg_codec, i,
14258a9ff04bSjmcneill 		    CORB_GET_CONFIGURATION_DEFAULT, 0);
14268a9ff04bSjmcneill 		dict = prop_dictionary_create();
14278a9ff04bSjmcneill 		if (dict == NULL)
14288a9ff04bSjmcneill 			return ENOMEM;
14298a9ff04bSjmcneill 		prop_dictionary_set_int16(dict, "nid", i);
14308a9ff04bSjmcneill 		prop_dictionary_set_uint32(dict, "config", config);
14318a9ff04bSjmcneill 		prop_array_add(array, dict);
14328a9ff04bSjmcneill 	}
14338a9ff04bSjmcneill 
14348a9ff04bSjmcneill 	prop_dictionary_set(response, "pin-config", array);
14358a9ff04bSjmcneill 
14368a9ff04bSjmcneill 	return 0;
14378a9ff04bSjmcneill }
14388a9ff04bSjmcneill 
14398a9ff04bSjmcneill static int
hdaudioioctl_fgrp_setconfig(struct hdaudio_softc * sc,prop_dictionary_t request,prop_dictionary_t response)14408a9ff04bSjmcneill hdaudioioctl_fgrp_setconfig(struct hdaudio_softc *sc,
14418a9ff04bSjmcneill     prop_dictionary_t request, prop_dictionary_t response)
14428a9ff04bSjmcneill {
14438a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
14448a9ff04bSjmcneill 	prop_array_t config;
14458a9ff04bSjmcneill 	int16_t codecid, nid;
14468a9ff04bSjmcneill 	int err;
14478a9ff04bSjmcneill 
14488a9ff04bSjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14498a9ff04bSjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14508a9ff04bSjmcneill 		return EINVAL;
14518a9ff04bSjmcneill 
14528a9ff04bSjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14538a9ff04bSjmcneill 	if (fg == NULL)
14548a9ff04bSjmcneill 		return ENODEV;
14558a9ff04bSjmcneill 
14568a9ff04bSjmcneill 	if (fg->fg_device) {
14578a9ff04bSjmcneill 		err = config_detach(fg->fg_device, 0);
14588a9ff04bSjmcneill 		if (err)
14598a9ff04bSjmcneill 			return err;
14608a9ff04bSjmcneill 		fg->fg_device = NULL;
14618a9ff04bSjmcneill 	}
14628a9ff04bSjmcneill 
14638a9ff04bSjmcneill 	/* "pin-config" may be NULL, this means "use BIOS configuration" */
14648a9ff04bSjmcneill 	config = prop_dictionary_get(request, "pin-config");
14658a9ff04bSjmcneill 	if (config && prop_object_type(config) != PROP_TYPE_ARRAY) {
14668a9ff04bSjmcneill 		prop_object_release(config);
14678a9ff04bSjmcneill 		return EINVAL;
14688a9ff04bSjmcneill 	}
14698a9ff04bSjmcneill 	hdaudio_attach_fg(fg, config);
14708a9ff04bSjmcneill 	if (config)
14718a9ff04bSjmcneill 		prop_object_release(config);
14728a9ff04bSjmcneill 
14738a9ff04bSjmcneill 	return 0;
14748a9ff04bSjmcneill }
14758a9ff04bSjmcneill 
14768a9ff04bSjmcneill static int
hdaudio_dispatch_fgrp_ioctl(struct hdaudio_softc * sc,u_long cmd,prop_dictionary_t request,prop_dictionary_t response)14778a9ff04bSjmcneill hdaudio_dispatch_fgrp_ioctl(struct hdaudio_softc *sc, u_long cmd,
14788a9ff04bSjmcneill     prop_dictionary_t request, prop_dictionary_t response)
14798a9ff04bSjmcneill {
14808a9ff04bSjmcneill 	struct hdaudio_function_group *fg;
14818a9ff04bSjmcneill 	int (*infocb)(void *, prop_dictionary_t, prop_dictionary_t);
14828a9ff04bSjmcneill 	prop_dictionary_t fgrp_dict;
14838a9ff04bSjmcneill 	uint64_t info_fn;
14848a9ff04bSjmcneill 	int16_t codecid, nid;
14858a9ff04bSjmcneill 	void *fgrp_sc;
14868a9ff04bSjmcneill 	bool rv;
14878a9ff04bSjmcneill 	int err;
14888a9ff04bSjmcneill 
14898a9ff04bSjmcneill 	if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
14908a9ff04bSjmcneill 	    !prop_dictionary_get_int16(request, "nid", &nid))
14918a9ff04bSjmcneill 		return EINVAL;
14928a9ff04bSjmcneill 
14938a9ff04bSjmcneill 	fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
14948a9ff04bSjmcneill 	if (fg == NULL)
14958a9ff04bSjmcneill 		return ENODEV;
14968a9ff04bSjmcneill 	if (fg->fg_device == NULL)
14978a9ff04bSjmcneill 		return ENXIO;
14988a9ff04bSjmcneill 	fgrp_sc = device_private(fg->fg_device);
14998a9ff04bSjmcneill 	fgrp_dict = device_properties(fg->fg_device);
15008a9ff04bSjmcneill 
15018a9ff04bSjmcneill 	switch (fg->fg_type) {
15028a9ff04bSjmcneill 	case HDAUDIO_GROUP_TYPE_AFG:
15038a9ff04bSjmcneill 		switch (cmd) {
15048a9ff04bSjmcneill 		case HDAUDIO_FGRP_CODEC_INFO:
15058a9ff04bSjmcneill 			rv = prop_dictionary_get_uint64(fgrp_dict,
15068a9ff04bSjmcneill 			    "codecinfo-callback", &info_fn);
15078a9ff04bSjmcneill 			if (!rv)
15088a9ff04bSjmcneill 				return ENXIO;
15098a9ff04bSjmcneill 			infocb = (void *)(uintptr_t)info_fn;
15108a9ff04bSjmcneill 			err = infocb(fgrp_sc, request, response);
15118a9ff04bSjmcneill 			break;
15128a9ff04bSjmcneill 		case HDAUDIO_FGRP_WIDGET_INFO:
15138a9ff04bSjmcneill 			rv = prop_dictionary_get_uint64(fgrp_dict,
15148a9ff04bSjmcneill 			    "widgetinfo-callback", &info_fn);
15158a9ff04bSjmcneill 			if (!rv)
15168a9ff04bSjmcneill 				return ENXIO;
15178a9ff04bSjmcneill 			infocb = (void *)(uintptr_t)info_fn;
15188a9ff04bSjmcneill 			err = infocb(fgrp_sc, request, response);
15198a9ff04bSjmcneill 			break;
15208a9ff04bSjmcneill 		default:
15218a9ff04bSjmcneill 			err = EINVAL;
15228a9ff04bSjmcneill 			break;
15238a9ff04bSjmcneill 		}
15248a9ff04bSjmcneill 		break;
15258a9ff04bSjmcneill 
15268a9ff04bSjmcneill 	default:
15278a9ff04bSjmcneill 		err = EINVAL;
15288a9ff04bSjmcneill 		break;
15298a9ff04bSjmcneill 	}
15308a9ff04bSjmcneill 	return err;
15318a9ff04bSjmcneill }
15328a9ff04bSjmcneill 
15338a9ff04bSjmcneill int
hdaudioopen(dev_t dev,int flag,int mode,struct lwp * l)15348a9ff04bSjmcneill hdaudioopen(dev_t dev, int flag, int mode, struct lwp *l)
15358a9ff04bSjmcneill {
15368a9ff04bSjmcneill 	device_t self;
15378a9ff04bSjmcneill 
15388a9ff04bSjmcneill 	self = device_lookup(&hdaudio_cd, HDAUDIOUNIT(dev));
15398a9ff04bSjmcneill 	if (self == NULL)
15408a9ff04bSjmcneill 		return ENXIO;
15418a9ff04bSjmcneill 
15428a9ff04bSjmcneill 	return 0;
15438a9ff04bSjmcneill }
15448a9ff04bSjmcneill 
15458a9ff04bSjmcneill int
hdaudioclose(dev_t dev,int flag,int mode,struct lwp * l)15468a9ff04bSjmcneill hdaudioclose(dev_t dev, int flag, int mode, struct lwp *l)
15478a9ff04bSjmcneill {
15488a9ff04bSjmcneill 	return 0;
15498a9ff04bSjmcneill }
15508a9ff04bSjmcneill 
15518a9ff04bSjmcneill int
hdaudioioctl(dev_t dev,u_long cmd,void * addr,int flag,struct lwp * l)15528a9ff04bSjmcneill hdaudioioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
15538a9ff04bSjmcneill {
15548a9ff04bSjmcneill 	struct hdaudio_softc *sc;
15558a9ff04bSjmcneill 	struct plistref *pref = addr;
15568a9ff04bSjmcneill 	prop_dictionary_t request, response;
15578a9ff04bSjmcneill 	int err;
15588a9ff04bSjmcneill 
15598a9ff04bSjmcneill 	sc = device_lookup_private(&hdaudio_cd, HDAUDIOUNIT(dev));
15608a9ff04bSjmcneill 	if (sc == NULL)
15618a9ff04bSjmcneill 		return ENXIO;
15628a9ff04bSjmcneill 
15638a9ff04bSjmcneill 	response = prop_dictionary_create();
15648a9ff04bSjmcneill 	if (response == NULL)
15658a9ff04bSjmcneill 		return ENOMEM;
15668a9ff04bSjmcneill 
15678a9ff04bSjmcneill 	err = prop_dictionary_copyin_ioctl(pref, cmd, &request);
15688a9ff04bSjmcneill 	if (err) {
15698a9ff04bSjmcneill 		prop_object_release(response);
15708a9ff04bSjmcneill 		return err;
15718a9ff04bSjmcneill 	}
15728a9ff04bSjmcneill 
15738a9ff04bSjmcneill 	switch (cmd) {
15748a9ff04bSjmcneill 	case HDAUDIO_FGRP_INFO:
15758a9ff04bSjmcneill 		err = hdaudioioctl_fgrp_info(sc, request, response);
15768a9ff04bSjmcneill 		break;
15778a9ff04bSjmcneill 	case HDAUDIO_FGRP_GETCONFIG:
15788a9ff04bSjmcneill 		err = hdaudioioctl_fgrp_getconfig(sc, request, response);
15798a9ff04bSjmcneill 		break;
15808a9ff04bSjmcneill 	case HDAUDIO_FGRP_SETCONFIG:
15818a9ff04bSjmcneill 		err = hdaudioioctl_fgrp_setconfig(sc, request, response);
15828a9ff04bSjmcneill 		break;
15838a9ff04bSjmcneill 	case HDAUDIO_FGRP_CODEC_INFO:
15848a9ff04bSjmcneill 	case HDAUDIO_FGRP_WIDGET_INFO:
15858a9ff04bSjmcneill 		err = hdaudio_dispatch_fgrp_ioctl(sc, cmd, request, response);
15868a9ff04bSjmcneill 		break;
15878a9ff04bSjmcneill 	default:
15888a9ff04bSjmcneill 		err = EINVAL;
15898a9ff04bSjmcneill 		break;
15908a9ff04bSjmcneill 	}
15918a9ff04bSjmcneill 
15928a9ff04bSjmcneill 	if (!err)
15938a9ff04bSjmcneill 		err = prop_dictionary_copyout_ioctl(pref, cmd, response);
15948a9ff04bSjmcneill 
15958a9ff04bSjmcneill 	if (response)
15968a9ff04bSjmcneill 		prop_object_release(response);
15978a9ff04bSjmcneill 	prop_object_release(request);
15988a9ff04bSjmcneill 	return err;
15998a9ff04bSjmcneill }
16008a9ff04bSjmcneill 
160152b02286Spgoyette MODULE(MODULE_CLASS_DRIVER, hdaudio, "audio");
160252b02286Spgoyette #ifdef _MODULE
160352b02286Spgoyette static const struct cfiattrdata hdaudiobuscf_iattrdata = {
160452b02286Spgoyette         "hdaudiobus", 1, {
160552b02286Spgoyette                 { "nid", "-1", -1 },
160652b02286Spgoyette         }
160752b02286Spgoyette };
160852b02286Spgoyette static const struct cfiattrdata * const hdaudio_attrs[] = {
160952b02286Spgoyette 	&hdaudiobuscf_iattrdata, NULL
161052b02286Spgoyette };
161152b02286Spgoyette CFDRIVER_DECL(hdaudio, DV_AUDIODEV, hdaudio_attrs);
161252b02286Spgoyette #endif
16138a9ff04bSjmcneill 
16148a9ff04bSjmcneill static int
hdaudio_modcmd(modcmd_t cmd,void * opaque)16158a9ff04bSjmcneill hdaudio_modcmd(modcmd_t cmd, void *opaque)
16168a9ff04bSjmcneill {
16178a9ff04bSjmcneill 	int error = 0;
16188a9ff04bSjmcneill #ifdef _MODULE
16198a9ff04bSjmcneill 	int bmaj = -1, cmaj = -1;
16208a9ff04bSjmcneill #endif
16218a9ff04bSjmcneill 
16228a9ff04bSjmcneill 	switch (cmd) {
16238a9ff04bSjmcneill 	case MODULE_CMD_INIT:
16248a9ff04bSjmcneill #ifdef _MODULE
16258a9ff04bSjmcneill 		error = devsw_attach("hdaudio", NULL, &bmaj,
16268a9ff04bSjmcneill 		    &hdaudio_cdevsw, &cmaj);
162752b02286Spgoyette 		if (error)
162852b02286Spgoyette 			break;
162952b02286Spgoyette 		error = config_cfdriver_attach(&hdaudio_cd);
163052b02286Spgoyette 		if (error)
16318a9ff04bSjmcneill 			devsw_detach(NULL, &hdaudio_cdevsw);
16328a9ff04bSjmcneill #endif
163352b02286Spgoyette 		break;
163452b02286Spgoyette 	case MODULE_CMD_FINI:
163552b02286Spgoyette #ifdef _MODULE
163652b02286Spgoyette 		error = config_cfdriver_detach(&hdaudio_cd);
163752b02286Spgoyette 		if (error)
163852b02286Spgoyette 			break;
1639e7bed289Sriastradh 		devsw_detach(NULL, &hdaudio_cdevsw);
164052b02286Spgoyette #endif
164152b02286Spgoyette 		break;
164252b02286Spgoyette 	default:
164352b02286Spgoyette 		error = ENOTTY;
164452b02286Spgoyette 		break;
164552b02286Spgoyette 	}
164652b02286Spgoyette 	return error;
16478a9ff04bSjmcneill }
16488a9ff04bSjmcneill 
16498a9ff04bSjmcneill DEV_VERBOSE_DEFINE(hdaudio);
1650