NetBSD/sys/dev/hdaudio/hdaudio.c

1653 lines
40 KiB
C

/* $NetBSD: hdaudio.c,v 1.8 2017/11/24 17:51:10 jmcneill Exp $ */
/*
* Copyright (c) 2009 Precedence Technologies Ltd <support@precedence.co.uk>
* Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Precedence Technologies Ltd
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hdaudio.c,v 1.8 2017/11/24 17:51:10 jmcneill Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <sys/kmem.h>
#include <sys/module.h>
#include "hdaudiovar.h"
#include "hdaudioreg.h"
#include "hdaudioio.h"
#include "hdaudio_verbose.h"
/* #define HDAUDIO_DEBUG */
#define HDAUDIO_RESET_TIMEOUT 5000
#define HDAUDIO_CORB_TIMEOUT 1000
#define HDAUDIO_RIRB_TIMEOUT 5000
#define HDAUDIO_CODEC_DELAY 1000 /* spec calls for 250 */
dev_type_open(hdaudioopen);
dev_type_close(hdaudioclose);
dev_type_ioctl(hdaudioioctl);
const struct cdevsw hdaudio_cdevsw = {
.d_open = hdaudioopen,
.d_close = hdaudioclose,
.d_read = noread,
.d_write = nowrite,
.d_ioctl = hdaudioioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_OTHER
};
extern struct cfdriver hdaudio_cd;
#define HDAUDIOUNIT(x) minor((x))
static void
hdaudio_stream_init(struct hdaudio_softc *sc, int nis, int nos, int nbidir)
{
int i, cnt = 0;
for (i = 0; i < nis && cnt < HDAUDIO_MAX_STREAMS; i++) {
sc->sc_stream[cnt].st_host = sc;
sc->sc_stream[cnt].st_enable = true;
sc->sc_stream[cnt].st_shift = cnt;
sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_ISS;
}
for (i = 0; i < nos && cnt < HDAUDIO_MAX_STREAMS; i++) {
sc->sc_stream[cnt].st_host = sc;
sc->sc_stream[cnt].st_enable = true;
sc->sc_stream[cnt].st_shift = cnt;
sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_OSS;
}
for (i = 0; i < nbidir && cnt < HDAUDIO_MAX_STREAMS; i++) {
sc->sc_stream[cnt].st_host = sc;
sc->sc_stream[cnt].st_enable = true;
sc->sc_stream[cnt].st_shift = cnt;
sc->sc_stream[cnt++].st_type = HDAUDIO_STREAM_BSS;
}
for (i = 0; i < cnt; i++)
hdaudio_stream_stop(&sc->sc_stream[i]);
sc->sc_stream_mask = 0;
}
static void
hdaudio_codec_init(struct hdaudio_softc *sc)
{
int i;
for (i = 0; i < HDAUDIO_MAX_CODECS; i++) {
sc->sc_codec[i].co_addr = i;
sc->sc_codec[i].co_host = sc;
}
}
static void
hdaudio_init(struct hdaudio_softc *sc)
{
uint16_t gcap;
int nos, nis, nbidir;
#if defined(HDAUDIO_DEBUG)
uint8_t vmin, vmaj;
int nsdo, addr64;
#endif
#if defined(HDAUDIO_DEBUG)
vmaj = hda_read1(sc, HDAUDIO_MMIO_VMAJ);
vmin = hda_read1(sc, HDAUDIO_MMIO_VMIN);
hda_print(sc, "High Definition Audio version %d.%d\n", vmaj, vmin);
#endif
gcap = hda_read2(sc, HDAUDIO_MMIO_GCAP);
nis = HDAUDIO_GCAP_ISS(gcap);
nos = HDAUDIO_GCAP_OSS(gcap);
nbidir = HDAUDIO_GCAP_BSS(gcap);
/* Initialize codecs and streams */
hdaudio_codec_init(sc);
hdaudio_stream_init(sc, nis, nos, nbidir);
#if defined(HDAUDIO_DEBUG)
nsdo = HDAUDIO_GCAP_NSDO(gcap);
addr64 = HDAUDIO_GCAP_64OK(gcap);
hda_print(sc, "OSS %d ISS %d BSS %d SDO %d%s\n",
nos, nis, nbidir, nsdo, addr64 ? " 64-bit" : "");
#endif
}
static int
hdaudio_codec_probe(struct hdaudio_softc *sc)
{
uint16_t statests;
int codecid;
statests = hda_read2(sc, HDAUDIO_MMIO_STATESTS);
for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++)
if (statests & (1 << codecid))
sc->sc_codec[codecid].co_valid = true;
hda_write2(sc, HDAUDIO_MMIO_STATESTS, statests);
return statests;
}
int
hdaudio_dma_alloc(struct hdaudio_softc *sc, struct hdaudio_dma *dma,
int flags)
{
int err;
KASSERT(dma->dma_size > 0);
err = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, 128, 0,
dma->dma_segs, sizeof(dma->dma_segs) / sizeof(dma->dma_segs[0]),
&dma->dma_nsegs, BUS_DMA_WAITOK);
if (err)
return err;
err = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs,
dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | flags);
if (err)
goto free;
err = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs,
dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map);
if (err)
goto unmap;
err = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr,
dma->dma_size, NULL, BUS_DMA_WAITOK | flags);
if (err)
goto destroy;
dma->dma_valid = true;
return 0;
destroy:
bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
unmap:
bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
free:
bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
dma->dma_valid = false;
return err;
}
void
hdaudio_dma_free(struct hdaudio_softc *sc, struct hdaudio_dma *dma)
{
if (dma->dma_valid == false)
return;
bus_dmamap_unload(sc->sc_dmat, dma->dma_map);
bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
dma->dma_valid = false;
}
static void
hdaudio_corb_enqueue(struct hdaudio_softc *sc, int addr, int nid,
uint32_t control, uint32_t param)
{
uint32_t *corb = DMA_KERNADDR(&sc->sc_corb);
uint32_t verb;
uint16_t corbrp;
int wp;
/* Build command */
verb = (addr << 28) | (nid << 20) | (control << 8) | param;
/* Fetch and update write pointer */
corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBWP);
wp = (corbrp & 0xff) + 1;
if (wp >= (sc->sc_corb.dma_size / sizeof(*corb)))
wp = 0;
/* Enqueue command */
bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
sc->sc_corb.dma_size, BUS_DMASYNC_POSTWRITE);
corb[wp] = verb;
bus_dmamap_sync(sc->sc_dmat, sc->sc_corb.dma_map, 0,
sc->sc_corb.dma_size, BUS_DMASYNC_PREWRITE);
/* Commit updated write pointer */
hda_write2(sc, HDAUDIO_MMIO_CORBWP, wp);
}
static void
hdaudio_rirb_unsol(struct hdaudio_softc *sc, struct rirb_entry *entry)
{
struct hdaudio_codec *co;
struct hdaudio_function_group *fg;
uint8_t codecid = RIRB_CODEC_ID(entry);
unsigned int i;
if (codecid >= HDAUDIO_MAX_CODECS) {
hda_error(sc, "unsol: codec id 0x%02x out of range\n", codecid);
return;
}
co = &sc->sc_codec[codecid];
if (sc->sc_codec[codecid].co_valid == false) {
hda_error(sc, "unsol: codec id 0x%02x not valid\n", codecid);
return;
}
for (i = 0; i < co->co_nfg; i++) {
fg = &co->co_fg[i];
if (fg->fg_device && fg->fg_unsol)
fg->fg_unsol(fg->fg_device, entry->resp);
}
}
static uint32_t
hdaudio_rirb_dequeue(struct hdaudio_softc *sc, bool unsol)
{
uint16_t rirbwp;
uint64_t *rirb = DMA_KERNADDR(&sc->sc_rirb);
struct rirb_entry entry;
int retry;
for (;;) {
retry = HDAUDIO_RIRB_TIMEOUT;
rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
while (--retry > 0 && (rirbwp & 0xff) == sc->sc_rirbrp) {
if (unsol) {
/* don't wait for more unsol events */
hda_trace(sc, "unsol: rirb empty\n");
return 0xffffffff;
}
hda_delay(10);
rirbwp = hda_read2(sc, HDAUDIO_MMIO_RIRBWP);
}
if (retry == 0) {
hda_error(sc, "RIRB timeout\n");
return 0xffffffff;
}
sc->sc_rirbrp++;
if (sc->sc_rirbrp >= (sc->sc_rirb.dma_size / sizeof(*rirb)))
sc->sc_rirbrp = 0;
bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
sc->sc_rirb.dma_size, BUS_DMASYNC_POSTREAD);
entry = *(struct rirb_entry *)&rirb[sc->sc_rirbrp];
bus_dmamap_sync(sc->sc_dmat, sc->sc_rirb.dma_map, 0,
sc->sc_rirb.dma_size, BUS_DMASYNC_PREREAD);
hda_trace(sc, "%s: response %08X %08X\n",
unsol ? "unsol" : "cmd ",
entry.resp, entry.resp_ex);
if (RIRB_UNSOL(&entry)) {
hdaudio_rirb_unsol(sc, &entry);
continue;
}
return entry.resp;
}
}
uint32_t
hdaudio_command(struct hdaudio_codec *co, int nid, uint32_t control,
uint32_t param)
{
uint32_t result;
struct hdaudio_softc *sc = co->co_host;
mutex_enter(&sc->sc_corb_mtx);
result = hdaudio_command_unlocked(co, nid, control, param);
mutex_exit(&sc->sc_corb_mtx);
return result;
}
uint32_t
hdaudio_command_unlocked(struct hdaudio_codec *co, int nid, uint32_t control,
uint32_t param)
{
struct hdaudio_softc *sc = co->co_host;
uint32_t result;
hda_trace(sc, "cmd : request %08X %08X (%02X)\n",
control, param, nid);
hdaudio_corb_enqueue(sc, co->co_addr, nid, control, param);
result = hdaudio_rirb_dequeue(sc, false);
/* Clear response interrupt status */
hda_write1(sc, HDAUDIO_MMIO_RIRBSTS, hda_read1(sc, HDAUDIO_MMIO_RIRBSTS));
return result;
}
static int
hdaudio_corb_setsize(struct hdaudio_softc *sc)
{
uint8_t corbsize;
bus_size_t bufsize = 0;
/*
* The size of the CORB is programmable to 2, 16, or 256 entries
* by using the CORBSIZE register. Choose a size based on the
* controller capabilities, preferring a larger size when possible.
*/
corbsize = hda_read1(sc, HDAUDIO_MMIO_CORBSIZE);
corbsize &= ~0x3;
if ((corbsize >> 4) & 0x4) {
corbsize |= 0x2;
bufsize = 1024;
} else if ((corbsize >> 4) & 0x2) {
corbsize |= 0x1;
bufsize = 64;
} else if ((corbsize >> 4) & 0x1) {
corbsize |= 0x0;
bufsize = 8;
} else {
hda_error(sc, "couldn't configure CORB size\n");
return ENXIO;
}
#if defined(HDAUDIO_DEBUG)
hda_print(sc, "using %d byte CORB (cap %X)\n",
(int)bufsize, corbsize >> 4);
#endif
sc->sc_corb.dma_size = bufsize;
sc->sc_corb.dma_sizereg = corbsize;
return 0;
}
static int
hdaudio_corb_config(struct hdaudio_softc *sc)
{
uint32_t corbubase, corblbase;
uint16_t corbrp;
int retry = HDAUDIO_CORB_TIMEOUT;
/* Program command buffer base address and size */
corblbase = (uint32_t)DMA_DMAADDR(&sc->sc_corb);
corbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_corb)) >> 32);
hda_write4(sc, HDAUDIO_MMIO_CORBLBASE, corblbase);
hda_write4(sc, HDAUDIO_MMIO_CORBUBASE, corbubase);
hda_write1(sc, HDAUDIO_MMIO_CORBSIZE, sc->sc_corb.dma_sizereg);
/* Clear the read and write pointers */
hda_write2(sc, HDAUDIO_MMIO_CORBRP, HDAUDIO_CORBRP_RP_RESET);
hda_write2(sc, HDAUDIO_MMIO_CORBRP, 0);
do {
hda_delay(10);
corbrp = hda_read2(sc, HDAUDIO_MMIO_CORBRP);
} while (--retry > 0 && (corbrp & HDAUDIO_CORBRP_RP_RESET) != 0);
if (retry == 0) {
hda_error(sc, "timeout resetting CORB\n");
return ETIME;
}
hda_write2(sc, HDAUDIO_MMIO_CORBWP, 0);
return 0;
}
static int
hdaudio_corb_stop(struct hdaudio_softc *sc)
{
uint8_t corbctl;
int retry = HDAUDIO_CORB_TIMEOUT;
/* Stop the CORB if necessary */
corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
if (corbctl & HDAUDIO_CORBCTL_RUN) {
corbctl &= ~HDAUDIO_CORBCTL_RUN;
hda_write1(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
do {
hda_delay(10);
corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) != 0);
if (retry == 0) {
hda_error(sc, "timeout stopping CORB\n");
return ETIME;
}
}
return 0;
}
static int
hdaudio_corb_start(struct hdaudio_softc *sc)
{
uint8_t corbctl;
int retry = HDAUDIO_CORB_TIMEOUT;
/* Start the CORB if necessary */
corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
if ((corbctl & HDAUDIO_CORBCTL_RUN) == 0) {
corbctl |= HDAUDIO_CORBCTL_RUN;
hda_write1(sc, HDAUDIO_MMIO_CORBCTL, corbctl);
do {
hda_delay(10);
corbctl = hda_read1(sc, HDAUDIO_MMIO_CORBCTL);
} while (--retry > 0 && (corbctl & HDAUDIO_CORBCTL_RUN) == 0);
if (retry == 0) {
hda_error(sc, "timeout starting CORB\n");
return ETIME;
}
}
return 0;
}
static int
hdaudio_rirb_stop(struct hdaudio_softc *sc)
{
uint8_t rirbctl;
int retry = HDAUDIO_RIRB_TIMEOUT;
/* Stop the RIRB if necessary */
rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
if (rirbctl & (HDAUDIO_RIRBCTL_RUN|HDAUDIO_RIRBCTL_ROI_EN)) {
rirbctl &= ~HDAUDIO_RIRBCTL_RUN;
rirbctl &= ~HDAUDIO_RIRBCTL_ROI_EN;
hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
do {
hda_delay(10);
rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) != 0);
if (retry == 0) {
hda_error(sc, "timeout stopping RIRB\n");
return ETIME;
}
}
return 0;
}
static int
hdaudio_rirb_start(struct hdaudio_softc *sc)
{
uint8_t rirbctl;
int retry = HDAUDIO_RIRB_TIMEOUT;
/* Set the RIRB interrupt count */
hda_write2(sc, HDAUDIO_MMIO_RINTCNT, 1);
/* Start the RIRB */
rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
rirbctl |= HDAUDIO_RIRBCTL_RUN;
rirbctl |= HDAUDIO_RIRBCTL_INT_EN;
hda_write1(sc, HDAUDIO_MMIO_RIRBCTL, rirbctl);
do {
hda_delay(10);
rirbctl = hda_read1(sc, HDAUDIO_MMIO_RIRBCTL);
} while (--retry > 0 && (rirbctl & HDAUDIO_RIRBCTL_RUN) == 0);
if (retry == 0) {
hda_error(sc, "timeout starting RIRB\n");
return ETIME;
}
return 0;
}
static int
hdaudio_rirb_setsize(struct hdaudio_softc *sc)
{
uint8_t rirbsize;
bus_size_t bufsize = 0;
/*
* The size of the RIRB is programmable to 2, 16, or 256 entries
* by using the RIRBSIZE register. Choose a size based on the
* controller capabilities, preferring a larger size when possible.
*/
rirbsize = hda_read1(sc, HDAUDIO_MMIO_RIRBSIZE);
rirbsize &= ~0x3;
if ((rirbsize >> 4) & 0x4) {
rirbsize |= 0x2;
bufsize = 2048;
} else if ((rirbsize >> 4) & 0x2) {
rirbsize |= 0x1;
bufsize = 128;
} else if ((rirbsize >> 4) & 0x1) {
rirbsize |= 0x0;
bufsize = 16;
} else {
hda_error(sc, "couldn't configure RIRB size\n");
return ENXIO;
}
#if defined(HDAUDIO_DEBUG)
hda_print(sc, "using %d byte RIRB (cap %X)\n",
(int)bufsize, rirbsize >> 4);
#endif
sc->sc_rirb.dma_size = bufsize;
sc->sc_rirb.dma_sizereg = rirbsize;
return 0;
}
static int
hdaudio_rirb_config(struct hdaudio_softc *sc)
{
uint32_t rirbubase, rirblbase;
/* Program command buffer base address and size */
rirblbase = (uint32_t)DMA_DMAADDR(&sc->sc_rirb);
rirbubase = (uint32_t)(((uint64_t)DMA_DMAADDR(&sc->sc_rirb)) >> 32);
hda_write4(sc, HDAUDIO_MMIO_RIRBLBASE, rirblbase);
hda_write4(sc, HDAUDIO_MMIO_RIRBUBASE, rirbubase);
hda_write1(sc, HDAUDIO_MMIO_RIRBSIZE, sc->sc_rirb.dma_sizereg);
/* Clear the write pointer */
hda_write2(sc, HDAUDIO_MMIO_RIRBWP, HDAUDIO_RIRBWP_WP_RESET);
sc->sc_rirbrp = 0;
return 0;
}
static int
hdaudio_reset(struct hdaudio_softc *sc)
{
int retry = HDAUDIO_RESET_TIMEOUT;
uint32_t gctl;
int err;
if ((err = hdaudio_rirb_stop(sc)) != 0) {
hda_error(sc, "couldn't reset because RIRB is busy\n");
return err;
}
if ((err = hdaudio_corb_stop(sc)) != 0) {
hda_error(sc, "couldn't reset because CORB is busy\n");
return err;
}
/* Disable wake events */
hda_write2(sc, HDAUDIO_MMIO_WAKEEN, 0);
/* Disable interrupts */
hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
/* Clear state change status register */
hda_write2(sc, HDAUDIO_MMIO_STATESTS,
hda_read2(sc, HDAUDIO_MMIO_STATESTS));
hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
hda_read1(sc, HDAUDIO_MMIO_RIRBSTS));
/* Put the controller into reset state */
gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
gctl &= ~HDAUDIO_GCTL_CRST;
hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl);
do {
hda_delay(10);
gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) != 0);
if (retry == 0) {
hda_error(sc, "timeout entering reset state\n");
return ETIME;
}
hda_delay(1000);
/* Now the controller is in reset state, so bring it out */
retry = HDAUDIO_RESET_TIMEOUT;
hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_CRST);
do {
hda_delay(10);
gctl = hda_read4(sc, HDAUDIO_MMIO_GCTL);
} while (--retry > 0 && (gctl & HDAUDIO_GCTL_CRST) == 0);
if (retry == 0) {
hda_error(sc, "timeout leaving reset state\n");
return ETIME;
}
hda_delay(2000);
/* Accept unsolicited responses */
hda_write4(sc, HDAUDIO_MMIO_GCTL, gctl | HDAUDIO_GCTL_UNSOL_EN);
return 0;
}
static void
hdaudio_intr_enable(struct hdaudio_softc *sc)
{
hda_write4(sc, HDAUDIO_MMIO_INTSTS,
hda_read4(sc, HDAUDIO_MMIO_INTSTS));
hda_write4(sc, HDAUDIO_MMIO_INTCTL,
HDAUDIO_INTCTL_GIE | HDAUDIO_INTCTL_CIE);
}
static void
hdaudio_intr_disable(struct hdaudio_softc *sc)
{
hda_write4(sc, HDAUDIO_MMIO_INTCTL, 0);
}
static int
hdaudio_config_print(void *opaque, const char *pnp)
{
prop_dictionary_t dict = opaque;
uint8_t fgtype, nid;
uint16_t vendor, product;
const char *type = "unknown";
prop_dictionary_get_uint8(dict, "function-group-type", &fgtype);
prop_dictionary_get_uint8(dict, "node-id", &nid);
prop_dictionary_get_uint16(dict, "vendor-id", &vendor);
prop_dictionary_get_uint16(dict, "product-id", &product);
if (pnp) {
if (fgtype == HDAUDIO_GROUP_TYPE_AFG)
type = "hdafg";
else if (fgtype == HDAUDIO_GROUP_TYPE_VSM_FG)
type = "hdvsmfg";
aprint_normal("%s at %s", type, pnp);
}
aprint_debug(" vendor 0x%04X product 0x%04X nid 0x%02X",
vendor, product, nid);
return UNCONF;
}
static void
hdaudio_attach_fg(struct hdaudio_function_group *fg, prop_array_t config)
{
struct hdaudio_codec *co = fg->fg_codec;
struct hdaudio_softc *sc = co->co_host;
prop_dictionary_t args = prop_dictionary_create();
uint64_t fgptr = (vaddr_t)fg;
int locs[1];
prop_dictionary_set_uint8(args, "function-group-type", fg->fg_type);
prop_dictionary_set_uint64(args, "function-group", fgptr);
prop_dictionary_set_uint8(args, "node-id", fg->fg_nid);
prop_dictionary_set_uint16(args, "vendor-id", fg->fg_vendor);
prop_dictionary_set_uint16(args, "product-id", fg->fg_product);
if (config)
prop_dictionary_set(args, "pin-config", config);
locs[0] = fg->fg_nid;
fg->fg_device = config_found_sm_loc(sc->sc_dev, "hdaudiobus",
locs, args, hdaudio_config_print, config_stdsubmatch);
prop_object_release(args);
}
static void
hdaudio_codec_attach(struct hdaudio_codec *co)
{
struct hdaudio_function_group *fg;
uint32_t vid, snc, fgrp;
int starting_node, num_nodes, nid;
if (co->co_valid == false)
return;
vid = hdaudio_command(co, 0, CORB_GET_PARAMETER, COP_VENDOR_ID);
snc = hdaudio_command(co, 0, CORB_GET_PARAMETER,
COP_SUBORDINATE_NODE_COUNT);
/* make sure the vendor and product IDs are valid */
if (vid == 0xffffffff || vid == 0x00000000)
return;
#ifdef HDAUDIO_DEBUG
struct hdaudio_softc *sc = co->co_host;
uint32_t rid = hdaudio_command(co, 0, CORB_GET_PARAMETER,
COP_REVISION_ID);
hda_print(sc, "Codec%02X: %04X:%04X HDA %d.%d rev %d stepping %d\n",
co->co_addr, vid >> 16, vid & 0xffff,
(rid >> 20) & 0xf, (rid >> 16) & 0xf,
(rid >> 8) & 0xff, rid & 0xff);
#endif
starting_node = (snc >> 16) & 0xff;
num_nodes = snc & 0xff;
co->co_nfg = num_nodes;
co->co_fg = kmem_zalloc(co->co_nfg * sizeof(*co->co_fg), KM_SLEEP);
for (nid = starting_node; nid < starting_node + num_nodes; nid++) {
fg = &co->co_fg[nid - starting_node];
fg->fg_codec = co;
fg->fg_nid = nid;
fg->fg_vendor = vid >> 16;
fg->fg_product = vid & 0xffff;
fgrp = hdaudio_command(co, nid, CORB_GET_PARAMETER,
COP_FUNCTION_GROUP_TYPE);
switch (fgrp & 0xff) {
case 0x01: /* Audio Function Group */
fg->fg_type = HDAUDIO_GROUP_TYPE_AFG;
break;
case 0x02: /* Vendor Specific Modem Function Group */
fg->fg_type = HDAUDIO_GROUP_TYPE_VSM_FG;
break;
default:
/* Function group type not supported */
fg->fg_type = HDAUDIO_GROUP_TYPE_UNKNOWN;
break;
}
hdaudio_attach_fg(fg, NULL);
}
}
int
hdaudio_stream_tag(struct hdaudio_stream *st)
{
int ret = 0;
switch (st->st_type) {
case HDAUDIO_STREAM_ISS:
ret = 1;
break;
case HDAUDIO_STREAM_OSS:
ret = 2;
break;
case HDAUDIO_STREAM_BSS:
ret = 3;
break;
}
return ret;
}
int
hdaudio_attach(device_t dev, struct hdaudio_softc *sc)
{
int err, i;
KASSERT(sc->sc_memvalid == true);
sc->sc_dev = dev;
mutex_init(&sc->sc_corb_mtx, MUTEX_DEFAULT, IPL_AUDIO);
mutex_init(&sc->sc_stream_mtx, MUTEX_DEFAULT, IPL_AUDIO);
hdaudio_init(sc);
/*
* Put the controller into a known state by entering and leaving
* CRST as necessary.
*/
if ((err = hdaudio_reset(sc)) != 0)
goto fail;
/*
* From the spec:
*
* Must wait 250us after reading CRST as a 1 before assuming that
* codecs have all made status change requests and have been
* registered by the controller.
*
* In reality, we need to wait longer than this.
*/
hda_delay(HDAUDIO_CODEC_DELAY);
if (hdaudio_codec_probe(sc) == 0) {
hda_error(sc, "no codecs found\n");
err = ENODEV;
goto fail;
}
/*
* Ensure that the device is in a known state
*/
hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
hda_write4(sc, HDAUDIO_MMIO_INTSTS,
hda_read4(sc, HDAUDIO_MMIO_INTSTS));
hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
/*
* Initialize the CORB. First negotiate a command buffer size,
* then allocate and configure it.
*/
if ((err = hdaudio_corb_setsize(sc)) != 0)
goto fail;
if ((err = hdaudio_dma_alloc(sc, &sc->sc_corb, BUS_DMA_WRITE)) != 0)
goto fail;
if ((err = hdaudio_corb_config(sc)) != 0)
goto fail;
/*
* Initialize the RIRB.
*/
if ((err = hdaudio_rirb_setsize(sc)) != 0)
goto fail;
if ((err = hdaudio_dma_alloc(sc, &sc->sc_rirb, BUS_DMA_READ)) != 0)
goto fail;
if ((err = hdaudio_rirb_config(sc)) != 0)
goto fail;
/*
* Start the CORB and RIRB
*/
if ((err = hdaudio_corb_start(sc)) != 0)
goto fail;
if ((err = hdaudio_rirb_start(sc)) != 0)
goto fail;
/*
* Identify and attach discovered codecs
*/
for (i = 0; i < HDAUDIO_MAX_CODECS; i++)
hdaudio_codec_attach(&sc->sc_codec[i]);
/*
* Enable interrupts
*/
hdaudio_intr_enable(sc);
fail:
if (err)
hda_error(sc, "device driver failed to attach\n");
return err;
}
int
hdaudio_detach(struct hdaudio_softc *sc, int flags)
{
int error;
/* Disable interrupts */
hdaudio_intr_disable(sc);
error = config_detach_children(sc->sc_dev, flags);
if (error != 0) {
hdaudio_intr_enable(sc);
return error;
}
mutex_destroy(&sc->sc_corb_mtx);
mutex_destroy(&sc->sc_stream_mtx);
hdaudio_dma_free(sc, &sc->sc_corb);
hdaudio_dma_free(sc, &sc->sc_rirb);
return 0;
}
bool
hdaudio_resume(struct hdaudio_softc *sc)
{
if (hdaudio_reset(sc) != 0)
return false;
hda_delay(HDAUDIO_CODEC_DELAY);
/*
* Ensure that the device is in a known state
*/
hda_write2(sc, HDAUDIO_MMIO_STATESTS, HDAUDIO_STATESTS_SDIWAKE);
hda_write1(sc, HDAUDIO_MMIO_RIRBSTS,
HDAUDIO_RIRBSTS_RIRBOIS | HDAUDIO_RIRBSTS_RINTFL);
hda_write4(sc, HDAUDIO_MMIO_INTSTS,
hda_read4(sc, HDAUDIO_MMIO_INTSTS));
hda_write4(sc, HDAUDIO_MMIO_DPLBASE, 0);
hda_write4(sc, HDAUDIO_MMIO_DPUBASE, 0);
if (hdaudio_corb_config(sc) != 0)
return false;
if (hdaudio_rirb_config(sc) != 0)
return false;
if (hdaudio_corb_start(sc) != 0)
return false;
if (hdaudio_rirb_start(sc) != 0)
return false;
hdaudio_intr_enable(sc);
return true;
}
int
hdaudio_rescan(struct hdaudio_softc *sc, const char *ifattr, const int *locs)
{
struct hdaudio_codec *co;
struct hdaudio_function_group *fg;
unsigned int codec;
if (!ifattr_match(ifattr, "hdaudiobus"))
return 0;
for (codec = 0; codec < HDAUDIO_MAX_CODECS; codec++) {
co = &sc->sc_codec[codec];
fg = co->co_fg;
if (!co->co_valid || fg == NULL)
continue;
if (fg->fg_device)
continue;
hdaudio_attach_fg(fg, NULL);
}
return 0;
}
void
hdaudio_childdet(struct hdaudio_softc *sc, device_t child)
{
struct hdaudio_codec *co;
struct hdaudio_function_group *fg;
unsigned int codec;
for (codec = 0; codec < HDAUDIO_MAX_CODECS; codec++) {
co = &sc->sc_codec[codec];
fg = co->co_fg;
if (!co->co_valid || fg == NULL)
continue;
if (fg->fg_device == child)
fg->fg_device = NULL;
}
}
int
hdaudio_intr(struct hdaudio_softc *sc)
{
struct hdaudio_stream *st;
uint32_t intsts, stream_mask;
int streamid = 0;
uint8_t rirbsts;
intsts = hda_read4(sc, HDAUDIO_MMIO_INTSTS);
if (!(intsts & HDAUDIO_INTSTS_GIS))
return 0;
if (intsts & HDAUDIO_INTSTS_CIS) {
rirbsts = hda_read1(sc, HDAUDIO_MMIO_RIRBSTS);
if (rirbsts & HDAUDIO_RIRBSTS_RINTFL) {
mutex_enter(&sc->sc_corb_mtx);
hdaudio_rirb_dequeue(sc, true);
mutex_exit(&sc->sc_corb_mtx);
}
if (rirbsts & (HDAUDIO_RIRBSTS_RIRBOIS|HDAUDIO_RIRBSTS_RINTFL))
hda_write1(sc, HDAUDIO_MMIO_RIRBSTS, rirbsts);
hda_write4(sc, HDAUDIO_MMIO_INTSTS, HDAUDIO_INTSTS_CIS);
}
if (intsts & HDAUDIO_INTSTS_SIS_MASK) {
mutex_enter(&sc->sc_stream_mtx);
stream_mask = intsts & sc->sc_stream_mask;
while (streamid < HDAUDIO_MAX_STREAMS && stream_mask != 0) {
st = &sc->sc_stream[streamid++];
if ((stream_mask & 1) != 0 && st->st_intr) {
st->st_intr(st);
}
stream_mask >>= 1;
}
mutex_exit(&sc->sc_stream_mtx);
hda_write4(sc, HDAUDIO_MMIO_INTSTS, HDAUDIO_INTSTS_SIS_MASK);
}
return 1;
}
struct hdaudio_stream *
hdaudio_stream_establish(struct hdaudio_softc *sc,
enum hdaudio_stream_type type, int (*intr)(struct hdaudio_stream *),
void *cookie)
{
struct hdaudio_stream *st;
struct hdaudio_dma dma;
int i, err;
dma.dma_size = sizeof(struct hdaudio_bdl_entry) * HDAUDIO_BDL_MAX;
dma.dma_sizereg = 0;
err = hdaudio_dma_alloc(sc, &dma, BUS_DMA_COHERENT | BUS_DMA_NOCACHE);
if (err)
return NULL;
mutex_enter(&sc->sc_stream_mtx);
for (i = 0; i < HDAUDIO_MAX_STREAMS; i++) {
st = &sc->sc_stream[i];
if (st->st_enable == false)
break;
if (st->st_type != type)
continue;
if (sc->sc_stream_mask & (1 << i))
continue;
/* Allocate stream */
st->st_bdl = dma;
st->st_intr = intr;
st->st_cookie = cookie;
sc->sc_stream_mask |= (1 << i);
mutex_exit(&sc->sc_stream_mtx);
return st;
}
mutex_exit(&sc->sc_stream_mtx);
/* No streams of requested type available */
hdaudio_dma_free(sc, &dma);
return NULL;
}
void
hdaudio_stream_disestablish(struct hdaudio_stream *st)
{
struct hdaudio_softc *sc = st->st_host;
struct hdaudio_dma dma;
KASSERT(sc->sc_stream_mask & (1 << st->st_shift));
mutex_enter(&sc->sc_stream_mtx);
sc->sc_stream_mask &= ~(1 << st->st_shift);
st->st_intr = NULL;
st->st_cookie = NULL;
dma = st->st_bdl;
st->st_bdl.dma_valid = false;
mutex_exit(&sc->sc_stream_mtx);
/* Can't bus_dmamem_unmap while holding a mutex. */
hdaudio_dma_free(sc, &dma);
}
/*
* Convert most of audio_params_t to stream fmt descriptor; noticably missing
* is the # channels bits, as this is encoded differently in codec and
* stream descriptors.
*
* TODO: validate that the stream and selected codecs can handle the fmt
*/
uint16_t
hdaudio_stream_param(struct hdaudio_stream *st, const audio_params_t *param)
{
uint16_t fmt = 0;
switch (param->encoding) {
case AUDIO_ENCODING_AC3:
fmt |= HDAUDIO_FMT_TYPE_NONPCM;
break;
default:
fmt |= HDAUDIO_FMT_TYPE_PCM;
break;
}
switch (param->sample_rate) {
case 8000:
fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1) |
HDAUDIO_FMT_DIV(6);
break;
case 11025:
fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1) |
HDAUDIO_FMT_DIV(4);
break;
case 16000:
fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1) |
HDAUDIO_FMT_DIV(3);
break;
case 22050:
fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1) |
HDAUDIO_FMT_DIV(2);
break;
case 32000:
fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(2) |
HDAUDIO_FMT_DIV(3);
break;
case 44100:
fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(1);
break;
case 48000:
fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(1);
break;
case 88200:
fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(2);
break;
case 96000:
fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(2);
break;
case 176400:
fmt |= HDAUDIO_FMT_BASE_44 | HDAUDIO_FMT_MULT(4);
break;
case 192000:
fmt |= HDAUDIO_FMT_BASE_48 | HDAUDIO_FMT_MULT(4);
break;
default:
return 0;
}
if (param->precision == 16 && param->validbits == 8)
fmt |= HDAUDIO_FMT_BITS_8_16;
else if (param->precision == 16 && param->validbits == 16)
fmt |= HDAUDIO_FMT_BITS_16_16;
else if (param->precision == 32 && param->validbits == 20)
fmt |= HDAUDIO_FMT_BITS_20_32;
else if (param->precision == 32 && param->validbits == 24)
fmt |= HDAUDIO_FMT_BITS_24_32;
else if (param->precision == 32 && param->validbits == 32)
fmt |= HDAUDIO_FMT_BITS_32_32;
else
return 0;
return fmt;
}
void
hdaudio_stream_reset(struct hdaudio_stream *st)
{
struct hdaudio_softc *sc = st->st_host;
int snum = st->st_shift;
int retry;
uint8_t ctl0;
ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
ctl0 |= HDAUDIO_CTL_SRST;
hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
retry = HDAUDIO_RESET_TIMEOUT;
do {
ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
if (ctl0 & HDAUDIO_CTL_SRST)
break;
hda_delay(10);
} while (--retry > 0);
if (retry == 0) {
hda_error(sc, "timeout entering stream reset state\n");
return;
}
ctl0 &= ~HDAUDIO_CTL_SRST;
hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
retry = HDAUDIO_RESET_TIMEOUT;
do {
ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
if (!(ctl0 & HDAUDIO_CTL_SRST))
break;
hda_delay(10);
} while (--retry > 0);
if (retry == 0) {
hda_error(sc, "timeout leaving stream reset state\n");
return;
}
}
void
hdaudio_stream_start(struct hdaudio_stream *st, int blksize,
bus_size_t dmasize, const audio_params_t *params)
{
struct hdaudio_softc *sc = st->st_host;
struct hdaudio_bdl_entry *bdl;
uint64_t dmaaddr;
uint32_t intctl;
uint16_t fmt;
uint8_t ctl0, ctl2;
int cnt, snum = st->st_shift;
KASSERT(sc->sc_stream_mask & (1 << st->st_shift));
KASSERT(st->st_data.dma_valid == true);
KASSERT(st->st_bdl.dma_valid == true);
hdaudio_stream_stop(st);
hdaudio_stream_reset(st);
/*
* Configure buffer descriptor list
*/
dmaaddr = DMA_DMAADDR(&st->st_data);
bdl = DMA_KERNADDR(&st->st_bdl);
for (cnt = 0; cnt < HDAUDIO_BDL_MAX; cnt++) {
bdl[cnt].address_lo = (uint32_t)dmaaddr;
bdl[cnt].address_hi = dmaaddr >> 32;
bdl[cnt].length = blksize;
bdl[cnt].flags = HDAUDIO_BDL_ENTRY_IOC;
dmaaddr += blksize;
if (dmaaddr >= DMA_DMAADDR(&st->st_data) + dmasize) {
cnt++;
break;
}
}
/*
* Program buffer descriptor list
*/
dmaaddr = DMA_DMAADDR(&st->st_bdl);
hda_write4(sc, HDAUDIO_SD_BDPL(snum), (uint32_t)dmaaddr);
hda_write4(sc, HDAUDIO_SD_BDPU(snum), (uint32_t)(dmaaddr >> 32));
hda_write2(sc, HDAUDIO_SD_LVI(snum), (cnt - 1) & 0xff);
/*
* Program cyclic buffer length
*/
hda_write4(sc, HDAUDIO_SD_CBL(snum), dmasize);
/*
* Program stream number (tag). Although controller hardware is
* capable of transmitting any stream number (0-15), by convention
* stream 0 is reserved as unused by software, so that converters
* whose stream numbers have been reset to 0 do not unintentionally
* decode data not intended for them.
*/
ctl2 = hda_read1(sc, HDAUDIO_SD_CTL2(snum));
ctl2 &= ~0xf0;
ctl2 |= hdaudio_stream_tag(st) << 4;
hda_write1(sc, HDAUDIO_SD_CTL2(snum), ctl2);
/*
* Program stream format
*/
fmt = hdaudio_stream_param(st, params) |
HDAUDIO_FMT_CHAN(params->channels);
hda_write2(sc, HDAUDIO_SD_FMT(snum), fmt);
/*
* Switch on interrupts for this stream
*/
intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
intctl |= (1 << st->st_shift);
hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
/*
* Start running the stream
*/
ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
ctl0 |= HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
HDAUDIO_CTL_RUN;
hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
}
void
hdaudio_stream_stop(struct hdaudio_stream *st)
{
struct hdaudio_softc *sc = st->st_host;
uint32_t intctl;
uint8_t ctl0;
int snum = st->st_shift;
/*
* Stop running the stream
*/
ctl0 = hda_read1(sc, HDAUDIO_SD_CTL0(snum));
ctl0 &= ~(HDAUDIO_CTL_DEIE | HDAUDIO_CTL_FEIE | HDAUDIO_CTL_IOCE |
HDAUDIO_CTL_RUN);
hda_write1(sc, HDAUDIO_SD_CTL0(snum), ctl0);
/*
* Switch off interrupts for this stream
*/
intctl = hda_read4(sc, HDAUDIO_MMIO_INTCTL);
intctl &= ~(1 << st->st_shift);
hda_write4(sc, HDAUDIO_MMIO_INTCTL, intctl);
}
/*
* /dev/hdaudioN interface
*/
static const char *
hdaudioioctl_fgrp_to_cstr(enum function_group_type type)
{
switch (type) {
case HDAUDIO_GROUP_TYPE_AFG:
return "afg";
case HDAUDIO_GROUP_TYPE_VSM_FG:
return "vsmfg";
default:
return "unknown";
}
}
static struct hdaudio_function_group *
hdaudioioctl_fgrp_lookup(struct hdaudio_softc *sc, int codecid, int nid)
{
struct hdaudio_codec *co;
struct hdaudio_function_group *fg = NULL;
int i;
if (codecid < 0 || codecid >= HDAUDIO_MAX_CODECS)
return NULL;
co = &sc->sc_codec[codecid];
if (co->co_valid == false)
return NULL;
for (i = 0; i < co->co_nfg; i++)
if (co->co_fg[i].fg_nid == nid) {
fg = &co->co_fg[i];
break;
}
return fg;
}
static int
hdaudioioctl_fgrp_info(struct hdaudio_softc *sc, prop_dictionary_t request,
prop_dictionary_t response)
{
struct hdaudio_codec *co;
struct hdaudio_function_group *fg;
prop_array_t array;
prop_dictionary_t dict;
int codecid, fgid;
array = prop_array_create();
if (array == NULL)
return ENOMEM;
for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++) {
co = &sc->sc_codec[codecid];
if (co->co_valid == false)
continue;
for (fgid = 0; fgid < co->co_nfg; fgid++) {
fg = &co->co_fg[fgid];
dict = prop_dictionary_create();
if (dict == NULL)
return ENOMEM;
prop_dictionary_set_cstring_nocopy(dict,
"type", hdaudioioctl_fgrp_to_cstr(fg->fg_type));
prop_dictionary_set_int16(dict, "nid", fg->fg_nid);
prop_dictionary_set_int16(dict, "codecid", codecid);
prop_dictionary_set_uint16(dict, "vendor-id",
fg->fg_vendor);
prop_dictionary_set_uint16(dict, "product-id",
fg->fg_product);
prop_dictionary_set_uint32(dict, "subsystem-id",
sc->sc_subsystem);
if (fg->fg_device)
prop_dictionary_set_cstring(dict, "device",
device_xname(fg->fg_device));
else
prop_dictionary_set_cstring_nocopy(dict,
"device", "<none>");
prop_array_add(array, dict);
}
}
prop_dictionary_set(response, "function-group-info", array);
return 0;
}
static int
hdaudioioctl_fgrp_getconfig(struct hdaudio_softc *sc,
prop_dictionary_t request, prop_dictionary_t response)
{
struct hdaudio_function_group *fg;
prop_dictionary_t dict;
prop_array_t array;
uint32_t nodecnt, wcap, config;
int16_t codecid, nid, i;
int startnode, endnode;
if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
!prop_dictionary_get_int16(request, "nid", &nid))
return EINVAL;
fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
if (fg == NULL)
return ENODEV;
array = prop_array_create();
if (array == NULL)
return ENOMEM;
nodecnt = hdaudio_command(fg->fg_codec, fg->fg_nid,
CORB_GET_PARAMETER, COP_SUBORDINATE_NODE_COUNT);
startnode = COP_NODECNT_STARTNODE(nodecnt);
endnode = startnode + COP_NODECNT_NUMNODES(nodecnt);
for (i = startnode; i < endnode; i++) {
wcap = hdaudio_command(fg->fg_codec, i,
CORB_GET_PARAMETER, COP_AUDIO_WIDGET_CAPABILITIES);
if (COP_AWCAP_TYPE(wcap) != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
config = hdaudio_command(fg->fg_codec, i,
CORB_GET_CONFIGURATION_DEFAULT, 0);
dict = prop_dictionary_create();
if (dict == NULL)
return ENOMEM;
prop_dictionary_set_int16(dict, "nid", i);
prop_dictionary_set_uint32(dict, "config", config);
prop_array_add(array, dict);
}
prop_dictionary_set(response, "pin-config", array);
return 0;
}
static int
hdaudioioctl_fgrp_setconfig(struct hdaudio_softc *sc,
prop_dictionary_t request, prop_dictionary_t response)
{
struct hdaudio_function_group *fg;
prop_array_t config;
int16_t codecid, nid;
int err;
if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
!prop_dictionary_get_int16(request, "nid", &nid))
return EINVAL;
fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
if (fg == NULL)
return ENODEV;
if (fg->fg_device) {
err = config_detach(fg->fg_device, 0);
if (err)
return err;
fg->fg_device = NULL;
}
/* "pin-config" may be NULL, this means "use BIOS configuration" */
config = prop_dictionary_get(request, "pin-config");
if (config && prop_object_type(config) != PROP_TYPE_ARRAY) {
prop_object_release(config);
return EINVAL;
}
hdaudio_attach_fg(fg, config);
if (config)
prop_object_release(config);
return 0;
}
static int
hdaudio_dispatch_fgrp_ioctl(struct hdaudio_softc *sc, u_long cmd,
prop_dictionary_t request, prop_dictionary_t response)
{
struct hdaudio_function_group *fg;
int (*infocb)(void *, prop_dictionary_t, prop_dictionary_t);
prop_dictionary_t fgrp_dict;
uint64_t info_fn;
int16_t codecid, nid;
void *fgrp_sc;
bool rv;
int err;
if (!prop_dictionary_get_int16(request, "codecid", &codecid) ||
!prop_dictionary_get_int16(request, "nid", &nid))
return EINVAL;
fg = hdaudioioctl_fgrp_lookup(sc, codecid, nid);
if (fg == NULL)
return ENODEV;
if (fg->fg_device == NULL)
return ENXIO;
fgrp_sc = device_private(fg->fg_device);
fgrp_dict = device_properties(fg->fg_device);
switch (fg->fg_type) {
case HDAUDIO_GROUP_TYPE_AFG:
switch (cmd) {
case HDAUDIO_FGRP_CODEC_INFO:
rv = prop_dictionary_get_uint64(fgrp_dict,
"codecinfo-callback", &info_fn);
if (!rv)
return ENXIO;
infocb = (void *)(uintptr_t)info_fn;
err = infocb(fgrp_sc, request, response);
break;
case HDAUDIO_FGRP_WIDGET_INFO:
rv = prop_dictionary_get_uint64(fgrp_dict,
"widgetinfo-callback", &info_fn);
if (!rv)
return ENXIO;
infocb = (void *)(uintptr_t)info_fn;
err = infocb(fgrp_sc, request, response);
break;
default:
err = EINVAL;
break;
}
break;
default:
err = EINVAL;
break;
}
return err;
}
int
hdaudioopen(dev_t dev, int flag, int mode, struct lwp *l)
{
device_t self;
self = device_lookup(&hdaudio_cd, HDAUDIOUNIT(dev));
if (self == NULL)
return ENXIO;
return 0;
}
int
hdaudioclose(dev_t dev, int flag, int mode, struct lwp *l)
{
return 0;
}
int
hdaudioioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
{
struct hdaudio_softc *sc;
struct plistref *pref = addr;
prop_dictionary_t request, response;
int err;
sc = device_lookup_private(&hdaudio_cd, HDAUDIOUNIT(dev));
if (sc == NULL)
return ENXIO;
response = prop_dictionary_create();
if (response == NULL)
return ENOMEM;
err = prop_dictionary_copyin_ioctl(pref, cmd, &request);
if (err) {
prop_object_release(response);
return err;
}
switch (cmd) {
case HDAUDIO_FGRP_INFO:
err = hdaudioioctl_fgrp_info(sc, request, response);
break;
case HDAUDIO_FGRP_GETCONFIG:
err = hdaudioioctl_fgrp_getconfig(sc, request, response);
break;
case HDAUDIO_FGRP_SETCONFIG:
err = hdaudioioctl_fgrp_setconfig(sc, request, response);
break;
case HDAUDIO_FGRP_CODEC_INFO:
case HDAUDIO_FGRP_WIDGET_INFO:
err = hdaudio_dispatch_fgrp_ioctl(sc, cmd, request, response);
break;
default:
err = EINVAL;
break;
}
if (!err)
err = prop_dictionary_copyout_ioctl(pref, cmd, response);
if (response)
prop_object_release(response);
prop_object_release(request);
return err;
}
MODULE(MODULE_CLASS_DRIVER, hdaudio, "audio");
#ifdef _MODULE
static const struct cfiattrdata hdaudiobuscf_iattrdata = {
"hdaudiobus", 1, {
{ "nid", "-1", -1 },
}
};
static const struct cfiattrdata * const hdaudio_attrs[] = {
&hdaudiobuscf_iattrdata, NULL
};
CFDRIVER_DECL(hdaudio, DV_AUDIODEV, hdaudio_attrs);
#endif
static int
hdaudio_modcmd(modcmd_t cmd, void *opaque)
{
int error = 0;
#ifdef _MODULE
int bmaj = -1, cmaj = -1;
#endif
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
error = devsw_attach("hdaudio", NULL, &bmaj,
&hdaudio_cdevsw, &cmaj);
if (error)
break;
error = config_cfdriver_attach(&hdaudio_cd);
if (error)
devsw_detach(NULL, &hdaudio_cdevsw);
#endif
break;
case MODULE_CMD_FINI:
#ifdef _MODULE
error = config_cfdriver_detach(&hdaudio_cd);
if (error)
break;
error = devsw_detach(NULL, &hdaudio_cdevsw);
if (error) {
config_cfdriver_attach(&hdaudio_cd);
break;
}
#endif
break;
default:
error = ENOTTY;
break;
}
return error;
}
DEV_VERBOSE_DEFINE(hdaudio);