NetBSD/sys/dev/hdaudio/hdafg.c

4521 lines
119 KiB
C

/* $NetBSD: hdafg.c,v 1.13 2017/08/04 00:25:23 mrg Exp $ */
/*
* Copyright (c) 2009 Precedence Technologies Ltd <support@precedence.co.uk>
* Copyright (c) 2009-2011 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.
*/
/*
* Widget parsing from FreeBSD hdac.c:
*
* Copyright (c) 2006 Stephane E. Potvin <sepotvin@videotron.ca>
* Copyright (c) 2006 Ariff Abdullah <ariff@FreeBSD.org>
* Copyright (c) 2008 Alexander Motin <mav@FreeBSD.org>
* All rights reserved.
*
* 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. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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: hdafg.c,v 1.13 2017/08/04 00:25:23 mrg Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/bus.h>
#include <sys/kmem.h>
#include <sys/module.h>
#include <sys/audioio.h>
#include <dev/audio_if.h>
#include <dev/auconv.h>
#ifdef _KERNEL_OPT
#include "opt_hdaudio.h"
#endif
#include "hdaudiovar.h"
#include "hdaudioreg.h"
#include "hdaudio_mixer.h"
#include "hdaudioio.h"
#include "hdaudio_verbose.h"
#include "hdaudiodevs.h"
#include "hdafg_dd.h"
#include "hdmireg.h"
#ifndef AUFMT_SURROUND_7_1
#define AUFMT_SURROUND_7_1 (AUFMT_DOLBY_5_1|AUFMT_SIDE_LEFT|AUFMT_SIDE_RIGHT)
#endif
#if defined(HDAFG_DEBUG)
static int hdafg_debug = HDAFG_DEBUG;
#else
static int hdafg_debug = 0;
#endif
#define hda_debug(sc, ...) \
if (hdafg_debug) hda_print(sc, __VA_ARGS__)
#define hda_debug1(sc, ...) \
if (hdafg_debug) hda_print1(sc, __VA_ARGS__)
#define HDAUDIO_MIXER_CLASS_OUTPUTS 0
#define HDAUDIO_MIXER_CLASS_INPUTS 1
#define HDAUDIO_MIXER_CLASS_RECORD 2
#define HDAUDIO_MIXER_CLASS_LAST HDAUDIO_MIXER_CLASS_RECORD
#define HDAUDIO_GPIO_MASK 0
#define HDAUDIO_GPIO_DIR 1
#define HDAUDIO_GPIO_DATA 2
#define HDAUDIO_UNSOLTAG_EVENT_HP 0x01
#define HDAUDIO_UNSOLTAG_EVENT_DD 0x02
#define HDAUDIO_HP_SENSE_PERIOD hz
const u_int hdafg_possible_rates[] = {
8000, 11025, 16000, 22050, 32000, 44100,
48000, 88200, 96000, 176500, 192000, /* 384000, */
};
static const char *hdafg_mixer_names[] = HDAUDIO_DEVICE_NAMES;
static const char *hdafg_port_connectivity[] = {
"Jack",
"Unconnected",
"Built-In",
"Jack & Built-In"
};
static const char *hdafg_default_device[] = {
"Line Out",
"Speaker",
"HP Out",
"CD",
"SPDIF Out",
"Digital Out",
"Modem Line Side",
"Modem Handset Side",
"Line In",
"AUX",
"Mic In",
"Telephony",
"SPDIF In",
"Digital In",
"Reserved",
"Other"
};
static const char *hdafg_color[] = {
"Unknown",
"Black",
"Grey",
"Blue",
"Green",
"Red",
"Orange",
"Yellow",
"Purple",
"Pink",
"ReservedA",
"ReservedB",
"ReservedC",
"ReservedD",
"White",
"Other"
};
#define HDAUDIO_MAXFORMATS 24
#define HDAUDIO_MAXCONNECTIONS 32
#define HDAUDIO_MAXPINS 16
#define HDAUDIO_PARSE_MAXDEPTH 10
#define HDAUDIO_AMP_VOL_DEFAULT (-1)
#define HDAUDIO_AMP_MUTE_DEFAULT (0xffffffff)
#define HDAUDIO_AMP_MUTE_NONE 0
#define HDAUDIO_AMP_MUTE_LEFT (1 << 0)
#define HDAUDIO_AMP_MUTE_RIGHT (1 << 1)
#define HDAUDIO_AMP_MUTE_ALL (HDAUDIO_AMP_MUTE_LEFT | HDAUDIO_AMP_MUTE_RIGHT)
#define HDAUDIO_AMP_LEFT_MUTED(x) ((x) & HDAUDIO_AMP_MUTE_LEFT)
#define HDAUDIO_AMP_RIGHT_MUTED(x) (((x) & HDAUDIO_AMP_MUTE_RIGHT) >> 1)
#define HDAUDIO_ADC_MONITOR 1
enum hdaudio_pindir {
HDAUDIO_PINDIR_NONE = 0,
HDAUDIO_PINDIR_OUT = 1,
HDAUDIO_PINDIR_IN = 2,
HDAUDIO_PINDIR_INOUT = 3,
};
#define hda_get_param(sc, cop) \
hdaudio_command((sc)->sc_codec, (sc)->sc_nid, \
CORB_GET_PARAMETER, COP_##cop)
#define hda_get_wparam(w, cop) \
hdaudio_command((w)->w_afg->sc_codec, (w)->w_nid, \
CORB_GET_PARAMETER, COP_##cop)
struct hdaudio_assoc {
bool as_enable;
bool as_activated;
u_char as_index;
enum hdaudio_pindir as_dir;
u_char as_pincnt;
u_char as_fakeredir;
int as_digital;
#define HDAFG_AS_ANALOG 0
#define HDAFG_AS_SPDIF 1
#define HDAFG_AS_HDMI 2
#define HDAFG_AS_DISPLAYPORT 3
bool as_displaydev;
int as_hpredir;
int as_pins[HDAUDIO_MAXPINS];
int as_dacs[HDAUDIO_MAXPINS];
};
struct hdaudio_widget {
struct hdafg_softc *w_afg;
char w_name[32];
int w_nid;
bool w_enable;
bool w_waspin;
int w_selconn;
int w_bindas;
int w_bindseqmask;
int w_pflags;
int w_audiodev;
uint32_t w_audiomask;
int w_nconns;
int w_conns[HDAUDIO_MAXCONNECTIONS];
bool w_connsenable[HDAUDIO_MAXCONNECTIONS];
int w_type;
struct {
uint32_t aw_cap;
uint32_t pcm_size_rate;
uint32_t stream_format;
uint32_t outamp_cap;
uint32_t inamp_cap;
uint32_t eapdbtl;
} w_p;
struct {
uint32_t config;
uint32_t biosconfig;
uint32_t cap;
uint32_t ctrl;
} w_pin;
};
struct hdaudio_control {
struct hdaudio_widget *ctl_widget, *ctl_childwidget;
bool ctl_enable;
int ctl_index;
enum hdaudio_pindir ctl_dir, ctl_ndir;
int ctl_mute, ctl_step, ctl_size, ctl_offset;
int ctl_left, ctl_right, ctl_forcemute;
uint32_t ctl_muted;
uint32_t ctl_audiomask, ctl_paudiomask;
};
#define HDAUDIO_CONTROL_GIVE(ctl) ((ctl)->ctl_step ? 1 : 0)
struct hdaudio_mixer {
struct hdaudio_control *mx_ctl;
mixer_devinfo_t mx_di;
};
struct hdaudio_audiodev {
struct hdafg_softc *ad_sc;
device_t ad_audiodev;
struct audio_encoding_set *ad_encodings;
int ad_nformats;
struct audio_format ad_formats[HDAUDIO_MAXFORMATS];
struct hdaudio_stream *ad_playback;
void (*ad_playbackintr)(void *);
void *ad_playbackintrarg;
int ad_playbacknid[HDAUDIO_MAXPINS];
struct hdaudio_assoc *ad_playbackassoc;
struct hdaudio_stream *ad_capture;
void (*ad_captureintr)(void *);
void *ad_captureintrarg;
int ad_capturenid[HDAUDIO_MAXPINS];
struct hdaudio_assoc *ad_captureassoc;
};
struct hdafg_softc {
device_t sc_dev;
kmutex_t sc_lock;
kmutex_t sc_intr_lock;
struct hdaudio_softc *sc_host;
struct hdaudio_codec *sc_codec;
struct hdaudio_function_group *sc_fg;
int sc_nid;
uint16_t sc_vendor, sc_product;
prop_array_t sc_config;
int sc_startnode, sc_endnode;
int sc_nwidgets;
struct hdaudio_widget *sc_widgets;
int sc_nassocs;
struct hdaudio_assoc *sc_assocs;
int sc_nctls;
struct hdaudio_control *sc_ctls;
int sc_nmixers;
struct hdaudio_mixer *sc_mixers;
bool sc_has_beepgen;
int sc_pchan, sc_rchan;
audio_params_t sc_pparam, sc_rparam;
struct callout sc_jack_callout;
bool sc_jack_polling;
struct {
uint32_t afg_cap;
uint32_t pcm_size_rate;
uint32_t stream_format;
uint32_t outamp_cap;
uint32_t inamp_cap;
uint32_t power_states;
uint32_t gpio_cnt;
} sc_p;
struct hdaudio_audiodev sc_audiodev;
uint16_t sc_fixed_rate;
bool sc_disable_dip;
};
static int hdafg_match(device_t, cfdata_t, void *);
static void hdafg_attach(device_t, device_t, void *);
static int hdafg_detach(device_t, int);
static void hdafg_childdet(device_t, device_t);
static bool hdafg_suspend(device_t, const pmf_qual_t *);
static bool hdafg_resume(device_t, const pmf_qual_t *);
static int hdafg_unsol(device_t, uint8_t);
static int hdafg_widget_info(void *, prop_dictionary_t,
prop_dictionary_t);
static int hdafg_codec_info(void *, prop_dictionary_t,
prop_dictionary_t);
static void hdafg_enable_analog_beep(struct hdafg_softc *);
CFATTACH_DECL2_NEW(
hdafg,
sizeof(struct hdafg_softc),
hdafg_match,
hdafg_attach,
hdafg_detach,
NULL,
NULL,
hdafg_childdet
);
static int hdafg_query_encoding(void *, struct audio_encoding *);
static int hdafg_set_params(void *, int, int,
audio_params_t *,
audio_params_t *,
stream_filter_list_t *,
stream_filter_list_t *);
static int hdafg_round_blocksize(void *, int, int,
const audio_params_t *);
static int hdafg_commit_settings(void *);
static int hdafg_halt_output(void *);
static int hdafg_halt_input(void *);
static int hdafg_set_port(void *, mixer_ctrl_t *);
static int hdafg_get_port(void *, mixer_ctrl_t *);
static int hdafg_query_devinfo(void *, mixer_devinfo_t *);
static void * hdafg_allocm(void *, int, size_t);
static void hdafg_freem(void *, void *, size_t);
static int hdafg_getdev(void *, struct audio_device *);
static size_t hdafg_round_buffersize(void *, int, size_t);
static paddr_t hdafg_mappage(void *, void *, off_t, int);
static int hdafg_get_props(void *);
static int hdafg_trigger_output(void *, void *, void *, int,
void (*)(void *), void *,
const audio_params_t *);
static int hdafg_trigger_input(void *, void *, void *, int,
void (*)(void *), void *,
const audio_params_t *);
static void hdafg_get_locks(void *, kmutex_t **, kmutex_t **);
static const struct audio_hw_if hdafg_hw_if = {
.query_encoding = hdafg_query_encoding,
.set_params = hdafg_set_params,
.round_blocksize = hdafg_round_blocksize,
.commit_settings = hdafg_commit_settings,
.halt_output = hdafg_halt_output,
.halt_input = hdafg_halt_input,
.getdev = hdafg_getdev,
.set_port = hdafg_set_port,
.get_port = hdafg_get_port,
.query_devinfo = hdafg_query_devinfo,
.allocm = hdafg_allocm,
.freem = hdafg_freem,
.round_buffersize = hdafg_round_buffersize,
.mappage = hdafg_mappage,
.get_props = hdafg_get_props,
.trigger_output = hdafg_trigger_output,
.trigger_input = hdafg_trigger_input,
.get_locks = hdafg_get_locks,
};
static int
hdafg_append_formats(struct hdaudio_audiodev *ad,
const struct audio_format *format)
{
if (ad->ad_nformats + 1 >= HDAUDIO_MAXFORMATS) {
hda_print1(ad->ad_sc, "[ENOMEM] ");
return ENOMEM;
}
ad->ad_formats[ad->ad_nformats++] = *format;
return 0;
}
static struct hdaudio_widget *
hdafg_widget_lookup(struct hdafg_softc *sc, int nid)
{
if (sc->sc_widgets == NULL || sc->sc_nwidgets == 0) {
hda_error(sc, "lookup failed; widgets %p nwidgets %d\n",
sc->sc_widgets, sc->sc_nwidgets);
return NULL;
}
if (nid < sc->sc_startnode || nid >= sc->sc_endnode) {
hda_debug(sc, "nid %02X out of range (%02X-%02X)\n",
nid, sc->sc_startnode, sc->sc_endnode);
return NULL;
}
return &sc->sc_widgets[nid - sc->sc_startnode];
}
static struct hdaudio_control *
hdafg_control_lookup(struct hdafg_softc *sc, int nid,
enum hdaudio_pindir dir, int index, int cnt)
{
struct hdaudio_control *ctl;
int i, found = 0;
if (sc->sc_ctls == NULL)
return NULL;
for (i = 0; i < sc->sc_nctls; i++) {
ctl = &sc->sc_ctls[i];
if (ctl->ctl_enable == false)
continue;
if (ctl->ctl_widget->w_nid != nid)
continue;
if (dir && ctl->ctl_ndir != dir)
continue;
if (index >= 0 && ctl->ctl_ndir == HDAUDIO_PINDIR_IN &&
ctl->ctl_dir == ctl->ctl_ndir && ctl->ctl_index != index)
continue;
found++;
if (found == cnt || cnt <= 0)
return ctl;
}
return NULL;
}
static void
hdafg_widget_connection_parse(struct hdaudio_widget *w)
{
struct hdafg_softc *sc = w->w_afg;
uint32_t res;
int i, j, maxconns, ents, entnum;
int cnid, addcnid, prevcnid;
w->w_nconns = 0;
res = hda_get_wparam(w, CONNECTION_LIST_LENGTH);
ents = COP_CONNECTION_LIST_LENGTH_LEN(res);
if (ents < 1)
return;
if (res & COP_CONNECTION_LIST_LENGTH_LONG_FORM)
entnum = 2;
else
entnum = 4;
maxconns = (sizeof(w->w_conns) / sizeof(w->w_conns[0])) - 1;
prevcnid = 0;
#define CONN_RMASK(e) (1 << ((32 / (e)) - 1))
#define CONN_NMASK(e) (CONN_RMASK(e) - 1)
#define CONN_RESVAL(r, e, n) ((r) >> ((32 / (e)) * (n)))
#define CONN_RANGE(r, e, n) (CONN_RESVAL(r, e, n) & CONN_RMASK(e))
#define CONN_CNID(r, e, n) (CONN_RESVAL(r, e, n) & CONN_NMASK(e))
for (i = 0; i < ents; i += entnum) {
res = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_CONNECTION_LIST_ENTRY, i);
for (j = 0; j < entnum; j++) {
cnid = CONN_CNID(res, entnum, j);
if (cnid == 0) {
if (w->w_nconns < ents) {
hda_error(sc, "WARNING: zero cnid\n");
} else {
goto getconns_out;
}
}
if (cnid < sc->sc_startnode || cnid >= sc->sc_endnode)
hda_debug(sc, "ghost nid=%02X\n", cnid);
if (CONN_RANGE(res, entnum, j) == 0)
addcnid = cnid;
else if (prevcnid == 0 || prevcnid >= cnid) {
hda_error(sc, "invalid child range\n");
addcnid = cnid;
} else
addcnid = prevcnid + 1;
while (addcnid <= cnid) {
if (w->w_nconns > maxconns) {
hda_error(sc,
"max connections reached\n");
goto getconns_out;
}
w->w_connsenable[w->w_nconns] = true;
w->w_conns[w->w_nconns++] = addcnid++;
hda_trace(sc, "add connection %02X->%02X\n",
w->w_nid, addcnid - 1);
}
prevcnid = cnid;
}
}
#undef CONN_RMASK
#undef CONN_NMASK
#undef CONN_RESVAL
#undef CONN_RANGE
#undef CONN_CNID
getconns_out:
return;
}
static void
hdafg_widget_pin_dump(struct hdafg_softc *sc)
{
struct hdaudio_widget *w;
int i, conn;
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
if (conn != 1) {
#ifdef HDAUDIO_DEBUG
int color = COP_CFG_COLOR(w->w_pin.config);
int defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
hda_trace(sc, "io %02X: %s (%s, %s)\n",
w->w_nid,
hdafg_default_device[defdev],
hdafg_color[color],
hdafg_port_connectivity[conn]);
#endif
}
}
}
static void
hdafg_widget_setconfig(struct hdaudio_widget *w, uint32_t cfg)
{
struct hdafg_softc *sc = w->w_afg;
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONFIGURATION_DEFAULT_1, (cfg >> 0) & 0xff);
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONFIGURATION_DEFAULT_2, (cfg >> 8) & 0xff);
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONFIGURATION_DEFAULT_3, (cfg >> 16) & 0xff);
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONFIGURATION_DEFAULT_4, (cfg >> 24) & 0xff);
}
static uint32_t
hdafg_widget_getconfig(struct hdaudio_widget *w)
{
struct hdafg_softc *sc = w->w_afg;
uint32_t config = 0;
prop_object_iterator_t iter;
prop_dictionary_t dict;
prop_object_t obj;
int16_t nid;
if (sc->sc_config == NULL)
goto biosconfig;
iter = prop_array_iterator(sc->sc_config);
if (iter == NULL)
goto biosconfig;
prop_object_iterator_reset(iter);
while ((obj = prop_object_iterator_next(iter)) != NULL) {
if (prop_object_type(obj) != PROP_TYPE_DICTIONARY)
continue;
dict = (prop_dictionary_t)obj;
if (!prop_dictionary_get_int16(dict, "nid", &nid) ||
!prop_dictionary_get_uint32(dict, "config", &config))
continue;
if (nid == w->w_nid)
return config;
}
biosconfig:
return hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_CONFIGURATION_DEFAULT, 0);
}
static void
hdafg_widget_pin_parse(struct hdaudio_widget *w)
{
struct hdafg_softc *sc = w->w_afg;
int conn, color, defdev;
w->w_pin.cap = hda_get_wparam(w, PIN_CAPABILITIES);
w->w_pin.config = hdafg_widget_getconfig(w);
w->w_pin.biosconfig = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_CONFIGURATION_DEFAULT, 0);
w->w_pin.ctrl = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_PIN_WIDGET_CONTROL, 0);
/* treat line-out as speaker, unless connection type is RCA */
if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) == COP_DEVICE_LINE_OUT &&
COP_CFG_CONNECTION_TYPE(w->w_pin.config) != COP_CONN_TYPE_RCA) {
w->w_pin.config &= ~COP_DEVICE_MASK;
w->w_pin.config |= (COP_DEVICE_SPEAKER << COP_DEVICE_SHIFT);
}
if (w->w_pin.cap & COP_PINCAP_EAPD_CAPABLE) {
w->w_p.eapdbtl = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_EAPD_BTL_ENABLE, 0);
w->w_p.eapdbtl &= 0x7;
w->w_p.eapdbtl |= COP_EAPD_ENABLE_EAPD;
} else
w->w_p.eapdbtl = 0xffffffff;
#if 0
/* XXX VT1708 */
if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) == COP_DEVICE_SPEAKER &&
COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config) == 15) {
hda_trace(sc, "forcing speaker nid %02X to assoc=14\n",
w->w_nid);
/* set assoc=14 */
w->w_pin.config &= ~0xf0;
w->w_pin.config |= 0xe0;
}
if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) == COP_DEVICE_HP_OUT &&
COP_CFG_PORT_CONNECTIVITY(w->w_pin.config) == COP_PORT_NONE) {
hda_trace(sc, "forcing hp out nid %02X to assoc=14\n",
w->w_nid);
/* set connectivity to 'jack' */
w->w_pin.config &= ~(COP_PORT_BOTH << 30);
w->w_pin.config |= (COP_PORT_JACK << 30);
/* set seq=15 */
w->w_pin.config &= ~0xf;
w->w_pin.config |= 15;
/* set assoc=14 */
w->w_pin.config &= ~0xf0;
w->w_pin.config |= 0xe0;
}
#endif
conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
color = COP_CFG_COLOR(w->w_pin.config);
defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
strlcat(w->w_name, ": ", sizeof(w->w_name));
strlcat(w->w_name, hdafg_default_device[defdev], sizeof(w->w_name));
strlcat(w->w_name, " (", sizeof(w->w_name));
if (conn == 0 && color != 0 && color != 15) {
strlcat(w->w_name, hdafg_color[color], sizeof(w->w_name));
strlcat(w->w_name, " ", sizeof(w->w_name));
}
strlcat(w->w_name, hdafg_port_connectivity[conn], sizeof(w->w_name));
strlcat(w->w_name, ")", sizeof(w->w_name));
}
static uint32_t
hdafg_widget_getcaps(struct hdaudio_widget *w)
{
struct hdafg_softc *sc = w->w_afg;
uint32_t wcap, config;
bool pcbeep = false;
wcap = hda_get_wparam(w, AUDIO_WIDGET_CAPABILITIES);
config = hdafg_widget_getconfig(w);
w->w_waspin = false;
switch (sc->sc_vendor) {
case HDAUDIO_VENDOR_ANALOG:
/*
* help the parser by marking the analog
* beeper as a beep generator
*/
if (w->w_nid == 0x1a &&
COP_CFG_SEQUENCE(config) == 0x0 &&
COP_CFG_DEFAULT_ASSOCIATION(config) == 0xf &&
COP_CFG_PORT_CONNECTIVITY(config) ==
COP_PORT_FIXED_FUNCTION &&
COP_CFG_DEFAULT_DEVICE(config) ==
COP_DEVICE_OTHER) {
pcbeep = true;
}
break;
}
if (pcbeep ||
(sc->sc_has_beepgen == false &&
COP_CFG_DEFAULT_DEVICE(config) == COP_DEVICE_SPEAKER &&
(wcap & (COP_AWCAP_INAMP_PRESENT|COP_AWCAP_OUTAMP_PRESENT)) == 0)) {
wcap &= ~COP_AWCAP_TYPE_MASK;
wcap |= (COP_AWCAP_TYPE_BEEP_GENERATOR << COP_AWCAP_TYPE_SHIFT);
w->w_waspin = true;
}
return wcap;
}
static void
hdafg_widget_parse(struct hdaudio_widget *w)
{
struct hdafg_softc *sc = w->w_afg;
const char *tstr;
w->w_p.aw_cap = hdafg_widget_getcaps(w);
w->w_type = COP_AWCAP_TYPE(w->w_p.aw_cap);
switch (w->w_type) {
case COP_AWCAP_TYPE_AUDIO_OUTPUT: tstr = "audio output"; break;
case COP_AWCAP_TYPE_AUDIO_INPUT: tstr = "audio input"; break;
case COP_AWCAP_TYPE_AUDIO_MIXER: tstr = "audio mixer"; break;
case COP_AWCAP_TYPE_AUDIO_SELECTOR: tstr = "audio selector"; break;
case COP_AWCAP_TYPE_PIN_COMPLEX: tstr = "pin"; break;
case COP_AWCAP_TYPE_POWER_WIDGET: tstr = "power widget"; break;
case COP_AWCAP_TYPE_VOLUME_KNOB: tstr = "volume knob"; break;
case COP_AWCAP_TYPE_BEEP_GENERATOR: tstr = "beep generator"; break;
case COP_AWCAP_TYPE_VENDOR_DEFINED: tstr = "vendor defined"; break;
default: tstr = "unknown"; break;
}
strlcpy(w->w_name, tstr, sizeof(w->w_name));
hdafg_widget_connection_parse(w);
if (w->w_p.aw_cap & COP_AWCAP_INAMP_PRESENT) {
if (w->w_p.aw_cap & COP_AWCAP_AMP_PARAM_OVERRIDE)
w->w_p.inamp_cap = hda_get_wparam(w,
AMPLIFIER_CAPABILITIES_INAMP);
else
w->w_p.inamp_cap = sc->sc_p.inamp_cap;
}
if (w->w_p.aw_cap & COP_AWCAP_OUTAMP_PRESENT) {
if (w->w_p.aw_cap & COP_AWCAP_AMP_PARAM_OVERRIDE)
w->w_p.outamp_cap = hda_get_wparam(w,
AMPLIFIER_CAPABILITIES_OUTAMP);
else
w->w_p.outamp_cap = sc->sc_p.outamp_cap;
}
w->w_p.stream_format = 0;
w->w_p.pcm_size_rate = 0;
switch (w->w_type) {
case COP_AWCAP_TYPE_AUDIO_OUTPUT:
case COP_AWCAP_TYPE_AUDIO_INPUT:
if (w->w_p.aw_cap & COP_AWCAP_FORMAT_OVERRIDE) {
w->w_p.stream_format = hda_get_wparam(w,
SUPPORTED_STREAM_FORMATS);
w->w_p.pcm_size_rate = hda_get_wparam(w,
SUPPORTED_PCM_SIZE_RATES);
} else {
w->w_p.stream_format = sc->sc_p.stream_format;
w->w_p.pcm_size_rate = sc->sc_p.pcm_size_rate;
}
break;
case COP_AWCAP_TYPE_PIN_COMPLEX:
hdafg_widget_pin_parse(w);
hdafg_widget_setconfig(w, w->w_pin.config);
break;
}
}
static int
hdafg_assoc_count_channels(struct hdafg_softc *sc,
struct hdaudio_assoc *as, enum hdaudio_pindir dir)
{
struct hdaudio_widget *w;
int *dacmap;
int i, dacmapsz = sizeof(*dacmap) * sc->sc_endnode;
int nchans = 0;
if (as->as_enable == false || as->as_dir != dir)
return 0;
dacmap = kmem_zalloc(dacmapsz, KM_SLEEP);
for (i = 0; i < HDAUDIO_MAXPINS; i++)
if (as->as_dacs[i])
dacmap[as->as_dacs[i]] = 1;
for (i = 1; i < sc->sc_endnode; i++) {
if (!dacmap[i])
continue;
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
nchans += COP_AWCAP_CHANNEL_COUNT(w->w_p.aw_cap);
}
kmem_free(dacmap, dacmapsz);
return nchans;
}
static const char *
hdafg_assoc_type_string(struct hdaudio_assoc *as)
{
switch (as->as_digital) {
case HDAFG_AS_ANALOG:
return as->as_dir == HDAUDIO_PINDIR_IN ?
"ADC" : "DAC";
case HDAFG_AS_SPDIF:
return as->as_dir == HDAUDIO_PINDIR_IN ?
"DIG-In" : "DIG";
case HDAFG_AS_HDMI:
return as->as_dir == HDAUDIO_PINDIR_IN ?
"HDMI-In" : "HDMI";
case HDAFG_AS_DISPLAYPORT:
return as->as_dir == HDAUDIO_PINDIR_IN ?
"DP-In" : "DP";
default:
return as->as_dir == HDAUDIO_PINDIR_IN ?
"Unknown-In" : "Unknown-Out";
}
}
static void
hdafg_assoc_dump_dd(struct hdafg_softc *sc, struct hdaudio_assoc *as, int pin,
int lock)
{
struct hdafg_dd_info hdi;
struct hdaudio_widget *w;
uint8_t elddata[256];
unsigned int elddatalen = 0, i;
uint32_t res;
uint32_t (*cmd)(struct hdaudio_codec *, int, uint32_t, uint32_t) =
lock ? hdaudio_command : hdaudio_command_unlocked;
w = hdafg_widget_lookup(sc, as->as_pins[pin]);
if (w->w_pin.cap & COP_PINCAP_TRIGGER_REQD) {
(*cmd)(sc->sc_codec, as->as_pins[pin],
CORB_SET_PIN_SENSE, 0);
}
res = (*cmd)(sc->sc_codec, as->as_pins[pin],
CORB_GET_PIN_SENSE, 0);
#ifdef HDAFG_HDMI_DEBUG
hda_print(sc, "Display Device, pin=%02X\n", as->as_pins[pin]);
hda_print(sc, " COP_GET_PIN_SENSE_PRESENSE_DETECT=%d\n",
!!(res & COP_GET_PIN_SENSE_PRESENSE_DETECT));
hda_print(sc, " COP_GET_PIN_SENSE_ELD_VALID=%d\n",
!!(res & COP_GET_PIN_SENSE_ELD_VALID));
#endif
if ((res &
(COP_GET_PIN_SENSE_PRESENSE_DETECT|COP_GET_PIN_SENSE_ELD_VALID)) ==
(COP_GET_PIN_SENSE_PRESENSE_DETECT|COP_GET_PIN_SENSE_ELD_VALID)) {
res = (*cmd)(sc->sc_codec, as->as_pins[pin],
CORB_GET_HDMI_DIP_SIZE, COP_DIP_ELD_SIZE);
elddatalen = COP_DIP_BUFFER_SIZE(res);
if (elddatalen == 0)
elddatalen = sizeof(elddata); /* paranoid */
for (i = 0; i < elddatalen; i++) {
res = (*cmd)(sc->sc_codec, as->as_pins[pin],
CORB_GET_HDMI_ELD_DATA, i);
if (!(res & COP_ELD_VALID)) {
#ifdef HDAFG_HDMI_DEBUG
hda_error(sc, "bad ELD size (%u/%u)\n",
i, elddatalen);
#endif
break;
}
elddata[i] = COP_ELD_DATA(res);
}
if (hdafg_dd_parse_info(elddata, elddatalen, &hdi) != 0) {
#ifdef HDAFG_HDMI_DEBUG
hda_error(sc, "failed to parse ELD data\n");
#endif
return;
}
hda_print(sc, " ELD version=0x%x", ELD_VER(&hdi.eld));
hda_print1(sc, ",len=%u", hdi.eld.header.baseline_eld_len * 4);
hda_print1(sc, ",edid=0x%x", ELD_CEA_EDID_VER(&hdi.eld));
hda_print1(sc, ",port=0x%" PRIx64, hdi.eld.port_id);
hda_print1(sc, ",vendor=0x%04x", hdi.eld.vendor);
hda_print1(sc, ",product=0x%04x", hdi.eld.product);
hda_print1(sc, "\n");
hda_print(sc, " Monitor = '%s'\n", hdi.monitor);
for (i = 0; i < hdi.nsad; i++) {
hda_print(sc, " SAD id=%u", i);
hda_print1(sc, ",format=%u",
CEA_AUDIO_FORMAT(&hdi.sad[i]));
hda_print1(sc, ",channels=%u",
CEA_MAX_CHANNELS(&hdi.sad[i]));
hda_print1(sc, ",rate=0x%02x",
CEA_SAMPLE_RATE(&hdi.sad[i]));
if (CEA_AUDIO_FORMAT(&hdi.sad[i]) ==
CEA_AUDIO_FORMAT_LPCM)
hda_print1(sc, ",precision=0x%x",
CEA_PRECISION(&hdi.sad[i]));
else
hda_print1(sc, ",maxbitrate=%u",
CEA_MAX_BITRATE(&hdi.sad[i]));
hda_print1(sc, "\n");
}
}
}
static char *
hdafg_mixer_mask2allname(uint32_t mask, char *buf, size_t len)
{
static const char *audioname[] = HDAUDIO_DEVICE_NAMES;
int i, first = 1;
memset(buf, 0, len);
for (i = 0; i < HDAUDIO_MIXER_NRDEVICES; i++) {
if (mask & (1 << i)) {
if (first == 0)
strlcat(buf, ", ", len);
strlcat(buf, audioname[i], len);
first = 0;
}
}
return buf;
}
static void
hdafg_dump_dst_nid(struct hdafg_softc *sc, int nid, int depth)
{
struct hdaudio_widget *w, *cw;
char buf[64];
int i;
if (depth > HDAUDIO_PARSE_MAXDEPTH)
return;
w = hdafg_widget_lookup(sc, nid);
if (w == NULL || w->w_enable == false)
return;
aprint_debug("%*s", 4 + depth * 7, "");
aprint_debug("nid=%02X [%s]", w->w_nid, w->w_name);
if (depth > 0) {
if (w->w_audiomask == 0) {
aprint_debug("\n");
return;
}
aprint_debug(" [source: %s]",
hdafg_mixer_mask2allname(w->w_audiomask, buf, sizeof(buf)));
if (w->w_audiodev >= 0) {
aprint_debug("\n");
return;
}
}
aprint_debug("\n");
for (i = 0; i < w->w_nconns; i++) {
if (w->w_connsenable[i] == 0)
continue;
cw = hdafg_widget_lookup(sc, w->w_conns[i]);
if (cw == NULL || cw->w_enable == false || cw->w_bindas == -1)
continue;
hdafg_dump_dst_nid(sc, w->w_conns[i], depth + 1);
}
}
static void
hdafg_assoc_dump(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
uint32_t conn, defdev, curdev, curport;
int maxassocs = sc->sc_nassocs;
int i, j;
for (i = 0; i < maxassocs; i++) {
uint32_t devmask = 0, portmask = 0;
bool firstdev = true;
int nchan;
if (as[i].as_enable == false)
continue;
hda_print(sc, "%s%02X",
hdafg_assoc_type_string(&as[i]), i);
nchan = hdafg_assoc_count_channels(sc, &as[i],
as[i].as_dir);
hda_print1(sc, " %dch:", nchan);
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_dacs[j] == 0)
continue;
w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
if (w == NULL)
continue;
conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
if (conn != COP_PORT_NONE) {
devmask |= (1 << defdev);
portmask |= (1 << conn);
}
}
for (curdev = 0; curdev < 16; curdev++) {
bool firstport = true;
if ((devmask & (1 << curdev)) == 0)
continue;
if (firstdev == false)
hda_print1(sc, ",");
firstdev = false;
hda_print1(sc, " %s",
hdafg_default_device[curdev]);
for (curport = 0; curport < 4; curport++) {
bool devonport = false;
if ((portmask & (1 << curport)) == 0)
continue;
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_dacs[j] == 0)
continue;
w = hdafg_widget_lookup(sc,
as[i].as_pins[j]);
if (w == NULL)
continue;
conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
if (conn != curport || defdev != curdev)
continue;
devonport = true;
}
if (devonport == false)
continue;
hda_print1(sc, " [%s",
hdafg_port_connectivity[curport]);
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_dacs[j] == 0)
continue;
w = hdafg_widget_lookup(sc,
as[i].as_pins[j]);
if (w == NULL)
continue;
conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
defdev = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
if (conn != curport || defdev != curdev)
continue;
if (firstport == false)
hda_trace1(sc, ",");
else
hda_trace1(sc, " ");
firstport = false;
#ifdef HDAUDIO_DEBUG
int color =
COP_CFG_COLOR(w->w_pin.config);
hda_trace1(sc, "%s",
hdafg_color[color]);
#endif
hda_trace1(sc, "(%02X)", w->w_nid);
}
hda_print1(sc, "]");
}
}
hda_print1(sc, "\n");
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_pins[j] == 0)
continue;
hdafg_dump_dst_nid(sc, as[i].as_pins[j], 0);
}
if (as[i].as_displaydev == true) {
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_pins[j] == 0)
continue;
hdafg_assoc_dump_dd(sc, &as[i], j, 1);
}
}
}
}
static void
hdafg_assoc_parse(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as;
struct hdaudio_widget *w;
int i, j, cnt, maxassocs, type, assoc, seq, first, hpredir;
enum hdaudio_pindir dir;
hda_debug(sc, " count present associations\n");
/* Count present associations */
maxassocs = 0;
for (j = 1; j < HDAUDIO_MAXPINS; j++) {
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
if (COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config) != j)
continue;
maxassocs++;
if (j != 15) /* There could be many 1-pin assocs #15 */
break;
}
}
hda_debug(sc, " maxassocs %d\n", maxassocs);
sc->sc_nassocs = maxassocs;
if (maxassocs < 1)
return;
hda_debug(sc, " allocating memory\n");
as = kmem_zalloc(maxassocs * sizeof(*as), KM_SLEEP);
for (i = 0; i < maxassocs; i++) {
as[i].as_hpredir = -1;
/* as[i].as_chan = NULL; */
as[i].as_digital = HDAFG_AS_SPDIF;
}
hda_debug(sc, " scan associations, skipping as=0\n");
/* Scan associations skipping as=0 */
cnt = 0;
for (j = 1; j < HDAUDIO_MAXPINS && cnt < maxassocs; j++) {
first = 16;
hpredir = 0;
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
assoc = COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config);
seq = COP_CFG_SEQUENCE(w->w_pin.config);
if (assoc != j)
continue;
KASSERT(cnt < maxassocs);
type = COP_CFG_DEFAULT_DEVICE(w->w_pin.config);
/* Get pin direction */
switch (type) {
case COP_DEVICE_LINE_OUT:
case COP_DEVICE_SPEAKER:
case COP_DEVICE_HP_OUT:
case COP_DEVICE_SPDIF_OUT:
case COP_DEVICE_DIGITAL_OTHER_OUT:
dir = HDAUDIO_PINDIR_OUT;
break;
default:
dir = HDAUDIO_PINDIR_IN;
break;
}
/* If this is a first pin, create new association */
if (as[cnt].as_pincnt == 0) {
as[cnt].as_enable = true;
as[cnt].as_activated = true;
as[cnt].as_index = j;
as[cnt].as_dir = dir;
}
if (seq < first)
first = seq;
/* Check association correctness */
if (as[cnt].as_pins[seq] != 0) {
hda_error(sc, "duplicate pin in association\n");
as[cnt].as_enable = false;
}
if (dir != as[cnt].as_dir) {
hda_error(sc,
"pin %02X has wrong direction for %02X\n",
w->w_nid, j);
as[cnt].as_enable = false;
}
if ((w->w_p.aw_cap & COP_AWCAP_DIGITAL) == 0)
as[cnt].as_digital = HDAFG_AS_ANALOG;
if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP))
as[cnt].as_displaydev = true;
if (w->w_pin.cap & COP_PINCAP_HDMI)
as[cnt].as_digital = HDAFG_AS_HDMI;
if (w->w_pin.cap & COP_PINCAP_DP)
as[cnt].as_digital = HDAFG_AS_DISPLAYPORT;
/* Headphones with seq=15 may mean redirection */
if (type == COP_DEVICE_HP_OUT && seq == 15)
hpredir = 1;
as[cnt].as_pins[seq] = w->w_nid;
as[cnt].as_pincnt++;
if (j == 15)
cnt++;
}
if (j != 15 && cnt < maxassocs && as[cnt].as_pincnt > 0) {
if (hpredir && as[cnt].as_pincnt > 1)
as[cnt].as_hpredir = first;
cnt++;
}
}
hda_debug(sc, " all done\n");
sc->sc_assocs = as;
}
static void
hdafg_control_parse(struct hdafg_softc *sc)
{
struct hdaudio_control *ctl;
struct hdaudio_widget *w, *cw;
int i, j, cnt, maxctls, ocap, icap;
int mute, offset, step, size;
maxctls = 0;
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_p.outamp_cap)
maxctls++;
if (w->w_p.inamp_cap) {
switch (w->w_type) {
case COP_AWCAP_TYPE_AUDIO_SELECTOR:
case COP_AWCAP_TYPE_AUDIO_MIXER:
for (j = 0; j < w->w_nconns; j++) {
cw = hdafg_widget_lookup(sc,
w->w_conns[j]);
if (cw == NULL || cw->w_enable == false)
continue;
maxctls++;
}
break;
default:
maxctls++;
break;
}
}
}
sc->sc_nctls = maxctls;
if (maxctls < 1)
return;
ctl = kmem_zalloc(sc->sc_nctls * sizeof(*ctl), KM_SLEEP);
cnt = 0;
for (i = sc->sc_startnode; cnt < maxctls && i < sc->sc_endnode; i++) {
if (cnt >= maxctls) {
hda_error(sc, "ctl overflow\n");
break;
}
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
ocap = w->w_p.outamp_cap;
icap = w->w_p.inamp_cap;
if (ocap) {
hda_trace(sc, "add ctrl outamp %d:%02X:FF\n",
cnt, w->w_nid);
mute = COP_AMPCAP_MUTE_CAPABLE(ocap);
step = COP_AMPCAP_NUM_STEPS(ocap);
size = COP_AMPCAP_STEP_SIZE(ocap);
offset = COP_AMPCAP_OFFSET(ocap);
ctl[cnt].ctl_enable = true;
ctl[cnt].ctl_widget = w;
ctl[cnt].ctl_mute = mute;
ctl[cnt].ctl_step = step;
ctl[cnt].ctl_size = size;
ctl[cnt].ctl_offset = offset;
ctl[cnt].ctl_left = offset;
ctl[cnt].ctl_right = offset;
if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX ||
w->w_waspin == true)
ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_IN;
else
ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_OUT;
ctl[cnt++].ctl_dir = HDAUDIO_PINDIR_OUT;
}
if (icap) {
mute = COP_AMPCAP_MUTE_CAPABLE(icap);
step = COP_AMPCAP_NUM_STEPS(icap);
size = COP_AMPCAP_STEP_SIZE(icap);
offset = COP_AMPCAP_OFFSET(icap);
switch (w->w_type) {
case COP_AWCAP_TYPE_AUDIO_SELECTOR:
case COP_AWCAP_TYPE_AUDIO_MIXER:
for (j = 0; j < w->w_nconns; j++) {
if (cnt >= maxctls)
break;
cw = hdafg_widget_lookup(sc,
w->w_conns[j]);
if (cw == NULL || cw->w_enable == false)
continue;
hda_trace(sc, "add ctrl inamp selmix "
"%d:%02X:%02X\n", cnt, w->w_nid,
cw->w_nid);
ctl[cnt].ctl_enable = true;
ctl[cnt].ctl_widget = w;
ctl[cnt].ctl_childwidget = cw;
ctl[cnt].ctl_index = j;
ctl[cnt].ctl_mute = mute;
ctl[cnt].ctl_step = step;
ctl[cnt].ctl_size = size;
ctl[cnt].ctl_offset = offset;
ctl[cnt].ctl_left = offset;
ctl[cnt].ctl_right = offset;
ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_IN;
ctl[cnt++].ctl_dir = HDAUDIO_PINDIR_IN;
}
break;
default:
if (cnt >= maxctls)
break;
hda_trace(sc, "add ctrl inamp "
"%d:%02X:FF\n", cnt, w->w_nid);
ctl[cnt].ctl_enable = true;
ctl[cnt].ctl_widget = w;
ctl[cnt].ctl_mute = mute;
ctl[cnt].ctl_step = step;
ctl[cnt].ctl_size = size;
ctl[cnt].ctl_offset = offset;
ctl[cnt].ctl_left = offset;
ctl[cnt].ctl_right = offset;
if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX)
ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_OUT;
else
ctl[cnt].ctl_ndir = HDAUDIO_PINDIR_IN;
ctl[cnt++].ctl_dir = HDAUDIO_PINDIR_IN;
break;
}
}
}
sc->sc_ctls = ctl;
}
static void
hdafg_parse(struct hdafg_softc *sc)
{
struct hdaudio_widget *w;
uint32_t nodecnt, wcap;
int nid;
nodecnt = hda_get_param(sc, SUBORDINATE_NODE_COUNT);
sc->sc_startnode = COP_NODECNT_STARTNODE(nodecnt);
sc->sc_nwidgets = COP_NODECNT_NUMNODES(nodecnt);
sc->sc_endnode = sc->sc_startnode + sc->sc_nwidgets;
hda_debug(sc, "afg start %02X end %02X nwidgets %d\n",
sc->sc_startnode, sc->sc_endnode, sc->sc_nwidgets);
hda_debug(sc, "powering up widgets\n");
hdaudio_command(sc->sc_codec, sc->sc_nid,
CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
hda_delay(100);
for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++)
hdaudio_command(sc->sc_codec, nid,
CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
hda_delay(1000);
sc->sc_p.afg_cap = hda_get_param(sc, AUDIO_FUNCTION_GROUP_CAPABILITIES);
sc->sc_p.stream_format = hda_get_param(sc, SUPPORTED_STREAM_FORMATS);
sc->sc_p.pcm_size_rate = hda_get_param(sc, SUPPORTED_PCM_SIZE_RATES);
sc->sc_p.outamp_cap = hda_get_param(sc, AMPLIFIER_CAPABILITIES_OUTAMP);
sc->sc_p.inamp_cap = hda_get_param(sc, AMPLIFIER_CAPABILITIES_INAMP);
sc->sc_p.power_states = hda_get_param(sc, SUPPORTED_POWER_STATES);
sc->sc_p.gpio_cnt = hda_get_param(sc, GPIO_COUNT);
sc->sc_widgets = kmem_zalloc(sc->sc_nwidgets * sizeof(*w), KM_SLEEP);
hda_debug(sc, "afg widgets %p-%p\n",
sc->sc_widgets, sc->sc_widgets + sc->sc_nwidgets);
for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
w = hdafg_widget_lookup(sc, nid);
if (w == NULL)
continue;
wcap = hdaudio_command(sc->sc_codec, nid, CORB_GET_PARAMETER,
COP_AUDIO_WIDGET_CAPABILITIES);
switch (COP_AWCAP_TYPE(wcap)) {
case COP_AWCAP_TYPE_BEEP_GENERATOR:
sc->sc_has_beepgen = true;
break;
}
}
for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
w = hdafg_widget_lookup(sc, nid);
if (w == NULL)
continue;
w->w_afg = sc;
w->w_nid = nid;
w->w_enable = true;
w->w_pflags = 0;
w->w_audiodev = -1;
w->w_selconn = -1;
w->w_bindas = -1;
w->w_p.eapdbtl = 0xffffffff;
hdafg_widget_parse(w);
}
}
static void
hdafg_disable_nonaudio(struct hdafg_softc *sc)
{
struct hdaudio_widget *w;
int i;
/* Disable power and volume widgets */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type == COP_AWCAP_TYPE_POWER_WIDGET ||
w->w_type == COP_AWCAP_TYPE_VOLUME_KNOB) {
hda_trace(w->w_afg, "disable %02X [nonaudio]\n",
w->w_nid);
w->w_enable = false;
}
}
}
static void
hdafg_disable_useless(struct hdafg_softc *sc)
{
struct hdaudio_widget *w, *cw;
struct hdaudio_control *ctl;
int done, found, i, j, k;
int conn, assoc;
/* Disable useless pins */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
conn = COP_CFG_PORT_CONNECTIVITY(w->w_pin.config);
assoc = COP_CFG_DEFAULT_ASSOCIATION(w->w_pin.config);
if (conn == COP_PORT_NONE) {
hda_trace(w->w_afg, "disable %02X [no connectivity]\n",
w->w_nid);
w->w_enable = false;
}
if (assoc == 0) {
hda_trace(w->w_afg, "disable %02X [no association]\n",
w->w_nid);
w->w_enable = false;
}
}
do {
done = 1;
/* Disable and mute controls for disabled widgets */
for (i = 0; i < sc->sc_nctls; i++) {
ctl = &sc->sc_ctls[i];
if (ctl->ctl_enable == false)
continue;
if (ctl->ctl_widget->w_enable == false ||
(ctl->ctl_childwidget != NULL &&
ctl->ctl_childwidget->w_enable == false)) {
ctl->ctl_forcemute = 1;
ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
ctl->ctl_left = ctl->ctl_right = 0;
ctl->ctl_enable = false;
if (ctl->ctl_ndir == HDAUDIO_PINDIR_IN)
ctl->ctl_widget->w_connsenable[
ctl->ctl_index] = false;
done = 0;
hda_trace(ctl->ctl_widget->w_afg,
"disable ctl %d:%02X:%02X [widget disabled]\n",
i, ctl->ctl_widget->w_nid,
ctl->ctl_childwidget ?
ctl->ctl_childwidget->w_nid : 0xff);
}
}
/* Disable useless widgets */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
/* Disable inputs with disabled child widgets */
for (j = 0; j < w->w_nconns; j++) {
if (!w->w_connsenable[j])
continue;
cw = hdafg_widget_lookup(sc,
w->w_conns[j]);
if (cw == NULL || cw->w_enable == false) {
w->w_connsenable[j] = false;
hda_trace(w->w_afg,
"disable conn %02X->%02X "
"[disabled child]\n",
w->w_nid, w->w_conns[j]);
}
}
if (w->w_type != COP_AWCAP_TYPE_AUDIO_SELECTOR &&
w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER)
continue;
/* Disable mixers and selectors without inputs */
found = 0;
for (j = 0; j < w->w_nconns; j++)
if (w->w_connsenable[j]) {
found = 1;
break;
}
if (found == 0) {
w->w_enable = false;
done = 0;
hda_trace(w->w_afg,
"disable %02X [inputs disabled]\n",
w->w_nid);
}
/* Disable nodes without consumers */
if (w->w_type != COP_AWCAP_TYPE_AUDIO_SELECTOR &&
w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER)
continue;
found = 0;
for (k = sc->sc_startnode; k < sc->sc_endnode; k++) {
cw = hdafg_widget_lookup(sc, k);
if (cw == NULL || cw->w_enable == false)
continue;
for (j = 0; j < cw->w_nconns; j++) {
if (cw->w_connsenable[j] &&
cw->w_conns[j] == i) {
found = 1;
break;
}
}
}
if (found == 0) {
w->w_enable = false;
done = 0;
hda_trace(w->w_afg,
"disable %02X [consumers disabled]\n",
w->w_nid);
}
}
} while (done == 0);
}
static void
hdafg_assoc_trace_undo(struct hdafg_softc *sc, int as, int seq)
{
struct hdaudio_widget *w;
int i;
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_bindas != as)
continue;
if (seq >= 0) {
w->w_bindseqmask &= ~(1 << seq);
if (w->w_bindseqmask == 0) {
w->w_bindas = -1;
w->w_selconn = -1;
}
} else {
w->w_bindas = -1;
w->w_bindseqmask = 0;
w->w_selconn = -1;
}
}
}
static int
hdafg_assoc_trace_dac(struct hdafg_softc *sc, int as, int seq,
int nid, int dupseq, int minassoc, int only, int depth)
{
struct hdaudio_widget *w;
int i, im = -1;
int m = 0, ret;
if (depth >= HDAUDIO_PARSE_MAXDEPTH)
return 0;
w = hdafg_widget_lookup(sc, nid);
if (w == NULL || w->w_enable == false)
return 0;
/* We use only unused widgets */
if (w->w_bindas >= 0 && w->w_bindas != as) {
if (!only)
hda_trace(sc, "depth %d nid %02X busy by assoc %d\n",
depth + 1, nid, w->w_bindas);
return 0;
}
if (dupseq < 0) {
if (w->w_bindseqmask != 0) {
if (!only)
hda_trace(sc,
"depth %d nid %02X busy by seqmask %x\n",
depth + 1, nid, w->w_bindas);
return 0;
}
} else {
/* If this is headphones, allow duplicate first pin */
if (w->w_bindseqmask != 0 &&
(w->w_bindseqmask & (1 << dupseq)) == 0)
return 0;
}
switch (w->w_type) {
case COP_AWCAP_TYPE_AUDIO_INPUT:
break;
case COP_AWCAP_TYPE_AUDIO_OUTPUT:
/* If we are tracing HP take only dac of first pin */
if ((only == 0 || only == w->w_nid) &&
(w->w_nid >= minassoc) && (dupseq < 0 || w->w_nid ==
sc->sc_assocs[as].as_dacs[dupseq]))
m = w->w_nid;
break;
case COP_AWCAP_TYPE_PIN_COMPLEX:
if (depth > 0)
break;
/* FALLTHROUGH */
default:
for (i = 0; i < w->w_nconns; i++) {
if (w->w_connsenable[i] == false)
continue;
if (w->w_selconn != -1 && w->w_selconn != i)
continue;
ret = hdafg_assoc_trace_dac(sc, as, seq,
w->w_conns[i], dupseq, minassoc, only, depth + 1);
if (ret) {
if (m == 0 || ret < m) {
m = ret;
im = i;
}
if (only || dupseq >= 0)
break;
}
}
if (m && only && ((w->w_nconns > 1 &&
w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER) ||
w->w_type == COP_AWCAP_TYPE_AUDIO_SELECTOR))
w->w_selconn = im;
break;
}
if (m && only) {
w->w_bindas = as;
w->w_bindseqmask |= (1 << seq);
}
if (!only)
hda_trace(sc, "depth %d nid %02X dupseq %d returned %02X\n",
depth + 1, nid, dupseq, m);
return m;
}
static int
hdafg_assoc_trace_out(struct hdafg_softc *sc, int as, int seq)
{
struct hdaudio_assoc *assocs = sc->sc_assocs;
int i, hpredir;
int minassoc, res;
/* Find next pin */
for (i = seq; i < HDAUDIO_MAXPINS && assocs[as].as_pins[i] == 0; i++)
;
/* Check if there is any left, if not then we have succeeded */
if (i == HDAUDIO_MAXPINS)
return 1;
hpredir = (i == 15 && assocs[as].as_fakeredir == 0) ?
assocs[as].as_hpredir : -1;
minassoc = res = 0;
do {
/* Trace this pin taking min nid into account */
res = hdafg_assoc_trace_dac(sc, as, i,
assocs[as].as_pins[i], hpredir, minassoc, 0, 0);
if (res == 0) {
/* If we failed, return to previous and redo it */
hda_trace(sc, " trace failed as=%d seq=%d pin=%02X "
"hpredir=%d minassoc=%d\n",
as, seq, assocs[as].as_pins[i], hpredir, minassoc);
return 0;
}
/* Trace again to mark the path */
hdafg_assoc_trace_dac(sc, as, i,
assocs[as].as_pins[i], hpredir, minassoc, res, 0);
assocs[as].as_dacs[i] = res;
/* We succeeded, so call next */
if (hdafg_assoc_trace_out(sc, as, i + 1))
return 1;
/* If next failed, we should retry with next min */
hdafg_assoc_trace_undo(sc, as, i);
assocs[as].as_dacs[i] = 0;
minassoc = res + 1;
} while (1);
}
static int
hdafg_assoc_trace_adc(struct hdafg_softc *sc, int assoc, int seq,
int nid, int only, int depth)
{
struct hdaudio_widget *w, *wc;
int i, j;
int res = 0;
if (depth > HDAUDIO_PARSE_MAXDEPTH)
return 0;
w = hdafg_widget_lookup(sc, nid);
if (w == NULL || w->w_enable == false)
return 0;
/* Use only unused widgets */
if (w->w_bindas >= 0 && w->w_bindas != assoc)
return 0;
switch (w->w_type) {
case COP_AWCAP_TYPE_AUDIO_INPUT:
if (only == w->w_nid)
res = 1;
break;
case COP_AWCAP_TYPE_PIN_COMPLEX:
if (depth > 0)
break;
/* FALLTHROUGH */
default:
/* Try to find reachable ADCs with specified nid */
for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
wc = hdafg_widget_lookup(sc, j);
if (w == NULL || w->w_enable == false)
continue;
for (i = 0; i < wc->w_nconns; i++) {
if (wc->w_connsenable[i] == false)
continue;
if (wc->w_conns[i] != nid)
continue;
if (hdafg_assoc_trace_adc(sc, assoc, seq,
j, only, depth + 1) != 0) {
res = 1;
if (((wc->w_nconns > 1 &&
wc->w_type != COP_AWCAP_TYPE_AUDIO_MIXER) ||
wc->w_type != COP_AWCAP_TYPE_AUDIO_SELECTOR)
&& wc->w_selconn == -1)
wc->w_selconn = i;
}
}
}
break;
}
if (res) {
w->w_bindas = assoc;
w->w_bindseqmask |= (1 << seq);
}
return res;
}
static int
hdafg_assoc_trace_in(struct hdafg_softc *sc, int assoc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
int i, j, k;
for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
w = hdafg_widget_lookup(sc, j);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_AUDIO_INPUT)
continue;
if (w->w_bindas >= 0 && w->w_bindas != assoc)
continue;
/* Find next pin */
for (i = 0; i < HDAUDIO_MAXPINS; i++) {
if (as[assoc].as_pins[i] == 0)
continue;
/* Trace this pin taking goal into account */
if (hdafg_assoc_trace_adc(sc, assoc, i,
as[assoc].as_pins[i], j, 0) == 0) {
hdafg_assoc_trace_undo(sc, assoc, -1);
for (k = 0; k < HDAUDIO_MAXPINS; k++)
as[assoc].as_dacs[k] = 0;
break;
}
as[assoc].as_dacs[i] = j;
}
if (i == HDAUDIO_MAXPINS)
return 1;
}
return 0;
}
static int
hdafg_assoc_trace_to_out(struct hdafg_softc *sc, int nid, int depth)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w, *wc;
int i, j;
int res = 0;
if (depth > HDAUDIO_PARSE_MAXDEPTH)
return 0;
w = hdafg_widget_lookup(sc, nid);
if (w == NULL || w->w_enable == false)
return 0;
/* Use only unused widgets */
if (depth > 0 && w->w_bindas != -1) {
if (w->w_bindas < 0 ||
as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT) {
return 1;
} else {
return 0;
}
}
switch (w->w_type) {
case COP_AWCAP_TYPE_AUDIO_INPUT:
/* Do not traverse input (not yet supported) */
break;
case COP_AWCAP_TYPE_PIN_COMPLEX:
if (depth > 0)
break;
/* FALLTHROUGH */
default:
/* Try to find reachable ADCs with specified nid */
for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
wc = hdafg_widget_lookup(sc, j);
if (wc == NULL || wc->w_enable == false)
continue;
for (i = 0; i < wc->w_nconns; i++) {
if (wc->w_connsenable[i] == false)
continue;
if (wc->w_conns[i] != nid)
continue;
if (hdafg_assoc_trace_to_out(sc,
j, depth + 1) != 0) {
res = 1;
if (wc->w_type ==
COP_AWCAP_TYPE_AUDIO_SELECTOR &&
wc->w_selconn == -1)
wc->w_selconn = i;
}
}
}
break;
}
if (res)
w->w_bindas = -2;
return res;
}
static void
hdafg_assoc_trace_misc(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
int j;
/* Input monitor */
/*
* Find mixer associated with input, but supplying signal
* for output associations. Hope it will be input monitor.
*/
for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
w = hdafg_widget_lookup(sc, j);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_AUDIO_MIXER)
continue;
if (w->w_bindas < 0 ||
as[w->w_bindas].as_dir != HDAUDIO_PINDIR_IN)
continue;
if (hdafg_assoc_trace_to_out(sc, w->w_nid, 0)) {
w->w_pflags |= HDAUDIO_ADC_MONITOR;
w->w_audiodev = HDAUDIO_MIXER_IMIX;
}
}
/* Beeper */
for (j = sc->sc_startnode; j < sc->sc_endnode; j++) {
w = hdafg_widget_lookup(sc, j);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_BEEP_GENERATOR)
continue;
if (hdafg_assoc_trace_to_out(sc, w->w_nid, 0)) {
hda_debug(sc, "beeper %02X traced to out\n", w->w_nid);
}
w->w_bindas = -2;
}
}
static void
hdafg_build_tree(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
int i, j, res;
/* Trace all associations in order of their numbers */
/* Trace DACs first */
for (j = 0; j < sc->sc_nassocs; j++) {
if (as[j].as_enable == false)
continue;
if (as[j].as_dir != HDAUDIO_PINDIR_OUT)
continue;
retry:
res = hdafg_assoc_trace_out(sc, j, 0);
if (res == 0 && as[j].as_hpredir >= 0 &&
as[j].as_fakeredir == 0) {
/*
* If codec can't do analog HP redirection
* try to make it using one more DAC
*/
as[j].as_fakeredir = 1;
goto retry;
}
if (!res) {
hda_debug(sc, "disable assoc %d (%d) [trace failed]\n",
j, as[j].as_index);
for (i = 0; i < HDAUDIO_MAXPINS; i++) {
if (as[j].as_pins[i] == 0)
continue;
hda_debug(sc, " assoc %d pin%d: %02X\n", j, i,
as[j].as_pins[i]);
}
for (i = 0; i < HDAUDIO_MAXPINS; i++) {
if (as[j].as_dacs[i] == 0)
continue;
hda_debug(sc, " assoc %d dac%d: %02X\n", j, i,
as[j].as_dacs[i]);
}
as[j].as_enable = false;
}
}
/* Trace ADCs */
for (j = 0; j < sc->sc_nassocs; j++) {
if (as[j].as_enable == false)
continue;
if (as[j].as_dir != HDAUDIO_PINDIR_IN)
continue;
res = hdafg_assoc_trace_in(sc, j);
if (!res) {
hda_debug(sc, "disable assoc %d (%d) [trace failed]\n",
j, as[j].as_index);
for (i = 0; i < HDAUDIO_MAXPINS; i++) {
if (as[j].as_pins[i] == 0)
continue;
hda_debug(sc, " assoc %d pin%d: %02X\n", j, i,
as[j].as_pins[i]);
}
for (i = 0; i < HDAUDIO_MAXPINS; i++) {
if (as[j].as_dacs[i] == 0)
continue;
hda_debug(sc, " assoc %d adc%d: %02X\n", j, i,
as[j].as_dacs[i]);
}
as[j].as_enable = false;
}
}
/* Trace mixer and beeper pseudo associations */
hdafg_assoc_trace_misc(sc);
}
static void
hdafg_prepare_pin_controls(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
uint32_t pincap;
int i;
hda_debug(sc, "*** prepare pin controls, nwidgets = %d\n",
sc->sc_nwidgets);
for (i = 0; i < sc->sc_nwidgets; i++) {
w = &sc->sc_widgets[i];
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX) {
hda_debug(sc, " skipping pin %02X type 0x%x\n",
w->w_nid, w->w_type);
continue;
}
pincap = w->w_pin.cap;
/* Disable everything */
w->w_pin.ctrl &= ~(
COP_PWC_VREF_ENABLE_MASK |
COP_PWC_IN_ENABLE |
COP_PWC_OUT_ENABLE |
COP_PWC_HPHN_ENABLE);
if (w->w_enable == false ||
w->w_bindas < 0 || as[w->w_bindas].as_enable == false) {
/* Pin is unused so leave it disabled */
if ((pincap & (COP_PINCAP_OUTPUT_CAPABLE |
COP_PINCAP_INPUT_CAPABLE)) ==
(COP_PINCAP_OUTPUT_CAPABLE |
COP_PINCAP_INPUT_CAPABLE)) {
hda_debug(sc, "pin %02X off, "
"in/out capable (bindas=%d "
"enable=%d as_enable=%d)\n",
w->w_nid, w->w_bindas, w->w_enable,
w->w_bindas >= 0 ?
as[w->w_bindas].as_enable : -1);
w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
} else
hda_debug(sc, "pin %02X off\n", w->w_nid);
continue;
} else if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN) {
/* Input pin, configure for input */
if (pincap & COP_PINCAP_INPUT_CAPABLE)
w->w_pin.ctrl |= COP_PWC_IN_ENABLE;
hda_debug(sc, "pin %02X in ctrl 0x%x\n", w->w_nid,
w->w_pin.ctrl);
if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) !=
COP_DEVICE_MIC_IN)
continue;
if (COP_PINCAP_VREF_CONTROL(pincap) & COP_VREF_80)
w->w_pin.ctrl |= COP_PWC_VREF_80;
else if (COP_PINCAP_VREF_CONTROL(pincap) & COP_VREF_50)
w->w_pin.ctrl |= COP_PWC_VREF_50;
} else {
/* Output pin, configure for output */
if (pincap & COP_PINCAP_OUTPUT_CAPABLE)
w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
if ((pincap & COP_PINCAP_HEADPHONE_DRIVE_CAPABLE) &&
(COP_CFG_DEFAULT_DEVICE(w->w_pin.config) ==
COP_DEVICE_HP_OUT))
w->w_pin.ctrl |= COP_PWC_HPHN_ENABLE;
/* XXX VREF */
hda_debug(sc, "pin %02X out ctrl 0x%x\n", w->w_nid,
w->w_pin.ctrl);
}
}
}
#if defined(HDAFG_DEBUG) && HDAFG_DEBUG > 1
static void
hdafg_dump_ctl(const struct hdafg_softc *sc, const struct hdaudio_control *ctl)
{
int type = ctl->ctl_widget ? ctl->ctl_widget->w_type : -1;
int i = (int)(ctl - sc->sc_ctls);
hda_print(sc, "%03X: nid %02X type %d %s (%s) index %d",
i, (ctl->ctl_widget ? ctl->ctl_widget->w_nid : -1), type,
ctl->ctl_ndir == HDAUDIO_PINDIR_IN ? "in " : "out",
ctl->ctl_dir == HDAUDIO_PINDIR_IN ? "in " : "out",
ctl->ctl_index);
if (ctl->ctl_childwidget)
hda_print1(sc, " cnid %02X", ctl->ctl_childwidget->w_nid);
else
hda_print1(sc, " ");
hda_print1(sc, "\n");
hda_print(sc, " mute: %d step: %3d size: %3d off: %3d%s\n",
ctl->ctl_mute, ctl->ctl_step, ctl->ctl_size,
ctl->ctl_offset, ctl->ctl_enable == false ? " [DISABLED]" : "");
}
#endif
static void
hdafg_dump(const struct hdafg_softc *sc)
{
#if defined(HDAFG_DEBUG) && HDAFG_DEBUG > 1
for (int i = 0; i < sc->sc_nctls; i++)
hdafg_dump_ctl(sc, &sc->sc_ctls[i]);
#endif
}
static int
hdafg_match(device_t parent, cfdata_t match, void *opaque)
{
prop_dictionary_t args = opaque;
uint8_t fgtype;
bool rv;
rv = prop_dictionary_get_uint8(args, "function-group-type", &fgtype);
if (rv == false || fgtype != HDAUDIO_GROUP_TYPE_AFG)
return 0;
return 1;
}
static void
hdafg_disable_unassoc(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w, *cw;
struct hdaudio_control *ctl;
int i, j, k;
/* Disable unassociated widgets */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_bindas == -1) {
w->w_enable = 0;
hda_trace(sc, "disable %02X [unassociated]\n",
w->w_nid);
}
}
/* Disable input connections on input pin and output on output */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
if (w->w_bindas < 0)
continue;
if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN) {
hda_trace(sc, "disable %02X input connections\n",
w->w_nid);
for (j = 0; j < w->w_nconns; j++)
w->w_connsenable[j] = false;
ctl = hdafg_control_lookup(sc, w->w_nid,
HDAUDIO_PINDIR_IN, -1, 1);
if (ctl && ctl->ctl_enable == true) {
ctl->ctl_forcemute = 1;
ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
ctl->ctl_left = ctl->ctl_right = 0;
ctl->ctl_enable = false;
}
} else {
ctl = hdafg_control_lookup(sc, w->w_nid,
HDAUDIO_PINDIR_OUT, -1, 1);
if (ctl && ctl->ctl_enable == true) {
ctl->ctl_forcemute = 1;
ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
ctl->ctl_left = ctl->ctl_right = 0;
ctl->ctl_enable = false;
}
for (k = sc->sc_startnode; k < sc->sc_endnode; k++) {
cw = hdafg_widget_lookup(sc, k);
if (cw == NULL || cw->w_enable == false)
continue;
for (j = 0; j < cw->w_nconns; j++) {
if (!cw->w_connsenable[j])
continue;
if (cw->w_conns[j] != i)
continue;
hda_trace(sc, "disable %02X -> %02X "
"output connection\n",
cw->w_nid, cw->w_conns[j]);
cw->w_connsenable[j] = false;
if (cw->w_type ==
COP_AWCAP_TYPE_PIN_COMPLEX &&
cw->w_nconns > 1)
continue;
ctl = hdafg_control_lookup(sc,
k, HDAUDIO_PINDIR_IN, j, 1);
if (ctl && ctl->ctl_enable == true) {
ctl->ctl_forcemute = 1;
ctl->ctl_muted =
HDAUDIO_AMP_MUTE_ALL;
ctl->ctl_left =
ctl->ctl_right = 0;
ctl->ctl_enable = false;
}
}
}
}
}
}
static void
hdafg_disable_unsel(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
int i, j;
/* On playback path we can safely disable all unselected inputs */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_nconns <= 1)
continue;
if (w->w_type == COP_AWCAP_TYPE_AUDIO_MIXER)
continue;
if (w->w_bindas < 0 ||
as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN)
continue;
for (j = 0; j < w->w_nconns; j++) {
if (w->w_connsenable[j] == false)
continue;
if (w->w_selconn < 0 || w->w_selconn == j)
continue;
hda_trace(sc, "disable %02X->%02X [unselected]\n",
w->w_nid, w->w_conns[j]);
w->w_connsenable[j] = false;
}
}
}
static void
hdafg_disable_crossassoc(struct hdafg_softc *sc)
{
struct hdaudio_widget *w, *cw;
struct hdaudio_control *ctl;
int i, j;
/* Disable cross associated and unwanted cross channel connections */
/* ... using selectors */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_nconns <= 1)
continue;
if (w->w_type == COP_AWCAP_TYPE_AUDIO_MIXER)
continue;
if (w->w_bindas == -2)
continue;
for (j = 0; j < w->w_nconns; j++) {
if (w->w_connsenable[j] == false)
continue;
cw = hdafg_widget_lookup(sc, w->w_conns[j]);
if (cw == NULL || cw->w_enable == false)
continue;
if (cw->w_bindas == -2)
continue;
if (w->w_bindas == cw->w_bindas &&
(w->w_bindseqmask & cw->w_bindseqmask) != 0)
continue;
hda_trace(sc, "disable %02X->%02X [crossassoc]\n",
w->w_nid, w->w_conns[j]);
w->w_connsenable[j] = false;
}
}
/* ... using controls */
for (i = 0; i < sc->sc_nctls; i++) {
ctl = &sc->sc_ctls[i];
if (ctl->ctl_enable == false || ctl->ctl_childwidget == NULL)
continue;
if (ctl->ctl_widget->w_bindas == -2 ||
ctl->ctl_childwidget->w_bindas == -2)
continue;
if (ctl->ctl_widget->w_bindas !=
ctl->ctl_childwidget->w_bindas ||
(ctl->ctl_widget->w_bindseqmask &
ctl->ctl_childwidget->w_bindseqmask) == 0) {
ctl->ctl_forcemute = 1;
ctl->ctl_muted = HDAUDIO_AMP_MUTE_ALL;
ctl->ctl_left = ctl->ctl_right = 0;
ctl->ctl_enable = false;
if (ctl->ctl_ndir == HDAUDIO_PINDIR_IN) {
hda_trace(sc, "disable ctl %d:%02X:%02X "
"[crossassoc]\n",
i, ctl->ctl_widget->w_nid,
ctl->ctl_widget->w_conns[ctl->ctl_index]);
ctl->ctl_widget->w_connsenable[
ctl->ctl_index] = false;
}
}
}
}
static struct hdaudio_control *
hdafg_control_amp_get(struct hdafg_softc *sc, int nid,
enum hdaudio_pindir dir, int index, int cnt)
{
struct hdaudio_control *ctl;
int i, found = 0;
for (i = 0; i < sc->sc_nctls; i++) {
ctl = &sc->sc_ctls[i];
if (ctl->ctl_enable == false)
continue;
if (ctl->ctl_widget->w_nid != nid)
continue;
if (dir && ctl->ctl_ndir != dir)
continue;
if (index >= 0 && ctl->ctl_ndir == HDAUDIO_PINDIR_IN &&
ctl->ctl_dir == ctl->ctl_ndir &&
ctl->ctl_index != index)
continue;
++found;
if (found == cnt || cnt <= 0)
return ctl;
}
return NULL;
}
static void
hdafg_control_amp_set1(struct hdaudio_control *ctl, int lmute, int rmute,
int left, int right, int dir)
{
struct hdafg_softc *sc = ctl->ctl_widget->w_afg;
int index = ctl->ctl_index;
uint16_t v = 0;
if (left != right || lmute != rmute) {
v = (1 << (15 - dir)) | (1 << 13) | (index << 8) |
(lmute << 7) | left;
hdaudio_command(sc->sc_codec, ctl->ctl_widget->w_nid,
CORB_SET_AMPLIFIER_GAIN_MUTE, v);
v = (1 << (15 - dir)) | (1 << 12) | (index << 8) |
(rmute << 7) | right;
} else
v = (1 << (15 - dir)) | (3 << 12) | (index << 8) |
(lmute << 7) | left;
hdaudio_command(sc->sc_codec, ctl->ctl_widget->w_nid,
CORB_SET_AMPLIFIER_GAIN_MUTE, v);
}
static void
hdafg_control_amp_set(struct hdaudio_control *ctl, uint32_t mute,
int left, int right)
{
int lmute, rmute;
/* Save new values if valid */
if (mute != HDAUDIO_AMP_MUTE_DEFAULT)
ctl->ctl_muted = mute;
if (left != HDAUDIO_AMP_VOL_DEFAULT)
ctl->ctl_left = left;
if (right != HDAUDIO_AMP_VOL_DEFAULT)
ctl->ctl_right = right;
/* Prepare effective values */
if (ctl->ctl_forcemute) {
lmute = rmute = 1;
left = right = 0;
} else {
lmute = HDAUDIO_AMP_LEFT_MUTED(ctl->ctl_muted);
rmute = HDAUDIO_AMP_RIGHT_MUTED(ctl->ctl_muted);
left = ctl->ctl_left;
right = ctl->ctl_right;
}
/* Apply effective values */
if (ctl->ctl_dir & HDAUDIO_PINDIR_OUT)
hdafg_control_amp_set1(ctl, lmute, rmute, left, right, 0);
if (ctl->ctl_dir & HDAUDIO_PINDIR_IN)
hdafg_control_amp_set1(ctl, lmute, rmute, left, right, 1);
}
/*
* Muting the input pins directly does not work, we mute the mixers which
* are parents to them
*/
static bool
hdafg_mixer_child_is_input(const struct hdafg_softc *sc,
const struct hdaudio_control *ctl)
{
const struct hdaudio_widget *w;
const struct hdaudio_assoc *as = sc->sc_assocs;
switch (ctl->ctl_widget->w_type) {
case COP_AWCAP_TYPE_AUDIO_INPUT:
return true;
case COP_AWCAP_TYPE_AUDIO_MIXER:
w = ctl->ctl_childwidget;
if (w == NULL)
return false;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
return false;
if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
return false;
switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
case COP_DEVICE_MIC_IN:
case COP_DEVICE_LINE_IN:
case COP_DEVICE_SPDIF_IN:
case COP_DEVICE_DIGITAL_OTHER_IN:
return true;
default:
return false;
}
default:
return false;
}
}
static void
hdafg_control_commit(struct hdafg_softc *sc)
{
struct hdaudio_control *ctl;
int i, z;
for (i = 0; i < sc->sc_nctls; i++) {
ctl = &sc->sc_ctls[i];
//if (ctl->ctl_enable == false || ctl->ctl_audiomask != 0)
if (ctl->ctl_enable == false)
continue;
/* Init fixed controls to 0dB amplification */
z = ctl->ctl_offset;
if (z > ctl->ctl_step)
z = ctl->ctl_step;
if (hdafg_mixer_child_is_input(sc, ctl))
hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_ALL, z, z);
else
hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_NONE, z, z);
}
}
static void
hdafg_widget_connection_select(struct hdaudio_widget *w, uint8_t index)
{
struct hdafg_softc *sc = w->w_afg;
if (w->w_nconns < 1 || index > (w->w_nconns - 1))
return;
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONNECTION_SELECT_CONTROL, index);
w->w_selconn = index;
}
static void
hdafg_assign_names(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
int i, j;
int type = -1, use, used =0;
static const int types[7][13] = {
{ HDAUDIO_MIXER_LINE, HDAUDIO_MIXER_LINE1, HDAUDIO_MIXER_LINE2,
HDAUDIO_MIXER_LINE3, -1 },
{ HDAUDIO_MIXER_MONITOR, HDAUDIO_MIXER_MIC, -1 }, /* int mic */
{ HDAUDIO_MIXER_MIC, HDAUDIO_MIXER_MONITOR, -1 }, /* ext mic */
{ HDAUDIO_MIXER_CD, -1 },
{ HDAUDIO_MIXER_SPEAKER, -1 },
{ HDAUDIO_MIXER_DIGITAL1, HDAUDIO_MIXER_DIGITAL2,
HDAUDIO_MIXER_DIGITAL3, -1 },
{ HDAUDIO_MIXER_LINE, HDAUDIO_MIXER_LINE1, HDAUDIO_MIXER_LINE2,
HDAUDIO_MIXER_LINE3, HDAUDIO_MIXER_PHONEIN,
HDAUDIO_MIXER_PHONEOUT, HDAUDIO_MIXER_VIDEO, HDAUDIO_MIXER_RADIO,
HDAUDIO_MIXER_DIGITAL1, HDAUDIO_MIXER_DIGITAL2,
HDAUDIO_MIXER_DIGITAL3, HDAUDIO_MIXER_MONITOR, -1 } /* others */
};
/* Surely known names */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_bindas == -1)
continue;
use = -1;
switch (w->w_type) {
case COP_AWCAP_TYPE_PIN_COMPLEX:
if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
break;
type = -1;
switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
case COP_DEVICE_LINE_IN:
type = 0;
break;
case COP_DEVICE_MIC_IN:
if (COP_CFG_PORT_CONNECTIVITY(w->w_pin.config)
== COP_PORT_JACK)
break;
type = 1;
break;
case COP_DEVICE_CD:
type = 3;
break;
case COP_DEVICE_SPEAKER:
type = 4;
break;
case COP_DEVICE_SPDIF_IN:
case COP_DEVICE_DIGITAL_OTHER_IN:
type = 5;
break;
}
if (type == -1)
break;
j = 0;
while (types[type][j] >= 0 &&
(used & (1 << types[type][j])) != 0) {
j++;
}
if (types[type][j] >= 0)
use = types[type][j];
break;
case COP_AWCAP_TYPE_AUDIO_OUTPUT:
use = HDAUDIO_MIXER_PCM;
break;
case COP_AWCAP_TYPE_BEEP_GENERATOR:
use = HDAUDIO_MIXER_SPEAKER;
break;
default:
break;
}
if (use >= 0) {
w->w_audiodev = use;
used |= (1 << use);
}
}
/* Semi-known names */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_audiodev >= 0)
continue;
if (w->w_bindas == -1)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
continue;
type = -1;
switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
case COP_DEVICE_LINE_OUT:
case COP_DEVICE_SPEAKER:
case COP_DEVICE_HP_OUT:
case COP_DEVICE_AUX:
type = 0;
break;
case COP_DEVICE_MIC_IN:
type = 2;
break;
case COP_DEVICE_SPDIF_OUT:
case COP_DEVICE_DIGITAL_OTHER_OUT:
type = 5;
break;
}
if (type == -1)
break;
j = 0;
while (types[type][j] >= 0 &&
(used & (1 << types[type][j])) != 0) {
j++;
}
if (types[type][j] >= 0) {
w->w_audiodev = types[type][j];
used |= (1 << types[type][j]);
}
}
/* Others */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_audiodev >= 0)
continue;
if (w->w_bindas == -1)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
if (as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT)
continue;
j = 0;
while (types[6][j] >= 0 &&
(used & (1 << types[6][j])) != 0) {
j++;
}
if (types[6][j] >= 0) {
w->w_audiodev = types[6][j];
used |= (1 << types[6][j]);
}
}
}
static int
hdafg_control_source_amp(struct hdafg_softc *sc, int nid, int index,
int audiodev, int ctlable, int depth, int need)
{
struct hdaudio_widget *w, *wc;
struct hdaudio_control *ctl;
int i, j, conns = 0, rneed;
if (depth >= HDAUDIO_PARSE_MAXDEPTH)
return need;
w = hdafg_widget_lookup(sc, nid);
if (w == NULL || w->w_enable == false)
return need;
/* Count number of active inputs */
if (depth > 0) {
for (j = 0; j < w->w_nconns; j++) {
if (w->w_connsenable[j])
++conns;
}
}
/*
* If this is not a first step, use input mixer. Pins have common
* input ctl so care must be taken
*/
if (depth > 0 && ctlable && (conns == 1 ||
w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)) {
ctl = hdafg_control_amp_get(sc, w->w_nid,
HDAUDIO_PINDIR_IN, index, 1);
if (ctl) {
if (HDAUDIO_CONTROL_GIVE(ctl) & need)
ctl->ctl_audiomask |= (1 << audiodev);
else
ctl->ctl_paudiomask |= (1 << audiodev);
need &= ~HDAUDIO_CONTROL_GIVE(ctl);
}
}
/* If widget has own audiodev, don't traverse it. */
if (w->w_audiodev >= 0 && depth > 0)
return need;
/* We must not traverse pins */
if ((w->w_type == COP_AWCAP_TYPE_AUDIO_INPUT ||
w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX) && depth > 0)
return need;
/* Record that this widget exports such signal */
w->w_audiomask |= (1 << audiodev);
/*
* If signals mixed, we can't assign controls further. Ignore this
* on depth zero. Caller must know why. Ignore this for static
* selectors if this input is selected.
*/
if (conns > 1)
ctlable = 0;
if (ctlable) {
ctl = hdafg_control_amp_get(sc, w->w_nid,
HDAUDIO_PINDIR_OUT, -1, 1);
if (ctl) {
if (HDAUDIO_CONTROL_GIVE(ctl) & need)
ctl->ctl_audiomask |= (1 << audiodev);
else
ctl->ctl_paudiomask |= (1 << audiodev);
need &= ~HDAUDIO_CONTROL_GIVE(ctl);
}
}
rneed = 0;
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
wc = hdafg_widget_lookup(sc, i);
if (wc == NULL || wc->w_enable == false)
continue;
for (j = 0; j < wc->w_nconns; j++) {
if (wc->w_connsenable[j] && wc->w_conns[j] == nid) {
rneed |= hdafg_control_source_amp(sc,
wc->w_nid, j, audiodev, ctlable, depth + 1,
need);
}
}
}
rneed &= need;
return rneed;
}
static void
hdafg_control_dest_amp(struct hdafg_softc *sc, int nid,
int audiodev, int depth, int need)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w, *wc;
struct hdaudio_control *ctl;
int i, j, consumers;
if (depth > HDAUDIO_PARSE_MAXDEPTH)
return;
w = hdafg_widget_lookup(sc, nid);
if (w == NULL || w->w_enable == false)
return;
if (depth > 0) {
/*
* If this node produces output for several consumers,
* we can't touch it
*/
consumers = 0;
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
wc = hdafg_widget_lookup(sc, i);
if (wc == NULL || wc->w_enable == false)
continue;
for (j = 0; j < wc->w_nconns; j++) {
if (wc->w_connsenable[j] &&
wc->w_conns[j] == nid)
++consumers;
}
}
/*
* The only exception is if real HP redirection is configured
* and this is a duplication point.
* XXX: Not completely correct.
*/
if ((consumers == 2 && (w->w_bindas < 0 ||
as[w->w_bindas].as_hpredir < 0 ||
as[w->w_bindas].as_fakeredir ||
(w->w_bindseqmask & (1 << 15)) == 0)) ||
consumers > 2)
return;
/* Else use its output mixer */
ctl = hdafg_control_amp_get(sc, w->w_nid,
HDAUDIO_PINDIR_OUT, -1, 1);
if (ctl) {
if (HDAUDIO_CONTROL_GIVE(ctl) & need)
ctl->ctl_audiomask |= (1 << audiodev);
else
ctl->ctl_paudiomask |= (1 << audiodev);
need &= ~HDAUDIO_CONTROL_GIVE(ctl);
}
}
/* We must not traverse pin */
if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX && depth > 0)
return;
for (i = 0; i < w->w_nconns; i++) {
int tneed = need;
if (w->w_connsenable[i] == false)
continue;
ctl = hdafg_control_amp_get(sc, w->w_nid,
HDAUDIO_PINDIR_IN, i, 1);
if (ctl) {
if (HDAUDIO_CONTROL_GIVE(ctl) & tneed)
ctl->ctl_audiomask |= (1 << audiodev);
else
ctl->ctl_paudiomask |= (1 << audiodev);
tneed &= ~HDAUDIO_CONTROL_GIVE(ctl);
}
hdafg_control_dest_amp(sc, w->w_conns[i], audiodev,
depth + 1, tneed);
}
}
static void
hdafg_assign_mixers(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_control *ctl;
struct hdaudio_widget *w;
int i;
/* Assign mixers to the tree */
for (i = sc->sc_startnode; i < sc->sc_endnode; i++) {
w = hdafg_widget_lookup(sc, i);
if (w == NULL || w->w_enable == FALSE)
continue;
if (w->w_type == COP_AWCAP_TYPE_AUDIO_OUTPUT ||
w->w_type == COP_AWCAP_TYPE_BEEP_GENERATOR ||
(w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX &&
as[w->w_bindas].as_dir == HDAUDIO_PINDIR_IN)) {
if (w->w_audiodev < 0)
continue;
hdafg_control_source_amp(sc, w->w_nid, -1,
w->w_audiodev, 1, 0, 1);
} else if (w->w_pflags & HDAUDIO_ADC_MONITOR) {
if (w->w_audiodev < 0)
continue;
if (hdafg_control_source_amp(sc, w->w_nid, -1,
w->w_audiodev, 1, 0, 1)) {
/* If we are unable to control input monitor
as source, try to control it as dest */
hdafg_control_dest_amp(sc, w->w_nid,
w->w_audiodev, 0, 1);
}
} else if (w->w_type == COP_AWCAP_TYPE_AUDIO_INPUT) {
hdafg_control_dest_amp(sc, w->w_nid,
HDAUDIO_MIXER_RECLEV, 0, 1);
} else if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX &&
as[w->w_bindas].as_dir == HDAUDIO_PINDIR_OUT) {
hdafg_control_dest_amp(sc, w->w_nid,
HDAUDIO_MIXER_VOLUME, 0, 1);
}
}
/* Treat unrequired as possible */
for (i = 0; i < sc->sc_nctls; i++) {
ctl = &sc->sc_ctls[i];
if (ctl->ctl_audiomask == 0)
ctl->ctl_audiomask = ctl->ctl_paudiomask;
}
}
static void
hdafg_build_mixers(struct hdafg_softc *sc)
{
struct hdaudio_mixer *mx;
struct hdaudio_control *ctl, *masterctl = NULL;
uint32_t audiomask = 0;
int nmixers = 0;
int i, j, index = 0;
int ndac, nadc;
int ctrlcnt[HDAUDIO_MIXER_NRDEVICES];
memset(ctrlcnt, 0, sizeof(ctrlcnt));
/* Count the number of required mixers */
for (i = 0; i < sc->sc_nctls; i++) {
ctl = &sc->sc_ctls[i];
if (ctl->ctl_enable == false ||
ctl->ctl_audiomask == 0)
continue;
audiomask |= ctl->ctl_audiomask;
++nmixers;
if (ctl->ctl_mute)
++nmixers;
}
/* XXXJDM TODO: softvol */
/* Declare master volume if needed */
if ((audiomask & (HDAUDIO_MASK(VOLUME) | HDAUDIO_MASK(PCM))) ==
HDAUDIO_MASK(PCM)) {
audiomask |= HDAUDIO_MASK(VOLUME);
for (i = 0; i < sc->sc_nctls; i++) {
if (sc->sc_ctls[i].ctl_audiomask == HDAUDIO_MASK(PCM)) {
masterctl = &sc->sc_ctls[i];
++nmixers;
if (masterctl->ctl_mute)
++nmixers;
break;
}
}
}
/* Make room for mixer classes */
nmixers += (HDAUDIO_MIXER_CLASS_LAST + 1);
/* count DACs and ADCs for selectors */
ndac = nadc = 0;
for (i = 0; i < sc->sc_nassocs; i++) {
if (sc->sc_assocs[i].as_enable == false)
continue;
if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_OUT)
++ndac;
else if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_IN)
++nadc;
}
/* Make room for selectors */
if (ndac > 0)
++nmixers;
if (nadc > 0)
++nmixers;
hda_trace(sc, " need %d mixers (3 classes%s)\n",
nmixers, masterctl ? " + fake master" : "");
/* Allocate memory for the mixers */
mx = kmem_zalloc(nmixers * sizeof(*mx), KM_SLEEP);
sc->sc_nmixers = nmixers;
/* Build class mixers */
for (i = 0; i <= HDAUDIO_MIXER_CLASS_LAST; i++) {
mx[index].mx_ctl = NULL;
mx[index].mx_di.index = index;
mx[index].mx_di.type = AUDIO_MIXER_CLASS;
mx[index].mx_di.mixer_class = i;
mx[index].mx_di.next = mx[index].mx_di.prev = AUDIO_MIXER_LAST;
switch (i) {
case HDAUDIO_MIXER_CLASS_OUTPUTS:
strcpy(mx[index].mx_di.label.name, AudioCoutputs);
break;
case HDAUDIO_MIXER_CLASS_INPUTS:
strcpy(mx[index].mx_di.label.name, AudioCinputs);
break;
case HDAUDIO_MIXER_CLASS_RECORD:
strcpy(mx[index].mx_di.label.name, AudioCrecord);
break;
}
++index;
}
/* Shadow master control */
if (masterctl != NULL) {
mx[index].mx_ctl = masterctl;
mx[index].mx_di.index = index;
mx[index].mx_di.type = AUDIO_MIXER_VALUE;
mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
mx[index].mx_di.un.v.num_channels = 2; /* XXX */
mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_OUTPUTS;
mx[index].mx_di.un.v.delta = 256 / (masterctl->ctl_step + 1);
strcpy(mx[index].mx_di.label.name, AudioNmaster);
strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);
hda_trace(sc, " adding outputs.%s\n",
mx[index].mx_di.label.name);
++index;
if (masterctl->ctl_mute) {
mx[index] = mx[index - 1];
mx[index].mx_di.index = index;
mx[index].mx_di.type = AUDIO_MIXER_ENUM;
mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
strcpy(mx[index].mx_di.label.name, AudioNmaster "." AudioNmute);
mx[index].mx_di.un.e.num_mem = 2;
strcpy(mx[index].mx_di.un.e.member[0].label.name, AudioNoff);
mx[index].mx_di.un.e.member[0].ord = 0;
strcpy(mx[index].mx_di.un.e.member[1].label.name, AudioNon);
mx[index].mx_di.un.e.member[1].ord = 1;
++index;
}
}
/* Build volume mixers */
for (i = 0; i < sc->sc_nctls; i++) {
uint32_t audiodev;
ctl = &sc->sc_ctls[i];
if (ctl->ctl_enable == false ||
ctl->ctl_audiomask == 0)
continue;
audiodev = ffs(ctl->ctl_audiomask) - 1;
mx[index].mx_ctl = ctl;
mx[index].mx_di.index = index;
mx[index].mx_di.type = AUDIO_MIXER_VALUE;
mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
mx[index].mx_di.un.v.num_channels = 2; /* XXX */
mx[index].mx_di.un.v.delta = 256 / (ctl->ctl_step + 1);
if (ctrlcnt[audiodev] > 0)
snprintf(mx[index].mx_di.label.name,
sizeof(mx[index].mx_di.label.name),
"%s%d",
hdafg_mixer_names[audiodev],
ctrlcnt[audiodev] + 1);
else
strcpy(mx[index].mx_di.label.name,
hdafg_mixer_names[audiodev]);
ctrlcnt[audiodev]++;
switch (audiodev) {
case HDAUDIO_MIXER_VOLUME:
case HDAUDIO_MIXER_BASS:
case HDAUDIO_MIXER_TREBLE:
case HDAUDIO_MIXER_OGAIN:
mx[index].mx_di.mixer_class =
HDAUDIO_MIXER_CLASS_OUTPUTS;
hda_trace(sc, " adding outputs.%s\n",
mx[index].mx_di.label.name);
break;
case HDAUDIO_MIXER_MIC:
case HDAUDIO_MIXER_MONITOR:
mx[index].mx_di.mixer_class =
HDAUDIO_MIXER_CLASS_RECORD;
hda_trace(sc, " adding record.%s\n",
mx[index].mx_di.label.name);
break;
default:
mx[index].mx_di.mixer_class =
HDAUDIO_MIXER_CLASS_INPUTS;
hda_trace(sc, " adding inputs.%s\n",
mx[index].mx_di.label.name);
break;
}
strcpy(mx[index].mx_di.un.v.units.name, AudioNvolume);
++index;
if (ctl->ctl_mute) {
mx[index] = mx[index - 1];
mx[index].mx_di.index = index;
mx[index].mx_di.type = AUDIO_MIXER_ENUM;
mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
snprintf(mx[index].mx_di.label.name,
sizeof(mx[index].mx_di.label.name),
"%s." AudioNmute,
mx[index - 1].mx_di.label.name);
mx[index].mx_di.un.e.num_mem = 2;
strcpy(mx[index].mx_di.un.e.member[0].label.name, AudioNoff);
mx[index].mx_di.un.e.member[0].ord = 0;
strcpy(mx[index].mx_di.un.e.member[1].label.name, AudioNon);
mx[index].mx_di.un.e.member[1].ord = 1;
++index;
}
}
/* DAC selector */
if (ndac > 0) {
mx[index].mx_ctl = NULL;
mx[index].mx_di.index = index;
mx[index].mx_di.type = AUDIO_MIXER_SET;
mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_OUTPUTS;
mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
strcpy(mx[index].mx_di.label.name, "dacsel"); /* AudioNselect */
mx[index].mx_di.un.s.num_mem = ndac;
for (i = 0, j = 0; i < sc->sc_nassocs; i++) {
if (sc->sc_assocs[i].as_enable == false)
continue;
if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_OUT)
continue;
mx[index].mx_di.un.s.member[j].mask = 1 << i;
snprintf(mx[index].mx_di.un.s.member[j].label.name,
sizeof(mx[index].mx_di.un.s.member[j].label.name),
"%s%02X",
hdafg_assoc_type_string(&sc->sc_assocs[i]), i);
++j;
}
++index;
}
/* ADC selector */
if (nadc > 0) {
mx[index].mx_ctl = NULL;
mx[index].mx_di.index = index;
mx[index].mx_di.type = AUDIO_MIXER_SET;
mx[index].mx_di.mixer_class = HDAUDIO_MIXER_CLASS_RECORD;
mx[index].mx_di.prev = mx[index].mx_di.next = AUDIO_MIXER_LAST;
strcpy(mx[index].mx_di.label.name, AudioNsource);
mx[index].mx_di.un.s.num_mem = nadc;
for (i = 0, j = 0; i < sc->sc_nassocs; i++) {
if (sc->sc_assocs[i].as_enable == false)
continue;
if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_IN)
continue;
mx[index].mx_di.un.s.member[j].mask = 1 << i;
snprintf(mx[index].mx_di.un.s.member[j].label.name,
sizeof(mx[index].mx_di.un.s.member[j].label.name),
"%s%02X",
hdafg_assoc_type_string(&sc->sc_assocs[i]), i);
++j;
}
++index;
}
sc->sc_mixers = mx;
}
static void
hdafg_commit(struct hdafg_softc *sc)
{
struct hdaudio_widget *w;
uint32_t gdata, gmask, gdir;
int commitgpio;
int i;
/* Commit controls */
hdafg_control_commit(sc);
/* Commit selectors, pins, and EAPD */
for (i = 0; i < sc->sc_nwidgets; i++) {
w = &sc->sc_widgets[i];
if (w->w_selconn == -1)
w->w_selconn = 0;
if (w->w_nconns > 0)
hdafg_widget_connection_select(w, w->w_selconn);
if (w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX)
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_PIN_WIDGET_CONTROL, w->w_pin.ctrl);
if (w->w_p.eapdbtl != 0xffffffff)
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_EAPD_BTL_ENABLE, w->w_p.eapdbtl);
}
gdata = gmask = gdir = commitgpio = 0;
#ifdef notyet
int numgpio = COP_GPIO_COUNT_NUM_GPIO(sc->sc_p.gpio_cnt);
hda_trace(sc, "found %d GPIOs\n", numgpio);
for (i = 0; i < numgpio && i < 8; i++) {
if (commitgpio == 0)
commitgpio = 1;
gdata |= 1 << i;
gmask |= 1 << i;
gdir |= 1 << i;
}
#endif
if (commitgpio) {
hda_trace(sc, "GPIO commit: data=%08X mask=%08X dir=%08X\n",
gdata, gmask, gdir);
hdaudio_command(sc->sc_codec, sc->sc_nid,
CORB_SET_GPIO_ENABLE_MASK, gmask);
hdaudio_command(sc->sc_codec, sc->sc_nid,
CORB_SET_GPIO_DIRECTION, gdir);
hdaudio_command(sc->sc_codec, sc->sc_nid,
CORB_SET_GPIO_DATA, gdata);
}
}
static void
hdafg_stream_connect_hdmi(struct hdafg_softc *sc, struct hdaudio_assoc *as,
struct hdaudio_widget *w, const audio_params_t *params)
{
struct hdmi_audio_infoframe hdmi;
/* TODO struct displayport_audio_infoframe dp; */
uint8_t *dip = NULL;
size_t diplen = 0;
int i;
#ifdef HDAFG_HDMI_DEBUG
uint32_t res;
res = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_HDMI_DIP_XMIT_CTRL, 0);
hda_print(sc, "connect HDMI nid %02X, xmitctrl = 0x%08X\n",
w->w_nid, res);
#endif
/* disable infoframe transmission */
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_HDMI_DIP_XMIT_CTRL, COP_DIP_XMIT_CTRL_DISABLE);
if (sc->sc_disable_dip)
return;
/* build new infoframe */
if (as->as_digital == HDAFG_AS_HDMI) {
dip = (uint8_t *)&hdmi;
diplen = sizeof(hdmi);
memset(&hdmi, 0, sizeof(hdmi));
hdmi.header.packet_type = HDMI_AI_PACKET_TYPE;
hdmi.header.version = HDMI_AI_VERSION;
hdmi.header.length = HDMI_AI_LENGTH;
hdmi.ct_cc = params->channels - 1;
if (params->channels > 2) {
hdmi.ca = 0x1f;
} else {
hdmi.ca = 0x00;
}
hdafg_dd_hdmi_ai_cksum(&hdmi);
}
/* update data island with new audio infoframe */
if (dip) {
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_HDMI_DIP_INDEX, 0);
for (i = 0; i < diplen; i++) {
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_HDMI_DIP_DATA, dip[i]);
}
}
/* enable infoframe transmission */
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_HDMI_DIP_XMIT_CTRL, COP_DIP_XMIT_CTRL_BEST_EFFORT);
}
static void
hdafg_stream_connect(struct hdafg_softc *sc, int mode)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
const audio_params_t *params;
uint16_t fmt, dfmt;
int tag, chn, maxchan, c;
int i, j, k;
KASSERT(mode == AUMODE_PLAY || mode == AUMODE_RECORD);
if (mode == AUMODE_PLAY) {
fmt = hdaudio_stream_param(sc->sc_audiodev.ad_playback,
&sc->sc_pparam);
params = &sc->sc_pparam;
} else {
fmt = hdaudio_stream_param(sc->sc_audiodev.ad_capture,
&sc->sc_rparam);
params = &sc->sc_rparam;
}
for (i = 0; i < sc->sc_nassocs; i++) {
if (as[i].as_enable == false)
continue;
if (mode == AUMODE_PLAY && as[i].as_dir != HDAUDIO_PINDIR_OUT)
continue;
if (mode == AUMODE_RECORD && as[i].as_dir != HDAUDIO_PINDIR_IN)
continue;
fmt &= ~HDAUDIO_FMT_CHAN_MASK;
if (as[i].as_dir == HDAUDIO_PINDIR_OUT &&
sc->sc_audiodev.ad_playback != NULL) {
tag = hdaudio_stream_tag(sc->sc_audiodev.ad_playback);
fmt |= HDAUDIO_FMT_CHAN(sc->sc_pparam.channels);
maxchan = sc->sc_pparam.channels;
} else if (as[i].as_dir == HDAUDIO_PINDIR_IN &&
sc->sc_audiodev.ad_capture != NULL) {
tag = hdaudio_stream_tag(sc->sc_audiodev.ad_capture);
fmt |= HDAUDIO_FMT_CHAN(sc->sc_rparam.channels);
maxchan = sc->sc_rparam.channels;
} else {
tag = 0;
if (as[i].as_dir == HDAUDIO_PINDIR_OUT) {
fmt |= HDAUDIO_FMT_CHAN(sc->sc_pchan);
maxchan = sc->sc_pchan;
} else {
fmt |= HDAUDIO_FMT_CHAN(sc->sc_rchan);
maxchan = sc->sc_rchan;
}
}
chn = 0;
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_dacs[j] == 0)
continue;
w = hdafg_widget_lookup(sc, as[i].as_dacs[j]);
if (w == NULL || w->w_enable == FALSE)
continue;
if (as[i].as_hpredir >= 0 && i == as[i].as_pincnt)
chn = 0;
if (chn >= maxchan)
chn = 0; /* XXX */
c = (tag << 4) | chn;
if (as[i].as_activated == false)
c = 0;
/*
* If a non-PCM stream is being connected, and the
* analog converter doesn't support non-PCM streams,
* then don't decode it
*/
if (!(w->w_p.aw_cap & COP_AWCAP_DIGITAL) &&
!(w->w_p.stream_format & COP_STREAM_FORMAT_AC3) &&
(fmt & HDAUDIO_FMT_TYPE_NONPCM)) {
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONVERTER_STREAM_CHANNEL, 0);
continue;
}
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONVERTER_FORMAT, fmt);
if (w->w_p.aw_cap & COP_AWCAP_DIGITAL) {
dfmt = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_DIGITAL_CONVERTER_CONTROL, 0) &
0xff;
dfmt |= COP_DIGITAL_CONVCTRL1_DIGEN;
if (fmt & HDAUDIO_FMT_TYPE_NONPCM)
dfmt |= COP_DIGITAL_CONVCTRL1_NAUDIO;
else
dfmt &= ~COP_DIGITAL_CONVCTRL1_NAUDIO;
if (sc->sc_vendor == HDAUDIO_VENDOR_NVIDIA)
dfmt |= COP_DIGITAL_CONVCTRL1_COPY;
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_DIGITAL_CONVERTER_CONTROL_1, dfmt);
}
if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP)) {
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONVERTER_CHANNEL_COUNT,
maxchan - 1);
for (k = 0; k < maxchan; k++) {
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_ASP_SET_CHANNEL_MAPPING,
(k << 4) | k);
}
}
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_CONVERTER_STREAM_CHANNEL, c);
chn += COP_AWCAP_CHANNEL_COUNT(w->w_p.aw_cap);
}
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_pins[j] == 0)
continue;
w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
if (w == NULL || w->w_enable == FALSE)
continue;
if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP))
hdafg_stream_connect_hdmi(sc, &as[i],
w, params);
}
}
}
static int
hdafg_stream_intr(struct hdaudio_stream *st)
{
struct hdaudio_audiodev *ad = st->st_cookie;
int handled = 0;
(void)hda_read1(ad->ad_sc->sc_host, HDAUDIO_SD_STS(st->st_shift));
hda_write1(ad->ad_sc->sc_host, HDAUDIO_SD_STS(st->st_shift),
HDAUDIO_STS_DESE | HDAUDIO_STS_FIFOE | HDAUDIO_STS_BCIS);
mutex_spin_enter(&ad->ad_sc->sc_intr_lock);
/* XXX test (sts & HDAUDIO_STS_BCIS)? */
if (st == ad->ad_playback && ad->ad_playbackintr) {
ad->ad_playbackintr(ad->ad_playbackintrarg);
handled = 1;
} else if (st == ad->ad_capture && ad->ad_captureintr) {
ad->ad_captureintr(ad->ad_captureintrarg);
handled = 1;
}
mutex_spin_exit(&ad->ad_sc->sc_intr_lock);
return handled;
}
static bool
hdafg_rate_supported(struct hdafg_softc *sc, u_int frequency)
{
uint32_t caps = sc->sc_p.pcm_size_rate;
if (sc->sc_fixed_rate)
return frequency == sc->sc_fixed_rate;
#define ISFREQOK(shift) ((caps & (1 << (shift))) ? true : false)
switch (frequency) {
case 8000:
return ISFREQOK(0);
case 11025:
return ISFREQOK(1);
case 16000:
return ISFREQOK(2);
case 22050:
return ISFREQOK(3);
case 32000:
return ISFREQOK(4);
case 44100:
return ISFREQOK(5);
return true;
case 48000:
return true; /* Must be supported by all codecs */
case 88200:
return ISFREQOK(7);
case 96000:
return ISFREQOK(8);
case 176400:
return ISFREQOK(9);
case 192000:
return ISFREQOK(10);
case 384000:
return ISFREQOK(11);
default:
return false;
}
#undef ISFREQOK
}
static bool
hdafg_bits_supported(struct hdafg_softc *sc, u_int bits)
{
uint32_t caps = sc->sc_p.pcm_size_rate;
#define ISBITSOK(shift) ((caps & (1 << (shift))) ? true : false)
switch (bits) {
case 8:
return ISBITSOK(16);
case 16:
return ISBITSOK(17);
case 20:
return ISBITSOK(18);
case 24:
return ISBITSOK(19);
case 32:
return ISBITSOK(20);
default:
return false;
}
#undef ISBITSOK
}
static bool
hdafg_probe_encoding(struct hdafg_softc *sc,
u_int validbits, u_int precision, int encoding, bool force)
{
struct audio_format f;
int i;
if (!force && hdafg_bits_supported(sc, validbits) == false)
return false;
memset(&f, 0, sizeof(f));
f.driver_data = NULL;
f.mode = 0;
f.encoding = encoding;
f.validbits = validbits;
f.precision = precision;
f.channels = 0;
f.channel_mask = 0;
f.frequency_type = 0;
for (i = 0; i < __arraycount(hdafg_possible_rates); i++) {
u_int rate = hdafg_possible_rates[i];
if (hdafg_rate_supported(sc, rate))
f.frequency[f.frequency_type++] = rate;
}
#define HDAUDIO_INITFMT(ch, chmask) \
do { \
f.channels = (ch); \
f.channel_mask = (chmask); \
f.mode = 0; \
if (sc->sc_pchan >= (ch)) \
f.mode |= AUMODE_PLAY; \
if (sc->sc_rchan >= (ch)) \
f.mode |= AUMODE_RECORD; \
if (f.mode != 0) \
hdafg_append_formats(&sc->sc_audiodev, &f); \
} while (0)
/* Commented out, otherwise monaural samples play through left
* channel only
*/
/* HDAUDIO_INITFMT(1, AUFMT_MONAURAL); */
HDAUDIO_INITFMT(2, AUFMT_STEREO);
HDAUDIO_INITFMT(4, AUFMT_SURROUND4);
HDAUDIO_INITFMT(6, AUFMT_DOLBY_5_1);
HDAUDIO_INITFMT(8, AUFMT_SURROUND_7_1);
#undef HDAUDIO_INITFMT
return true;
}
static void
hdafg_configure_encodings(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
struct audio_format f;
uint32_t stream_format, caps;
int nchan, i, nid;
sc->sc_pchan = sc->sc_rchan = 0;
for (i = 0; i < sc->sc_nassocs; i++) {
nchan = hdafg_assoc_count_channels(sc, &as[i],
HDAUDIO_PINDIR_OUT);
if (nchan > sc->sc_pchan)
sc->sc_pchan = nchan;
}
for (i = 0; i < sc->sc_nassocs; i++) {
nchan = hdafg_assoc_count_channels(sc, &as[i],
HDAUDIO_PINDIR_IN);
if (nchan > sc->sc_rchan)
sc->sc_rchan = nchan;
}
hda_print(sc, "%dch/%dch", sc->sc_pchan, sc->sc_rchan);
for (i = 0; i < __arraycount(hdafg_possible_rates); i++)
if (hdafg_rate_supported(sc,
hdafg_possible_rates[i]))
hda_print1(sc, " %uHz", hdafg_possible_rates[i]);
stream_format = sc->sc_p.stream_format;
caps = 0;
for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
w = hdafg_widget_lookup(sc, nid);
if (w == NULL)
continue;
stream_format |= w->w_p.stream_format;
caps |= w->w_p.aw_cap;
}
if (stream_format == 0) {
hda_print(sc,
"WARNING: unsupported stream format mask 0x%X, assuming PCM\n",
stream_format);
stream_format |= COP_STREAM_FORMAT_PCM;
}
if (stream_format & COP_STREAM_FORMAT_PCM) {
int e = AUDIO_ENCODING_SLINEAR_LE;
if (hdafg_probe_encoding(sc, 8, 16, e, false))
hda_print1(sc, " PCM8");
if (hdafg_probe_encoding(sc, 16, 16, e, false))
hda_print1(sc, " PCM16");
if (hdafg_probe_encoding(sc, 20, 32, e, false))
hda_print1(sc, " PCM20");
if (hdafg_probe_encoding(sc, 24, 32, e, false))
hda_print1(sc, " PCM24");
if (hdafg_probe_encoding(sc, 32, 32, e, false))
hda_print1(sc, " PCM32");
}
if ((stream_format & COP_STREAM_FORMAT_AC3) ||
(caps & COP_AWCAP_DIGITAL)) {
int e = AUDIO_ENCODING_AC3;
if (hdafg_probe_encoding(sc, 16, 16, e, false))
hda_print1(sc, " AC3");
}
if (sc->sc_audiodev.ad_nformats == 0) {
hdafg_probe_encoding(sc, 16, 16, AUDIO_ENCODING_SLINEAR_LE, true);
hda_print1(sc, " PCM16*");
}
/*
* XXX JDM 20090614
* MI audio assumes that at least one playback and one capture format
* is reported by the hw driver; until this bug is resolved just
* report 2ch capabilities if the function group does not support
* the direction.
*/
if (sc->sc_rchan == 0 || sc->sc_pchan == 0) {
memset(&f, 0, sizeof(f));
f.driver_data = NULL;
f.mode = 0;
f.encoding = AUDIO_ENCODING_SLINEAR_LE;
f.validbits = 16;
f.precision = 16;
f.channels = 2;
f.channel_mask = AUFMT_STEREO;
f.frequency_type = 0;
f.frequency[0] = f.frequency[1] = sc->sc_fixed_rate ?
sc->sc_fixed_rate : 48000;
f.mode = AUMODE_PLAY|AUMODE_RECORD;
hdafg_append_formats(&sc->sc_audiodev, &f);
}
hda_print1(sc, "\n");
}
static void
hdafg_hp_switch_handler(void *opaque)
{
struct hdafg_softc *sc = opaque;
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
uint32_t res = 0;
int i, j;
if (!device_is_active(sc->sc_dev))
goto resched;
for (i = 0; i < sc->sc_nassocs; i++) {
if (as[i].as_digital != HDAFG_AS_ANALOG &&
as[i].as_digital != HDAFG_AS_SPDIF)
continue;
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_pins[j] == 0)
continue;
w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
if (COP_CFG_DEFAULT_DEVICE(w->w_pin.config) !=
COP_DEVICE_HP_OUT)
continue;
res |= hdaudio_command(sc->sc_codec, as[i].as_pins[j],
CORB_GET_PIN_SENSE, 0) &
COP_GET_PIN_SENSE_PRESENSE_DETECT;
}
}
for (i = 0; i < sc->sc_nassocs; i++) {
if (as[i].as_digital != HDAFG_AS_ANALOG &&
as[i].as_digital != HDAFG_AS_SPDIF)
continue;
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_pins[j] == 0)
continue;
w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
switch (COP_CFG_DEFAULT_DEVICE(w->w_pin.config)) {
case COP_DEVICE_HP_OUT:
if (res & COP_GET_PIN_SENSE_PRESENSE_DETECT)
w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
else
w->w_pin.ctrl &= ~COP_PWC_OUT_ENABLE;
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_PIN_WIDGET_CONTROL, w->w_pin.ctrl);
break;
case COP_DEVICE_LINE_OUT:
case COP_DEVICE_SPEAKER:
case COP_DEVICE_AUX:
if (res & COP_GET_PIN_SENSE_PRESENSE_DETECT)
w->w_pin.ctrl &= ~COP_PWC_OUT_ENABLE;
else
w->w_pin.ctrl |= COP_PWC_OUT_ENABLE;
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_PIN_WIDGET_CONTROL, w->w_pin.ctrl);
break;
default:
break;
}
}
}
resched:
callout_schedule(&sc->sc_jack_callout, HDAUDIO_HP_SENSE_PERIOD);
}
static void
hdafg_hp_switch_init(struct hdafg_softc *sc)
{
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_widget *w;
bool enable = false;
int i, j;
for (i = 0; i < sc->sc_nassocs; i++) {
if (as[i].as_hpredir < 0 && as[i].as_displaydev == false)
continue;
if (as[i].as_displaydev == false)
w = hdafg_widget_lookup(sc, as[i].as_pins[15]);
else {
w = NULL;
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_pins[j] == 0)
continue;
w = hdafg_widget_lookup(sc, as[i].as_pins[j]);
if (w && w->w_enable &&
w->w_type == COP_AWCAP_TYPE_PIN_COMPLEX)
break;
w = NULL;
}
}
if (w == NULL || w->w_enable == false)
continue;
if (w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
if (!(w->w_pin.cap & COP_PINCAP_PRESENSE_DETECT_CAPABLE)) {
continue;
}
if (COP_CFG_MISC(w->w_pin.config) & 1) {
hda_trace(sc, "no presence detect on pin %02X\n",
w->w_nid);
continue;
}
if ((w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP)) == 0)
enable = true;
if (w->w_p.aw_cap & COP_AWCAP_UNSOL_CAPABLE) {
uint8_t val = COP_SET_UNSOLICITED_RESPONSE_ENABLE;
if (w->w_pin.cap & (COP_PINCAP_HDMI|COP_PINCAP_DP))
val |= HDAUDIO_UNSOLTAG_EVENT_DD;
else
val |= HDAUDIO_UNSOLTAG_EVENT_HP;
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_UNSOLICITED_RESPONSE, val);
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_AMPLIFIER_GAIN_MUTE, 0xb000);
}
hda_trace(sc, "presence detect [pin=%02X,%s",
w->w_nid,
(w->w_p.aw_cap & COP_AWCAP_UNSOL_CAPABLE) ?
"unsol" : "poll"
);
if (w->w_pin.cap & COP_PINCAP_HDMI)
hda_trace1(sc, ",hdmi");
if (w->w_pin.cap & COP_PINCAP_DP)
hda_trace1(sc, ",displayport");
hda_trace1(sc, "]\n");
}
if (enable) {
sc->sc_jack_polling = true;
hdafg_hp_switch_handler(sc);
} else
hda_trace(sc, "jack detect not enabled\n");
}
static void
hdafg_attach(device_t parent, device_t self, void *opaque)
{
struct hdafg_softc *sc = device_private(self);
audio_params_t defparams;
prop_dictionary_t args = opaque;
char vendor[MAX_AUDIO_DEV_LEN], product[MAX_AUDIO_DEV_LEN];
uint64_t fgptr = 0;
uint32_t astype = 0;
uint8_t nid = 0;
int err, i;
bool rv;
aprint_naive("\n");
sc->sc_dev = self;
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);
callout_init(&sc->sc_jack_callout, 0);
callout_setfunc(&sc->sc_jack_callout,
hdafg_hp_switch_handler, sc);
if (!pmf_device_register(self, hdafg_suspend, hdafg_resume))
aprint_error_dev(self, "couldn't establish power handler\n");
sc->sc_config = prop_dictionary_get(args, "pin-config");
if (sc->sc_config && prop_object_type(sc->sc_config) != PROP_TYPE_ARRAY)
sc->sc_config = NULL;
prop_dictionary_get_uint16(args, "vendor-id", &sc->sc_vendor);
prop_dictionary_get_uint16(args, "product-id", &sc->sc_product);
hdaudio_findvendor(vendor, sizeof(vendor), sc->sc_vendor);
hdaudio_findproduct(product, sizeof(product), sc->sc_vendor,
sc->sc_product);
hda_print1(sc, ": %s %s%s\n", vendor, product,
sc->sc_config ? " (custom configuration)" : "");
switch (sc->sc_vendor) {
case HDAUDIO_VENDOR_NVIDIA:
switch (sc->sc_product) {
case HDAUDIO_PRODUCT_NVIDIA_TEGRA124_HDMI:
sc->sc_fixed_rate = 44100;
sc->sc_disable_dip = true;
break;
}
break;
}
rv = prop_dictionary_get_uint64(args, "function-group", &fgptr);
if (rv == false || fgptr == 0) {
hda_error(sc, "missing function-group property\n");
return;
}
rv = prop_dictionary_get_uint8(args, "node-id", &nid);
if (rv == false || nid == 0) {
hda_error(sc, "missing node-id property\n");
return;
}
prop_dictionary_set_uint64(device_properties(self),
"codecinfo-callback",
(uint64_t)(uintptr_t)hdafg_codec_info);
prop_dictionary_set_uint64(device_properties(self),
"widgetinfo-callback",
(uint64_t)(uintptr_t)hdafg_widget_info);
sc->sc_nid = nid;
sc->sc_fg = (struct hdaudio_function_group *)(vaddr_t)fgptr;
sc->sc_fg->fg_unsol = hdafg_unsol;
sc->sc_codec = sc->sc_fg->fg_codec;
KASSERT(sc->sc_codec != NULL);
sc->sc_host = sc->sc_codec->co_host;
KASSERT(sc->sc_host != NULL);
hda_debug(sc, "parsing widgets\n");
hdafg_parse(sc);
hda_debug(sc, "parsing controls\n");
hdafg_control_parse(sc);
hda_debug(sc, "disabling non-audio devices\n");
hdafg_disable_nonaudio(sc);
hda_debug(sc, "disabling useless devices\n");
hdafg_disable_useless(sc);
hda_debug(sc, "parsing associations\n");
hdafg_assoc_parse(sc);
hda_debug(sc, "building tree\n");
hdafg_build_tree(sc);
hda_debug(sc, "disabling unassociated pins\n");
hdafg_disable_unassoc(sc);
hda_debug(sc, "disabling unselected pins\n");
hdafg_disable_unsel(sc);
hda_debug(sc, "disabling useless devices\n");
hdafg_disable_useless(sc);
hda_debug(sc, "disabling cross-associated pins\n");
hdafg_disable_crossassoc(sc);
hda_debug(sc, "disabling useless devices\n");
hdafg_disable_useless(sc);
hda_debug(sc, "assigning mixer names to sound sources\n");
hdafg_assign_names(sc);
hda_debug(sc, "assigning mixers to device tree\n");
hdafg_assign_mixers(sc);
hda_debug(sc, "preparing pin controls\n");
hdafg_prepare_pin_controls(sc);
hda_debug(sc, "commiting settings\n");
hdafg_commit(sc);
hda_debug(sc, "setup jack sensing\n");
hdafg_hp_switch_init(sc);
hda_debug(sc, "building mixer controls\n");
hdafg_build_mixers(sc);
hdafg_dump(sc);
if (1) hdafg_widget_pin_dump(sc);
hdafg_assoc_dump(sc);
hda_debug(sc, "enabling analog beep\n");
hdafg_enable_analog_beep(sc);
hda_debug(sc, "configuring encodings\n");
sc->sc_audiodev.ad_sc = sc;
hdafg_configure_encodings(sc);
err = auconv_create_encodings(sc->sc_audiodev.ad_formats,
sc->sc_audiodev.ad_nformats, &sc->sc_audiodev.ad_encodings);
if (err) {
hda_error(sc, "couldn't create encodings\n");
return;
}
hda_debug(sc, "reserving streams\n");
sc->sc_audiodev.ad_capture = hdaudio_stream_establish(sc->sc_host,
HDAUDIO_STREAM_ISS, hdafg_stream_intr, &sc->sc_audiodev);
sc->sc_audiodev.ad_playback = hdaudio_stream_establish(sc->sc_host,
HDAUDIO_STREAM_OSS, hdafg_stream_intr, &sc->sc_audiodev);
hda_debug(sc, "connecting streams\n");
defparams.channels = 2;
defparams.sample_rate = sc->sc_fixed_rate ? sc->sc_fixed_rate : 48000;
defparams.precision = defparams.validbits = 16;
defparams.encoding = AUDIO_ENCODING_SLINEAR_LE;
sc->sc_pparam = sc->sc_rparam = defparams;
hdafg_stream_connect(sc, AUMODE_PLAY);
hdafg_stream_connect(sc, AUMODE_RECORD);
for (i = 0; i < sc->sc_nassocs; i++) {
astype |= (1 << sc->sc_assocs[i].as_digital);
}
hda_debug(sc, "assoc type mask: %x\n", astype);
#ifndef HDAUDIO_ENABLE_HDMI
astype &= ~(1 << HDAFG_AS_HDMI);
#endif
#ifndef HDAUDIO_ENABLE_DISPLAYPORT
astype &= ~(1 << HDAFG_AS_DISPLAYPORT);
#endif
if (astype == 0)
return;
hda_debug(sc, "attaching audio device\n");
sc->sc_audiodev.ad_audiodev = audio_attach_mi(&hdafg_hw_if,
&sc->sc_audiodev, self);
}
static int
hdafg_detach(device_t self, int flags)
{
struct hdafg_softc *sc = device_private(self);
struct hdaudio_widget *wl, *w = sc->sc_widgets;
struct hdaudio_assoc *as = sc->sc_assocs;
struct hdaudio_control *ctl = sc->sc_ctls;
struct hdaudio_mixer *mx = sc->sc_mixers;
int nid;
callout_halt(&sc->sc_jack_callout, NULL);
callout_destroy(&sc->sc_jack_callout);
if (sc->sc_config)
prop_object_release(sc->sc_config);
if (sc->sc_audiodev.ad_audiodev)
config_detach(sc->sc_audiodev.ad_audiodev, flags);
if (sc->sc_audiodev.ad_encodings)
auconv_delete_encodings(sc->sc_audiodev.ad_encodings);
if (sc->sc_audiodev.ad_playback)
hdaudio_stream_disestablish(sc->sc_audiodev.ad_playback);
if (sc->sc_audiodev.ad_capture)
hdaudio_stream_disestablish(sc->sc_audiodev.ad_capture);
/* restore bios pin widget configuration */
for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
wl = hdafg_widget_lookup(sc, nid);
if (wl == NULL || wl->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
hdafg_widget_setconfig(wl, wl->w_pin.biosconfig);
}
if (w)
kmem_free(w, sc->sc_nwidgets * sizeof(*w));
if (as)
kmem_free(as, sc->sc_nassocs * sizeof(*as));
if (ctl)
kmem_free(ctl, sc->sc_nctls * sizeof(*ctl));
if (mx)
kmem_free(mx, sc->sc_nmixers * sizeof(*mx));
mutex_destroy(&sc->sc_lock);
mutex_destroy(&sc->sc_intr_lock);
pmf_device_deregister(self);
return 0;
}
static void
hdafg_childdet(device_t self, device_t child)
{
struct hdafg_softc *sc = device_private(self);
if (child == sc->sc_audiodev.ad_audiodev)
sc->sc_audiodev.ad_audiodev = NULL;
}
static bool
hdafg_suspend(device_t self, const pmf_qual_t *qual)
{
struct hdafg_softc *sc = device_private(self);
callout_halt(&sc->sc_jack_callout, NULL);
return true;
}
static bool
hdafg_resume(device_t self, const pmf_qual_t *qual)
{
struct hdafg_softc *sc = device_private(self);
struct hdaudio_widget *w;
int nid;
hdaudio_command(sc->sc_codec, sc->sc_nid,
CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
hda_delay(100);
for (nid = sc->sc_startnode; nid < sc->sc_endnode; nid++) {
hdaudio_command(sc->sc_codec, nid,
CORB_SET_POWER_STATE, COP_POWER_STATE_D0);
w = hdafg_widget_lookup(sc, nid);
/* restore pin widget configuration */
if (w == NULL || w->w_type != COP_AWCAP_TYPE_PIN_COMPLEX)
continue;
hdafg_widget_setconfig(w, w->w_pin.config);
}
hda_delay(1000);
hdafg_commit(sc);
hdafg_stream_connect(sc, AUMODE_PLAY);
hdafg_stream_connect(sc, AUMODE_RECORD);
if (sc->sc_jack_polling)
hdafg_hp_switch_handler(sc);
return true;
}
static int
hdafg_query_encoding(void *opaque, struct audio_encoding *ae)
{
struct hdaudio_audiodev *ad = opaque;
return auconv_query_encoding(ad->ad_encodings, ae);
}
static int
hdafg_set_params(void *opaque, int setmode, int usemode,
audio_params_t *play, audio_params_t *rec,
stream_filter_list_t *pfil, stream_filter_list_t *rfil)
{
struct hdaudio_audiodev *ad = opaque;
int index;
if (play && (setmode & AUMODE_PLAY)) {
index = auconv_set_converter(ad->ad_formats, ad->ad_nformats,
AUMODE_PLAY, play, TRUE, pfil);
if (index < 0)
return EINVAL;
ad->ad_sc->sc_pparam = pfil->req_size > 0 ?
pfil->filters[0].param : *play;
hdafg_stream_connect(ad->ad_sc, AUMODE_PLAY);
}
if (rec && (setmode & AUMODE_RECORD)) {
index = auconv_set_converter(ad->ad_formats, ad->ad_nformats,
AUMODE_RECORD, rec, TRUE, rfil);
if (index < 0)
return EINVAL;
ad->ad_sc->sc_rparam = rfil->req_size > 0 ?
rfil->filters[0].param : *rec;
hdafg_stream_connect(ad->ad_sc, AUMODE_RECORD);
}
return 0;
}
static int
hdafg_round_blocksize(void *opaque, int blksize, int mode,
const audio_params_t *param)
{
struct hdaudio_audiodev *ad = opaque;
struct hdaudio_stream *st;
int bufsize, nblksize;
st = (mode == AUMODE_PLAY) ? ad->ad_playback : ad->ad_capture;
if (st == NULL) {
hda_trace(ad->ad_sc,
"round_blocksize called for invalid stream\n");
return 128;
}
if (blksize > 8192)
blksize = 8192;
else if (blksize < 0)
blksize = 128;
/* HD audio wants a multiple of 128, and OSS wants a power of 2 */
for (nblksize = 128; nblksize < blksize; nblksize <<= 1)
;
/* Make sure there are enough BDL descriptors */
bufsize = st->st_data.dma_size;
if (bufsize > HDAUDIO_BDL_MAX * nblksize) {
blksize = bufsize / HDAUDIO_BDL_MAX;
for (nblksize = 128; nblksize < blksize; nblksize <<= 1)
;
}
return nblksize;
}
static int
hdafg_commit_settings(void *opaque)
{
return 0;
}
static int
hdafg_halt_output(void *opaque)
{
struct hdaudio_audiodev *ad = opaque;
struct hdafg_softc *sc = ad->ad_sc;
struct hdaudio_assoc *as = ad->ad_sc->sc_assocs;
struct hdaudio_widget *w;
uint16_t dfmt;
int i, j;
/* Disable digital outputs */
for (i = 0; i < sc->sc_nassocs; i++) {
if (as[i].as_enable == false)
continue;
if (as[i].as_dir != HDAUDIO_PINDIR_OUT)
continue;
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_dacs[j] == 0)
continue;
w = hdafg_widget_lookup(sc, as[i].as_dacs[j]);
if (w == NULL || w->w_enable == false)
continue;
if (w->w_p.aw_cap & COP_AWCAP_DIGITAL) {
dfmt = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_DIGITAL_CONVERTER_CONTROL, 0) &
0xff;
dfmt &= ~COP_DIGITAL_CONVCTRL1_DIGEN;
hdaudio_command(sc->sc_codec, w->w_nid,
CORB_SET_DIGITAL_CONVERTER_CONTROL_1, dfmt);
}
}
}
hdaudio_stream_stop(ad->ad_playback);
return 0;
}
static int
hdafg_halt_input(void *opaque)
{
struct hdaudio_audiodev *ad = opaque;
hdaudio_stream_stop(ad->ad_capture);
return 0;
}
static int
hdafg_getdev(void *opaque, struct audio_device *audiodev)
{
struct hdaudio_audiodev *ad = opaque;
struct hdafg_softc *sc = ad->ad_sc;
hdaudio_findvendor(audiodev->name, sizeof(audiodev->name),
sc->sc_vendor);
hdaudio_findproduct(audiodev->version, sizeof(audiodev->version),
sc->sc_vendor, sc->sc_product);
snprintf(audiodev->config, sizeof(audiodev->config) - 1,
"%02Xh", sc->sc_nid);
return 0;
}
static int
hdafg_set_port(void *opaque, mixer_ctrl_t *mc)
{
struct hdaudio_audiodev *ad = opaque;
struct hdafg_softc *sc = ad->ad_sc;
struct hdaudio_mixer *mx;
struct hdaudio_control *ctl;
int i, divisor;
if (mc->dev < 0 || mc->dev >= sc->sc_nmixers)
return EINVAL;
mx = &sc->sc_mixers[mc->dev];
ctl = mx->mx_ctl;
if (ctl == NULL) {
if (mx->mx_di.type != AUDIO_MIXER_SET)
return ENXIO;
if (mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_OUTPUTS &&
mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_RECORD)
return ENXIO;
for (i = 0; i < sc->sc_nassocs; i++) {
if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_OUT &&
mx->mx_di.mixer_class ==
HDAUDIO_MIXER_CLASS_OUTPUTS)
continue;
if (sc->sc_assocs[i].as_dir != HDAUDIO_PINDIR_IN &&
mx->mx_di.mixer_class ==
HDAUDIO_MIXER_CLASS_RECORD)
continue;
sc->sc_assocs[i].as_activated =
(mc->un.mask & (1 << i)) ? true : false;
}
hdafg_stream_connect(ad->ad_sc,
mx->mx_di.mixer_class == HDAUDIO_MIXER_CLASS_OUTPUTS ?
AUMODE_PLAY : AUMODE_RECORD);
return 0;
}
switch (mx->mx_di.type) {
case AUDIO_MIXER_VALUE:
if (ctl->ctl_step == 0)
divisor = 128; /* ??? - just avoid div by 0 */
else
divisor = 255 / ctl->ctl_step;
hdafg_control_amp_set(ctl, HDAUDIO_AMP_MUTE_NONE,
mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] / divisor,
mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] / divisor);
break;
case AUDIO_MIXER_ENUM:
hdafg_control_amp_set(ctl,
mc->un.ord ? HDAUDIO_AMP_MUTE_ALL : HDAUDIO_AMP_MUTE_NONE,
ctl->ctl_left, ctl->ctl_right);
break;
default:
return ENXIO;
}
return 0;
}
static int
hdafg_get_port(void *opaque, mixer_ctrl_t *mc)
{
struct hdaudio_audiodev *ad = opaque;
struct hdafg_softc *sc = ad->ad_sc;
struct hdaudio_mixer *mx;
struct hdaudio_control *ctl;
u_int mask = 0;
int i, factor;
if (mc->dev < 0 || mc->dev >= sc->sc_nmixers)
return EINVAL;
mx = &sc->sc_mixers[mc->dev];
ctl = mx->mx_ctl;
if (ctl == NULL) {
if (mx->mx_di.type != AUDIO_MIXER_SET)
return ENXIO;
if (mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_OUTPUTS &&
mx->mx_di.mixer_class != HDAUDIO_MIXER_CLASS_RECORD)
return ENXIO;
for (i = 0; i < sc->sc_nassocs; i++) {
if (sc->sc_assocs[i].as_enable == false)
continue;
if (sc->sc_assocs[i].as_activated == false)
continue;
if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_OUT &&
mx->mx_di.mixer_class ==
HDAUDIO_MIXER_CLASS_OUTPUTS)
mask |= (1 << i);
if (sc->sc_assocs[i].as_dir == HDAUDIO_PINDIR_IN &&
mx->mx_di.mixer_class ==
HDAUDIO_MIXER_CLASS_RECORD)
mask |= (1 << i);
}
mc->un.mask = mask;
return 0;
}
switch (mx->mx_di.type) {
case AUDIO_MIXER_VALUE:
if (ctl->ctl_step == 0)
factor = 128; /* ??? - just avoid div by 0 */
else
factor = 255 / ctl->ctl_step;
mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = ctl->ctl_left * factor;
mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = ctl->ctl_right * factor;
break;
case AUDIO_MIXER_ENUM:
mc->un.ord = (ctl->ctl_muted || ctl->ctl_forcemute) ? 1 : 0;
break;
default:
return ENXIO;
}
return 0;
}
static int
hdafg_query_devinfo(void *opaque, mixer_devinfo_t *di)
{
struct hdaudio_audiodev *ad = opaque;
struct hdafg_softc *sc = ad->ad_sc;
if (di->index < 0 || di->index >= sc->sc_nmixers)
return ENXIO;
*di = sc->sc_mixers[di->index].mx_di;
return 0;
}
static void *
hdafg_allocm(void *opaque, int direction, size_t size)
{
struct hdaudio_audiodev *ad = opaque;
struct hdaudio_stream *st;
int err;
st = (direction == AUMODE_PLAY) ? ad->ad_playback : ad->ad_capture;
if (st == NULL)
return NULL;
if (st->st_data.dma_valid == true)
hda_error(ad->ad_sc, "WARNING: allocm leak\n");
st->st_data.dma_size = size;
err = hdaudio_dma_alloc(st->st_host, &st->st_data,
BUS_DMA_COHERENT | BUS_DMA_NOCACHE);
if (err || st->st_data.dma_valid == false)
return NULL;
return DMA_KERNADDR(&st->st_data);
}
static void
hdafg_freem(void *opaque, void *addr, size_t size)
{
struct hdaudio_audiodev *ad = opaque;
struct hdaudio_stream *st;
if (addr == DMA_KERNADDR(&ad->ad_playback->st_data))
st = ad->ad_playback;
else if (addr == DMA_KERNADDR(&ad->ad_capture->st_data))
st = ad->ad_capture;
else
return;
hdaudio_dma_free(st->st_host, &st->st_data);
}
static size_t
hdafg_round_buffersize(void *opaque, int direction, size_t bufsize)
{
/* Multiple of 128 */
bufsize &= ~127;
if (bufsize <= 0)
bufsize = 128;
return bufsize;
}
static paddr_t
hdafg_mappage(void *opaque, void *addr, off_t off, int prot)
{
struct hdaudio_audiodev *ad = opaque;
struct hdaudio_stream *st;
if (addr == DMA_KERNADDR(&ad->ad_playback->st_data))
st = ad->ad_playback;
else if (addr == DMA_KERNADDR(&ad->ad_capture->st_data))
st = ad->ad_capture;
else
return -1;
if (st->st_data.dma_valid == false)
return -1;
return bus_dmamem_mmap(st->st_host->sc_dmat, st->st_data.dma_segs,
st->st_data.dma_nsegs, off, prot, BUS_DMA_WAITOK);
}
static int
hdafg_get_props(void *opaque)
{
struct hdaudio_audiodev *ad = opaque;
int props = AUDIO_PROP_MMAP;
if (ad->ad_playback)
props |= AUDIO_PROP_PLAYBACK;
if (ad->ad_capture)
props |= AUDIO_PROP_CAPTURE;
if (ad->ad_playback && ad->ad_capture) {
props |= AUDIO_PROP_FULLDUPLEX;
props |= AUDIO_PROP_INDEPENDENT;
}
return props;
}
static int
hdafg_trigger_output(void *opaque, void *start, void *end, int blksize,
void (*intr)(void *), void *intrarg, const audio_params_t *param)
{
struct hdaudio_audiodev *ad = opaque;
bus_size_t dmasize;
if (ad->ad_playback == NULL)
return ENXIO;
if (ad->ad_playback->st_data.dma_valid == false)
return ENOMEM;
ad->ad_playbackintr = intr;
ad->ad_playbackintrarg = intrarg;
dmasize = (char *)end - (char *)start;
hdafg_stream_connect(ad->ad_sc, AUMODE_PLAY);
hdaudio_stream_start(ad->ad_playback, blksize, dmasize,
&ad->ad_sc->sc_pparam);
return 0;
}
static int
hdafg_trigger_input(void *opaque, void *start, void *end, int blksize,
void (*intr)(void *), void *intrarg, const audio_params_t *param)
{
struct hdaudio_audiodev *ad = opaque;
bus_size_t dmasize;
if (ad->ad_capture == NULL)
return ENXIO;
if (ad->ad_capture->st_data.dma_valid == false)
return ENOMEM;
ad->ad_captureintr = intr;
ad->ad_captureintrarg = intrarg;
dmasize = (char *)end - (char *)start;
hdafg_stream_connect(ad->ad_sc, AUMODE_RECORD);
hdaudio_stream_start(ad->ad_capture, blksize, dmasize,
&ad->ad_sc->sc_rparam);
return 0;
}
static void
hdafg_get_locks(void *opaque, kmutex_t **intr, kmutex_t **thread)
{
struct hdaudio_audiodev *ad = opaque;
*intr = &ad->ad_sc->sc_intr_lock;
*thread = &ad->ad_sc->sc_lock;
}
static int
hdafg_unsol(device_t self, uint8_t tag)
{
struct hdafg_softc *sc = device_private(self);
struct hdaudio_assoc *as = sc->sc_assocs;
int i, j;
switch (tag) {
case HDAUDIO_UNSOLTAG_EVENT_DD:
#ifdef HDAFG_HDMI_DEBUG
hda_print(sc, "unsol: display device hotplug\n");
#endif
for (i = 0; i < sc->sc_nassocs; i++) {
if (as[i].as_displaydev == false)
continue;
for (j = 0; j < HDAUDIO_MAXPINS; j++) {
if (as[i].as_pins[j] == 0)
continue;
hdafg_assoc_dump_dd(sc, &as[i], j, 0);
}
}
break;
default:
#ifdef HDAFG_HDMI_DEBUG
hda_print(sc, "unsol: tag=%u\n", tag);
#endif
break;
}
return 0;
}
static int
hdafg_widget_info(void *opaque, prop_dictionary_t request,
prop_dictionary_t response)
{
struct hdafg_softc *sc = opaque;
struct hdaudio_widget *w;
prop_array_t connlist;
uint32_t config, wcap;
uint16_t index;
int nid;
int i;
if (prop_dictionary_get_uint16(request, "index", &index) == false)
return EINVAL;
nid = sc->sc_startnode + index;
if (nid >= sc->sc_endnode)
return EINVAL;
w = hdafg_widget_lookup(sc, nid);
if (w == NULL)
return ENXIO;
wcap = hda_get_wparam(w, PIN_CAPABILITIES);
config = hdaudio_command(sc->sc_codec, w->w_nid,
CORB_GET_CONFIGURATION_DEFAULT, 0);
prop_dictionary_set_cstring_nocopy(response, "name", w->w_name);
prop_dictionary_set_bool(response, "enable", w->w_enable);
prop_dictionary_set_uint8(response, "nid", w->w_nid);
prop_dictionary_set_uint8(response, "type", w->w_type);
prop_dictionary_set_uint32(response, "config", config);
prop_dictionary_set_uint32(response, "cap", wcap);
if (w->w_nconns == 0)
return 0;
connlist = prop_array_create();
for (i = 0; i < w->w_nconns; i++) {
if (w->w_conns[i] == 0)
continue;
prop_array_add(connlist,
prop_number_create_unsigned_integer(w->w_conns[i]));
}
prop_dictionary_set(response, "connlist", connlist);
prop_object_release(connlist);
return 0;
}
static int
hdafg_codec_info(void *opaque, prop_dictionary_t request,
prop_dictionary_t response)
{
struct hdafg_softc *sc = opaque;
prop_dictionary_set_uint16(response, "vendor-id",
sc->sc_vendor);
prop_dictionary_set_uint16(response, "product-id",
sc->sc_product);
return 0;
}
MODULE(MODULE_CLASS_DRIVER, hdafg, "hdaudio");
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
hdafg_modcmd(modcmd_t cmd, void *opaque)
{
int error = 0;
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
error = config_init_component(cfdriver_ioconf_hdafg,
cfattach_ioconf_hdafg, cfdata_ioconf_hdafg);
#endif
return error;
case MODULE_CMD_FINI:
#ifdef _MODULE
error = config_fini_component(cfdriver_ioconf_hdafg,
cfattach_ioconf_hdafg, cfdata_ioconf_hdafg);
#endif
return error;
default:
return ENOTTY;
}
}
#define HDAFG_GET_ANACTRL 0xfe0
#define HDAFG_SET_ANACTRL 0x7e0
#define HDAFG_ANALOG_BEEP_EN __BIT(5)
#define HDAFG_ALC231_MONO_OUT_MIXER 0xf
#define HDAFG_STAC9200_AFG 0x1
#define HDAFG_STAC9200_GET_ANACTRL_PAYLOAD 0x0
#define HDAFG_ALC231_INPUT_BOTH_CHANNELS_UNMUTE 0x7100
static void
hdafg_enable_analog_beep(struct hdafg_softc *sc)
{
int nid;
uint32_t response;
switch (sc->sc_vendor) {
case HDAUDIO_VENDOR_SIGMATEL:
switch (sc->sc_product) {
case HDAUDIO_PRODUCT_SIGMATEL_STAC9200:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9200D:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9202:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9202D:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9204:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9204D:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9205:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9205_1:
case HDAUDIO_PRODUCT_SIGMATEL_STAC9205D:
nid = HDAFG_STAC9200_AFG;
response = hdaudio_command(sc->sc_codec, nid,
HDAFG_GET_ANACTRL,
HDAFG_STAC9200_GET_ANACTRL_PAYLOAD);
hda_delay(100);
response |= HDAFG_ANALOG_BEEP_EN;
hdaudio_command(sc->sc_codec, nid, HDAFG_SET_ANACTRL,
response);
hda_delay(100);
break;
default:
break;
}
break;
case HDAUDIO_VENDOR_REALTEK:
switch (sc->sc_product) {
case HDAUDIO_PRODUCT_REALTEK_ALC269:
/* The Panasonic Toughbook CF19 - Mk 5 uses a Realtek
* ALC231 that identifies as an ALC269.
* This unmutes the PCBEEP on the speaker.
*/
nid = HDAFG_ALC231_MONO_OUT_MIXER;
response = hdaudio_command(sc->sc_codec, nid,
CORB_SET_AMPLIFIER_GAIN_MUTE,
HDAFG_ALC231_INPUT_BOTH_CHANNELS_UNMUTE);
hda_delay(100);
break;
default:
break;
}
default:
break;
}
}