fceeeec75f
with various laptops using hdaa(4) sound devices. We don't seem to know the "correct" configurations for these devices and the defaults are far superiour, e.g. they work if you don't nuke the default configs. PR: 200526 Differential Revision: https://reviews.freebsd.org/D17772
7160 lines
197 KiB
C
7160 lines
197 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2006 Stephane E. Potvin <sepotvin@videotron.ca>
|
|
* Copyright (c) 2006 Ariff Abdullah <ariff@FreeBSD.org>
|
|
* Copyright (c) 2008-2012 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.
|
|
*/
|
|
|
|
/*
|
|
* Intel High Definition Audio (Audio function) driver for FreeBSD.
|
|
*/
|
|
|
|
#ifdef HAVE_KERNEL_OPTION_HEADERS
|
|
#include "opt_snd.h"
|
|
#endif
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
|
|
#include <sys/ctype.h>
|
|
#include <sys/taskqueue.h>
|
|
|
|
#include <dev/sound/pci/hda/hdac.h>
|
|
#include <dev/sound/pci/hda/hdaa.h>
|
|
#include <dev/sound/pci/hda/hda_reg.h>
|
|
|
|
#include "mixer_if.h"
|
|
|
|
SND_DECLARE_FILE("$FreeBSD$");
|
|
|
|
#define hdaa_lock(devinfo) snd_mtxlock((devinfo)->lock)
|
|
#define hdaa_unlock(devinfo) snd_mtxunlock((devinfo)->lock)
|
|
#define hdaa_lockassert(devinfo) snd_mtxassert((devinfo)->lock)
|
|
#define hdaa_lockowned(devinfo) mtx_owned((devinfo)->lock)
|
|
|
|
static const struct {
|
|
const char *key;
|
|
uint32_t value;
|
|
} hdaa_quirks_tab[] = {
|
|
{ "softpcmvol", HDAA_QUIRK_SOFTPCMVOL },
|
|
{ "fixedrate", HDAA_QUIRK_FIXEDRATE },
|
|
{ "forcestereo", HDAA_QUIRK_FORCESTEREO },
|
|
{ "eapdinv", HDAA_QUIRK_EAPDINV },
|
|
{ "senseinv", HDAA_QUIRK_SENSEINV },
|
|
{ "ivref50", HDAA_QUIRK_IVREF50 },
|
|
{ "ivref80", HDAA_QUIRK_IVREF80 },
|
|
{ "ivref100", HDAA_QUIRK_IVREF100 },
|
|
{ "ovref50", HDAA_QUIRK_OVREF50 },
|
|
{ "ovref80", HDAA_QUIRK_OVREF80 },
|
|
{ "ovref100", HDAA_QUIRK_OVREF100 },
|
|
{ "ivref", HDAA_QUIRK_IVREF },
|
|
{ "ovref", HDAA_QUIRK_OVREF },
|
|
{ "vref", HDAA_QUIRK_VREF },
|
|
};
|
|
|
|
#define HDA_PARSE_MAXDEPTH 10
|
|
|
|
MALLOC_DEFINE(M_HDAA, "hdaa", "HDA Audio");
|
|
|
|
static const char *HDA_COLORS[16] = {"Unknown", "Black", "Grey", "Blue",
|
|
"Green", "Red", "Orange", "Yellow", "Purple", "Pink", "Res.A", "Res.B",
|
|
"Res.C", "Res.D", "White", "Other"};
|
|
|
|
static const char *HDA_DEVS[16] = {"Line-out", "Speaker", "Headphones", "CD",
|
|
"SPDIF-out", "Digital-out", "Modem-line", "Modem-handset", "Line-in",
|
|
"AUX", "Mic", "Telephony", "SPDIF-in", "Digital-in", "Res.E", "Other"};
|
|
|
|
static const char *HDA_CONNS[4] = {"Jack", "None", "Fixed", "Both"};
|
|
|
|
static const char *HDA_CONNECTORS[16] = {
|
|
"Unknown", "1/8", "1/4", "ATAPI", "RCA", "Optical", "Digital", "Analog",
|
|
"DIN", "XLR", "RJ-11", "Combo", "0xc", "0xd", "0xe", "Other" };
|
|
|
|
static const char *HDA_LOCS[64] = {
|
|
"0x00", "Rear", "Front", "Left", "Right", "Top", "Bottom", "Rear-panel",
|
|
"Drive-bay", "0x09", "0x0a", "0x0b", "0x0c", "0x0d", "0x0e", "0x0f",
|
|
"Internal", "0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "Riser",
|
|
"0x18", "Onboard", "0x1a", "0x1b", "0x1c", "0x1d", "0x1e", "0x1f",
|
|
"External", "Ext-Rear", "Ext-Front", "Ext-Left", "Ext-Right", "Ext-Top", "Ext-Bottom", "0x07",
|
|
"0x28", "0x29", "0x2a", "0x2b", "0x2c", "0x2d", "0x2e", "0x2f",
|
|
"Other", "0x31", "0x32", "0x33", "0x34", "0x35", "Other-Bott", "Lid-In",
|
|
"Lid-Out", "0x39", "0x3a", "0x3b", "0x3c", "0x3d", "0x3e", "0x3f" };
|
|
|
|
static const char *HDA_GPIO_ACTIONS[8] = {
|
|
"keep", "set", "clear", "disable", "input", "0x05", "0x06", "0x07"};
|
|
|
|
static const char *HDA_HDMI_CODING_TYPES[18] = {
|
|
"undefined", "LPCM", "AC-3", "MPEG1", "MP3", "MPEG2", "AAC-LC", "DTS",
|
|
"ATRAC", "DSD", "E-AC-3", "DTS-HD", "MLP", "DST", "WMAPro", "HE-AAC",
|
|
"HE-AACv2", "MPEG-Surround"
|
|
};
|
|
|
|
/* Default */
|
|
static uint32_t hdaa_fmt[] = {
|
|
SND_FORMAT(AFMT_S16_LE, 2, 0),
|
|
0
|
|
};
|
|
|
|
static struct pcmchan_caps hdaa_caps = {48000, 48000, hdaa_fmt, 0};
|
|
|
|
static const struct {
|
|
uint32_t rate;
|
|
int valid;
|
|
uint16_t base;
|
|
uint16_t mul;
|
|
uint16_t div;
|
|
} hda_rate_tab[] = {
|
|
{ 8000, 1, 0x0000, 0x0000, 0x0500 }, /* (48000 * 1) / 6 */
|
|
{ 9600, 0, 0x0000, 0x0000, 0x0400 }, /* (48000 * 1) / 5 */
|
|
{ 12000, 0, 0x0000, 0x0000, 0x0300 }, /* (48000 * 1) / 4 */
|
|
{ 16000, 1, 0x0000, 0x0000, 0x0200 }, /* (48000 * 1) / 3 */
|
|
{ 18000, 0, 0x0000, 0x1000, 0x0700 }, /* (48000 * 3) / 8 */
|
|
{ 19200, 0, 0x0000, 0x0800, 0x0400 }, /* (48000 * 2) / 5 */
|
|
{ 24000, 0, 0x0000, 0x0000, 0x0100 }, /* (48000 * 1) / 2 */
|
|
{ 28800, 0, 0x0000, 0x1000, 0x0400 }, /* (48000 * 3) / 5 */
|
|
{ 32000, 1, 0x0000, 0x0800, 0x0200 }, /* (48000 * 2) / 3 */
|
|
{ 36000, 0, 0x0000, 0x1000, 0x0300 }, /* (48000 * 3) / 4 */
|
|
{ 38400, 0, 0x0000, 0x1800, 0x0400 }, /* (48000 * 4) / 5 */
|
|
{ 48000, 1, 0x0000, 0x0000, 0x0000 }, /* (48000 * 1) / 1 */
|
|
{ 64000, 0, 0x0000, 0x1800, 0x0200 }, /* (48000 * 4) / 3 */
|
|
{ 72000, 0, 0x0000, 0x1000, 0x0100 }, /* (48000 * 3) / 2 */
|
|
{ 96000, 1, 0x0000, 0x0800, 0x0000 }, /* (48000 * 2) / 1 */
|
|
{ 144000, 0, 0x0000, 0x1000, 0x0000 }, /* (48000 * 3) / 1 */
|
|
{ 192000, 1, 0x0000, 0x1800, 0x0000 }, /* (48000 * 4) / 1 */
|
|
{ 8820, 0, 0x4000, 0x0000, 0x0400 }, /* (44100 * 1) / 5 */
|
|
{ 11025, 1, 0x4000, 0x0000, 0x0300 }, /* (44100 * 1) / 4 */
|
|
{ 12600, 0, 0x4000, 0x0800, 0x0600 }, /* (44100 * 2) / 7 */
|
|
{ 14700, 0, 0x4000, 0x0000, 0x0200 }, /* (44100 * 1) / 3 */
|
|
{ 17640, 0, 0x4000, 0x0800, 0x0400 }, /* (44100 * 2) / 5 */
|
|
{ 18900, 0, 0x4000, 0x1000, 0x0600 }, /* (44100 * 3) / 7 */
|
|
{ 22050, 1, 0x4000, 0x0000, 0x0100 }, /* (44100 * 1) / 2 */
|
|
{ 25200, 0, 0x4000, 0x1800, 0x0600 }, /* (44100 * 4) / 7 */
|
|
{ 26460, 0, 0x4000, 0x1000, 0x0400 }, /* (44100 * 3) / 5 */
|
|
{ 29400, 0, 0x4000, 0x0800, 0x0200 }, /* (44100 * 2) / 3 */
|
|
{ 33075, 0, 0x4000, 0x1000, 0x0300 }, /* (44100 * 3) / 4 */
|
|
{ 35280, 0, 0x4000, 0x1800, 0x0400 }, /* (44100 * 4) / 5 */
|
|
{ 44100, 1, 0x4000, 0x0000, 0x0000 }, /* (44100 * 1) / 1 */
|
|
{ 58800, 0, 0x4000, 0x1800, 0x0200 }, /* (44100 * 4) / 3 */
|
|
{ 66150, 0, 0x4000, 0x1000, 0x0100 }, /* (44100 * 3) / 2 */
|
|
{ 88200, 1, 0x4000, 0x0800, 0x0000 }, /* (44100 * 2) / 1 */
|
|
{ 132300, 0, 0x4000, 0x1000, 0x0000 }, /* (44100 * 3) / 1 */
|
|
{ 176400, 1, 0x4000, 0x1800, 0x0000 }, /* (44100 * 4) / 1 */
|
|
};
|
|
#define HDA_RATE_TAB_LEN (sizeof(hda_rate_tab) / sizeof(hda_rate_tab[0]))
|
|
|
|
const static char *ossnames[] = SOUND_DEVICE_NAMES;
|
|
|
|
/****************************************************************************
|
|
* Function prototypes
|
|
****************************************************************************/
|
|
static int hdaa_pcmchannel_setup(struct hdaa_chan *);
|
|
|
|
static void hdaa_widget_connection_select(struct hdaa_widget *, uint8_t);
|
|
static void hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *,
|
|
uint32_t, int, int);
|
|
static struct hdaa_audio_ctl *hdaa_audio_ctl_amp_get(struct hdaa_devinfo *,
|
|
nid_t, int, int, int);
|
|
static void hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *,
|
|
nid_t, int, int, int, int, int, int);
|
|
|
|
static void hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf);
|
|
|
|
static char *
|
|
hdaa_audio_ctl_ossmixer_mask2allname(uint32_t mask, char *buf, size_t len)
|
|
{
|
|
int i, first = 1;
|
|
|
|
bzero(buf, len);
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
|
|
if (mask & (1 << i)) {
|
|
if (first == 0)
|
|
strlcat(buf, ", ", len);
|
|
strlcat(buf, ossnames[i], len);
|
|
first = 0;
|
|
}
|
|
}
|
|
return (buf);
|
|
}
|
|
|
|
static struct hdaa_audio_ctl *
|
|
hdaa_audio_ctl_each(struct hdaa_devinfo *devinfo, int *index)
|
|
{
|
|
if (devinfo == NULL ||
|
|
index == NULL || devinfo->ctl == NULL ||
|
|
devinfo->ctlcnt < 1 ||
|
|
*index < 0 || *index >= devinfo->ctlcnt)
|
|
return (NULL);
|
|
return (&devinfo->ctl[(*index)++]);
|
|
}
|
|
|
|
static struct hdaa_audio_ctl *
|
|
hdaa_audio_ctl_amp_get(struct hdaa_devinfo *devinfo, nid_t nid, int dir,
|
|
int index, int cnt)
|
|
{
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, found = 0;
|
|
|
|
if (devinfo == NULL || devinfo->ctl == NULL)
|
|
return (NULL);
|
|
|
|
i = 0;
|
|
while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) {
|
|
if (ctl->enable == 0)
|
|
continue;
|
|
if (ctl->widget->nid != nid)
|
|
continue;
|
|
if (dir && ctl->ndir != dir)
|
|
continue;
|
|
if (index >= 0 && ctl->ndir == HDAA_CTL_IN &&
|
|
ctl->dir == ctl->ndir && ctl->index != index)
|
|
continue;
|
|
found++;
|
|
if (found == cnt || cnt <= 0)
|
|
return (ctl);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static const struct matrix {
|
|
struct pcmchan_matrix m;
|
|
int analog;
|
|
} matrixes[] = {
|
|
{ SND_CHN_MATRIX_MAP_1_0, 1 },
|
|
{ SND_CHN_MATRIX_MAP_2_0, 1 },
|
|
{ SND_CHN_MATRIX_MAP_2_1, 0 },
|
|
{ SND_CHN_MATRIX_MAP_3_0, 0 },
|
|
{ SND_CHN_MATRIX_MAP_3_1, 0 },
|
|
{ SND_CHN_MATRIX_MAP_4_0, 1 },
|
|
{ SND_CHN_MATRIX_MAP_4_1, 0 },
|
|
{ SND_CHN_MATRIX_MAP_5_0, 0 },
|
|
{ SND_CHN_MATRIX_MAP_5_1, 1 },
|
|
{ SND_CHN_MATRIX_MAP_6_0, 0 },
|
|
{ SND_CHN_MATRIX_MAP_6_1, 0 },
|
|
{ SND_CHN_MATRIX_MAP_7_0, 0 },
|
|
{ SND_CHN_MATRIX_MAP_7_1, 1 },
|
|
};
|
|
|
|
static const char *channel_names[] = SND_CHN_T_NAMES;
|
|
|
|
/*
|
|
* Connected channels change handler.
|
|
*/
|
|
static void
|
|
hdaa_channels_handler(struct hdaa_audio_as *as)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo;
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_chan *ch = &devinfo->chans[as->chans[0]];
|
|
struct hdaa_widget *w;
|
|
uint8_t *eld;
|
|
int i, total, sub, assume, channels;
|
|
uint16_t cpins, upins, tpins;
|
|
|
|
cpins = upins = 0;
|
|
eld = NULL;
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->pins[i] <= 0)
|
|
continue;
|
|
w = hdaa_widget_get(devinfo, as->pins[i]);
|
|
if (w == NULL)
|
|
continue;
|
|
if (w->wclass.pin.connected == 1)
|
|
cpins |= (1 << i);
|
|
else if (w->wclass.pin.connected != 0)
|
|
upins |= (1 << i);
|
|
if (w->eld != NULL && w->eld_len >= 8)
|
|
eld = w->eld;
|
|
}
|
|
tpins = cpins | upins;
|
|
if (as->hpredir >= 0)
|
|
tpins &= 0x7fff;
|
|
if (tpins == 0)
|
|
tpins = as->pinset;
|
|
|
|
total = sub = assume = channels = 0;
|
|
if (eld) {
|
|
/* Map CEA speakers to sound(4) channels. */
|
|
if (eld[7] & 0x01) /* Front Left/Right */
|
|
channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR;
|
|
if (eld[7] & 0x02) /* Low Frequency Effect */
|
|
channels |= SND_CHN_T_MASK_LF;
|
|
if (eld[7] & 0x04) /* Front Center */
|
|
channels |= SND_CHN_T_MASK_FC;
|
|
if (eld[7] & 0x08) { /* Rear Left/Right */
|
|
/* If we have both RLR and RLRC, report RLR as side. */
|
|
if (eld[7] & 0x40) /* Rear Left/Right Center */
|
|
channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR;
|
|
else
|
|
channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR;
|
|
}
|
|
if (eld[7] & 0x10) /* Rear center */
|
|
channels |= SND_CHN_T_MASK_BC;
|
|
if (eld[7] & 0x20) /* Front Left/Right Center */
|
|
channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC;
|
|
if (eld[7] & 0x40) /* Rear Left/Right Center */
|
|
channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR;
|
|
} else if (as->pinset != 0 && (tpins & 0xffe0) == 0) {
|
|
/* Map UAA speakers to sound(4) channels. */
|
|
if (tpins & 0x0001)
|
|
channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR;
|
|
if (tpins & 0x0002)
|
|
channels |= SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF;
|
|
if (tpins & 0x0004)
|
|
channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR;
|
|
if (tpins & 0x0008)
|
|
channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC;
|
|
if (tpins & 0x0010) {
|
|
/* If there is no back pin, report side as back. */
|
|
if ((as->pinset & 0x0004) == 0)
|
|
channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR;
|
|
else
|
|
channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR;
|
|
}
|
|
} else if (as->mixed) {
|
|
/* Mixed assoc can be only stereo or theoretically mono. */
|
|
if (ch->channels == 1)
|
|
channels |= SND_CHN_T_MASK_FC;
|
|
else
|
|
channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR;
|
|
}
|
|
if (channels) { /* We have some usable channels info. */
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(pdevinfo->dev, "%s channel set is: ",
|
|
as->dir == HDAA_CTL_OUT ? "Playback" : "Recording");
|
|
for (i = 0; i < SND_CHN_T_MAX; i++)
|
|
if (channels & (1 << i))
|
|
printf("%s, ", channel_names[i]);
|
|
printf("\n");
|
|
);
|
|
/* Look for maximal fitting matrix. */
|
|
for (i = 0; i < sizeof(matrixes) / sizeof(struct matrix); i++) {
|
|
if (as->pinset != 0 && matrixes[i].analog == 0)
|
|
continue;
|
|
if ((matrixes[i].m.mask & ~channels) == 0) {
|
|
total = matrixes[i].m.channels;
|
|
sub = matrixes[i].m.ext;
|
|
}
|
|
}
|
|
}
|
|
if (total == 0) {
|
|
assume = 1;
|
|
total = ch->channels;
|
|
sub = (total == 6 || total == 8) ? 1 : 0;
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(pdevinfo->dev,
|
|
"%s channel matrix is: %s%d.%d (%s)\n",
|
|
as->dir == HDAA_CTL_OUT ? "Playback" : "Recording",
|
|
assume ? "unknown, assuming " : "", total - sub, sub,
|
|
cpins != 0 ? "connected" :
|
|
(upins != 0 ? "unknown" : "disconnected"));
|
|
);
|
|
}
|
|
|
|
/*
|
|
* Headphones redirection change handler.
|
|
*/
|
|
static void
|
|
hdaa_hpredir_handler(struct hdaa_widget *w)
|
|
{
|
|
struct hdaa_devinfo *devinfo = w->devinfo;
|
|
struct hdaa_audio_as *as = &devinfo->as[w->bindas];
|
|
struct hdaa_widget *w1;
|
|
struct hdaa_audio_ctl *ctl;
|
|
uint32_t val;
|
|
int j, connected = w->wclass.pin.connected;
|
|
|
|
HDA_BOOTVERBOSE(
|
|
device_printf((as->pdevinfo && as->pdevinfo->dev) ?
|
|
as->pdevinfo->dev : devinfo->dev,
|
|
"Redirect output to: %s\n",
|
|
connected ? "headphones": "main");
|
|
);
|
|
/* (Un)Mute headphone pin. */
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo,
|
|
w->nid, HDAA_CTL_IN, -1, 1);
|
|
if (ctl != NULL && ctl->mute) {
|
|
/* If pin has muter - use it. */
|
|
val = connected ? 0 : 1;
|
|
if (val != ctl->forcemute) {
|
|
ctl->forcemute = val;
|
|
hdaa_audio_ctl_amp_set(ctl,
|
|
HDAA_AMP_MUTE_DEFAULT,
|
|
HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT);
|
|
}
|
|
} else {
|
|
/* If there is no muter - disable pin output. */
|
|
if (connected)
|
|
val = w->wclass.pin.ctrl |
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE;
|
|
else
|
|
val = w->wclass.pin.ctrl &
|
|
~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE;
|
|
if (val != w->wclass.pin.ctrl) {
|
|
w->wclass.pin.ctrl = val;
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL(0,
|
|
w->nid, w->wclass.pin.ctrl));
|
|
}
|
|
}
|
|
/* (Un)Mute other pins. */
|
|
for (j = 0; j < 15; j++) {
|
|
if (as->pins[j] <= 0)
|
|
continue;
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo,
|
|
as->pins[j], HDAA_CTL_IN, -1, 1);
|
|
if (ctl != NULL && ctl->mute) {
|
|
/* If pin has muter - use it. */
|
|
val = connected ? 1 : 0;
|
|
if (val == ctl->forcemute)
|
|
continue;
|
|
ctl->forcemute = val;
|
|
hdaa_audio_ctl_amp_set(ctl,
|
|
HDAA_AMP_MUTE_DEFAULT,
|
|
HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT);
|
|
continue;
|
|
}
|
|
/* If there is no muter - disable pin output. */
|
|
w1 = hdaa_widget_get(devinfo, as->pins[j]);
|
|
if (w1 != NULL) {
|
|
if (connected)
|
|
val = w1->wclass.pin.ctrl &
|
|
~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE;
|
|
else
|
|
val = w1->wclass.pin.ctrl |
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE;
|
|
if (val != w1->wclass.pin.ctrl) {
|
|
w1->wclass.pin.ctrl = val;
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL(0,
|
|
w1->nid, w1->wclass.pin.ctrl));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Recording source change handler.
|
|
*/
|
|
static void
|
|
hdaa_autorecsrc_handler(struct hdaa_audio_as *as, struct hdaa_widget *w)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo;
|
|
struct hdaa_devinfo *devinfo;
|
|
struct hdaa_widget *w1;
|
|
int i, mask, fullmask, prio, bestprio;
|
|
char buf[128];
|
|
|
|
if (!as->mixed || pdevinfo == NULL || pdevinfo->mixer == NULL)
|
|
return;
|
|
/* Don't touch anything if we asked not to. */
|
|
if (pdevinfo->autorecsrc == 0 ||
|
|
(pdevinfo->autorecsrc == 1 && w != NULL))
|
|
return;
|
|
/* Don't touch anything if "mix" or "speaker" selected. */
|
|
if (pdevinfo->recsrc & (SOUND_MASK_IMIX | SOUND_MASK_SPEAKER))
|
|
return;
|
|
/* Don't touch anything if several selected. */
|
|
if (ffs(pdevinfo->recsrc) != fls(pdevinfo->recsrc))
|
|
return;
|
|
devinfo = pdevinfo->devinfo;
|
|
mask = fullmask = 0;
|
|
bestprio = 0;
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->pins[i] <= 0)
|
|
continue;
|
|
w1 = hdaa_widget_get(devinfo, as->pins[i]);
|
|
if (w1 == NULL || w1->enable == 0)
|
|
continue;
|
|
if (w1->wclass.pin.connected == 0)
|
|
continue;
|
|
prio = (w1->wclass.pin.connected == 1) ? 2 : 1;
|
|
if (prio < bestprio)
|
|
continue;
|
|
if (prio > bestprio) {
|
|
mask = 0;
|
|
bestprio = prio;
|
|
}
|
|
mask |= (1 << w1->ossdev);
|
|
fullmask |= (1 << w1->ossdev);
|
|
}
|
|
if (mask == 0)
|
|
return;
|
|
/* Prefer newly connected input. */
|
|
if (w != NULL && (mask & (1 << w->ossdev)))
|
|
mask = (1 << w->ossdev);
|
|
/* Prefer previously selected input */
|
|
if (mask & pdevinfo->recsrc)
|
|
mask &= pdevinfo->recsrc;
|
|
/* Prefer mic. */
|
|
if (mask & SOUND_MASK_MIC)
|
|
mask = SOUND_MASK_MIC;
|
|
/* Prefer monitor (2nd mic). */
|
|
if (mask & SOUND_MASK_MONITOR)
|
|
mask = SOUND_MASK_MONITOR;
|
|
/* Just take first one. */
|
|
mask = (1 << (ffs(mask) - 1));
|
|
HDA_BOOTVERBOSE(
|
|
hdaa_audio_ctl_ossmixer_mask2allname(mask, buf, sizeof(buf));
|
|
device_printf(pdevinfo->dev,
|
|
"Automatically set rec source to: %s\n", buf);
|
|
);
|
|
hdaa_unlock(devinfo);
|
|
mix_setrecsrc(pdevinfo->mixer, mask);
|
|
hdaa_lock(devinfo);
|
|
}
|
|
|
|
/*
|
|
* Jack presence detection event handler.
|
|
*/
|
|
static void
|
|
hdaa_presence_handler(struct hdaa_widget *w)
|
|
{
|
|
struct hdaa_devinfo *devinfo = w->devinfo;
|
|
struct hdaa_audio_as *as;
|
|
uint32_t res;
|
|
int connected, old;
|
|
|
|
if (w->enable == 0 || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
return;
|
|
|
|
if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 ||
|
|
(HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0)
|
|
return;
|
|
|
|
res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid));
|
|
connected = (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) != 0;
|
|
if (devinfo->quirks & HDAA_QUIRK_SENSEINV)
|
|
connected = !connected;
|
|
old = w->wclass.pin.connected;
|
|
if (connected == old)
|
|
return;
|
|
w->wclass.pin.connected = connected;
|
|
HDA_BOOTVERBOSE(
|
|
if (connected || old != 2) {
|
|
device_printf(devinfo->dev,
|
|
"Pin sense: nid=%d sense=0x%08x (%sconnected)\n",
|
|
w->nid, res, !connected ? "dis" : "");
|
|
}
|
|
);
|
|
|
|
as = &devinfo->as[w->bindas];
|
|
if (as->hpredir >= 0 && as->pins[15] == w->nid)
|
|
hdaa_hpredir_handler(w);
|
|
if (as->dir == HDAA_CTL_IN && old != 2)
|
|
hdaa_autorecsrc_handler(as, w);
|
|
if (old != 2)
|
|
hdaa_channels_handler(as);
|
|
}
|
|
|
|
/*
|
|
* Callback for poll based presence detection.
|
|
*/
|
|
static void
|
|
hdaa_jack_poll_callback(void *arg)
|
|
{
|
|
struct hdaa_devinfo *devinfo = arg;
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
hdaa_lock(devinfo);
|
|
if (devinfo->poll_ival == 0) {
|
|
hdaa_unlock(devinfo);
|
|
return;
|
|
}
|
|
for (i = 0; i < devinfo->ascnt; i++) {
|
|
if (devinfo->as[i].hpredir < 0)
|
|
continue;
|
|
w = hdaa_widget_get(devinfo, devinfo->as[i].pins[15]);
|
|
if (w == NULL || w->enable == 0 || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
hdaa_presence_handler(w);
|
|
}
|
|
callout_reset(&devinfo->poll_jack, devinfo->poll_ival,
|
|
hdaa_jack_poll_callback, devinfo);
|
|
hdaa_unlock(devinfo);
|
|
}
|
|
|
|
static void
|
|
hdaa_eld_dump(struct hdaa_widget *w)
|
|
{
|
|
struct hdaa_devinfo *devinfo = w->devinfo;
|
|
device_t dev = devinfo->dev;
|
|
uint8_t *sad;
|
|
int len, mnl, i, sadc, fmt;
|
|
|
|
if (w->eld == NULL || w->eld_len < 4)
|
|
return;
|
|
device_printf(dev,
|
|
"ELD nid=%d: ELD_Ver=%u Baseline_ELD_Len=%u\n",
|
|
w->nid, w->eld[0] >> 3, w->eld[2]);
|
|
if ((w->eld[0] >> 3) != 0x02)
|
|
return;
|
|
len = min(w->eld_len, (u_int)w->eld[2] * 4);
|
|
mnl = w->eld[4] & 0x1f;
|
|
device_printf(dev,
|
|
"ELD nid=%d: CEA_EDID_Ver=%u MNL=%u\n",
|
|
w->nid, w->eld[4] >> 5, mnl);
|
|
sadc = w->eld[5] >> 4;
|
|
device_printf(dev,
|
|
"ELD nid=%d: SAD_Count=%u Conn_Type=%u S_AI=%u HDCP=%u\n",
|
|
w->nid, sadc, (w->eld[5] >> 2) & 0x3,
|
|
(w->eld[5] >> 1) & 0x1, w->eld[5] & 0x1);
|
|
device_printf(dev,
|
|
"ELD nid=%d: Aud_Synch_Delay=%ums\n",
|
|
w->nid, w->eld[6] * 2);
|
|
device_printf(dev,
|
|
"ELD nid=%d: Channels=0x%b\n",
|
|
w->nid, w->eld[7],
|
|
"\020\07RLRC\06FLRC\05RC\04RLR\03FC\02LFE\01FLR");
|
|
device_printf(dev,
|
|
"ELD nid=%d: Port_ID=0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
|
|
w->nid, w->eld[8], w->eld[9], w->eld[10], w->eld[11],
|
|
w->eld[12], w->eld[13], w->eld[14], w->eld[15]);
|
|
device_printf(dev,
|
|
"ELD nid=%d: Manufacturer_Name=0x%02x%02x\n",
|
|
w->nid, w->eld[16], w->eld[17]);
|
|
device_printf(dev,
|
|
"ELD nid=%d: Product_Code=0x%02x%02x\n",
|
|
w->nid, w->eld[18], w->eld[19]);
|
|
device_printf(dev,
|
|
"ELD nid=%d: Monitor_Name_String='%.*s'\n",
|
|
w->nid, mnl, &w->eld[20]);
|
|
for (i = 0; i < sadc; i++) {
|
|
sad = &w->eld[20 + mnl + i * 3];
|
|
fmt = (sad[0] >> 3) & 0x0f;
|
|
if (fmt == HDA_HDMI_CODING_TYPE_REF_CTX) {
|
|
fmt = (sad[2] >> 3) & 0x1f;
|
|
if (fmt < 1 || fmt > 3)
|
|
fmt = 0;
|
|
else
|
|
fmt += 14;
|
|
}
|
|
device_printf(dev,
|
|
"ELD nid=%d: %s %dch freqs=0x%b",
|
|
w->nid, HDA_HDMI_CODING_TYPES[fmt], (sad[0] & 0x07) + 1,
|
|
sad[1], "\020\007192\006176\00596\00488\00348\00244\00132");
|
|
switch (fmt) {
|
|
case HDA_HDMI_CODING_TYPE_LPCM:
|
|
printf(" sizes=0x%b",
|
|
sad[2] & 0x07, "\020\00324\00220\00116");
|
|
break;
|
|
case HDA_HDMI_CODING_TYPE_AC3:
|
|
case HDA_HDMI_CODING_TYPE_MPEG1:
|
|
case HDA_HDMI_CODING_TYPE_MP3:
|
|
case HDA_HDMI_CODING_TYPE_MPEG2:
|
|
case HDA_HDMI_CODING_TYPE_AACLC:
|
|
case HDA_HDMI_CODING_TYPE_DTS:
|
|
case HDA_HDMI_CODING_TYPE_ATRAC:
|
|
printf(" max_bitrate=%d", sad[2] * 8000);
|
|
break;
|
|
case HDA_HDMI_CODING_TYPE_WMAPRO:
|
|
printf(" profile=%d", sad[2] & 0x07);
|
|
break;
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_eld_handler(struct hdaa_widget *w)
|
|
{
|
|
struct hdaa_devinfo *devinfo = w->devinfo;
|
|
uint32_t res;
|
|
int i;
|
|
|
|
if (w->enable == 0 || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
return;
|
|
|
|
if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 ||
|
|
(HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0)
|
|
return;
|
|
|
|
res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid));
|
|
if ((w->eld != 0) == ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) != 0))
|
|
return;
|
|
if (w->eld != NULL) {
|
|
w->eld_len = 0;
|
|
free(w->eld, M_HDAA);
|
|
w->eld = NULL;
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Pin sense: nid=%d sense=0x%08x "
|
|
"(%sconnected, ELD %svalid)\n",
|
|
w->nid, res,
|
|
(res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ? "" : "dis",
|
|
(res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) ? "" : "in");
|
|
);
|
|
if ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) == 0)
|
|
return;
|
|
|
|
res = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_HDMI_DIP_SIZE(0, w->nid, 0x08));
|
|
if (res == HDA_INVALID)
|
|
return;
|
|
w->eld_len = res & 0xff;
|
|
if (w->eld_len != 0)
|
|
w->eld = malloc(w->eld_len, M_HDAA, M_ZERO | M_NOWAIT);
|
|
if (w->eld == NULL) {
|
|
w->eld_len = 0;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < w->eld_len; i++) {
|
|
res = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_HDMI_ELDD(0, w->nid, i));
|
|
if (res & 0x80000000)
|
|
w->eld[i] = res & 0xff;
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
hdaa_eld_dump(w);
|
|
);
|
|
hdaa_channels_handler(&devinfo->as[w->bindas]);
|
|
}
|
|
|
|
/*
|
|
* Pin sense initializer.
|
|
*/
|
|
static void
|
|
hdaa_sense_init(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as;
|
|
struct hdaa_widget *w;
|
|
int i, poll = 0;
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0 || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) {
|
|
if (w->unsol < 0)
|
|
w->unsol = HDAC_UNSOL_ALLOC(
|
|
device_get_parent(devinfo->dev),
|
|
devinfo->dev, w->nid);
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid,
|
|
HDA_CMD_SET_UNSOLICITED_RESPONSE_ENABLE | w->unsol));
|
|
}
|
|
as = &devinfo->as[w->bindas];
|
|
if (as->hpredir >= 0 && as->pins[15] == w->nid) {
|
|
if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 ||
|
|
(HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) {
|
|
device_printf(devinfo->dev,
|
|
"No presence detection support at nid %d\n",
|
|
w->nid);
|
|
} else {
|
|
if (w->unsol < 0)
|
|
poll = 1;
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Headphones redirection for "
|
|
"association %d nid=%d using %s.\n",
|
|
w->bindas, w->nid,
|
|
(w->unsol < 0) ? "polling" :
|
|
"unsolicited responses");
|
|
);
|
|
}
|
|
}
|
|
hdaa_presence_handler(w);
|
|
if (!HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) &&
|
|
!HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap))
|
|
continue;
|
|
hdaa_eld_handler(w);
|
|
}
|
|
if (poll) {
|
|
callout_reset(&devinfo->poll_jack, 1,
|
|
hdaa_jack_poll_callback, devinfo);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_sense_deinit(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
callout_stop(&devinfo->poll_jack);
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0 || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (w->unsol < 0)
|
|
continue;
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid, 0));
|
|
HDAC_UNSOL_FREE(
|
|
device_get_parent(devinfo->dev), devinfo->dev,
|
|
w->unsol);
|
|
w->unsol = -1;
|
|
}
|
|
}
|
|
|
|
uint32_t
|
|
hdaa_widget_pin_patch(uint32_t config, const char *str)
|
|
{
|
|
char buf[256];
|
|
char *key, *value, *rest, *bad;
|
|
int ival, i;
|
|
|
|
strlcpy(buf, str, sizeof(buf));
|
|
rest = buf;
|
|
while ((key = strsep(&rest, "=")) != NULL) {
|
|
value = strsep(&rest, " \t");
|
|
if (value == NULL)
|
|
break;
|
|
ival = strtol(value, &bad, 10);
|
|
if (strcmp(key, "seq") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK;
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_SEQUENCE_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK);
|
|
} else if (strcmp(key, "as") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK;
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_ASSOCIATION_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK);
|
|
} else if (strcmp(key, "misc") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_MISC_MASK;
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_MISC_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_MISC_MASK);
|
|
} else if (strcmp(key, "color") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_COLOR_MASK;
|
|
if (bad[0] == 0) {
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_COLOR_MASK);
|
|
}
|
|
for (i = 0; i < 16; i++) {
|
|
if (strcasecmp(HDA_COLORS[i], value) == 0) {
|
|
config |= (i << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT);
|
|
break;
|
|
}
|
|
}
|
|
} else if (strcmp(key, "ctype") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK;
|
|
if (bad[0] == 0) {
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK);
|
|
}
|
|
for (i = 0; i < 16; i++) {
|
|
if (strcasecmp(HDA_CONNECTORS[i], value) == 0) {
|
|
config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT);
|
|
break;
|
|
}
|
|
}
|
|
} else if (strcmp(key, "device") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK;
|
|
if (bad[0] == 0) {
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_DEVICE_MASK);
|
|
continue;
|
|
}
|
|
for (i = 0; i < 16; i++) {
|
|
if (strcasecmp(HDA_DEVS[i], value) == 0) {
|
|
config |= (i << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT);
|
|
break;
|
|
}
|
|
}
|
|
} else if (strcmp(key, "loc") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_LOCATION_MASK;
|
|
if (bad[0] == 0) {
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_LOCATION_MASK);
|
|
continue;
|
|
}
|
|
for (i = 0; i < 64; i++) {
|
|
if (strcasecmp(HDA_LOCS[i], value) == 0) {
|
|
config |= (i << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT);
|
|
break;
|
|
}
|
|
}
|
|
} else if (strcmp(key, "conn") == 0) {
|
|
config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK;
|
|
if (bad[0] == 0) {
|
|
config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT) &
|
|
HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK);
|
|
continue;
|
|
}
|
|
for (i = 0; i < 4; i++) {
|
|
if (strcasecmp(HDA_CONNS[i], value) == 0) {
|
|
config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return (config);
|
|
}
|
|
|
|
uint32_t
|
|
hdaa_gpio_patch(uint32_t gpio, const char *str)
|
|
{
|
|
char buf[256];
|
|
char *key, *value, *rest;
|
|
int ikey, i;
|
|
|
|
strlcpy(buf, str, sizeof(buf));
|
|
rest = buf;
|
|
while ((key = strsep(&rest, "=")) != NULL) {
|
|
value = strsep(&rest, " \t");
|
|
if (value == NULL)
|
|
break;
|
|
ikey = strtol(key, NULL, 10);
|
|
if (ikey < 0 || ikey > 7)
|
|
continue;
|
|
for (i = 0; i < 7; i++) {
|
|
if (strcasecmp(HDA_GPIO_ACTIONS[i], value) == 0) {
|
|
gpio &= ~HDAA_GPIO_MASK(ikey);
|
|
gpio |= i << HDAA_GPIO_SHIFT(ikey);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return (gpio);
|
|
}
|
|
|
|
static void
|
|
hdaa_local_patch_pin(struct hdaa_widget *w)
|
|
{
|
|
device_t dev = w->devinfo->dev;
|
|
const char *res = NULL;
|
|
uint32_t config, orig;
|
|
char buf[32];
|
|
|
|
config = orig = w->wclass.pin.config;
|
|
snprintf(buf, sizeof(buf), "cad%u.nid%u.config",
|
|
hda_get_codec_id(dev), w->nid);
|
|
if (resource_string_value(device_get_name(
|
|
device_get_parent(device_get_parent(dev))),
|
|
device_get_unit(device_get_parent(device_get_parent(dev))),
|
|
buf, &res) == 0) {
|
|
if (strncmp(res, "0x", 2) == 0) {
|
|
config = strtol(res + 2, NULL, 16);
|
|
} else {
|
|
config = hdaa_widget_pin_patch(config, res);
|
|
}
|
|
}
|
|
snprintf(buf, sizeof(buf), "nid%u.config", w->nid);
|
|
if (resource_string_value(device_get_name(dev), device_get_unit(dev),
|
|
buf, &res) == 0) {
|
|
if (strncmp(res, "0x", 2) == 0) {
|
|
config = strtol(res + 2, NULL, 16);
|
|
} else {
|
|
config = hdaa_widget_pin_patch(config, res);
|
|
}
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
if (config != orig)
|
|
device_printf(w->devinfo->dev,
|
|
"Patching pin config nid=%u 0x%08x -> 0x%08x\n",
|
|
w->nid, orig, config);
|
|
);
|
|
w->wclass.pin.newconf = w->wclass.pin.config = config;
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_audio_formats_sb(struct sbuf *sb, uint32_t fcap, uint32_t pcmcap)
|
|
{
|
|
uint32_t cap;
|
|
|
|
cap = fcap;
|
|
if (cap != 0) {
|
|
sbuf_printf(sb, " Stream cap: 0x%08x", cap);
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap))
|
|
sbuf_printf(sb, " AC3");
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap))
|
|
sbuf_printf(sb, " FLOAT32");
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap))
|
|
sbuf_printf(sb, " PCM");
|
|
sbuf_printf(sb, "\n");
|
|
}
|
|
cap = pcmcap;
|
|
if (cap != 0) {
|
|
sbuf_printf(sb, " PCM cap: 0x%08x", cap);
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap))
|
|
sbuf_printf(sb, " 8");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap))
|
|
sbuf_printf(sb, " 16");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap))
|
|
sbuf_printf(sb, " 20");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap))
|
|
sbuf_printf(sb, " 24");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap))
|
|
sbuf_printf(sb, " 32");
|
|
sbuf_printf(sb, " bits,");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap))
|
|
sbuf_printf(sb, " 8");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap))
|
|
sbuf_printf(sb, " 11");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap))
|
|
sbuf_printf(sb, " 16");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap))
|
|
sbuf_printf(sb, " 22");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap))
|
|
sbuf_printf(sb, " 32");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap))
|
|
sbuf_printf(sb, " 44");
|
|
sbuf_printf(sb, " 48");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap))
|
|
sbuf_printf(sb, " 88");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap))
|
|
sbuf_printf(sb, " 96");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap))
|
|
sbuf_printf(sb, " 176");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap))
|
|
sbuf_printf(sb, " 192");
|
|
sbuf_printf(sb, " KHz\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_pin_sb(struct sbuf *sb, struct hdaa_widget *w)
|
|
{
|
|
uint32_t pincap, conf;
|
|
|
|
pincap = w->wclass.pin.cap;
|
|
|
|
sbuf_printf(sb, " Pin cap: 0x%08x", pincap);
|
|
if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap))
|
|
sbuf_printf(sb, " ISC");
|
|
if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap))
|
|
sbuf_printf(sb, " TRQD");
|
|
if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap))
|
|
sbuf_printf(sb, " PDC");
|
|
if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap))
|
|
sbuf_printf(sb, " HP");
|
|
if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap))
|
|
sbuf_printf(sb, " OUT");
|
|
if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap))
|
|
sbuf_printf(sb, " IN");
|
|
if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap))
|
|
sbuf_printf(sb, " BAL");
|
|
if (HDA_PARAM_PIN_CAP_HDMI(pincap))
|
|
sbuf_printf(sb, " HDMI");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) {
|
|
sbuf_printf(sb, " VREF[");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap))
|
|
sbuf_printf(sb, " 50");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap))
|
|
sbuf_printf(sb, " 80");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap))
|
|
sbuf_printf(sb, " 100");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap))
|
|
sbuf_printf(sb, " GROUND");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap))
|
|
sbuf_printf(sb, " HIZ");
|
|
sbuf_printf(sb, " ]");
|
|
}
|
|
if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap))
|
|
sbuf_printf(sb, " EAPD");
|
|
if (HDA_PARAM_PIN_CAP_DP(pincap))
|
|
sbuf_printf(sb, " DP");
|
|
if (HDA_PARAM_PIN_CAP_HBR(pincap))
|
|
sbuf_printf(sb, " HBR");
|
|
sbuf_printf(sb, "\n");
|
|
conf = w->wclass.pin.config;
|
|
sbuf_printf(sb, " Pin config: 0x%08x", conf);
|
|
sbuf_printf(sb, " as=%d seq=%d "
|
|
"device=%s conn=%s ctype=%s loc=%s color=%s misc=%d\n",
|
|
HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf),
|
|
HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf),
|
|
HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)],
|
|
HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)],
|
|
HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)],
|
|
HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)],
|
|
HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)],
|
|
HDA_CONFIG_DEFAULTCONF_MISC(conf));
|
|
sbuf_printf(sb, " Pin control: 0x%08x", w->wclass.pin.ctrl);
|
|
if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE)
|
|
sbuf_printf(sb, " HP");
|
|
if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE)
|
|
sbuf_printf(sb, " IN");
|
|
if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE)
|
|
sbuf_printf(sb, " OUT");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) {
|
|
if ((w->wclass.pin.ctrl &
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03)
|
|
sbuf_printf(sb, " HBR");
|
|
else if ((w->wclass.pin.ctrl &
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0)
|
|
sbuf_printf(sb, " EPTs");
|
|
} else {
|
|
if ((w->wclass.pin.ctrl &
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0)
|
|
sbuf_printf(sb, " VREFs");
|
|
}
|
|
sbuf_printf(sb, "\n");
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_amp_sb(struct sbuf *sb, uint32_t cap, const char *banner)
|
|
{
|
|
int offset, size, step;
|
|
|
|
offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap);
|
|
size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap);
|
|
step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap);
|
|
sbuf_printf(sb, " %s amp: 0x%08x "
|
|
"mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n",
|
|
banner, cap,
|
|
HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap),
|
|
step, size, offset,
|
|
((0 - offset) * (size + 1)) / 4,
|
|
((step - offset) * (size + 1)) / 4);
|
|
}
|
|
|
|
|
|
static int
|
|
hdaa_sysctl_caps(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct hdaa_devinfo *devinfo;
|
|
struct hdaa_widget *w, *cw;
|
|
struct sbuf sb;
|
|
char buf[64];
|
|
int error, j;
|
|
|
|
w = (struct hdaa_widget *)oidp->oid_arg1;
|
|
devinfo = w->devinfo;
|
|
sbuf_new_for_sysctl(&sb, NULL, 256, req);
|
|
|
|
sbuf_printf(&sb, "%s%s\n", w->name,
|
|
(w->enable == 0) ? " [DISABLED]" : "");
|
|
sbuf_printf(&sb, " Widget cap: 0x%08x",
|
|
w->param.widget_cap);
|
|
if (w->param.widget_cap & 0x0ee1) {
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap))
|
|
sbuf_printf(&sb, " LRSWAP");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap))
|
|
sbuf_printf(&sb, " PWR");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap))
|
|
sbuf_printf(&sb, " DIGITAL");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap))
|
|
sbuf_printf(&sb, " UNSOL");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap))
|
|
sbuf_printf(&sb, " PROC");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap))
|
|
sbuf_printf(&sb, " STRIPE(x%d)",
|
|
1 << (fls(w->wclass.conv.stripecap) - 1));
|
|
j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap);
|
|
if (j == 1)
|
|
sbuf_printf(&sb, " STEREO");
|
|
else if (j > 1)
|
|
sbuf_printf(&sb, " %dCH", j + 1);
|
|
}
|
|
sbuf_printf(&sb, "\n");
|
|
if (w->bindas != -1) {
|
|
sbuf_printf(&sb, " Association: %d (0x%04x)\n",
|
|
w->bindas, w->bindseqmask);
|
|
}
|
|
if (w->ossmask != 0 || w->ossdev >= 0) {
|
|
sbuf_printf(&sb, " OSS: %s",
|
|
hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf)));
|
|
if (w->ossdev >= 0)
|
|
sbuf_printf(&sb, " (%s)", ossnames[w->ossdev]);
|
|
sbuf_printf(&sb, "\n");
|
|
}
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) {
|
|
hdaa_dump_audio_formats_sb(&sb,
|
|
w->param.supp_stream_formats,
|
|
w->param.supp_pcm_size_rate);
|
|
} else if (w->type ==
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin)
|
|
hdaa_dump_pin_sb(&sb, w);
|
|
if (w->param.eapdbtl != HDA_INVALID) {
|
|
sbuf_printf(&sb, " EAPD: 0x%08x%s%s%s\n",
|
|
w->param.eapdbtl,
|
|
(w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_LR_SWAP) ?
|
|
" LRSWAP" : "",
|
|
(w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD) ?
|
|
" EAPD" : "",
|
|
(w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_BTL) ?
|
|
" BTL" : "");
|
|
}
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) &&
|
|
w->param.outamp_cap != 0)
|
|
hdaa_dump_amp_sb(&sb, w->param.outamp_cap, "Output");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) &&
|
|
w->param.inamp_cap != 0)
|
|
hdaa_dump_amp_sb(&sb, w->param.inamp_cap, " Input");
|
|
if (w->nconns > 0)
|
|
sbuf_printf(&sb, " Connections: %d\n", w->nconns);
|
|
for (j = 0; j < w->nconns; j++) {
|
|
cw = hdaa_widget_get(devinfo, w->conns[j]);
|
|
sbuf_printf(&sb, " + %s<- nid=%d [%s]",
|
|
(w->connsenable[j] == 0)?"[DISABLED] ":"",
|
|
w->conns[j], (cw == NULL) ? "GHOST!" : cw->name);
|
|
if (cw == NULL)
|
|
sbuf_printf(&sb, " [UNKNOWN]");
|
|
else if (cw->enable == 0)
|
|
sbuf_printf(&sb, " [DISABLED]");
|
|
if (w->nconns > 1 && w->selconn == j && w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
sbuf_printf(&sb, " (selected)");
|
|
sbuf_printf(&sb, "\n");
|
|
}
|
|
error = sbuf_finish(&sb);
|
|
sbuf_delete(&sb);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_config(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
char buf[256];
|
|
int error;
|
|
uint32_t conf;
|
|
|
|
conf = *(uint32_t *)oidp->oid_arg1;
|
|
snprintf(buf, sizeof(buf), "0x%08x as=%d seq=%d "
|
|
"device=%s conn=%s ctype=%s loc=%s color=%s misc=%d",
|
|
conf,
|
|
HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf),
|
|
HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf),
|
|
HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)],
|
|
HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)],
|
|
HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)],
|
|
HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)],
|
|
HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)],
|
|
HDA_CONFIG_DEFAULTCONF_MISC(conf));
|
|
error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
|
|
if (error != 0 || req->newptr == NULL)
|
|
return (error);
|
|
if (strncmp(buf, "0x", 2) == 0)
|
|
conf = strtol(buf + 2, NULL, 16);
|
|
else
|
|
conf = hdaa_widget_pin_patch(conf, buf);
|
|
*(uint32_t *)oidp->oid_arg1 = conf;
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
hdaa_config_fetch(const char *str, uint32_t *on, uint32_t *off)
|
|
{
|
|
int i = 0, j, k, len, inv;
|
|
|
|
for (;;) {
|
|
while (str[i] != '\0' &&
|
|
(str[i] == ',' || isspace(str[i]) != 0))
|
|
i++;
|
|
if (str[i] == '\0')
|
|
return;
|
|
j = i;
|
|
while (str[j] != '\0' &&
|
|
!(str[j] == ',' || isspace(str[j]) != 0))
|
|
j++;
|
|
len = j - i;
|
|
if (len > 2 && strncmp(str + i, "no", 2) == 0)
|
|
inv = 2;
|
|
else
|
|
inv = 0;
|
|
for (k = 0; len > inv && k < nitems(hdaa_quirks_tab); k++) {
|
|
if (strncmp(str + i + inv,
|
|
hdaa_quirks_tab[k].key, len - inv) != 0)
|
|
continue;
|
|
if (len - inv != strlen(hdaa_quirks_tab[k].key))
|
|
continue;
|
|
if (inv == 0) {
|
|
*on |= hdaa_quirks_tab[k].value;
|
|
*off &= ~hdaa_quirks_tab[k].value;
|
|
} else {
|
|
*off |= hdaa_quirks_tab[k].value;
|
|
*on &= ~hdaa_quirks_tab[k].value;
|
|
}
|
|
break;
|
|
}
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_quirks(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
char buf[256];
|
|
int error, n = 0, i;
|
|
uint32_t quirks, quirks_off;
|
|
|
|
quirks = *(uint32_t *)oidp->oid_arg1;
|
|
buf[0] = 0;
|
|
for (i = 0; i < nitems(hdaa_quirks_tab); i++) {
|
|
if ((quirks & hdaa_quirks_tab[i].value) != 0)
|
|
n += snprintf(buf + n, sizeof(buf) - n, "%s%s",
|
|
n != 0 ? "," : "", hdaa_quirks_tab[i].key);
|
|
}
|
|
error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
|
|
if (error != 0 || req->newptr == NULL)
|
|
return (error);
|
|
if (strncmp(buf, "0x", 2) == 0)
|
|
quirks = strtol(buf + 2, NULL, 16);
|
|
else {
|
|
quirks = 0;
|
|
hdaa_config_fetch(buf, &quirks, &quirks_off);
|
|
}
|
|
*(uint32_t *)oidp->oid_arg1 = quirks;
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
hdaa_local_patch(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w;
|
|
const char *res = NULL;
|
|
uint32_t quirks_on = 0, quirks_off = 0, x;
|
|
int i;
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL)
|
|
continue;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
hdaa_local_patch_pin(w);
|
|
}
|
|
|
|
if (resource_string_value(device_get_name(devinfo->dev),
|
|
device_get_unit(devinfo->dev), "config", &res) == 0) {
|
|
if (res != NULL && strlen(res) > 0)
|
|
hdaa_config_fetch(res, &quirks_on, &quirks_off);
|
|
devinfo->quirks |= quirks_on;
|
|
devinfo->quirks &= ~quirks_off;
|
|
}
|
|
if (devinfo->newquirks == -1)
|
|
devinfo->newquirks = devinfo->quirks;
|
|
else
|
|
devinfo->quirks = devinfo->newquirks;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Config options: 0x%08x\n", devinfo->quirks);
|
|
);
|
|
|
|
if (resource_string_value(device_get_name(devinfo->dev),
|
|
device_get_unit(devinfo->dev), "gpio_config", &res) == 0) {
|
|
if (strncmp(res, "0x", 2) == 0) {
|
|
devinfo->gpio = strtol(res + 2, NULL, 16);
|
|
} else {
|
|
devinfo->gpio = hdaa_gpio_patch(devinfo->gpio, res);
|
|
}
|
|
}
|
|
if (devinfo->newgpio == -1)
|
|
devinfo->newgpio = devinfo->gpio;
|
|
else
|
|
devinfo->gpio = devinfo->newgpio;
|
|
if (devinfo->newgpo == -1)
|
|
devinfo->newgpo = devinfo->gpo;
|
|
else
|
|
devinfo->gpo = devinfo->newgpo;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev, "GPIO config options:");
|
|
for (i = 0; i < 7; i++) {
|
|
x = (devinfo->gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i);
|
|
if (x != 0)
|
|
printf(" %d=%s", i, HDA_GPIO_ACTIONS[x]);
|
|
}
|
|
printf("\n");
|
|
);
|
|
}
|
|
|
|
static void
|
|
hdaa_widget_connection_parse(struct hdaa_widget *w)
|
|
{
|
|
uint32_t res;
|
|
int i, j, max, ents, entnum;
|
|
nid_t nid = w->nid;
|
|
nid_t cnid, addcnid, prevcnid;
|
|
|
|
w->nconns = 0;
|
|
|
|
res = hda_command(w->devinfo->dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_CONN_LIST_LENGTH));
|
|
|
|
ents = HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH(res);
|
|
|
|
if (ents < 1)
|
|
return;
|
|
|
|
entnum = HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM(res) ? 2 : 4;
|
|
max = (sizeof(w->conns) / sizeof(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 = hda_command(w->devinfo->dev,
|
|
HDA_CMD_GET_CONN_LIST_ENTRY(0, nid, i));
|
|
for (j = 0; j < entnum; j++) {
|
|
cnid = CONN_CNID(res, entnum, j);
|
|
if (cnid == 0) {
|
|
if (w->nconns < ents)
|
|
device_printf(w->devinfo->dev,
|
|
"WARNING: nid=%d has zero cnid "
|
|
"entnum=%d j=%d index=%d "
|
|
"entries=%d found=%d res=0x%08x\n",
|
|
nid, entnum, j, i,
|
|
ents, w->nconns, res);
|
|
else
|
|
goto getconns_out;
|
|
}
|
|
if (cnid < w->devinfo->startnode ||
|
|
cnid >= w->devinfo->endnode) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(w->devinfo->dev,
|
|
"WARNING: nid=%d has cnid outside "
|
|
"of the AFG range j=%d "
|
|
"entnum=%d index=%d res=0x%08x\n",
|
|
nid, j, entnum, i, res);
|
|
);
|
|
}
|
|
if (CONN_RANGE(res, entnum, j) == 0)
|
|
addcnid = cnid;
|
|
else if (prevcnid == 0 || prevcnid >= cnid) {
|
|
device_printf(w->devinfo->dev,
|
|
"WARNING: Invalid child range "
|
|
"nid=%d index=%d j=%d entnum=%d "
|
|
"prevcnid=%d cnid=%d res=0x%08x\n",
|
|
nid, i, j, entnum, prevcnid,
|
|
cnid, res);
|
|
addcnid = cnid;
|
|
} else
|
|
addcnid = prevcnid + 1;
|
|
while (addcnid <= cnid) {
|
|
if (w->nconns > max) {
|
|
device_printf(w->devinfo->dev,
|
|
"Adding %d (nid=%d): "
|
|
"Max connection reached! max=%d\n",
|
|
addcnid, nid, max + 1);
|
|
goto getconns_out;
|
|
}
|
|
w->connsenable[w->nconns] = 1;
|
|
w->conns[w->nconns++] = addcnid++;
|
|
}
|
|
prevcnid = cnid;
|
|
}
|
|
}
|
|
|
|
getconns_out:
|
|
return;
|
|
}
|
|
|
|
static void
|
|
hdaa_widget_parse(struct hdaa_widget *w)
|
|
{
|
|
device_t dev = w->devinfo->dev;
|
|
uint32_t wcap, cap;
|
|
nid_t nid = w->nid;
|
|
char buf[64];
|
|
|
|
w->param.widget_cap = wcap = hda_command(dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_AUDIO_WIDGET_CAP));
|
|
w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(wcap);
|
|
|
|
hdaa_widget_connection_parse(w);
|
|
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(wcap)) {
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap))
|
|
w->param.outamp_cap =
|
|
hda_command(dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid,
|
|
HDA_PARAM_OUTPUT_AMP_CAP));
|
|
else
|
|
w->param.outamp_cap =
|
|
w->devinfo->outamp_cap;
|
|
} else
|
|
w->param.outamp_cap = 0;
|
|
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(wcap)) {
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap))
|
|
w->param.inamp_cap =
|
|
hda_command(dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid,
|
|
HDA_PARAM_INPUT_AMP_CAP));
|
|
else
|
|
w->param.inamp_cap =
|
|
w->devinfo->inamp_cap;
|
|
} else
|
|
w->param.inamp_cap = 0;
|
|
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) {
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR(wcap)) {
|
|
cap = hda_command(dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid,
|
|
HDA_PARAM_SUPP_STREAM_FORMATS));
|
|
w->param.supp_stream_formats = (cap != 0) ? cap :
|
|
w->devinfo->supp_stream_formats;
|
|
cap = hda_command(dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid,
|
|
HDA_PARAM_SUPP_PCM_SIZE_RATE));
|
|
w->param.supp_pcm_size_rate = (cap != 0) ? cap :
|
|
w->devinfo->supp_pcm_size_rate;
|
|
} else {
|
|
w->param.supp_stream_formats =
|
|
w->devinfo->supp_stream_formats;
|
|
w->param.supp_pcm_size_rate =
|
|
w->devinfo->supp_pcm_size_rate;
|
|
}
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) {
|
|
w->wclass.conv.stripecap = hda_command(dev,
|
|
HDA_CMD_GET_STRIPE_CONTROL(0, w->nid)) >> 20;
|
|
} else
|
|
w->wclass.conv.stripecap = 1;
|
|
} else {
|
|
w->param.supp_stream_formats = 0;
|
|
w->param.supp_pcm_size_rate = 0;
|
|
}
|
|
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) {
|
|
w->wclass.pin.original = w->wclass.pin.newconf =
|
|
w->wclass.pin.config = hda_command(dev,
|
|
HDA_CMD_GET_CONFIGURATION_DEFAULT(0, w->nid));
|
|
w->wclass.pin.cap = hda_command(dev,
|
|
HDA_CMD_GET_PARAMETER(0, w->nid, HDA_PARAM_PIN_CAP));
|
|
w->wclass.pin.ctrl = hda_command(dev,
|
|
HDA_CMD_GET_PIN_WIDGET_CTRL(0, nid));
|
|
w->wclass.pin.connected = 2;
|
|
if (HDA_PARAM_PIN_CAP_EAPD_CAP(w->wclass.pin.cap)) {
|
|
w->param.eapdbtl = hda_command(dev,
|
|
HDA_CMD_GET_EAPD_BTL_ENABLE(0, nid));
|
|
w->param.eapdbtl &= 0x7;
|
|
w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD;
|
|
} else
|
|
w->param.eapdbtl = HDA_INVALID;
|
|
}
|
|
w->unsol = -1;
|
|
|
|
hdaa_unlock(w->devinfo);
|
|
snprintf(buf, sizeof(buf), "nid%d", w->nid);
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
|
|
w, 0, hdaa_sysctl_caps, "A", "Node capabilities");
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) {
|
|
snprintf(buf, sizeof(buf), "nid%d_config", w->nid);
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
buf, CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE,
|
|
&w->wclass.pin.newconf, 0, hdaa_sysctl_config, "A",
|
|
"Current pin configuration");
|
|
snprintf(buf, sizeof(buf), "nid%d_original", w->nid);
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
|
|
&w->wclass.pin.original, 0, hdaa_sysctl_config, "A",
|
|
"Original pin configuration");
|
|
}
|
|
hdaa_lock(w->devinfo);
|
|
}
|
|
|
|
static void
|
|
hdaa_widget_postprocess(struct hdaa_widget *w)
|
|
{
|
|
const char *typestr;
|
|
|
|
w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(w->param.widget_cap);
|
|
switch (w->type) {
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT:
|
|
typestr = "audio output";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT:
|
|
typestr = "audio input";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER:
|
|
typestr = "audio mixer";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR:
|
|
typestr = "audio selector";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX:
|
|
typestr = "pin";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET:
|
|
typestr = "power widget";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET:
|
|
typestr = "volume widget";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET:
|
|
typestr = "beep widget";
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VENDOR_WIDGET:
|
|
typestr = "vendor widget";
|
|
break;
|
|
default:
|
|
typestr = "unknown type";
|
|
break;
|
|
}
|
|
strlcpy(w->name, typestr, sizeof(w->name));
|
|
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) {
|
|
uint32_t config;
|
|
const char *devstr;
|
|
int conn, color;
|
|
|
|
config = w->wclass.pin.config;
|
|
devstr = HDA_DEVS[(config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) >>
|
|
HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT];
|
|
conn = (config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) >>
|
|
HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT;
|
|
color = (config & HDA_CONFIG_DEFAULTCONF_COLOR_MASK) >>
|
|
HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT;
|
|
strlcat(w->name, ": ", sizeof(w->name));
|
|
strlcat(w->name, devstr, sizeof(w->name));
|
|
strlcat(w->name, " (", sizeof(w->name));
|
|
if (conn == 0 && color != 0 && color != 15) {
|
|
strlcat(w->name, HDA_COLORS[color], sizeof(w->name));
|
|
strlcat(w->name, " ", sizeof(w->name));
|
|
}
|
|
strlcat(w->name, HDA_CONNS[conn], sizeof(w->name));
|
|
strlcat(w->name, ")", sizeof(w->name));
|
|
}
|
|
}
|
|
|
|
struct hdaa_widget *
|
|
hdaa_widget_get(struct hdaa_devinfo *devinfo, nid_t nid)
|
|
{
|
|
if (devinfo == NULL || devinfo->widget == NULL ||
|
|
nid < devinfo->startnode || nid >= devinfo->endnode)
|
|
return (NULL);
|
|
return (&devinfo->widget[nid - devinfo->startnode]);
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *devinfo, nid_t nid,
|
|
int index, int lmute, int rmute,
|
|
int left, int right, int dir)
|
|
{
|
|
uint16_t v = 0;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Setting amplifier nid=%d index=%d %s mute=%d/%d vol=%d/%d\n",
|
|
nid,index,dir ? "in" : "out",lmute,rmute,left,right);
|
|
);
|
|
if (left != right || lmute != rmute) {
|
|
v = (1 << (15 - dir)) | (1 << 13) | (index << 8) |
|
|
(lmute << 7) | left;
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v));
|
|
v = (1 << (15 - dir)) | (1 << 12) | (index << 8) |
|
|
(rmute << 7) | right;
|
|
} else
|
|
v = (1 << (15 - dir)) | (3 << 12) | (index << 8) |
|
|
(lmute << 7) | left;
|
|
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v));
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *ctl, uint32_t mute,
|
|
int left, int right)
|
|
{
|
|
nid_t nid;
|
|
int lmute, rmute;
|
|
|
|
nid = ctl->widget->nid;
|
|
|
|
/* Save new values if valid. */
|
|
if (mute != HDAA_AMP_MUTE_DEFAULT)
|
|
ctl->muted = mute;
|
|
if (left != HDAA_AMP_VOL_DEFAULT)
|
|
ctl->left = left;
|
|
if (right != HDAA_AMP_VOL_DEFAULT)
|
|
ctl->right = right;
|
|
/* Prepare effective values */
|
|
if (ctl->forcemute) {
|
|
lmute = 1;
|
|
rmute = 1;
|
|
left = 0;
|
|
right = 0;
|
|
} else {
|
|
lmute = HDAA_AMP_LEFT_MUTED(ctl->muted);
|
|
rmute = HDAA_AMP_RIGHT_MUTED(ctl->muted);
|
|
left = ctl->left;
|
|
right = ctl->right;
|
|
}
|
|
/* Apply effective values */
|
|
if (ctl->dir & HDAA_CTL_OUT)
|
|
hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index,
|
|
lmute, rmute, left, right, 0);
|
|
if (ctl->dir & HDAA_CTL_IN)
|
|
hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index,
|
|
lmute, rmute, left, right, 1);
|
|
}
|
|
|
|
static void
|
|
hdaa_widget_connection_select(struct hdaa_widget *w, uint8_t index)
|
|
{
|
|
if (w == NULL || w->nconns < 1 || index > (w->nconns - 1))
|
|
return;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(w->devinfo->dev,
|
|
"Setting selector nid=%d index=%d\n", w->nid, index);
|
|
);
|
|
hda_command(w->devinfo->dev,
|
|
HDA_CMD_SET_CONNECTION_SELECT_CONTROL(0, w->nid, index));
|
|
w->selconn = index;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Device Methods
|
|
****************************************************************************/
|
|
|
|
static void *
|
|
hdaa_channel_init(kobj_t obj, void *data, struct snd_dbuf *b,
|
|
struct pcm_channel *c, int dir)
|
|
{
|
|
struct hdaa_chan *ch = data;
|
|
struct hdaa_pcm_devinfo *pdevinfo = ch->pdevinfo;
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
|
|
hdaa_lock(devinfo);
|
|
if (devinfo->quirks & HDAA_QUIRK_FIXEDRATE) {
|
|
ch->caps.minspeed = ch->caps.maxspeed = 48000;
|
|
ch->pcmrates[0] = 48000;
|
|
ch->pcmrates[1] = 0;
|
|
}
|
|
ch->dir = dir;
|
|
ch->b = b;
|
|
ch->c = c;
|
|
ch->blksz = pdevinfo->chan_size / pdevinfo->chan_blkcnt;
|
|
ch->blkcnt = pdevinfo->chan_blkcnt;
|
|
hdaa_unlock(devinfo);
|
|
|
|
if (sndbuf_alloc(ch->b, bus_get_dma_tag(devinfo->dev),
|
|
hda_get_dma_nocache(devinfo->dev) ? BUS_DMA_NOCACHE :
|
|
BUS_DMA_COHERENT,
|
|
pdevinfo->chan_size) != 0)
|
|
return (NULL);
|
|
|
|
return (ch);
|
|
}
|
|
|
|
static int
|
|
hdaa_channel_setformat(kobj_t obj, void *data, uint32_t format)
|
|
{
|
|
struct hdaa_chan *ch = data;
|
|
int i;
|
|
|
|
for (i = 0; ch->caps.fmtlist[i] != 0; i++) {
|
|
if (format == ch->caps.fmtlist[i]) {
|
|
ch->fmt = format;
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
return (EINVAL);
|
|
}
|
|
|
|
static uint32_t
|
|
hdaa_channel_setspeed(kobj_t obj, void *data, uint32_t speed)
|
|
{
|
|
struct hdaa_chan *ch = data;
|
|
uint32_t spd = 0, threshold;
|
|
int i;
|
|
|
|
/* First look for equal or multiple frequency. */
|
|
for (i = 0; ch->pcmrates[i] != 0; i++) {
|
|
spd = ch->pcmrates[i];
|
|
if (speed != 0 && spd / speed * speed == spd) {
|
|
ch->spd = spd;
|
|
return (spd);
|
|
}
|
|
}
|
|
/* If no match, just find nearest. */
|
|
for (i = 0; ch->pcmrates[i] != 0; i++) {
|
|
spd = ch->pcmrates[i];
|
|
threshold = spd + ((ch->pcmrates[i + 1] != 0) ?
|
|
((ch->pcmrates[i + 1] - spd) >> 1) : 0);
|
|
if (speed < threshold)
|
|
break;
|
|
}
|
|
ch->spd = spd;
|
|
return (spd);
|
|
}
|
|
|
|
static uint16_t
|
|
hdaa_stream_format(struct hdaa_chan *ch)
|
|
{
|
|
int i;
|
|
uint16_t fmt;
|
|
|
|
fmt = 0;
|
|
if (ch->fmt & AFMT_S16_LE)
|
|
fmt |= ch->bit16 << 4;
|
|
else if (ch->fmt & AFMT_S32_LE)
|
|
fmt |= ch->bit32 << 4;
|
|
else
|
|
fmt |= 1 << 4;
|
|
for (i = 0; i < HDA_RATE_TAB_LEN; i++) {
|
|
if (hda_rate_tab[i].valid && ch->spd == hda_rate_tab[i].rate) {
|
|
fmt |= hda_rate_tab[i].base;
|
|
fmt |= hda_rate_tab[i].mul;
|
|
fmt |= hda_rate_tab[i].div;
|
|
break;
|
|
}
|
|
}
|
|
fmt |= (AFMT_CHANNEL(ch->fmt) - 1);
|
|
|
|
return (fmt);
|
|
}
|
|
|
|
static int
|
|
hdaa_allowed_stripes(uint16_t fmt)
|
|
{
|
|
static const int bits[8] = { 8, 16, 20, 24, 32, 32, 32, 32 };
|
|
int size;
|
|
|
|
size = bits[(fmt >> 4) & 0x03];
|
|
size *= (fmt & 0x0f) + 1;
|
|
size *= ((fmt >> 11) & 0x07) + 1;
|
|
return (0xffffffffU >> (32 - fls(size / 8)));
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_setup(struct hdaa_chan *ch)
|
|
{
|
|
struct hdaa_audio_as *as = &ch->devinfo->as[ch->as];
|
|
struct hdaa_widget *w, *wp;
|
|
int i, j, k, chn, cchn, totalchn, totalextchn, c;
|
|
uint16_t fmt, dfmt;
|
|
/* Mapping channel pairs to codec pins/converters. */
|
|
const static uint16_t convmap[2][5] =
|
|
/* 1.0 2.0 4.0 5.1 7.1 */
|
|
{{ 0x0010, 0x0001, 0x0201, 0x0231, 0x4231 }, /* no dup. */
|
|
{ 0x0010, 0x0001, 0x2201, 0x2231, 0x4231 }}; /* side dup. */
|
|
/* Mapping formats to HDMI channel allocations. */
|
|
const static uint8_t hdmica[2][8] =
|
|
/* 1 2 3 4 5 6 7 8 */
|
|
{{ 0x02, 0x00, 0x04, 0x08, 0x0a, 0x0e, 0x12, 0x12 }, /* x.0 */
|
|
{ 0x01, 0x03, 0x01, 0x03, 0x09, 0x0b, 0x0f, 0x13 }}; /* x.1 */
|
|
/* Mapping formats to HDMI channels order. */
|
|
const static uint32_t hdmich[2][8] =
|
|
/* 1 / 5 2 / 6 3 / 7 4 / 8 */
|
|
{{ 0xFFFF0F00, 0xFFFFFF10, 0xFFF2FF10, 0xFF32FF10,
|
|
0xFF324F10, 0xF5324F10, 0x54326F10, 0x54326F10 }, /* x.0 */
|
|
{ 0xFFFFF000, 0xFFFF0100, 0xFFFFF210, 0xFFFF2310,
|
|
0xFF32F410, 0xFF324510, 0xF6324510, 0x76325410 }}; /* x.1 */
|
|
int convmapid = -1;
|
|
nid_t nid;
|
|
uint8_t csum;
|
|
|
|
totalchn = AFMT_CHANNEL(ch->fmt);
|
|
totalextchn = AFMT_EXTCHANNEL(ch->fmt);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(ch->pdevinfo->dev,
|
|
"PCMDIR_%s: Stream setup fmt=%08x (%d.%d) speed=%d\n",
|
|
(ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC",
|
|
ch->fmt, totalchn - totalextchn, totalextchn, ch->spd);
|
|
);
|
|
fmt = hdaa_stream_format(ch);
|
|
|
|
/* Set channels to I/O converters mapping for known speaker setups. */
|
|
if ((as->pinset == 0x0007 || as->pinset == 0x0013) || /* Standard 5.1 */
|
|
(as->pinset == 0x0017)) /* Standard 7.1 */
|
|
convmapid = (ch->dir == PCMDIR_PLAY);
|
|
|
|
dfmt = HDA_CMD_SET_DIGITAL_CONV_FMT1_DIGEN;
|
|
if (ch->fmt & AFMT_AC3)
|
|
dfmt |= HDA_CMD_SET_DIGITAL_CONV_FMT1_NAUDIO;
|
|
|
|
chn = 0;
|
|
for (i = 0; ch->io[i] != -1; i++) {
|
|
w = hdaa_widget_get(ch->devinfo, ch->io[i]);
|
|
if (w == NULL)
|
|
continue;
|
|
|
|
/* If HP redirection is enabled, but failed to use same
|
|
DAC, make last DAC to duplicate first one. */
|
|
if (as->fakeredir && i == (as->pincnt - 1)) {
|
|
c = (ch->sid << 4);
|
|
} else {
|
|
/* Map channels to I/O converters, if set. */
|
|
if (convmapid >= 0)
|
|
chn = (((convmap[convmapid][totalchn / 2]
|
|
>> i * 4) & 0xf) - 1) * 2;
|
|
if (chn < 0 || chn >= totalchn) {
|
|
c = 0;
|
|
} else {
|
|
c = (ch->sid << 4) | chn;
|
|
}
|
|
}
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_CONV_FMT(0, ch->io[i], fmt));
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) {
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], dfmt));
|
|
}
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], c));
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) {
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_STRIPE_CONTROL(0, w->nid, ch->stripectl));
|
|
}
|
|
cchn = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap);
|
|
if (cchn > 1 && chn < totalchn) {
|
|
cchn = min(cchn, totalchn - chn - 1);
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_CONV_CHAN_COUNT(0, ch->io[i], cchn));
|
|
}
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(ch->pdevinfo->dev,
|
|
"PCMDIR_%s: Stream setup nid=%d: "
|
|
"fmt=0x%04x, dfmt=0x%04x, chan=0x%04x, "
|
|
"chan_count=0x%02x, stripe=%d\n",
|
|
(ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC",
|
|
ch->io[i], fmt, dfmt, c, cchn, ch->stripectl);
|
|
);
|
|
for (j = 0; j < 16; j++) {
|
|
if (as->dacs[ch->asindex][j] != ch->io[i])
|
|
continue;
|
|
nid = as->pins[j];
|
|
wp = hdaa_widget_get(ch->devinfo, nid);
|
|
if (wp == NULL)
|
|
continue;
|
|
if (!HDA_PARAM_PIN_CAP_DP(wp->wclass.pin.cap) &&
|
|
!HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap))
|
|
continue;
|
|
|
|
/* Set channel mapping. */
|
|
for (k = 0; k < 8; k++) {
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_CHAN_SLOT(0, nid,
|
|
(((hdmich[totalextchn == 0 ? 0 : 1][totalchn - 1]
|
|
>> (k * 4)) & 0xf) << 4) | k));
|
|
}
|
|
|
|
/*
|
|
* Enable High Bit Rate (HBR) Encoded Packet Type
|
|
* (EPT), if supported and needed (8ch data).
|
|
*/
|
|
if (HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap) &&
|
|
HDA_PARAM_PIN_CAP_HBR(wp->wclass.pin.cap)) {
|
|
wp->wclass.pin.ctrl &=
|
|
~HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK;
|
|
if ((ch->fmt & AFMT_AC3) && (cchn == 7))
|
|
wp->wclass.pin.ctrl |= 0x03;
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL(0, nid,
|
|
wp->wclass.pin.ctrl));
|
|
}
|
|
|
|
/* Stop audio infoframe transmission. */
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0x00));
|
|
|
|
/* Clear audio infoframe buffer. */
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00));
|
|
for (k = 0; k < 32; k++)
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00));
|
|
|
|
/* Write HDMI/DisplayPort audio infoframe. */
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00));
|
|
if (w->eld != NULL && w->eld_len >= 6 &&
|
|
((w->eld[5] >> 2) & 0x3) == 1) { /* DisplayPort */
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x1b));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x44));
|
|
} else { /* HDMI */
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x01));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x0a));
|
|
csum = 0;
|
|
csum -= 0x84 + 0x01 + 0x0a + (totalchn - 1) +
|
|
hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1];
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, csum));
|
|
}
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, totalchn - 1));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_DATA(0, nid,
|
|
hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1]));
|
|
|
|
/* Start audio infoframe transmission. */
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00));
|
|
hda_command(ch->devinfo->dev,
|
|
HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0xc0));
|
|
}
|
|
chn += cchn + 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Greatest Common Divisor.
|
|
*/
|
|
static unsigned
|
|
gcd(unsigned a, unsigned b)
|
|
{
|
|
u_int c;
|
|
|
|
while (b != 0) {
|
|
c = a;
|
|
a = b;
|
|
b = (c % b);
|
|
}
|
|
return (a);
|
|
}
|
|
|
|
/*
|
|
* Least Common Multiple.
|
|
*/
|
|
static unsigned
|
|
lcm(unsigned a, unsigned b)
|
|
{
|
|
|
|
return ((a * b) / gcd(a, b));
|
|
}
|
|
|
|
static int
|
|
hdaa_channel_setfragments(kobj_t obj, void *data,
|
|
uint32_t blksz, uint32_t blkcnt)
|
|
{
|
|
struct hdaa_chan *ch = data;
|
|
|
|
blksz -= blksz % lcm(HDA_DMA_ALIGNMENT, sndbuf_getalign(ch->b));
|
|
|
|
if (blksz > (sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN))
|
|
blksz = sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN;
|
|
if (blksz < HDA_BLK_MIN)
|
|
blksz = HDA_BLK_MIN;
|
|
if (blkcnt > HDA_BDL_MAX)
|
|
blkcnt = HDA_BDL_MAX;
|
|
if (blkcnt < HDA_BDL_MIN)
|
|
blkcnt = HDA_BDL_MIN;
|
|
|
|
while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->b)) {
|
|
if ((blkcnt >> 1) >= HDA_BDL_MIN)
|
|
blkcnt >>= 1;
|
|
else if ((blksz >> 1) >= HDA_BLK_MIN)
|
|
blksz >>= 1;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if ((sndbuf_getblksz(ch->b) != blksz ||
|
|
sndbuf_getblkcnt(ch->b) != blkcnt) &&
|
|
sndbuf_resize(ch->b, blkcnt, blksz) != 0)
|
|
device_printf(ch->devinfo->dev, "%s: failed blksz=%u blkcnt=%u\n",
|
|
__func__, blksz, blkcnt);
|
|
|
|
ch->blksz = sndbuf_getblksz(ch->b);
|
|
ch->blkcnt = sndbuf_getblkcnt(ch->b);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static uint32_t
|
|
hdaa_channel_setblocksize(kobj_t obj, void *data, uint32_t blksz)
|
|
{
|
|
struct hdaa_chan *ch = data;
|
|
|
|
hdaa_channel_setfragments(obj, data, blksz, ch->pdevinfo->chan_blkcnt);
|
|
|
|
return (ch->blksz);
|
|
}
|
|
|
|
static void
|
|
hdaa_channel_stop(struct hdaa_chan *ch)
|
|
{
|
|
struct hdaa_devinfo *devinfo = ch->devinfo;
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
if ((ch->flags & HDAA_CHN_RUNNING) == 0)
|
|
return;
|
|
ch->flags &= ~HDAA_CHN_RUNNING;
|
|
HDAC_STREAM_STOP(device_get_parent(devinfo->dev), devinfo->dev,
|
|
ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid);
|
|
for (i = 0; ch->io[i] != -1; i++) {
|
|
w = hdaa_widget_get(ch->devinfo, ch->io[i]);
|
|
if (w == NULL)
|
|
continue;
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) {
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], 0));
|
|
}
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i],
|
|
0));
|
|
}
|
|
HDAC_STREAM_FREE(device_get_parent(devinfo->dev), devinfo->dev,
|
|
ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid);
|
|
}
|
|
|
|
static int
|
|
hdaa_channel_start(struct hdaa_chan *ch)
|
|
{
|
|
struct hdaa_devinfo *devinfo = ch->devinfo;
|
|
uint32_t fmt;
|
|
|
|
fmt = hdaa_stream_format(ch);
|
|
ch->stripectl = fls(ch->stripecap & hdaa_allowed_stripes(fmt) &
|
|
hda_get_stripes_mask(devinfo->dev)) - 1;
|
|
ch->sid = HDAC_STREAM_ALLOC(device_get_parent(devinfo->dev), devinfo->dev,
|
|
ch->dir == PCMDIR_PLAY ? 1 : 0, fmt, ch->stripectl, &ch->dmapos);
|
|
if (ch->sid <= 0)
|
|
return (EBUSY);
|
|
hdaa_audio_setup(ch);
|
|
HDAC_STREAM_RESET(device_get_parent(devinfo->dev), devinfo->dev,
|
|
ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid);
|
|
HDAC_STREAM_START(device_get_parent(devinfo->dev), devinfo->dev,
|
|
ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid,
|
|
sndbuf_getbufaddr(ch->b), ch->blksz, ch->blkcnt);
|
|
ch->flags |= HDAA_CHN_RUNNING;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_channel_trigger(kobj_t obj, void *data, int go)
|
|
{
|
|
struct hdaa_chan *ch = data;
|
|
int error = 0;
|
|
|
|
if (!PCMTRIG_COMMON(go))
|
|
return (0);
|
|
|
|
hdaa_lock(ch->devinfo);
|
|
switch (go) {
|
|
case PCMTRIG_START:
|
|
error = hdaa_channel_start(ch);
|
|
break;
|
|
case PCMTRIG_STOP:
|
|
case PCMTRIG_ABORT:
|
|
hdaa_channel_stop(ch);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
hdaa_unlock(ch->devinfo);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static uint32_t
|
|
hdaa_channel_getptr(kobj_t obj, void *data)
|
|
{
|
|
struct hdaa_chan *ch = data;
|
|
struct hdaa_devinfo *devinfo = ch->devinfo;
|
|
uint32_t ptr;
|
|
|
|
hdaa_lock(devinfo);
|
|
if (ch->dmapos != NULL) {
|
|
ptr = *(ch->dmapos);
|
|
} else {
|
|
ptr = HDAC_STREAM_GETPTR(
|
|
device_get_parent(devinfo->dev), devinfo->dev,
|
|
ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid);
|
|
}
|
|
hdaa_unlock(devinfo);
|
|
|
|
/*
|
|
* Round to available space and force 128 bytes aligment.
|
|
*/
|
|
ptr %= ch->blksz * ch->blkcnt;
|
|
ptr &= HDA_BLK_ALIGN;
|
|
|
|
return (ptr);
|
|
}
|
|
|
|
static struct pcmchan_caps *
|
|
hdaa_channel_getcaps(kobj_t obj, void *data)
|
|
{
|
|
return (&((struct hdaa_chan *)data)->caps);
|
|
}
|
|
|
|
static kobj_method_t hdaa_channel_methods[] = {
|
|
KOBJMETHOD(channel_init, hdaa_channel_init),
|
|
KOBJMETHOD(channel_setformat, hdaa_channel_setformat),
|
|
KOBJMETHOD(channel_setspeed, hdaa_channel_setspeed),
|
|
KOBJMETHOD(channel_setblocksize, hdaa_channel_setblocksize),
|
|
KOBJMETHOD(channel_setfragments, hdaa_channel_setfragments),
|
|
KOBJMETHOD(channel_trigger, hdaa_channel_trigger),
|
|
KOBJMETHOD(channel_getptr, hdaa_channel_getptr),
|
|
KOBJMETHOD(channel_getcaps, hdaa_channel_getcaps),
|
|
KOBJMETHOD_END
|
|
};
|
|
CHANNEL_DECLARE(hdaa_channel);
|
|
|
|
static int
|
|
hdaa_audio_ctl_ossmixer_init(struct snd_mixer *m)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m);
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w, *cw;
|
|
uint32_t mask, recmask;
|
|
int i, j;
|
|
|
|
hdaa_lock(devinfo);
|
|
pdevinfo->mixer = m;
|
|
|
|
/* Make sure that in case of soft volume it won't stay muted. */
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
|
|
pdevinfo->left[i] = 100;
|
|
pdevinfo->right[i] = 100;
|
|
}
|
|
|
|
/* Declare volume controls assigned to this association. */
|
|
mask = pdevinfo->ossmask;
|
|
if (pdevinfo->playas >= 0) {
|
|
/* Declate EAPD as ogain control. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX ||
|
|
w->param.eapdbtl == HDA_INVALID ||
|
|
w->bindas != pdevinfo->playas)
|
|
continue;
|
|
mask |= SOUND_MASK_OGAIN;
|
|
break;
|
|
}
|
|
|
|
/* Declare soft PCM volume if needed. */
|
|
if ((mask & SOUND_MASK_PCM) == 0 ||
|
|
(devinfo->quirks & HDAA_QUIRK_SOFTPCMVOL) ||
|
|
pdevinfo->minamp[SOUND_MIXER_PCM] ==
|
|
pdevinfo->maxamp[SOUND_MIXER_PCM]) {
|
|
mask |= SOUND_MASK_PCM;
|
|
pcm_setflags(pdevinfo->dev, pcm_getflags(pdevinfo->dev) | SD_F_SOFTPCMVOL);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(pdevinfo->dev,
|
|
"Forcing Soft PCM volume\n");
|
|
);
|
|
}
|
|
|
|
/* Declare master volume if needed. */
|
|
if ((mask & SOUND_MASK_VOLUME) == 0) {
|
|
mask |= SOUND_MASK_VOLUME;
|
|
mix_setparentchild(m, SOUND_MIXER_VOLUME,
|
|
SOUND_MASK_PCM);
|
|
mix_setrealdev(m, SOUND_MIXER_VOLUME,
|
|
SOUND_MIXER_NONE);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(pdevinfo->dev,
|
|
"Forcing master volume with PCM\n");
|
|
);
|
|
}
|
|
}
|
|
|
|
/* Declare record sources available to this association. */
|
|
recmask = 0;
|
|
if (pdevinfo->recas >= 0) {
|
|
for (i = 0; i < 16; i++) {
|
|
if (devinfo->as[pdevinfo->recas].dacs[0][i] < 0)
|
|
continue;
|
|
w = hdaa_widget_get(devinfo,
|
|
devinfo->as[pdevinfo->recas].dacs[0][i]);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (w->connsenable[j] == 0)
|
|
continue;
|
|
cw = hdaa_widget_get(devinfo, w->conns[j]);
|
|
if (cw == NULL || cw->enable == 0)
|
|
continue;
|
|
if (cw->bindas != pdevinfo->recas &&
|
|
cw->bindas != -2)
|
|
continue;
|
|
recmask |= cw->ossmask;
|
|
}
|
|
}
|
|
}
|
|
|
|
recmask &= (1 << SOUND_MIXER_NRDEVICES) - 1;
|
|
mask &= (1 << SOUND_MIXER_NRDEVICES) - 1;
|
|
pdevinfo->ossmask = mask;
|
|
|
|
mix_setrecdevs(m, recmask);
|
|
mix_setdevs(m, mask);
|
|
|
|
hdaa_unlock(devinfo);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Update amplification per pdevinfo per ossdev, calculate summary coefficient
|
|
* and write it to codec, update *left and *right to reflect remaining error.
|
|
*/
|
|
static void
|
|
hdaa_audio_ctl_dev_set(struct hdaa_audio_ctl *ctl, int ossdev,
|
|
int mute, int *left, int *right)
|
|
{
|
|
int i, zleft, zright, sleft, sright, smute, lval, rval;
|
|
|
|
ctl->devleft[ossdev] = *left;
|
|
ctl->devright[ossdev] = *right;
|
|
ctl->devmute[ossdev] = mute;
|
|
smute = sleft = sright = zleft = zright = 0;
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
|
|
sleft += ctl->devleft[i];
|
|
sright += ctl->devright[i];
|
|
smute |= ctl->devmute[i];
|
|
if (i == ossdev)
|
|
continue;
|
|
zleft += ctl->devleft[i];
|
|
zright += ctl->devright[i];
|
|
}
|
|
lval = QDB2VAL(ctl, sleft);
|
|
rval = QDB2VAL(ctl, sright);
|
|
hdaa_audio_ctl_amp_set(ctl, smute, lval, rval);
|
|
*left -= VAL2QDB(ctl, lval) - VAL2QDB(ctl, QDB2VAL(ctl, zleft));
|
|
*right -= VAL2QDB(ctl, rval) - VAL2QDB(ctl, QDB2VAL(ctl, zright));
|
|
}
|
|
|
|
/*
|
|
* Trace signal from source, setting volumes on the way.
|
|
*/
|
|
static void
|
|
hdaa_audio_ctl_source_volume(struct hdaa_pcm_devinfo *pdevinfo,
|
|
int ossdev, nid_t nid, int index, int mute, int left, int right, int depth)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w, *wc;
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, j, conns = 0;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return;
|
|
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return;
|
|
|
|
/* Count number of active inputs. */
|
|
if (depth > 0) {
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (!w->connsenable[j])
|
|
continue;
|
|
conns++;
|
|
}
|
|
}
|
|
|
|
/* If this is not a first step - use input mixer.
|
|
Pins have common input ctl so care must be taken. */
|
|
if (depth > 0 && (conns == 1 ||
|
|
w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) {
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN,
|
|
index, 1);
|
|
if (ctl)
|
|
hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right);
|
|
}
|
|
|
|
/* If widget has own ossdev - not traverse it.
|
|
It will be traversed on its own. */
|
|
if (w->ossdev >= 0 && depth > 0)
|
|
return;
|
|
|
|
/* We must not traverse pin */
|
|
if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) &&
|
|
depth > 0)
|
|
return;
|
|
|
|
/*
|
|
* If signals mixed, we can't assign controls farther.
|
|
* Ignore this on depth zero. Caller must knows why.
|
|
*/
|
|
if (conns > 1 &&
|
|
(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER ||
|
|
w->selconn != index))
|
|
return;
|
|
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1);
|
|
if (ctl)
|
|
hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right);
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
wc = hdaa_widget_get(devinfo, i);
|
|
if (wc == NULL || wc->enable == 0)
|
|
continue;
|
|
for (j = 0; j < wc->nconns; j++) {
|
|
if (wc->connsenable[j] && wc->conns[j] == nid) {
|
|
hdaa_audio_ctl_source_volume(pdevinfo, ossdev,
|
|
wc->nid, j, mute, left, right, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Trace signal from destination, setting volumes on the way.
|
|
*/
|
|
static void
|
|
hdaa_audio_ctl_dest_volume(struct hdaa_pcm_devinfo *pdevinfo,
|
|
int ossdev, nid_t nid, int index, int mute, int left, int right, int depth)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w, *wc;
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, j, consumers, cleft, cright;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return;
|
|
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return;
|
|
|
|
if (depth > 0) {
|
|
/* If this node produce output for several consumers,
|
|
we can't touch it. */
|
|
consumers = 0;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
wc = hdaa_widget_get(devinfo, i);
|
|
if (wc == NULL || wc->enable == 0)
|
|
continue;
|
|
for (j = 0; j < wc->nconns; j++) {
|
|
if (wc->connsenable[j] && wc->conns[j] == nid)
|
|
consumers++;
|
|
}
|
|
}
|
|
/* The only exception is if real HP redirection is configured
|
|
and this is a duplication point.
|
|
XXX: Actually exception is not completely correct.
|
|
XXX: Duplication point check is not perfect. */
|
|
if ((consumers == 2 && (w->bindas < 0 ||
|
|
as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir ||
|
|
(w->bindseqmask & (1 << 15)) == 0)) ||
|
|
consumers > 2)
|
|
return;
|
|
|
|
/* Else use it's output mixer. */
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid,
|
|
HDAA_CTL_OUT, -1, 1);
|
|
if (ctl)
|
|
hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right);
|
|
}
|
|
|
|
/* We must not traverse pin */
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX &&
|
|
depth > 0)
|
|
return;
|
|
|
|
for (i = 0; i < w->nconns; i++) {
|
|
if (w->connsenable[i] == 0)
|
|
continue;
|
|
if (index >= 0 && i != index)
|
|
continue;
|
|
cleft = left;
|
|
cright = right;
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid,
|
|
HDAA_CTL_IN, i, 1);
|
|
if (ctl)
|
|
hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &cleft, &cright);
|
|
hdaa_audio_ctl_dest_volume(pdevinfo, ossdev, w->conns[i], -1,
|
|
mute, cleft, cright, depth + 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set volumes for the specified pdevinfo and ossdev.
|
|
*/
|
|
static void
|
|
hdaa_audio_ctl_dev_volume(struct hdaa_pcm_devinfo *pdevinfo, unsigned dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w, *cw;
|
|
uint32_t mute;
|
|
int lvol, rvol;
|
|
int i, j;
|
|
|
|
mute = 0;
|
|
if (pdevinfo->left[dev] == 0) {
|
|
mute |= HDAA_AMP_MUTE_LEFT;
|
|
lvol = -4000;
|
|
} else
|
|
lvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) *
|
|
pdevinfo->left[dev] + 50) / 100 + pdevinfo->minamp[dev];
|
|
if (pdevinfo->right[dev] == 0) {
|
|
mute |= HDAA_AMP_MUTE_RIGHT;
|
|
rvol = -4000;
|
|
} else
|
|
rvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) *
|
|
pdevinfo->right[dev] + 50) / 100 + pdevinfo->minamp[dev];
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->bindas < 0) {
|
|
if (pdevinfo->index != 0)
|
|
continue;
|
|
} else {
|
|
if (w->bindas != pdevinfo->playas &&
|
|
w->bindas != pdevinfo->recas)
|
|
continue;
|
|
}
|
|
if (dev == SOUND_MIXER_RECLEV &&
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) {
|
|
hdaa_audio_ctl_dest_volume(pdevinfo, dev,
|
|
w->nid, -1, mute, lvol, rvol, 0);
|
|
continue;
|
|
}
|
|
if (dev == SOUND_MIXER_VOLUME &&
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX &&
|
|
devinfo->as[w->bindas].dir == HDAA_CTL_OUT) {
|
|
hdaa_audio_ctl_dest_volume(pdevinfo, dev,
|
|
w->nid, -1, mute, lvol, rvol, 0);
|
|
continue;
|
|
}
|
|
if (dev == SOUND_MIXER_IGAIN &&
|
|
w->pflags & HDAA_ADC_MONITOR) {
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (!w->connsenable[j])
|
|
continue;
|
|
cw = hdaa_widget_get(devinfo, w->conns[j]);
|
|
if (cw == NULL || cw->enable == 0)
|
|
continue;
|
|
if (cw->bindas == -1)
|
|
continue;
|
|
if (cw->bindas >= 0 &&
|
|
devinfo->as[cw->bindas].dir != HDAA_CTL_IN)
|
|
continue;
|
|
hdaa_audio_ctl_dest_volume(pdevinfo, dev,
|
|
w->nid, j, mute, lvol, rvol, 0);
|
|
}
|
|
continue;
|
|
}
|
|
if (w->ossdev != dev)
|
|
continue;
|
|
hdaa_audio_ctl_source_volume(pdevinfo, dev,
|
|
w->nid, -1, mute, lvol, rvol, 0);
|
|
if (dev == SOUND_MIXER_IMIX && (w->pflags & HDAA_IMIX_AS_DST))
|
|
hdaa_audio_ctl_dest_volume(pdevinfo, dev,
|
|
w->nid, -1, mute, lvol, rvol, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* OSS Mixer set method.
|
|
*/
|
|
static int
|
|
hdaa_audio_ctl_ossmixer_set(struct snd_mixer *m, unsigned dev,
|
|
unsigned left, unsigned right)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m);
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
hdaa_lock(devinfo);
|
|
|
|
/* Save new values. */
|
|
pdevinfo->left[dev] = left;
|
|
pdevinfo->right[dev] = right;
|
|
|
|
/* 'ogain' is the special case implemented with EAPD. */
|
|
if (dev == SOUND_MIXER_OGAIN) {
|
|
uint32_t orig;
|
|
w = NULL;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX ||
|
|
w->param.eapdbtl == HDA_INVALID)
|
|
continue;
|
|
break;
|
|
}
|
|
if (i >= devinfo->endnode) {
|
|
hdaa_unlock(devinfo);
|
|
return (-1);
|
|
}
|
|
orig = w->param.eapdbtl;
|
|
if (left == 0)
|
|
w->param.eapdbtl &= ~HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD;
|
|
else
|
|
w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD;
|
|
if (orig != w->param.eapdbtl) {
|
|
uint32_t val;
|
|
|
|
val = w->param.eapdbtl;
|
|
if (devinfo->quirks & HDAA_QUIRK_EAPDINV)
|
|
val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD;
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid, val));
|
|
}
|
|
hdaa_unlock(devinfo);
|
|
return (left | (left << 8));
|
|
}
|
|
|
|
/* Recalculate all controls related to this OSS device. */
|
|
hdaa_audio_ctl_dev_volume(pdevinfo, dev);
|
|
|
|
hdaa_unlock(devinfo);
|
|
return (left | (right << 8));
|
|
}
|
|
|
|
/*
|
|
* Set mixer settings to our own default values:
|
|
* +20dB for mics, -10dB for analog vol, mute for igain, 0dB for others.
|
|
*/
|
|
static void
|
|
hdaa_audio_ctl_set_defaults(struct hdaa_pcm_devinfo *pdevinfo)
|
|
{
|
|
int amp, vol, dev;
|
|
|
|
for (dev = 0; dev < SOUND_MIXER_NRDEVICES; dev++) {
|
|
if ((pdevinfo->ossmask & (1 << dev)) == 0)
|
|
continue;
|
|
|
|
/* If the value was overriden, leave it as is. */
|
|
if (resource_int_value(device_get_name(pdevinfo->dev),
|
|
device_get_unit(pdevinfo->dev), ossnames[dev], &vol) == 0)
|
|
continue;
|
|
|
|
vol = -1;
|
|
if (dev == SOUND_MIXER_OGAIN)
|
|
vol = 100;
|
|
else if (dev == SOUND_MIXER_IGAIN)
|
|
vol = 0;
|
|
else if (dev == SOUND_MIXER_MIC ||
|
|
dev == SOUND_MIXER_MONITOR)
|
|
amp = 20 * 4; /* +20dB */
|
|
else if (dev == SOUND_MIXER_VOLUME && !pdevinfo->digital)
|
|
amp = -10 * 4; /* -10dB */
|
|
else
|
|
amp = 0;
|
|
if (vol < 0 &&
|
|
(pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) <= 0) {
|
|
vol = 100;
|
|
} else if (vol < 0) {
|
|
vol = ((amp - pdevinfo->minamp[dev]) * 100 +
|
|
(pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) / 2) /
|
|
(pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]);
|
|
vol = imin(imax(vol, 1), 100);
|
|
}
|
|
mix_set(pdevinfo->mixer, dev, vol, vol);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Recursively commutate specified record source.
|
|
*/
|
|
static uint32_t
|
|
hdaa_audio_ctl_recsel_comm(struct hdaa_pcm_devinfo *pdevinfo, uint32_t src, nid_t nid, int depth)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w, *cw;
|
|
struct hdaa_audio_ctl *ctl;
|
|
char buf[64];
|
|
int i, muted;
|
|
uint32_t res = 0;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return (0);
|
|
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return (0);
|
|
|
|
for (i = 0; i < w->nconns; i++) {
|
|
if (w->connsenable[i] == 0)
|
|
continue;
|
|
cw = hdaa_widget_get(devinfo, w->conns[i]);
|
|
if (cw == NULL || cw->enable == 0 || cw->bindas == -1)
|
|
continue;
|
|
/* Call recursively to trace signal to it's source if needed. */
|
|
if ((src & cw->ossmask) != 0) {
|
|
if (cw->ossdev < 0) {
|
|
res |= hdaa_audio_ctl_recsel_comm(pdevinfo, src,
|
|
w->conns[i], depth + 1);
|
|
} else {
|
|
res |= cw->ossmask;
|
|
}
|
|
}
|
|
/* We have two special cases: mixers and others (selectors). */
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) {
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo,
|
|
w->nid, HDAA_CTL_IN, i, 1);
|
|
if (ctl == NULL)
|
|
continue;
|
|
/* If we have input control on this node mute them
|
|
* according to requested sources. */
|
|
muted = (src & cw->ossmask) ? 0 : 1;
|
|
if (muted != ctl->forcemute) {
|
|
ctl->forcemute = muted;
|
|
hdaa_audio_ctl_amp_set(ctl,
|
|
HDAA_AMP_MUTE_DEFAULT,
|
|
HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT);
|
|
}
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(pdevinfo->dev,
|
|
"Recsel (%s): nid %d source %d %s\n",
|
|
hdaa_audio_ctl_ossmixer_mask2allname(
|
|
src, buf, sizeof(buf)),
|
|
nid, i, muted?"mute":"unmute");
|
|
);
|
|
} else {
|
|
if (w->nconns == 1)
|
|
break;
|
|
if ((src & cw->ossmask) == 0)
|
|
continue;
|
|
/* If we found requested source - select it and exit. */
|
|
hdaa_widget_connection_select(w, i);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(pdevinfo->dev,
|
|
"Recsel (%s): nid %d source %d select\n",
|
|
hdaa_audio_ctl_ossmixer_mask2allname(
|
|
src, buf, sizeof(buf)),
|
|
nid, i);
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
return (res);
|
|
}
|
|
|
|
static uint32_t
|
|
hdaa_audio_ctl_ossmixer_setrecsrc(struct snd_mixer *m, uint32_t src)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m);
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w;
|
|
struct hdaa_audio_as *as;
|
|
struct hdaa_audio_ctl *ctl;
|
|
struct hdaa_chan *ch;
|
|
int i, j;
|
|
uint32_t ret = 0xffffffff;
|
|
|
|
hdaa_lock(devinfo);
|
|
if (pdevinfo->recas < 0) {
|
|
hdaa_unlock(devinfo);
|
|
return (0);
|
|
}
|
|
as = &devinfo->as[pdevinfo->recas];
|
|
|
|
/* For non-mixed associations we always recording everything. */
|
|
if (!as->mixed) {
|
|
hdaa_unlock(devinfo);
|
|
return (mix_getrecdevs(m));
|
|
}
|
|
|
|
/* Commutate requested recsrc for each ADC. */
|
|
for (j = 0; j < as->num_chans; j++) {
|
|
ch = &devinfo->chans[as->chans[j]];
|
|
for (i = 0; ch->io[i] >= 0; i++) {
|
|
w = hdaa_widget_get(devinfo, ch->io[i]);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
ret &= hdaa_audio_ctl_recsel_comm(pdevinfo, src,
|
|
ch->io[i], 0);
|
|
}
|
|
}
|
|
if (ret == 0xffffffff)
|
|
ret = 0;
|
|
|
|
/*
|
|
* Some controls could be shared. Reset volumes for controls
|
|
* related to previously chosen devices, as they may no longer
|
|
* affect the signal.
|
|
*/
|
|
i = 0;
|
|
while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) {
|
|
if (ctl->enable == 0 ||
|
|
!(ctl->ossmask & pdevinfo->recsrc))
|
|
continue;
|
|
if (!((pdevinfo->playas >= 0 &&
|
|
ctl->widget->bindas == pdevinfo->playas) ||
|
|
(pdevinfo->recas >= 0 &&
|
|
ctl->widget->bindas == pdevinfo->recas) ||
|
|
(pdevinfo->index == 0 &&
|
|
ctl->widget->bindas == -2)))
|
|
continue;
|
|
for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) {
|
|
if (pdevinfo->recsrc & (1 << j)) {
|
|
ctl->devleft[j] = 0;
|
|
ctl->devright[j] = 0;
|
|
ctl->devmute[j] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Some controls could be shared. Set volumes for controls
|
|
* related to devices selected both previously and now.
|
|
*/
|
|
for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) {
|
|
if ((ret | pdevinfo->recsrc) & (1 << j))
|
|
hdaa_audio_ctl_dev_volume(pdevinfo, j);
|
|
}
|
|
|
|
pdevinfo->recsrc = ret;
|
|
hdaa_unlock(devinfo);
|
|
return (ret);
|
|
}
|
|
|
|
static kobj_method_t hdaa_audio_ctl_ossmixer_methods[] = {
|
|
KOBJMETHOD(mixer_init, hdaa_audio_ctl_ossmixer_init),
|
|
KOBJMETHOD(mixer_set, hdaa_audio_ctl_ossmixer_set),
|
|
KOBJMETHOD(mixer_setrecsrc, hdaa_audio_ctl_ossmixer_setrecsrc),
|
|
KOBJMETHOD_END
|
|
};
|
|
MIXER_DECLARE(hdaa_audio_ctl_ossmixer);
|
|
|
|
static void
|
|
hdaa_dump_gpi(struct hdaa_devinfo *devinfo)
|
|
{
|
|
device_t dev = devinfo->dev;
|
|
int i;
|
|
uint32_t data, wake, unsol, sticky;
|
|
|
|
if (HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap) > 0) {
|
|
data = hda_command(dev,
|
|
HDA_CMD_GET_GPI_DATA(0, devinfo->nid));
|
|
wake = hda_command(dev,
|
|
HDA_CMD_GET_GPI_WAKE_ENABLE_MASK(0, devinfo->nid));
|
|
unsol = hda_command(dev,
|
|
HDA_CMD_GET_GPI_UNSOLICITED_ENABLE_MASK(0, devinfo->nid));
|
|
sticky = hda_command(dev,
|
|
HDA_CMD_GET_GPI_STICKY_MASK(0, devinfo->nid));
|
|
for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap); i++) {
|
|
device_printf(dev, " GPI%d:%s%s%s state=%d", i,
|
|
(sticky & (1 << i)) ? " sticky" : "",
|
|
(unsol & (1 << i)) ? " unsol" : "",
|
|
(wake & (1 << i)) ? " wake" : "",
|
|
(data >> i) & 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_gpio(struct hdaa_devinfo *devinfo)
|
|
{
|
|
device_t dev = devinfo->dev;
|
|
int i;
|
|
uint32_t data, dir, enable, wake, unsol, sticky;
|
|
|
|
if (HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap) > 0) {
|
|
data = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_DATA(0, devinfo->nid));
|
|
enable = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid));
|
|
dir = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid));
|
|
wake = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_WAKE_ENABLE_MASK(0, devinfo->nid));
|
|
unsol = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_UNSOLICITED_ENABLE_MASK(0, devinfo->nid));
|
|
sticky = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_STICKY_MASK(0, devinfo->nid));
|
|
for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); i++) {
|
|
device_printf(dev, " GPIO%d: ", i);
|
|
if ((enable & (1 << i)) == 0) {
|
|
printf("disabled\n");
|
|
continue;
|
|
}
|
|
if ((dir & (1 << i)) == 0) {
|
|
printf("input%s%s%s",
|
|
(sticky & (1 << i)) ? " sticky" : "",
|
|
(unsol & (1 << i)) ? " unsol" : "",
|
|
(wake & (1 << i)) ? " wake" : "");
|
|
} else
|
|
printf("output");
|
|
printf(" state=%d\n", (data >> i) & 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_gpo(struct hdaa_devinfo *devinfo)
|
|
{
|
|
device_t dev = devinfo->dev;
|
|
int i;
|
|
uint32_t data;
|
|
|
|
if (HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap) > 0) {
|
|
data = hda_command(dev,
|
|
HDA_CMD_GET_GPO_DATA(0, devinfo->nid));
|
|
for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); i++) {
|
|
device_printf(dev, " GPO%d: state=%d", i,
|
|
(data >> i) & 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_parse(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w;
|
|
uint32_t res;
|
|
int i;
|
|
nid_t nid;
|
|
|
|
nid = devinfo->nid;
|
|
|
|
res = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_GPIO_COUNT));
|
|
devinfo->gpio_cap = res;
|
|
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"NumGPIO=%d NumGPO=%d "
|
|
"NumGPI=%d GPIWake=%d GPIUnsol=%d\n",
|
|
HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap));
|
|
hdaa_dump_gpi(devinfo);
|
|
hdaa_dump_gpio(devinfo);
|
|
hdaa_dump_gpo(devinfo);
|
|
);
|
|
|
|
res = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_STREAM_FORMATS));
|
|
devinfo->supp_stream_formats = res;
|
|
|
|
res = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE));
|
|
devinfo->supp_pcm_size_rate = res;
|
|
|
|
res = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_OUTPUT_AMP_CAP));
|
|
devinfo->outamp_cap = res;
|
|
|
|
res = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_INPUT_AMP_CAP));
|
|
devinfo->inamp_cap = res;
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL)
|
|
device_printf(devinfo->dev, "Ghost widget! nid=%d!\n", i);
|
|
else {
|
|
w->devinfo = devinfo;
|
|
w->nid = i;
|
|
w->enable = 1;
|
|
w->selconn = -1;
|
|
w->pflags = 0;
|
|
w->ossdev = -1;
|
|
w->bindas = -1;
|
|
w->param.eapdbtl = HDA_INVALID;
|
|
hdaa_widget_parse(w);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_postprocess(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL)
|
|
continue;
|
|
hdaa_widget_postprocess(w);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_ctl_parse(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_ctl *ctls;
|
|
struct hdaa_widget *w, *cw;
|
|
int i, j, cnt, max, ocap, icap;
|
|
int mute, offset, step, size;
|
|
|
|
/* XXX This is redundant */
|
|
max = 0;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->param.outamp_cap != 0)
|
|
max++;
|
|
if (w->param.inamp_cap != 0) {
|
|
switch (w->type) {
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR:
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER:
|
|
for (j = 0; j < w->nconns; j++) {
|
|
cw = hdaa_widget_get(devinfo,
|
|
w->conns[j]);
|
|
if (cw == NULL || cw->enable == 0)
|
|
continue;
|
|
max++;
|
|
}
|
|
break;
|
|
default:
|
|
max++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
devinfo->ctlcnt = max;
|
|
|
|
if (max < 1)
|
|
return;
|
|
|
|
ctls = (struct hdaa_audio_ctl *)malloc(
|
|
sizeof(*ctls) * max, M_HDAA, M_ZERO | M_NOWAIT);
|
|
|
|
if (ctls == NULL) {
|
|
/* Blekh! */
|
|
device_printf(devinfo->dev, "unable to allocate ctls!\n");
|
|
devinfo->ctlcnt = 0;
|
|
return;
|
|
}
|
|
|
|
cnt = 0;
|
|
for (i = devinfo->startnode; cnt < max && i < devinfo->endnode; i++) {
|
|
if (cnt >= max) {
|
|
device_printf(devinfo->dev, "%s: Ctl overflow!\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
ocap = w->param.outamp_cap;
|
|
icap = w->param.inamp_cap;
|
|
if (ocap != 0) {
|
|
mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(ocap);
|
|
step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(ocap);
|
|
size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(ocap);
|
|
offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(ocap);
|
|
/*if (offset > step) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"BUGGY outamp: nid=%d "
|
|
"[offset=%d > step=%d]\n",
|
|
w->nid, offset, step);
|
|
);
|
|
offset = step;
|
|
}*/
|
|
ctls[cnt].enable = 1;
|
|
ctls[cnt].widget = w;
|
|
ctls[cnt].mute = mute;
|
|
ctls[cnt].step = step;
|
|
ctls[cnt].size = size;
|
|
ctls[cnt].offset = offset;
|
|
ctls[cnt].left = offset;
|
|
ctls[cnt].right = offset;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX ||
|
|
w->waspin)
|
|
ctls[cnt].ndir = HDAA_CTL_IN;
|
|
else
|
|
ctls[cnt].ndir = HDAA_CTL_OUT;
|
|
ctls[cnt++].dir = HDAA_CTL_OUT;
|
|
}
|
|
|
|
if (icap != 0) {
|
|
mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(icap);
|
|
step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(icap);
|
|
size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(icap);
|
|
offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(icap);
|
|
/*if (offset > step) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"BUGGY inamp: nid=%d "
|
|
"[offset=%d > step=%d]\n",
|
|
w->nid, offset, step);
|
|
);
|
|
offset = step;
|
|
}*/
|
|
switch (w->type) {
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR:
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER:
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (cnt >= max) {
|
|
device_printf(devinfo->dev,
|
|
"%s: Ctl overflow!\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
cw = hdaa_widget_get(devinfo,
|
|
w->conns[j]);
|
|
if (cw == NULL || cw->enable == 0)
|
|
continue;
|
|
ctls[cnt].enable = 1;
|
|
ctls[cnt].widget = w;
|
|
ctls[cnt].childwidget = cw;
|
|
ctls[cnt].index = j;
|
|
ctls[cnt].mute = mute;
|
|
ctls[cnt].step = step;
|
|
ctls[cnt].size = size;
|
|
ctls[cnt].offset = offset;
|
|
ctls[cnt].left = offset;
|
|
ctls[cnt].right = offset;
|
|
ctls[cnt].ndir = HDAA_CTL_IN;
|
|
ctls[cnt++].dir = HDAA_CTL_IN;
|
|
}
|
|
break;
|
|
default:
|
|
if (cnt >= max) {
|
|
device_printf(devinfo->dev,
|
|
"%s: Ctl overflow!\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
ctls[cnt].enable = 1;
|
|
ctls[cnt].widget = w;
|
|
ctls[cnt].mute = mute;
|
|
ctls[cnt].step = step;
|
|
ctls[cnt].size = size;
|
|
ctls[cnt].offset = offset;
|
|
ctls[cnt].left = offset;
|
|
ctls[cnt].right = offset;
|
|
if (w->type ==
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
ctls[cnt].ndir = HDAA_CTL_OUT;
|
|
else
|
|
ctls[cnt].ndir = HDAA_CTL_IN;
|
|
ctls[cnt++].dir = HDAA_CTL_IN;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
devinfo->ctl = ctls;
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_as_parse(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as;
|
|
struct hdaa_widget *w;
|
|
int i, j, cnt, max, type, dir, assoc, seq, first, hpredir;
|
|
|
|
/* Count present associations */
|
|
max = 0;
|
|
for (j = 1; j < 16; j++) {
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config)
|
|
!= j)
|
|
continue;
|
|
max++;
|
|
if (j != 15) /* There could be many 1-pin assocs #15 */
|
|
break;
|
|
}
|
|
}
|
|
|
|
devinfo->ascnt = max;
|
|
|
|
if (max < 1)
|
|
return;
|
|
|
|
as = (struct hdaa_audio_as *)malloc(
|
|
sizeof(*as) * max, M_HDAA, M_ZERO | M_NOWAIT);
|
|
|
|
if (as == NULL) {
|
|
/* Blekh! */
|
|
device_printf(devinfo->dev, "unable to allocate assocs!\n");
|
|
devinfo->ascnt = 0;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < max; i++) {
|
|
as[i].hpredir = -1;
|
|
as[i].digital = 0;
|
|
as[i].num_chans = 1;
|
|
as[i].location = -1;
|
|
}
|
|
|
|
/* Scan associations skipping as=0. */
|
|
cnt = 0;
|
|
for (j = 1; j < 16 && cnt < max; j++) {
|
|
first = 16;
|
|
hpredir = 0;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
assoc = HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config);
|
|
seq = HDA_CONFIG_DEFAULTCONF_SEQUENCE(w->wclass.pin.config);
|
|
if (assoc != j) {
|
|
continue;
|
|
}
|
|
KASSERT(cnt < max,
|
|
("%s: Associations owerflow (%d of %d)",
|
|
__func__, cnt, max));
|
|
type = w->wclass.pin.config &
|
|
HDA_CONFIG_DEFAULTCONF_DEVICE_MASK;
|
|
/* Get pin direction. */
|
|
if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT ||
|
|
type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER ||
|
|
type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT ||
|
|
type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT ||
|
|
type == HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT)
|
|
dir = HDAA_CTL_OUT;
|
|
else
|
|
dir = HDAA_CTL_IN;
|
|
/* If this is a first pin - create new association. */
|
|
if (as[cnt].pincnt == 0) {
|
|
as[cnt].enable = 1;
|
|
as[cnt].index = j;
|
|
as[cnt].dir = dir;
|
|
}
|
|
if (seq < first)
|
|
first = seq;
|
|
/* Check association correctness. */
|
|
if (as[cnt].pins[seq] != 0) {
|
|
device_printf(devinfo->dev, "%s: Duplicate pin %d (%d) "
|
|
"in association %d! Disabling association.\n",
|
|
__func__, seq, w->nid, j);
|
|
as[cnt].enable = 0;
|
|
}
|
|
if (dir != as[cnt].dir) {
|
|
device_printf(devinfo->dev, "%s: Pin %d has wrong "
|
|
"direction for association %d! Disabling "
|
|
"association.\n",
|
|
__func__, w->nid, j);
|
|
as[cnt].enable = 0;
|
|
}
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) {
|
|
as[cnt].digital |= 0x1;
|
|
if (HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap))
|
|
as[cnt].digital |= 0x2;
|
|
if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap))
|
|
as[cnt].digital |= 0x4;
|
|
}
|
|
if (as[cnt].location == -1) {
|
|
as[cnt].location =
|
|
HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config);
|
|
} else if (as[cnt].location !=
|
|
HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config)) {
|
|
as[cnt].location = -2;
|
|
}
|
|
/* Headphones with seq=15 may mean redirection. */
|
|
if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT &&
|
|
seq == 15)
|
|
hpredir = 1;
|
|
as[cnt].pins[seq] = w->nid;
|
|
as[cnt].pincnt++;
|
|
/* Association 15 is a multiple unassociated pins. */
|
|
if (j == 15)
|
|
cnt++;
|
|
}
|
|
if (j != 15 && as[cnt].pincnt > 0) {
|
|
if (hpredir && as[cnt].pincnt > 1)
|
|
as[cnt].hpredir = first;
|
|
cnt++;
|
|
}
|
|
}
|
|
for (i = 0; i < max; i++) {
|
|
if (as[i].dir == HDAA_CTL_IN && (as[i].pincnt == 1 ||
|
|
as[i].pins[14] > 0 || as[i].pins[15] > 0))
|
|
as[i].mixed = 1;
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"%d associations found:\n", max);
|
|
for (i = 0; i < max; i++) {
|
|
device_printf(devinfo->dev,
|
|
"Association %d (%d) %s%s:\n",
|
|
i, as[i].index, (as[i].dir == HDAA_CTL_IN)?"in":"out",
|
|
as[i].enable?"":" (disabled)");
|
|
for (j = 0; j < 16; j++) {
|
|
if (as[i].pins[j] == 0)
|
|
continue;
|
|
device_printf(devinfo->dev,
|
|
" Pin nid=%d seq=%d\n",
|
|
as[i].pins[j], j);
|
|
}
|
|
}
|
|
);
|
|
|
|
devinfo->as = as;
|
|
}
|
|
|
|
/*
|
|
* Trace path from DAC to pin.
|
|
*/
|
|
static nid_t
|
|
hdaa_audio_trace_dac(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid,
|
|
int dupseq, int min, int only, int depth)
|
|
{
|
|
struct hdaa_widget *w;
|
|
int i, im = -1;
|
|
nid_t m = 0, ret;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return (0);
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return (0);
|
|
HDA_BOOTHVERBOSE(
|
|
if (!only) {
|
|
device_printf(devinfo->dev,
|
|
" %*stracing via nid %d\n",
|
|
depth + 1, "", w->nid);
|
|
}
|
|
);
|
|
/* Use only unused widgets */
|
|
if (w->bindas >= 0 && w->bindas != as) {
|
|
HDA_BOOTHVERBOSE(
|
|
if (!only) {
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d busy by association %d\n",
|
|
depth + 1, "", w->nid, w->bindas);
|
|
}
|
|
);
|
|
return (0);
|
|
}
|
|
if (dupseq < 0) {
|
|
if (w->bindseqmask != 0) {
|
|
HDA_BOOTHVERBOSE(
|
|
if (!only) {
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d busy by seqmask %x\n",
|
|
depth + 1, "", w->nid, w->bindseqmask);
|
|
}
|
|
);
|
|
return (0);
|
|
}
|
|
} else {
|
|
/* If this is headphones - allow duplicate first pin. */
|
|
if (w->bindseqmask != 0 &&
|
|
(w->bindseqmask & (1 << dupseq)) == 0) {
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d busy by seqmask %x\n",
|
|
depth + 1, "", w->nid, w->bindseqmask);
|
|
);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
switch (w->type) {
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT:
|
|
/* Do not traverse input. AD1988 has digital monitor
|
|
for which we are not ready. */
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT:
|
|
/* If we are tracing HP take only dac of first pin. */
|
|
if ((only == 0 || only == w->nid) &&
|
|
(w->nid >= min) && (dupseq < 0 || w->nid ==
|
|
devinfo->as[as].dacs[0][dupseq]))
|
|
m = w->nid;
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX:
|
|
if (depth > 0)
|
|
break;
|
|
/* Fall */
|
|
default:
|
|
/* Find reachable DACs with smallest nid respecting constraints. */
|
|
for (i = 0; i < w->nconns; i++) {
|
|
if (w->connsenable[i] == 0)
|
|
continue;
|
|
if (w->selconn != -1 && w->selconn != i)
|
|
continue;
|
|
if ((ret = hdaa_audio_trace_dac(devinfo, as, seq,
|
|
w->conns[i], dupseq, min, only, depth + 1)) != 0) {
|
|
if (m == 0 || ret < m) {
|
|
m = ret;
|
|
im = i;
|
|
}
|
|
if (only || dupseq >= 0)
|
|
break;
|
|
}
|
|
}
|
|
if (im >= 0 && only && ((w->nconns > 1 &&
|
|
w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR))
|
|
w->selconn = im;
|
|
break;
|
|
}
|
|
if (m && only) {
|
|
w->bindas = as;
|
|
w->bindseqmask |= (1 << seq);
|
|
}
|
|
HDA_BOOTHVERBOSE(
|
|
if (!only) {
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d returned %d\n",
|
|
depth + 1, "", w->nid, m);
|
|
}
|
|
);
|
|
return (m);
|
|
}
|
|
|
|
/*
|
|
* Trace path from widget to ADC.
|
|
*/
|
|
static nid_t
|
|
hdaa_audio_trace_adc(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid,
|
|
int mixed, int min, int only, int depth, int *length, int onlylength)
|
|
{
|
|
struct hdaa_widget *w, *wc;
|
|
int i, j, im, lm = HDA_PARSE_MAXDEPTH;
|
|
nid_t m = 0, ret;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return (0);
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return (0);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*stracing via nid %d\n",
|
|
depth + 1, "", w->nid);
|
|
);
|
|
/* Use only unused widgets */
|
|
if (w->bindas >= 0 && w->bindas != as) {
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d busy by association %d\n",
|
|
depth + 1, "", w->nid, w->bindas);
|
|
);
|
|
return (0);
|
|
}
|
|
if (!mixed && w->bindseqmask != 0) {
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d busy by seqmask %x\n",
|
|
depth + 1, "", w->nid, w->bindseqmask);
|
|
);
|
|
return (0);
|
|
}
|
|
switch (w->type) {
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT:
|
|
if ((only == 0 || only == w->nid) && (w->nid >= min) &&
|
|
(onlylength == 0 || onlylength == depth)) {
|
|
m = w->nid;
|
|
*length = depth;
|
|
}
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX:
|
|
if (depth > 0)
|
|
break;
|
|
/* Fall */
|
|
default:
|
|
/* Try to find reachable ADCs with specified nid. */
|
|
for (j = devinfo->startnode; j < devinfo->endnode; j++) {
|
|
wc = hdaa_widget_get(devinfo, j);
|
|
if (wc == NULL || wc->enable == 0)
|
|
continue;
|
|
im = -1;
|
|
for (i = 0; i < wc->nconns; i++) {
|
|
if (wc->connsenable[i] == 0)
|
|
continue;
|
|
if (wc->conns[i] != nid)
|
|
continue;
|
|
if ((ret = hdaa_audio_trace_adc(devinfo, as, seq,
|
|
j, mixed, min, only, depth + 1,
|
|
length, onlylength)) != 0) {
|
|
if (m == 0 || ret < m ||
|
|
(ret == m && *length < lm)) {
|
|
m = ret;
|
|
im = i;
|
|
lm = *length;
|
|
} else
|
|
*length = lm;
|
|
if (only)
|
|
break;
|
|
}
|
|
}
|
|
if (im >= 0 && only && ((wc->nconns > 1 &&
|
|
wc->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) ||
|
|
wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR))
|
|
wc->selconn = im;
|
|
}
|
|
break;
|
|
}
|
|
if (m && only) {
|
|
w->bindas = as;
|
|
w->bindseqmask |= (1 << seq);
|
|
}
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d returned %d\n",
|
|
depth + 1, "", w->nid, m);
|
|
);
|
|
return (m);
|
|
}
|
|
|
|
/*
|
|
* Erase trace path of the specified association.
|
|
*/
|
|
static void
|
|
hdaa_audio_undo_trace(struct hdaa_devinfo *devinfo, int as, int seq)
|
|
{
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->bindas == as) {
|
|
if (seq >= 0) {
|
|
w->bindseqmask &= ~(1 << seq);
|
|
if (w->bindseqmask == 0) {
|
|
w->bindas = -1;
|
|
w->selconn = -1;
|
|
}
|
|
} else {
|
|
w->bindas = -1;
|
|
w->bindseqmask = 0;
|
|
w->selconn = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Trace association path from DAC to output
|
|
*/
|
|
static int
|
|
hdaa_audio_trace_as_out(struct hdaa_devinfo *devinfo, int as, int seq)
|
|
{
|
|
struct hdaa_audio_as *ases = devinfo->as;
|
|
int i, hpredir;
|
|
nid_t min, res;
|
|
|
|
/* Find next pin */
|
|
for (i = seq; i < 16 && ases[as].pins[i] == 0; i++)
|
|
;
|
|
/* Check if there is no any left. If so - we succeeded. */
|
|
if (i == 16)
|
|
return (1);
|
|
|
|
hpredir = (i == 15 && ases[as].fakeredir == 0)?ases[as].hpredir:-1;
|
|
min = 0;
|
|
do {
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Tracing pin %d with min nid %d",
|
|
ases[as].pins[i], min);
|
|
if (hpredir >= 0)
|
|
printf(" and hpredir %d", hpredir);
|
|
printf("\n");
|
|
);
|
|
/* Trace this pin taking min nid into account. */
|
|
res = hdaa_audio_trace_dac(devinfo, as, i,
|
|
ases[as].pins[i], hpredir, min, 0, 0);
|
|
if (res == 0) {
|
|
/* If we failed - return to previous and redo it. */
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Unable to trace pin %d seq %d with min "
|
|
"nid %d",
|
|
ases[as].pins[i], i, min);
|
|
if (hpredir >= 0)
|
|
printf(" and hpredir %d", hpredir);
|
|
printf("\n");
|
|
);
|
|
return (0);
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Pin %d traced to DAC %d",
|
|
ases[as].pins[i], res);
|
|
if (hpredir >= 0)
|
|
printf(" and hpredir %d", hpredir);
|
|
if (ases[as].fakeredir)
|
|
printf(" with fake redirection");
|
|
printf("\n");
|
|
);
|
|
/* Trace again to mark the path */
|
|
hdaa_audio_trace_dac(devinfo, as, i,
|
|
ases[as].pins[i], hpredir, min, res, 0);
|
|
ases[as].dacs[0][i] = res;
|
|
/* We succeeded, so call next. */
|
|
if (hdaa_audio_trace_as_out(devinfo, as, i + 1))
|
|
return (1);
|
|
/* If next failed, we should retry with next min */
|
|
hdaa_audio_undo_trace(devinfo, as, i);
|
|
ases[as].dacs[0][i] = 0;
|
|
min = res + 1;
|
|
} while (1);
|
|
}
|
|
|
|
/*
|
|
* Check equivalency of two DACs.
|
|
*/
|
|
static int
|
|
hdaa_audio_dacs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2)
|
|
{
|
|
struct hdaa_devinfo *devinfo = w1->devinfo;
|
|
struct hdaa_widget *w3;
|
|
int i, j, c1, c2;
|
|
|
|
if (memcmp(&w1->param, &w2->param, sizeof(w1->param)))
|
|
return (0);
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w3 = hdaa_widget_get(devinfo, i);
|
|
if (w3 == NULL || w3->enable == 0)
|
|
continue;
|
|
if (w3->bindas != w1->bindas)
|
|
continue;
|
|
if (w3->nconns == 0)
|
|
continue;
|
|
c1 = c2 = -1;
|
|
for (j = 0; j < w3->nconns; j++) {
|
|
if (w3->connsenable[j] == 0)
|
|
continue;
|
|
if (w3->conns[j] == w1->nid)
|
|
c1 = j;
|
|
if (w3->conns[j] == w2->nid)
|
|
c2 = j;
|
|
}
|
|
if (c1 < 0)
|
|
continue;
|
|
if (c2 < 0)
|
|
return (0);
|
|
if (w3->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Check equivalency of two ADCs.
|
|
*/
|
|
static int
|
|
hdaa_audio_adcs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2)
|
|
{
|
|
struct hdaa_devinfo *devinfo = w1->devinfo;
|
|
struct hdaa_widget *w3, *w4;
|
|
int i;
|
|
|
|
if (memcmp(&w1->param, &w2->param, sizeof(w1->param)))
|
|
return (0);
|
|
if (w1->nconns != 1 || w2->nconns != 1)
|
|
return (0);
|
|
if (w1->conns[0] == w2->conns[0])
|
|
return (1);
|
|
w3 = hdaa_widget_get(devinfo, w1->conns[0]);
|
|
if (w3 == NULL || w3->enable == 0)
|
|
return (0);
|
|
w4 = hdaa_widget_get(devinfo, w2->conns[0]);
|
|
if (w4 == NULL || w4->enable == 0)
|
|
return (0);
|
|
if (w3->bindas == w4->bindas && w3->bindseqmask == w4->bindseqmask)
|
|
return (1);
|
|
if (w4->bindas >= 0)
|
|
return (0);
|
|
if (w3->type != w4->type)
|
|
return (0);
|
|
if (memcmp(&w3->param, &w4->param, sizeof(w3->param)))
|
|
return (0);
|
|
if (w3->nconns != w4->nconns)
|
|
return (0);
|
|
for (i = 0; i < w3->nconns; i++) {
|
|
if (w3->conns[i] != w4->conns[i])
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Look for equivalent DAC/ADC to implement second channel.
|
|
*/
|
|
static void
|
|
hdaa_audio_adddac(struct hdaa_devinfo *devinfo, int asid)
|
|
{
|
|
struct hdaa_audio_as *as = &devinfo->as[asid];
|
|
struct hdaa_widget *w1, *w2;
|
|
int i, pos;
|
|
nid_t nid1, nid2;
|
|
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Looking for additional %sC "
|
|
"for association %d (%d)\n",
|
|
(as->dir == HDAA_CTL_OUT) ? "DA" : "AD",
|
|
asid, as->index);
|
|
);
|
|
|
|
/* Find the exisitng DAC position and return if found more the one. */
|
|
pos = -1;
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->dacs[0][i] <= 0)
|
|
continue;
|
|
if (pos >= 0 && as->dacs[0][i] != as->dacs[0][pos])
|
|
return;
|
|
pos = i;
|
|
}
|
|
|
|
nid1 = as->dacs[0][pos];
|
|
w1 = hdaa_widget_get(devinfo, nid1);
|
|
w2 = NULL;
|
|
for (nid2 = devinfo->startnode; nid2 < devinfo->endnode; nid2++) {
|
|
w2 = hdaa_widget_get(devinfo, nid2);
|
|
if (w2 == NULL || w2->enable == 0)
|
|
continue;
|
|
if (w2->bindas >= 0)
|
|
continue;
|
|
if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) {
|
|
if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT)
|
|
continue;
|
|
if (hdaa_audio_dacs_equal(w1, w2))
|
|
break;
|
|
} else {
|
|
if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT)
|
|
continue;
|
|
if (hdaa_audio_adcs_equal(w1, w2))
|
|
break;
|
|
}
|
|
}
|
|
if (nid2 >= devinfo->endnode)
|
|
return;
|
|
w2->bindas = w1->bindas;
|
|
w2->bindseqmask = w1->bindseqmask;
|
|
if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" ADC %d considered equal to ADC %d\n", nid2, nid1);
|
|
);
|
|
w1 = hdaa_widget_get(devinfo, w1->conns[0]);
|
|
w2 = hdaa_widget_get(devinfo, w2->conns[0]);
|
|
w2->bindas = w1->bindas;
|
|
w2->bindseqmask = w1->bindseqmask;
|
|
} else {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" DAC %d considered equal to DAC %d\n", nid2, nid1);
|
|
);
|
|
}
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->dacs[0][i] <= 0)
|
|
continue;
|
|
as->dacs[as->num_chans][i] = nid2;
|
|
}
|
|
as->num_chans++;
|
|
}
|
|
|
|
/*
|
|
* Trace association path from input to ADC
|
|
*/
|
|
static int
|
|
hdaa_audio_trace_as_in(struct hdaa_devinfo *devinfo, int as)
|
|
{
|
|
struct hdaa_audio_as *ases = devinfo->as;
|
|
struct hdaa_widget *w;
|
|
int i, j, k, length;
|
|
|
|
for (j = devinfo->startnode; j < devinfo->endnode; j++) {
|
|
w = hdaa_widget_get(devinfo, j);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT)
|
|
continue;
|
|
if (w->bindas >= 0 && w->bindas != as)
|
|
continue;
|
|
|
|
/* Find next pin */
|
|
for (i = 0; i < 16; i++) {
|
|
if (ases[as].pins[i] == 0)
|
|
continue;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Tracing pin %d to ADC %d\n",
|
|
ases[as].pins[i], j);
|
|
);
|
|
/* Trace this pin taking goal into account. */
|
|
if (hdaa_audio_trace_adc(devinfo, as, i,
|
|
ases[as].pins[i], 1, 0, j, 0, &length, 0) == 0) {
|
|
/* If we failed - return to previous and redo it. */
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Unable to trace pin %d to ADC %d, undo traces\n",
|
|
ases[as].pins[i], j);
|
|
);
|
|
hdaa_audio_undo_trace(devinfo, as, -1);
|
|
for (k = 0; k < 16; k++)
|
|
ases[as].dacs[0][k] = 0;
|
|
break;
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Pin %d traced to ADC %d\n",
|
|
ases[as].pins[i], j);
|
|
);
|
|
ases[as].dacs[0][i] = j;
|
|
}
|
|
if (i == 16)
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Trace association path from input to multiple ADCs
|
|
*/
|
|
static int
|
|
hdaa_audio_trace_as_in_mch(struct hdaa_devinfo *devinfo, int as, int seq)
|
|
{
|
|
struct hdaa_audio_as *ases = devinfo->as;
|
|
int i, length;
|
|
nid_t min, res;
|
|
|
|
/* Find next pin */
|
|
for (i = seq; i < 16 && ases[as].pins[i] == 0; i++)
|
|
;
|
|
/* Check if there is no any left. If so - we succeeded. */
|
|
if (i == 16)
|
|
return (1);
|
|
|
|
min = 0;
|
|
do {
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Tracing pin %d with min nid %d",
|
|
ases[as].pins[i], min);
|
|
printf("\n");
|
|
);
|
|
/* Trace this pin taking min nid into account. */
|
|
res = hdaa_audio_trace_adc(devinfo, as, i,
|
|
ases[as].pins[i], 0, min, 0, 0, &length, 0);
|
|
if (res == 0) {
|
|
/* If we failed - return to previous and redo it. */
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Unable to trace pin %d seq %d with min "
|
|
"nid %d",
|
|
ases[as].pins[i], i, min);
|
|
printf("\n");
|
|
);
|
|
return (0);
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Pin %d traced to ADC %d\n",
|
|
ases[as].pins[i], res);
|
|
);
|
|
/* Trace again to mark the path */
|
|
hdaa_audio_trace_adc(devinfo, as, i,
|
|
ases[as].pins[i], 0, min, res, 0, &length, length);
|
|
ases[as].dacs[0][i] = res;
|
|
/* We succeeded, so call next. */
|
|
if (hdaa_audio_trace_as_in_mch(devinfo, as, i + 1))
|
|
return (1);
|
|
/* If next failed, we should retry with next min */
|
|
hdaa_audio_undo_trace(devinfo, as, i);
|
|
ases[as].dacs[0][i] = 0;
|
|
min = res + 1;
|
|
} while (1);
|
|
}
|
|
|
|
/*
|
|
* Trace input monitor path from mixer to output association.
|
|
*/
|
|
static int
|
|
hdaa_audio_trace_to_out(struct hdaa_devinfo *devinfo, nid_t nid, int depth)
|
|
{
|
|
struct hdaa_audio_as *ases = devinfo->as;
|
|
struct hdaa_widget *w, *wc;
|
|
int i, j;
|
|
nid_t res = 0;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return (0);
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return (0);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*stracing via nid %d\n",
|
|
depth + 1, "", w->nid);
|
|
);
|
|
/* Use only unused widgets */
|
|
if (depth > 0 && w->bindas != -1) {
|
|
if (w->bindas < 0 || ases[w->bindas].dir == HDAA_CTL_OUT) {
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d found output association %d\n",
|
|
depth + 1, "", w->nid, w->bindas);
|
|
);
|
|
if (w->bindas >= 0)
|
|
w->pflags |= HDAA_ADC_MONITOR;
|
|
return (1);
|
|
} else {
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d busy by input association %d\n",
|
|
depth + 1, "", w->nid, w->bindas);
|
|
);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
switch (w->type) {
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT:
|
|
/* Do not traverse input. AD1988 has digital monitor
|
|
for which we are not ready. */
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX:
|
|
if (depth > 0)
|
|
break;
|
|
/* Fall */
|
|
default:
|
|
/* Try to find reachable ADCs with specified nid. */
|
|
for (j = devinfo->startnode; j < devinfo->endnode; j++) {
|
|
wc = hdaa_widget_get(devinfo, j);
|
|
if (wc == NULL || wc->enable == 0)
|
|
continue;
|
|
for (i = 0; i < wc->nconns; i++) {
|
|
if (wc->connsenable[i] == 0)
|
|
continue;
|
|
if (wc->conns[i] != nid)
|
|
continue;
|
|
if (hdaa_audio_trace_to_out(devinfo,
|
|
j, depth + 1) != 0) {
|
|
res = 1;
|
|
if (wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR &&
|
|
wc->selconn == -1)
|
|
wc->selconn = i;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (res && w->bindas == -1)
|
|
w->bindas = -2;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" %*snid %d returned %d\n",
|
|
depth + 1, "", w->nid, res);
|
|
);
|
|
return (res);
|
|
}
|
|
|
|
/*
|
|
* Trace extra associations (beeper, monitor)
|
|
*/
|
|
static void
|
|
hdaa_audio_trace_as_extra(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w;
|
|
int j;
|
|
|
|
/* Input monitor */
|
|
/* Find mixer associated with input, but supplying signal
|
|
for output associations. Hope it will be input monitor. */
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Tracing input monitor\n");
|
|
);
|
|
for (j = devinfo->startnode; j < devinfo->endnode; j++) {
|
|
w = hdaa_widget_get(devinfo, j);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
continue;
|
|
if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN)
|
|
continue;
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Tracing nid %d to out\n",
|
|
j);
|
|
);
|
|
if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" nid %d is input monitor\n",
|
|
w->nid);
|
|
);
|
|
w->ossdev = SOUND_MIXER_IMIX;
|
|
}
|
|
}
|
|
|
|
/* Other inputs monitor */
|
|
/* Find input pins supplying signal for output associations.
|
|
Hope it will be input monitoring. */
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Tracing other input monitors\n");
|
|
);
|
|
for (j = devinfo->startnode; j < devinfo->endnode; j++) {
|
|
w = hdaa_widget_get(devinfo, j);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN)
|
|
continue;
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Tracing nid %d to out\n",
|
|
j);
|
|
);
|
|
if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" nid %d is input monitor\n",
|
|
w->nid);
|
|
);
|
|
}
|
|
}
|
|
|
|
/* Beeper */
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Tracing beeper\n");
|
|
);
|
|
for (j = devinfo->startnode; j < devinfo->endnode; j++) {
|
|
w = hdaa_widget_get(devinfo, j);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET)
|
|
continue;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Tracing nid %d to out\n",
|
|
j);
|
|
);
|
|
if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" nid %d traced to out\n",
|
|
j);
|
|
);
|
|
}
|
|
w->bindas = -2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Bind assotiations to PCM channels
|
|
*/
|
|
static void
|
|
hdaa_audio_bind_as(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
int i, j, cnt = 0, free;
|
|
|
|
for (j = 0; j < devinfo->ascnt; j++) {
|
|
if (as[j].enable)
|
|
cnt += as[j].num_chans;
|
|
}
|
|
if (devinfo->num_chans == 0) {
|
|
devinfo->chans = (struct hdaa_chan *)malloc(
|
|
sizeof(struct hdaa_chan) * cnt,
|
|
M_HDAA, M_ZERO | M_NOWAIT);
|
|
if (devinfo->chans == NULL) {
|
|
device_printf(devinfo->dev,
|
|
"Channels memory allocation failed!\n");
|
|
return;
|
|
}
|
|
} else {
|
|
devinfo->chans = (struct hdaa_chan *)realloc(devinfo->chans,
|
|
sizeof(struct hdaa_chan) * (devinfo->num_chans + cnt),
|
|
M_HDAA, M_ZERO | M_NOWAIT);
|
|
if (devinfo->chans == NULL) {
|
|
devinfo->num_chans = 0;
|
|
device_printf(devinfo->dev,
|
|
"Channels memory allocation failed!\n");
|
|
return;
|
|
}
|
|
/* Fixup relative pointers after realloc */
|
|
for (j = 0; j < devinfo->num_chans; j++)
|
|
devinfo->chans[j].caps.fmtlist = devinfo->chans[j].fmtlist;
|
|
}
|
|
free = devinfo->num_chans;
|
|
devinfo->num_chans += cnt;
|
|
|
|
for (j = free; j < free + cnt; j++) {
|
|
devinfo->chans[j].devinfo = devinfo;
|
|
devinfo->chans[j].as = -1;
|
|
}
|
|
|
|
/* Assign associations in order of their numbers, */
|
|
for (j = 0; j < devinfo->ascnt; j++) {
|
|
if (as[j].enable == 0)
|
|
continue;
|
|
for (i = 0; i < as[j].num_chans; i++) {
|
|
devinfo->chans[free].as = j;
|
|
devinfo->chans[free].asindex = i;
|
|
devinfo->chans[free].dir =
|
|
(as[j].dir == HDAA_CTL_IN) ? PCMDIR_REC : PCMDIR_PLAY;
|
|
hdaa_pcmchannel_setup(&devinfo->chans[free]);
|
|
as[j].chans[i] = free;
|
|
free++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_disable_nonaudio(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
/* Disable power and volume widgets. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET) {
|
|
w->enable = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling nid %d due to it's"
|
|
" non-audio type.\n",
|
|
w->nid);
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_disable_useless(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w, *cw;
|
|
struct hdaa_audio_ctl *ctl;
|
|
int done, found, i, j, k;
|
|
|
|
/* Disable useless pins. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) {
|
|
if ((w->wclass.pin.config &
|
|
HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) ==
|
|
HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE) {
|
|
w->enable = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling pin nid %d due"
|
|
" to None connectivity.\n",
|
|
w->nid);
|
|
);
|
|
} else if ((w->wclass.pin.config &
|
|
HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK) == 0) {
|
|
w->enable = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling unassociated"
|
|
" pin nid %d.\n",
|
|
w->nid);
|
|
);
|
|
}
|
|
}
|
|
}
|
|
do {
|
|
done = 1;
|
|
/* Disable and mute controls for disabled widgets. */
|
|
i = 0;
|
|
while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) {
|
|
if (ctl->enable == 0)
|
|
continue;
|
|
if (ctl->widget->enable == 0 ||
|
|
(ctl->childwidget != NULL &&
|
|
ctl->childwidget->enable == 0)) {
|
|
ctl->forcemute = 1;
|
|
ctl->muted = HDAA_AMP_MUTE_ALL;
|
|
ctl->left = 0;
|
|
ctl->right = 0;
|
|
ctl->enable = 0;
|
|
if (ctl->ndir == HDAA_CTL_IN)
|
|
ctl->widget->connsenable[ctl->index] = 0;
|
|
done = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling ctl %d nid %d cnid %d due"
|
|
" to disabled widget.\n", i,
|
|
ctl->widget->nid,
|
|
(ctl->childwidget != NULL)?
|
|
ctl->childwidget->nid:-1);
|
|
);
|
|
}
|
|
}
|
|
/* Disable useless widgets. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
/* Disable inputs with disabled child widgets. */
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (w->connsenable[j]) {
|
|
cw = hdaa_widget_get(devinfo, w->conns[j]);
|
|
if (cw == NULL || cw->enable == 0) {
|
|
w->connsenable[j] = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling nid %d connection %d due"
|
|
" to disabled child widget.\n",
|
|
i, j);
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR &&
|
|
w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
continue;
|
|
/* Disable mixers and selectors without inputs. */
|
|
found = 0;
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (w->connsenable[j]) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (found == 0) {
|
|
w->enable = 0;
|
|
done = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling nid %d due to all it's"
|
|
" inputs disabled.\n", w->nid);
|
|
);
|
|
}
|
|
/* Disable nodes without consumers. */
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR &&
|
|
w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
continue;
|
|
found = 0;
|
|
for (k = devinfo->startnode; k < devinfo->endnode; k++) {
|
|
cw = hdaa_widget_get(devinfo, k);
|
|
if (cw == NULL || cw->enable == 0)
|
|
continue;
|
|
for (j = 0; j < cw->nconns; j++) {
|
|
if (cw->connsenable[j] && cw->conns[j] == i) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found == 0) {
|
|
w->enable = 0;
|
|
done = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling nid %d due to all it's"
|
|
" consumers disabled.\n", w->nid);
|
|
);
|
|
}
|
|
}
|
|
} while (done == 0);
|
|
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_disable_unas(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w, *cw;
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, j, k;
|
|
|
|
/* Disable unassosiated widgets. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->bindas == -1) {
|
|
w->enable = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling unassociated nid %d.\n",
|
|
w->nid);
|
|
);
|
|
}
|
|
}
|
|
/* Disable input connections on input pin and
|
|
* output on output. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (w->bindas < 0)
|
|
continue;
|
|
if (as[w->bindas].dir == HDAA_CTL_IN) {
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (w->connsenable[j] == 0)
|
|
continue;
|
|
w->connsenable[j] = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling connection to input pin "
|
|
"nid %d conn %d.\n",
|
|
i, j);
|
|
);
|
|
}
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid,
|
|
HDAA_CTL_IN, -1, 1);
|
|
if (ctl && ctl->enable) {
|
|
ctl->forcemute = 1;
|
|
ctl->muted = HDAA_AMP_MUTE_ALL;
|
|
ctl->left = 0;
|
|
ctl->right = 0;
|
|
ctl->enable = 0;
|
|
}
|
|
} else {
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid,
|
|
HDAA_CTL_OUT, -1, 1);
|
|
if (ctl && ctl->enable) {
|
|
ctl->forcemute = 1;
|
|
ctl->muted = HDAA_AMP_MUTE_ALL;
|
|
ctl->left = 0;
|
|
ctl->right = 0;
|
|
ctl->enable = 0;
|
|
}
|
|
for (k = devinfo->startnode; k < devinfo->endnode; k++) {
|
|
cw = hdaa_widget_get(devinfo, k);
|
|
if (cw == NULL || cw->enable == 0)
|
|
continue;
|
|
for (j = 0; j < cw->nconns; j++) {
|
|
if (cw->connsenable[j] && cw->conns[j] == i) {
|
|
cw->connsenable[j] = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling connection from output pin "
|
|
"nid %d conn %d cnid %d.\n",
|
|
k, j, i);
|
|
);
|
|
if (cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX &&
|
|
cw->nconns > 1)
|
|
continue;
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, k,
|
|
HDAA_CTL_IN, j, 1);
|
|
if (ctl && ctl->enable) {
|
|
ctl->forcemute = 1;
|
|
ctl->muted = HDAA_AMP_MUTE_ALL;
|
|
ctl->left = 0;
|
|
ctl->right = 0;
|
|
ctl->enable = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_disable_notselected(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w;
|
|
int i, j;
|
|
|
|
/* On playback path we can safely disable all unseleted inputs. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->nconns <= 1)
|
|
continue;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
continue;
|
|
if (w->bindas < 0 || as[w->bindas].dir == HDAA_CTL_IN)
|
|
continue;
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (w->connsenable[j] == 0)
|
|
continue;
|
|
if (w->selconn < 0 || w->selconn == j)
|
|
continue;
|
|
w->connsenable[j] = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling unselected connection "
|
|
"nid %d conn %d.\n",
|
|
i, j);
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_disable_crossas(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *ases = devinfo->as;
|
|
struct hdaa_widget *w, *cw;
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, j;
|
|
|
|
/* Disable crossassociatement and unwanted crosschannel connections. */
|
|
/* ... using selectors */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->nconns <= 1)
|
|
continue;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
continue;
|
|
/* Allow any -> mix */
|
|
if (w->bindas == -2)
|
|
continue;
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (w->connsenable[j] == 0)
|
|
continue;
|
|
cw = hdaa_widget_get(devinfo, w->conns[j]);
|
|
if (cw == NULL || w->enable == 0)
|
|
continue;
|
|
/* Allow mix -> out. */
|
|
if (cw->bindas == -2 && w->bindas >= 0 &&
|
|
ases[w->bindas].dir == HDAA_CTL_OUT)
|
|
continue;
|
|
/* Allow mix -> mixed-in. */
|
|
if (cw->bindas == -2 && w->bindas >= 0 &&
|
|
ases[w->bindas].mixed)
|
|
continue;
|
|
/* Allow in -> mix. */
|
|
if ((w->pflags & HDAA_ADC_MONITOR) &&
|
|
cw->bindas >= 0 &&
|
|
ases[cw->bindas].dir == HDAA_CTL_IN)
|
|
continue;
|
|
/* Allow if have common as/seqs. */
|
|
if (w->bindas == cw->bindas &&
|
|
(w->bindseqmask & cw->bindseqmask) != 0)
|
|
continue;
|
|
w->connsenable[j] = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling crossassociatement connection "
|
|
"nid %d conn %d cnid %d.\n",
|
|
i, j, cw->nid);
|
|
);
|
|
}
|
|
}
|
|
/* ... using controls */
|
|
i = 0;
|
|
while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) {
|
|
if (ctl->enable == 0 || ctl->childwidget == NULL)
|
|
continue;
|
|
/* Allow any -> mix */
|
|
if (ctl->widget->bindas == -2)
|
|
continue;
|
|
/* Allow mix -> out. */
|
|
if (ctl->childwidget->bindas == -2 &&
|
|
ctl->widget->bindas >= 0 &&
|
|
ases[ctl->widget->bindas].dir == HDAA_CTL_OUT)
|
|
continue;
|
|
/* Allow mix -> mixed-in. */
|
|
if (ctl->childwidget->bindas == -2 &&
|
|
ctl->widget->bindas >= 0 &&
|
|
ases[ctl->widget->bindas].mixed)
|
|
continue;
|
|
/* Allow in -> mix. */
|
|
if ((ctl->widget->pflags & HDAA_ADC_MONITOR) &&
|
|
ctl->childwidget->bindas >= 0 &&
|
|
ases[ctl->childwidget->bindas].dir == HDAA_CTL_IN)
|
|
continue;
|
|
/* Allow if have common as/seqs. */
|
|
if (ctl->widget->bindas == ctl->childwidget->bindas &&
|
|
(ctl->widget->bindseqmask & ctl->childwidget->bindseqmask) != 0)
|
|
continue;
|
|
ctl->forcemute = 1;
|
|
ctl->muted = HDAA_AMP_MUTE_ALL;
|
|
ctl->left = 0;
|
|
ctl->right = 0;
|
|
ctl->enable = 0;
|
|
if (ctl->ndir == HDAA_CTL_IN)
|
|
ctl->widget->connsenable[ctl->index] = 0;
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
" Disabling crossassociatement connection "
|
|
"ctl %d nid %d cnid %d.\n", i,
|
|
ctl->widget->nid,
|
|
ctl->childwidget->nid);
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Find controls to control amplification for source and calculate possible
|
|
* amplification range.
|
|
*/
|
|
static int
|
|
hdaa_audio_ctl_source_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index,
|
|
int ossdev, int ctlable, int depth, int *minamp, int *maxamp)
|
|
{
|
|
struct hdaa_widget *w, *wc;
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, j, conns = 0, tminamp, tmaxamp, cminamp, cmaxamp, found = 0;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return (found);
|
|
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return (found);
|
|
|
|
/* Count number of active inputs. */
|
|
if (depth > 0) {
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (!w->connsenable[j])
|
|
continue;
|
|
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->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) {
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN,
|
|
index, 1);
|
|
if (ctl) {
|
|
ctl->ossmask |= (1 << ossdev);
|
|
found++;
|
|
if (*minamp == *maxamp) {
|
|
*minamp += MINQDB(ctl);
|
|
*maxamp += MAXQDB(ctl);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If widget has own ossdev - not traverse it.
|
|
It will be traversed on its own. */
|
|
if (w->ossdev >= 0 && depth > 0)
|
|
return (found);
|
|
|
|
/* We must not traverse pin */
|
|
if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) &&
|
|
depth > 0)
|
|
return (found);
|
|
|
|
/* record that this widget exports such signal, */
|
|
w->ossmask |= (1 << ossdev);
|
|
|
|
/*
|
|
* If signals mixed, we can't assign controls farther.
|
|
* Ignore this on depth zero. Caller must knows why.
|
|
*/
|
|
if (conns > 1 &&
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
ctlable = 0;
|
|
|
|
if (ctlable) {
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1);
|
|
if (ctl) {
|
|
ctl->ossmask |= (1 << ossdev);
|
|
found++;
|
|
if (*minamp == *maxamp) {
|
|
*minamp += MINQDB(ctl);
|
|
*maxamp += MAXQDB(ctl);
|
|
}
|
|
}
|
|
}
|
|
|
|
cminamp = cmaxamp = 0;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
wc = hdaa_widget_get(devinfo, i);
|
|
if (wc == NULL || wc->enable == 0)
|
|
continue;
|
|
for (j = 0; j < wc->nconns; j++) {
|
|
if (wc->connsenable[j] && wc->conns[j] == nid) {
|
|
tminamp = tmaxamp = 0;
|
|
found += hdaa_audio_ctl_source_amp(devinfo,
|
|
wc->nid, j, ossdev, ctlable, depth + 1,
|
|
&tminamp, &tmaxamp);
|
|
if (cminamp == 0 && cmaxamp == 0) {
|
|
cminamp = tminamp;
|
|
cmaxamp = tmaxamp;
|
|
} else if (tminamp != tmaxamp) {
|
|
cminamp = imax(cminamp, tminamp);
|
|
cmaxamp = imin(cmaxamp, tmaxamp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (*minamp == *maxamp && cminamp < cmaxamp) {
|
|
*minamp += cminamp;
|
|
*maxamp += cmaxamp;
|
|
}
|
|
return (found);
|
|
}
|
|
|
|
/*
|
|
* Find controls to control amplification for destination and calculate
|
|
* possible amplification range.
|
|
*/
|
|
static int
|
|
hdaa_audio_ctl_dest_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index,
|
|
int ossdev, int depth, int *minamp, int *maxamp)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w, *wc;
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, j, consumers, tminamp, tmaxamp, cminamp, cmaxamp, found = 0;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return (found);
|
|
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return (found);
|
|
|
|
if (depth > 0) {
|
|
/* If this node produce output for several consumers,
|
|
we can't touch it. */
|
|
consumers = 0;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
wc = hdaa_widget_get(devinfo, i);
|
|
if (wc == NULL || wc->enable == 0)
|
|
continue;
|
|
for (j = 0; j < wc->nconns; j++) {
|
|
if (wc->connsenable[j] && wc->conns[j] == nid)
|
|
consumers++;
|
|
}
|
|
}
|
|
/* The only exception is if real HP redirection is configured
|
|
and this is a duplication point.
|
|
XXX: Actually exception is not completely correct.
|
|
XXX: Duplication point check is not perfect. */
|
|
if ((consumers == 2 && (w->bindas < 0 ||
|
|
as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir ||
|
|
(w->bindseqmask & (1 << 15)) == 0)) ||
|
|
consumers > 2)
|
|
return (found);
|
|
|
|
/* Else use it's output mixer. */
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid,
|
|
HDAA_CTL_OUT, -1, 1);
|
|
if (ctl) {
|
|
ctl->ossmask |= (1 << ossdev);
|
|
found++;
|
|
if (*minamp == *maxamp) {
|
|
*minamp += MINQDB(ctl);
|
|
*maxamp += MAXQDB(ctl);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We must not traverse pin */
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX &&
|
|
depth > 0)
|
|
return (found);
|
|
|
|
cminamp = cmaxamp = 0;
|
|
for (i = 0; i < w->nconns; i++) {
|
|
if (w->connsenable[i] == 0)
|
|
continue;
|
|
if (index >= 0 && i != index)
|
|
continue;
|
|
tminamp = tmaxamp = 0;
|
|
ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid,
|
|
HDAA_CTL_IN, i, 1);
|
|
if (ctl) {
|
|
ctl->ossmask |= (1 << ossdev);
|
|
found++;
|
|
if (*minamp == *maxamp) {
|
|
tminamp += MINQDB(ctl);
|
|
tmaxamp += MAXQDB(ctl);
|
|
}
|
|
}
|
|
found += hdaa_audio_ctl_dest_amp(devinfo, w->conns[i], -1, ossdev,
|
|
depth + 1, &tminamp, &tmaxamp);
|
|
if (cminamp == 0 && cmaxamp == 0) {
|
|
cminamp = tminamp;
|
|
cmaxamp = tmaxamp;
|
|
} else if (tminamp != tmaxamp) {
|
|
cminamp = imax(cminamp, tminamp);
|
|
cmaxamp = imin(cmaxamp, tmaxamp);
|
|
}
|
|
}
|
|
if (*minamp == *maxamp && cminamp < cmaxamp) {
|
|
*minamp += cminamp;
|
|
*maxamp += cmaxamp;
|
|
}
|
|
return (found);
|
|
}
|
|
|
|
/*
|
|
* Assign OSS names to sound sources
|
|
*/
|
|
static void
|
|
hdaa_audio_assign_names(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w;
|
|
int i, j;
|
|
int type = -1, use, used = 0;
|
|
static const int types[7][13] = {
|
|
{ SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2,
|
|
SOUND_MIXER_LINE3, -1 }, /* line */
|
|
{ SOUND_MIXER_MONITOR, SOUND_MIXER_MIC, -1 }, /* int mic */
|
|
{ SOUND_MIXER_MIC, SOUND_MIXER_MONITOR, -1 }, /* ext mic */
|
|
{ SOUND_MIXER_CD, -1 }, /* cd */
|
|
{ SOUND_MIXER_SPEAKER, -1 }, /* speaker */
|
|
{ SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3,
|
|
-1 }, /* digital */
|
|
{ SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2,
|
|
SOUND_MIXER_LINE3, SOUND_MIXER_PHONEIN, SOUND_MIXER_PHONEOUT,
|
|
SOUND_MIXER_VIDEO, SOUND_MIXER_RADIO, SOUND_MIXER_DIGITAL1,
|
|
SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, SOUND_MIXER_MONITOR,
|
|
-1 } /* others */
|
|
};
|
|
|
|
/* Surely known names */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->bindas == -1)
|
|
continue;
|
|
use = -1;
|
|
switch (w->type) {
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX:
|
|
if (as[w->bindas].dir == HDAA_CTL_OUT)
|
|
break;
|
|
type = -1;
|
|
switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) {
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN:
|
|
type = 0;
|
|
break;
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN:
|
|
if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK)
|
|
== HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK)
|
|
break;
|
|
type = 1;
|
|
break;
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_CD:
|
|
type = 3;
|
|
break;
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER:
|
|
type = 4;
|
|
break;
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_IN:
|
|
case HDA_CONFIG_DEFAULTCONF_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 HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT:
|
|
use = SOUND_MIXER_PCM;
|
|
break;
|
|
case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET:
|
|
use = SOUND_MIXER_SPEAKER;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (use >= 0) {
|
|
w->ossdev = use;
|
|
used |= (1 << use);
|
|
}
|
|
}
|
|
/* Semi-known names */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->ossdev >= 0)
|
|
continue;
|
|
if (w->bindas == -1)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (as[w->bindas].dir == HDAA_CTL_OUT)
|
|
continue;
|
|
type = -1;
|
|
switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) {
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT:
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER:
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT:
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_AUX:
|
|
type = 0;
|
|
break;
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN:
|
|
type = 2;
|
|
break;
|
|
case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT:
|
|
case HDA_CONFIG_DEFAULTCONF_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->ossdev = types[type][j];
|
|
used |= (1 << types[type][j]);
|
|
}
|
|
}
|
|
/* Others */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->ossdev >= 0)
|
|
continue;
|
|
if (w->bindas == -1)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (as[w->bindas].dir == HDAA_CTL_OUT)
|
|
continue;
|
|
j = 0;
|
|
while (types[6][j] >= 0 &&
|
|
(used & (1 << types[6][j])) != 0) {
|
|
j++;
|
|
}
|
|
if (types[6][j] >= 0) {
|
|
w->ossdev = types[6][j];
|
|
used |= (1 << types[6][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_build_tree(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
int j, res;
|
|
|
|
/* Trace all associations in order of their numbers. */
|
|
for (j = 0; j < devinfo->ascnt; j++) {
|
|
if (as[j].enable == 0)
|
|
continue;
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Tracing association %d (%d)\n", j, as[j].index);
|
|
);
|
|
if (as[j].dir == HDAA_CTL_OUT) {
|
|
retry:
|
|
res = hdaa_audio_trace_as_out(devinfo, j, 0);
|
|
if (res == 0 && as[j].hpredir >= 0 &&
|
|
as[j].fakeredir == 0) {
|
|
/* If CODEC can't do analog HP redirection
|
|
try to make it using one more DAC. */
|
|
as[j].fakeredir = 1;
|
|
goto retry;
|
|
}
|
|
} else if (as[j].mixed)
|
|
res = hdaa_audio_trace_as_in(devinfo, j);
|
|
else
|
|
res = hdaa_audio_trace_as_in_mch(devinfo, j, 0);
|
|
if (res) {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Association %d (%d) trace succeeded\n",
|
|
j, as[j].index);
|
|
);
|
|
} else {
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev,
|
|
"Association %d (%d) trace failed\n",
|
|
j, as[j].index);
|
|
);
|
|
as[j].enable = 0;
|
|
}
|
|
}
|
|
|
|
/* Look for additional DACs/ADCs. */
|
|
for (j = 0; j < devinfo->ascnt; j++) {
|
|
if (as[j].enable == 0)
|
|
continue;
|
|
hdaa_audio_adddac(devinfo, j);
|
|
}
|
|
|
|
/* Trace mixer and beeper pseudo associations. */
|
|
hdaa_audio_trace_as_extra(devinfo);
|
|
}
|
|
|
|
/*
|
|
* Store in pdevinfo new data about whether and how we can control signal
|
|
* for OSS device to/from specified widget.
|
|
*/
|
|
static void
|
|
hdaa_adjust_amp(struct hdaa_widget *w, int ossdev,
|
|
int found, int minamp, int maxamp)
|
|
{
|
|
struct hdaa_devinfo *devinfo = w->devinfo;
|
|
struct hdaa_pcm_devinfo *pdevinfo;
|
|
|
|
if (w->bindas >= 0)
|
|
pdevinfo = devinfo->as[w->bindas].pdevinfo;
|
|
else
|
|
pdevinfo = &devinfo->devs[0];
|
|
if (found)
|
|
pdevinfo->ossmask |= (1 << ossdev);
|
|
if (minamp == 0 && maxamp == 0)
|
|
return;
|
|
if (pdevinfo->minamp[ossdev] == 0 && pdevinfo->maxamp[ossdev] == 0) {
|
|
pdevinfo->minamp[ossdev] = minamp;
|
|
pdevinfo->maxamp[ossdev] = maxamp;
|
|
} else {
|
|
pdevinfo->minamp[ossdev] = imax(pdevinfo->minamp[ossdev], minamp);
|
|
pdevinfo->maxamp[ossdev] = imin(pdevinfo->maxamp[ossdev], maxamp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Trace signals from/to all possible sources/destionstions to find possible
|
|
* recording sources, OSS device control ranges and to assign controls.
|
|
*/
|
|
static void
|
|
hdaa_audio_assign_mixers(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w, *cw;
|
|
int i, j, minamp, maxamp, found;
|
|
|
|
/* Assign mixers to the tree. */
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
minamp = maxamp = 0;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET ||
|
|
(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX &&
|
|
as[w->bindas].dir == HDAA_CTL_IN)) {
|
|
if (w->ossdev < 0)
|
|
continue;
|
|
found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1,
|
|
w->ossdev, 1, 0, &minamp, &maxamp);
|
|
hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp);
|
|
} else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) {
|
|
found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1,
|
|
SOUND_MIXER_RECLEV, 0, &minamp, &maxamp);
|
|
hdaa_adjust_amp(w, SOUND_MIXER_RECLEV, found, minamp, maxamp);
|
|
} else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX &&
|
|
as[w->bindas].dir == HDAA_CTL_OUT) {
|
|
found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1,
|
|
SOUND_MIXER_VOLUME, 0, &minamp, &maxamp);
|
|
hdaa_adjust_amp(w, SOUND_MIXER_VOLUME, found, minamp, maxamp);
|
|
}
|
|
if (w->ossdev == SOUND_MIXER_IMIX) {
|
|
minamp = maxamp = 0;
|
|
found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1,
|
|
w->ossdev, 1, 0, &minamp, &maxamp);
|
|
if (minamp == maxamp) {
|
|
/* If we are unable to control input monitor
|
|
as source - try to control it as destination. */
|
|
found += hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1,
|
|
w->ossdev, 0, &minamp, &maxamp);
|
|
w->pflags |= HDAA_IMIX_AS_DST;
|
|
}
|
|
hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp);
|
|
}
|
|
if (w->pflags & HDAA_ADC_MONITOR) {
|
|
for (j = 0; j < w->nconns; j++) {
|
|
if (!w->connsenable[j])
|
|
continue;
|
|
cw = hdaa_widget_get(devinfo, w->conns[j]);
|
|
if (cw == NULL || cw->enable == 0)
|
|
continue;
|
|
if (cw->bindas == -1)
|
|
continue;
|
|
if (cw->bindas >= 0 &&
|
|
as[cw->bindas].dir != HDAA_CTL_IN)
|
|
continue;
|
|
minamp = maxamp = 0;
|
|
found = hdaa_audio_ctl_dest_amp(devinfo,
|
|
w->nid, j, SOUND_MIXER_IGAIN, 0,
|
|
&minamp, &maxamp);
|
|
hdaa_adjust_amp(w, SOUND_MIXER_IGAIN,
|
|
found, minamp, maxamp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_prepare_pin_ctrl(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w;
|
|
uint32_t pincap;
|
|
int i;
|
|
|
|
for (i = 0; i < devinfo->nodecnt; i++) {
|
|
w = &devinfo->widget[i];
|
|
if (w == NULL)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX &&
|
|
w->waspin == 0)
|
|
continue;
|
|
|
|
pincap = w->wclass.pin.cap;
|
|
|
|
/* Disable everything. */
|
|
if (devinfo->init_clear) {
|
|
w->wclass.pin.ctrl &= ~(
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE |
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE |
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE |
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK);
|
|
}
|
|
|
|
if (w->enable == 0) {
|
|
/* Pin is unused so left it disabled. */
|
|
continue;
|
|
} else if (w->waspin) {
|
|
/* Enable input for beeper input. */
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE;
|
|
} else if (w->bindas < 0 || as[w->bindas].enable == 0) {
|
|
/* Pin is unused so left it disabled. */
|
|
continue;
|
|
} else if (as[w->bindas].dir == HDAA_CTL_IN) {
|
|
/* Input pin, configure for input. */
|
|
if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE;
|
|
|
|
if ((devinfo->quirks & HDAA_QUIRK_IVREF100) &&
|
|
HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE(
|
|
HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100);
|
|
else if ((devinfo->quirks & HDAA_QUIRK_IVREF80) &&
|
|
HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE(
|
|
HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80);
|
|
else if ((devinfo->quirks & HDAA_QUIRK_IVREF50) &&
|
|
HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE(
|
|
HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50);
|
|
} else {
|
|
/* Output pin, configure for output. */
|
|
if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE;
|
|
|
|
if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap) &&
|
|
(w->wclass.pin.config &
|
|
HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) ==
|
|
HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT)
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE;
|
|
|
|
if ((devinfo->quirks & HDAA_QUIRK_OVREF100) &&
|
|
HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE(
|
|
HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100);
|
|
else if ((devinfo->quirks & HDAA_QUIRK_OVREF80) &&
|
|
HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE(
|
|
HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80);
|
|
else if ((devinfo->quirks & HDAA_QUIRK_OVREF50) &&
|
|
HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap))
|
|
w->wclass.pin.ctrl |=
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE(
|
|
HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_ctl_commit(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i, z;
|
|
|
|
i = 0;
|
|
while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) {
|
|
if (ctl->enable == 0 || ctl->ossmask != 0) {
|
|
/* Mute disabled and mixer controllable controls.
|
|
* Last will be initialized by mixer_init().
|
|
* This expected to reduce click on startup. */
|
|
hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_ALL, 0, 0);
|
|
continue;
|
|
}
|
|
/* Init fixed controls to 0dB amplification. */
|
|
z = ctl->offset;
|
|
if (z > ctl->step)
|
|
z = ctl->step;
|
|
hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_NONE, z, z);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_gpio_commit(struct hdaa_devinfo *devinfo)
|
|
{
|
|
uint32_t gdata, gmask, gdir;
|
|
int i, numgpio;
|
|
|
|
numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap);
|
|
if (devinfo->gpio != 0 && numgpio != 0) {
|
|
gdata = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_GPIO_DATA(0, devinfo->nid));
|
|
gmask = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid));
|
|
gdir = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid));
|
|
for (i = 0; i < numgpio; i++) {
|
|
if ((devinfo->gpio & HDAA_GPIO_MASK(i)) ==
|
|
HDAA_GPIO_SET(i)) {
|
|
gdata |= (1 << i);
|
|
gmask |= (1 << i);
|
|
gdir |= (1 << i);
|
|
} else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) ==
|
|
HDAA_GPIO_CLEAR(i)) {
|
|
gdata &= ~(1 << i);
|
|
gmask |= (1 << i);
|
|
gdir |= (1 << i);
|
|
} else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) ==
|
|
HDAA_GPIO_DISABLE(i)) {
|
|
gmask &= ~(1 << i);
|
|
} else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) ==
|
|
HDAA_GPIO_INPUT(i)) {
|
|
gmask |= (1 << i);
|
|
gdir &= ~(1 << i);
|
|
}
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev, "GPIO commit\n");
|
|
);
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_GPIO_ENABLE_MASK(0, devinfo->nid, gmask));
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_GPIO_DIRECTION(0, devinfo->nid, gdir));
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_GPIO_DATA(0, devinfo->nid, gdata));
|
|
HDA_BOOTVERBOSE(
|
|
hdaa_dump_gpio(devinfo);
|
|
);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_gpo_commit(struct hdaa_devinfo *devinfo)
|
|
{
|
|
uint32_t gdata;
|
|
int i, numgpo;
|
|
|
|
numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap);
|
|
if (devinfo->gpo != 0 && numgpo != 0) {
|
|
gdata = hda_command(devinfo->dev,
|
|
HDA_CMD_GET_GPO_DATA(0, devinfo->nid));
|
|
for (i = 0; i < numgpo; i++) {
|
|
if ((devinfo->gpio & HDAA_GPIO_MASK(i)) ==
|
|
HDAA_GPIO_SET(i)) {
|
|
gdata |= (1 << i);
|
|
} else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) ==
|
|
HDAA_GPIO_CLEAR(i)) {
|
|
gdata &= ~(1 << i);
|
|
}
|
|
}
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(devinfo->dev, "GPO commit\n");
|
|
);
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_GPO_DATA(0, devinfo->nid, gdata));
|
|
HDA_BOOTVERBOSE(
|
|
hdaa_dump_gpo(devinfo);
|
|
);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_audio_commit(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
/* Commit controls. */
|
|
hdaa_audio_ctl_commit(devinfo);
|
|
|
|
/* Commit selectors, pins and EAPD. */
|
|
for (i = 0; i < devinfo->nodecnt; i++) {
|
|
w = &devinfo->widget[i];
|
|
if (w == NULL)
|
|
continue;
|
|
if (w->selconn == -1)
|
|
w->selconn = 0;
|
|
if (w->nconns > 0)
|
|
hdaa_widget_connection_select(w, w->selconn);
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX ||
|
|
w->waspin) {
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL(0, w->nid,
|
|
w->wclass.pin.ctrl));
|
|
}
|
|
if (w->param.eapdbtl != HDA_INVALID) {
|
|
uint32_t val;
|
|
|
|
val = w->param.eapdbtl;
|
|
if (devinfo->quirks &
|
|
HDAA_QUIRK_EAPDINV)
|
|
val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD;
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid,
|
|
val));
|
|
}
|
|
}
|
|
|
|
hdaa_gpio_commit(devinfo);
|
|
hdaa_gpo_commit(devinfo);
|
|
}
|
|
|
|
static void
|
|
hdaa_powerup(struct hdaa_devinfo *devinfo)
|
|
{
|
|
int i;
|
|
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_POWER_STATE(0,
|
|
devinfo->nid, HDA_CMD_POWER_STATE_D0));
|
|
DELAY(100);
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_POWER_STATE(0,
|
|
i, HDA_CMD_POWER_STATE_D0));
|
|
}
|
|
DELAY(1000);
|
|
}
|
|
|
|
static int
|
|
hdaa_pcmchannel_setup(struct hdaa_chan *ch)
|
|
{
|
|
struct hdaa_devinfo *devinfo = ch->devinfo;
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
struct hdaa_widget *w;
|
|
uint32_t cap, fmtcap, pcmcap;
|
|
int i, j, ret, channels, onlystereo;
|
|
uint16_t pinset;
|
|
|
|
ch->caps = hdaa_caps;
|
|
ch->caps.fmtlist = ch->fmtlist;
|
|
ch->bit16 = 1;
|
|
ch->bit32 = 0;
|
|
ch->pcmrates[0] = 48000;
|
|
ch->pcmrates[1] = 0;
|
|
ch->stripecap = 0xff;
|
|
|
|
ret = 0;
|
|
channels = 0;
|
|
onlystereo = 1;
|
|
pinset = 0;
|
|
fmtcap = devinfo->supp_stream_formats;
|
|
pcmcap = devinfo->supp_pcm_size_rate;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
/* Check as is correct */
|
|
if (ch->as < 0)
|
|
break;
|
|
/* Cound only present DACs */
|
|
if (as[ch->as].dacs[ch->asindex][i] <= 0)
|
|
continue;
|
|
/* Ignore duplicates */
|
|
for (j = 0; j < ret; j++) {
|
|
if (ch->io[j] == as[ch->as].dacs[ch->asindex][i])
|
|
break;
|
|
}
|
|
if (j < ret)
|
|
continue;
|
|
|
|
w = hdaa_widget_get(devinfo, as[ch->as].dacs[ch->asindex][i]);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
cap = w->param.supp_stream_formats;
|
|
if (!HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap) &&
|
|
!HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap))
|
|
continue;
|
|
/* Many CODECs does not declare AC3 support on SPDIF.
|
|
I don't beleave that they doesn't support it! */
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap))
|
|
cap |= HDA_PARAM_SUPP_STREAM_FORMATS_AC3_MASK;
|
|
if (ret == 0) {
|
|
fmtcap = cap;
|
|
pcmcap = w->param.supp_pcm_size_rate;
|
|
} else {
|
|
fmtcap &= cap;
|
|
pcmcap &= w->param.supp_pcm_size_rate;
|
|
}
|
|
ch->io[ret++] = as[ch->as].dacs[ch->asindex][i];
|
|
ch->stripecap &= w->wclass.conv.stripecap;
|
|
/* Do not count redirection pin/dac channels. */
|
|
if (i == 15 && as[ch->as].hpredir >= 0)
|
|
continue;
|
|
channels += HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) + 1;
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) != 1)
|
|
onlystereo = 0;
|
|
pinset |= (1 << i);
|
|
}
|
|
ch->io[ret] = -1;
|
|
ch->channels = channels;
|
|
|
|
if (as[ch->as].fakeredir)
|
|
ret--;
|
|
/* Standard speaks only about stereo pins and playback, ... */
|
|
if ((!onlystereo) || as[ch->as].mixed)
|
|
pinset = 0;
|
|
/* ..., but there it gives us info about speakers layout. */
|
|
as[ch->as].pinset = pinset;
|
|
|
|
ch->supp_stream_formats = fmtcap;
|
|
ch->supp_pcm_size_rate = pcmcap;
|
|
|
|
/*
|
|
* 8bit = 0
|
|
* 16bit = 1
|
|
* 20bit = 2
|
|
* 24bit = 3
|
|
* 32bit = 4
|
|
*/
|
|
if (ret > 0) {
|
|
i = 0;
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(fmtcap)) {
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(pcmcap))
|
|
ch->bit16 = 1;
|
|
else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(pcmcap))
|
|
ch->bit16 = 0;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap))
|
|
ch->bit32 = 3;
|
|
else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap))
|
|
ch->bit32 = 2;
|
|
else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap))
|
|
ch->bit32 = 4;
|
|
if (!(devinfo->quirks & HDAA_QUIRK_FORCESTEREO)) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 1, 0);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 1, 0);
|
|
}
|
|
if (channels >= 2) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 2, 0);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 2, 0);
|
|
}
|
|
if (channels >= 3 && !onlystereo) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 0);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 0);
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 1);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 1);
|
|
}
|
|
if (channels >= 4) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 0);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 0);
|
|
if (!onlystereo) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 1);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 1);
|
|
}
|
|
}
|
|
if (channels >= 5 && !onlystereo) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 0);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 0);
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 1);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 1);
|
|
}
|
|
if (channels >= 6) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 1);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 1);
|
|
if (!onlystereo) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 0);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 0);
|
|
}
|
|
}
|
|
if (channels >= 7 && !onlystereo) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 0);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 0);
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 1);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 1);
|
|
}
|
|
if (channels >= 8) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 8, 1);
|
|
if (ch->bit32)
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 8, 1);
|
|
}
|
|
}
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(fmtcap)) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 2, 0);
|
|
if (channels >= 8) {
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 8, 0);
|
|
ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 8, 1);
|
|
}
|
|
}
|
|
ch->fmtlist[i] = 0;
|
|
i = 0;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 8000;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 11025;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 16000;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 22050;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 32000;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 44100;
|
|
/* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ(pcmcap)) */
|
|
ch->pcmrates[i++] = 48000;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 88200;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 96000;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 176400;
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(pcmcap))
|
|
ch->pcmrates[i++] = 192000;
|
|
/* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ(pcmcap)) */
|
|
ch->pcmrates[i] = 0;
|
|
if (i > 0) {
|
|
ch->caps.minspeed = ch->pcmrates[0];
|
|
ch->caps.maxspeed = ch->pcmrates[i - 1];
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
static void
|
|
hdaa_prepare_pcms(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_audio_as *as = devinfo->as;
|
|
int i, j, k, apdev = 0, ardev = 0, dpdev = 0, drdev = 0;
|
|
|
|
for (i = 0; i < devinfo->ascnt; i++) {
|
|
if (as[i].enable == 0)
|
|
continue;
|
|
if (as[i].dir == HDAA_CTL_IN) {
|
|
if (as[i].digital)
|
|
drdev++;
|
|
else
|
|
ardev++;
|
|
} else {
|
|
if (as[i].digital)
|
|
dpdev++;
|
|
else
|
|
apdev++;
|
|
}
|
|
}
|
|
devinfo->num_devs =
|
|
max(ardev, apdev) + max(drdev, dpdev);
|
|
devinfo->devs =
|
|
(struct hdaa_pcm_devinfo *)malloc(
|
|
devinfo->num_devs * sizeof(struct hdaa_pcm_devinfo),
|
|
M_HDAA, M_ZERO | M_NOWAIT);
|
|
if (devinfo->devs == NULL) {
|
|
device_printf(devinfo->dev,
|
|
"Unable to allocate memory for devices\n");
|
|
return;
|
|
}
|
|
for (i = 0; i < devinfo->num_devs; i++) {
|
|
devinfo->devs[i].index = i;
|
|
devinfo->devs[i].devinfo = devinfo;
|
|
devinfo->devs[i].playas = -1;
|
|
devinfo->devs[i].recas = -1;
|
|
devinfo->devs[i].digital = 255;
|
|
}
|
|
for (i = 0; i < devinfo->ascnt; i++) {
|
|
if (as[i].enable == 0)
|
|
continue;
|
|
for (j = 0; j < devinfo->num_devs; j++) {
|
|
if (devinfo->devs[j].digital != 255 &&
|
|
(!devinfo->devs[j].digital) !=
|
|
(!as[i].digital))
|
|
continue;
|
|
if (as[i].dir == HDAA_CTL_IN) {
|
|
if (devinfo->devs[j].recas >= 0)
|
|
continue;
|
|
devinfo->devs[j].recas = i;
|
|
} else {
|
|
if (devinfo->devs[j].playas >= 0)
|
|
continue;
|
|
devinfo->devs[j].playas = i;
|
|
}
|
|
as[i].pdevinfo = &devinfo->devs[j];
|
|
for (k = 0; k < as[i].num_chans; k++) {
|
|
devinfo->chans[as[i].chans[k]].pdevinfo =
|
|
&devinfo->devs[j];
|
|
}
|
|
devinfo->devs[j].digital = as[i].digital;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_create_pcms(struct hdaa_devinfo *devinfo)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < devinfo->num_devs; i++) {
|
|
struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i];
|
|
|
|
pdevinfo->dev = device_add_child(devinfo->dev, "pcm", -1);
|
|
device_set_ivars(pdevinfo->dev, (void *)pdevinfo);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_ctls(struct hdaa_pcm_devinfo *pdevinfo, const char *banner, uint32_t flag)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_audio_ctl *ctl;
|
|
char buf[64];
|
|
int i, j, printed = 0;
|
|
|
|
if (flag == 0) {
|
|
flag = ~(SOUND_MASK_VOLUME | SOUND_MASK_PCM |
|
|
SOUND_MASK_CD | SOUND_MASK_LINE | SOUND_MASK_RECLEV |
|
|
SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IGAIN |
|
|
SOUND_MASK_OGAIN | SOUND_MASK_IMIX | SOUND_MASK_MONITOR);
|
|
}
|
|
|
|
for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) {
|
|
if ((flag & (1 << j)) == 0)
|
|
continue;
|
|
i = 0;
|
|
printed = 0;
|
|
while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) {
|
|
if (ctl->enable == 0 ||
|
|
ctl->widget->enable == 0)
|
|
continue;
|
|
if (!((pdevinfo->playas >= 0 &&
|
|
ctl->widget->bindas == pdevinfo->playas) ||
|
|
(pdevinfo->recas >= 0 &&
|
|
ctl->widget->bindas == pdevinfo->recas) ||
|
|
(ctl->widget->bindas == -2 && pdevinfo->index == 0)))
|
|
continue;
|
|
if ((ctl->ossmask & (1 << j)) == 0)
|
|
continue;
|
|
|
|
if (printed == 0) {
|
|
if (banner != NULL) {
|
|
device_printf(pdevinfo->dev, "%s", banner);
|
|
} else {
|
|
device_printf(pdevinfo->dev, "Unknown Ctl");
|
|
}
|
|
printf(" (OSS: %s)",
|
|
hdaa_audio_ctl_ossmixer_mask2allname(1 << j,
|
|
buf, sizeof(buf)));
|
|
if (pdevinfo->ossmask & (1 << j)) {
|
|
printf(": %+d/%+ddB\n",
|
|
pdevinfo->minamp[j] / 4,
|
|
pdevinfo->maxamp[j] / 4);
|
|
} else
|
|
printf("\n");
|
|
printed = 1;
|
|
}
|
|
device_printf(pdevinfo->dev, " +- ctl %2d (nid %3d %s", i,
|
|
ctl->widget->nid,
|
|
(ctl->ndir == HDAA_CTL_IN)?"in ":"out");
|
|
if (ctl->ndir == HDAA_CTL_IN && ctl->ndir == ctl->dir)
|
|
printf(" %2d): ", ctl->index);
|
|
else
|
|
printf("): ");
|
|
if (ctl->step > 0) {
|
|
printf("%+d/%+ddB (%d steps)%s\n",
|
|
MINQDB(ctl) / 4,
|
|
MAXQDB(ctl) / 4,
|
|
ctl->step + 1,
|
|
ctl->mute?" + mute":"");
|
|
} else
|
|
printf("%s\n", ctl->mute?"mute":"");
|
|
}
|
|
}
|
|
if (printed)
|
|
device_printf(pdevinfo->dev, "\n");
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_audio_formats(device_t dev, uint32_t fcap, uint32_t pcmcap)
|
|
{
|
|
uint32_t cap;
|
|
|
|
cap = fcap;
|
|
if (cap != 0) {
|
|
device_printf(dev, " Stream cap: 0x%08x", cap);
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap))
|
|
printf(" AC3");
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap))
|
|
printf(" FLOAT32");
|
|
if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap))
|
|
printf(" PCM");
|
|
printf("\n");
|
|
}
|
|
cap = pcmcap;
|
|
if (cap != 0) {
|
|
device_printf(dev, " PCM cap: 0x%08x", cap);
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap))
|
|
printf(" 8");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap))
|
|
printf(" 16");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap))
|
|
printf(" 20");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap))
|
|
printf(" 24");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap))
|
|
printf(" 32");
|
|
printf(" bits,");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap))
|
|
printf(" 8");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap))
|
|
printf(" 11");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap))
|
|
printf(" 16");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap))
|
|
printf(" 22");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap))
|
|
printf(" 32");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap))
|
|
printf(" 44");
|
|
printf(" 48");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap))
|
|
printf(" 88");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap))
|
|
printf(" 96");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap))
|
|
printf(" 176");
|
|
if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap))
|
|
printf(" 192");
|
|
printf(" KHz\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_pin(struct hdaa_widget *w)
|
|
{
|
|
uint32_t pincap;
|
|
|
|
pincap = w->wclass.pin.cap;
|
|
|
|
device_printf(w->devinfo->dev, " Pin cap: 0x%08x", pincap);
|
|
if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap))
|
|
printf(" ISC");
|
|
if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap))
|
|
printf(" TRQD");
|
|
if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap))
|
|
printf(" PDC");
|
|
if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap))
|
|
printf(" HP");
|
|
if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap))
|
|
printf(" OUT");
|
|
if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap))
|
|
printf(" IN");
|
|
if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap))
|
|
printf(" BAL");
|
|
if (HDA_PARAM_PIN_CAP_HDMI(pincap))
|
|
printf(" HDMI");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) {
|
|
printf(" VREF[");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap))
|
|
printf(" 50");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap))
|
|
printf(" 80");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap))
|
|
printf(" 100");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap))
|
|
printf(" GROUND");
|
|
if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap))
|
|
printf(" HIZ");
|
|
printf(" ]");
|
|
}
|
|
if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap))
|
|
printf(" EAPD");
|
|
if (HDA_PARAM_PIN_CAP_DP(pincap))
|
|
printf(" DP");
|
|
if (HDA_PARAM_PIN_CAP_HBR(pincap))
|
|
printf(" HBR");
|
|
printf("\n");
|
|
device_printf(w->devinfo->dev, " Pin config: 0x%08x\n",
|
|
w->wclass.pin.config);
|
|
device_printf(w->devinfo->dev, " Pin control: 0x%08x", w->wclass.pin.ctrl);
|
|
if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE)
|
|
printf(" HP");
|
|
if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE)
|
|
printf(" IN");
|
|
if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE)
|
|
printf(" OUT");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) {
|
|
if ((w->wclass.pin.ctrl &
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03)
|
|
printf(" HBR");
|
|
else if ((w->wclass.pin.ctrl &
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0)
|
|
printf(" EPTs");
|
|
} else {
|
|
if ((w->wclass.pin.ctrl &
|
|
HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0)
|
|
printf(" VREFs");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf)
|
|
{
|
|
|
|
device_printf(w->devinfo->dev, "%2d %08x %-2d %-2d "
|
|
"%-13s %-5s %-7s %-10s %-7s %d%s\n",
|
|
w->nid, conf,
|
|
HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf),
|
|
HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf),
|
|
HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)],
|
|
HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)],
|
|
HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)],
|
|
HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)],
|
|
HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)],
|
|
HDA_CONFIG_DEFAULTCONF_MISC(conf),
|
|
(w->enable == 0)?" DISA":"");
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_pin_configs(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
|
|
device_printf(devinfo->dev, "nid 0x as seq "
|
|
"device conn jack loc color misc\n");
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
hdaa_dump_pin_config(w, w->wclass.pin.config);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_amp(device_t dev, uint32_t cap, const char *banner)
|
|
{
|
|
int offset, size, step;
|
|
|
|
offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap);
|
|
size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap);
|
|
step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap);
|
|
device_printf(dev, " %s amp: 0x%08x "
|
|
"mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n",
|
|
banner, cap,
|
|
HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap),
|
|
step, size, offset,
|
|
((0 - offset) * (size + 1)) / 4,
|
|
((step - offset) * (size + 1)) / 4);
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_nodes(struct hdaa_devinfo *devinfo)
|
|
{
|
|
struct hdaa_widget *w, *cw;
|
|
char buf[64];
|
|
int i, j;
|
|
|
|
device_printf(devinfo->dev, "\n");
|
|
device_printf(devinfo->dev, "Default parameters:\n");
|
|
hdaa_dump_audio_formats(devinfo->dev,
|
|
devinfo->supp_stream_formats,
|
|
devinfo->supp_pcm_size_rate);
|
|
hdaa_dump_amp(devinfo->dev, devinfo->inamp_cap, " Input");
|
|
hdaa_dump_amp(devinfo->dev, devinfo->outamp_cap, "Output");
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL) {
|
|
device_printf(devinfo->dev, "Ghost widget nid=%d\n", i);
|
|
continue;
|
|
}
|
|
device_printf(devinfo->dev, "\n");
|
|
device_printf(devinfo->dev, " nid: %d%s\n", w->nid,
|
|
(w->enable == 0) ? " [DISABLED]" : "");
|
|
device_printf(devinfo->dev, " Name: %s\n", w->name);
|
|
device_printf(devinfo->dev, " Widget cap: 0x%08x",
|
|
w->param.widget_cap);
|
|
if (w->param.widget_cap & 0x0ee1) {
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap))
|
|
printf(" LRSWAP");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap))
|
|
printf(" PWR");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap))
|
|
printf(" DIGITAL");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap))
|
|
printf(" UNSOL");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap))
|
|
printf(" PROC");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap))
|
|
printf(" STRIPE(x%d)",
|
|
1 << (fls(w->wclass.conv.stripecap) - 1));
|
|
j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap);
|
|
if (j == 1)
|
|
printf(" STEREO");
|
|
else if (j > 1)
|
|
printf(" %dCH", j + 1);
|
|
}
|
|
printf("\n");
|
|
if (w->bindas != -1) {
|
|
device_printf(devinfo->dev, " Association: %d (0x%04x)\n",
|
|
w->bindas, w->bindseqmask);
|
|
}
|
|
if (w->ossmask != 0 || w->ossdev >= 0) {
|
|
device_printf(devinfo->dev, " OSS: %s",
|
|
hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf)));
|
|
if (w->ossdev >= 0)
|
|
printf(" (%s)", ossnames[w->ossdev]);
|
|
printf("\n");
|
|
}
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT ||
|
|
w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) {
|
|
hdaa_dump_audio_formats(devinfo->dev,
|
|
w->param.supp_stream_formats,
|
|
w->param.supp_pcm_size_rate);
|
|
} else if (w->type ==
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin)
|
|
hdaa_dump_pin(w);
|
|
if (w->param.eapdbtl != HDA_INVALID)
|
|
device_printf(devinfo->dev, " EAPD: 0x%08x\n",
|
|
w->param.eapdbtl);
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) &&
|
|
w->param.outamp_cap != 0)
|
|
hdaa_dump_amp(devinfo->dev, w->param.outamp_cap, "Output");
|
|
if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) &&
|
|
w->param.inamp_cap != 0)
|
|
hdaa_dump_amp(devinfo->dev, w->param.inamp_cap, " Input");
|
|
if (w->nconns > 0)
|
|
device_printf(devinfo->dev, " Connections: %d\n", w->nconns);
|
|
for (j = 0; j < w->nconns; j++) {
|
|
cw = hdaa_widget_get(devinfo, w->conns[j]);
|
|
device_printf(devinfo->dev, " + %s<- nid=%d [%s]",
|
|
(w->connsenable[j] == 0)?"[DISABLED] ":"",
|
|
w->conns[j], (cw == NULL) ? "GHOST!" : cw->name);
|
|
if (cw == NULL)
|
|
printf(" [UNKNOWN]");
|
|
else if (cw->enable == 0)
|
|
printf(" [DISABLED]");
|
|
if (w->nconns > 1 && w->selconn == j && w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)
|
|
printf(" (selected)");
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_dst_nid(struct hdaa_pcm_devinfo *pdevinfo, nid_t nid, int depth)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w, *cw;
|
|
char buf[64];
|
|
int i;
|
|
|
|
if (depth > HDA_PARSE_MAXDEPTH)
|
|
return;
|
|
|
|
w = hdaa_widget_get(devinfo, nid);
|
|
if (w == NULL || w->enable == 0)
|
|
return;
|
|
|
|
if (depth == 0)
|
|
device_printf(pdevinfo->dev, "%*s", 4, "");
|
|
else
|
|
device_printf(pdevinfo->dev, "%*s + <- ", 4 + (depth - 1) * 7, "");
|
|
printf("nid=%d [%s]", w->nid, w->name);
|
|
|
|
if (depth > 0) {
|
|
if (w->ossmask == 0) {
|
|
printf("\n");
|
|
return;
|
|
}
|
|
printf(" [src: %s]",
|
|
hdaa_audio_ctl_ossmixer_mask2allname(
|
|
w->ossmask, buf, sizeof(buf)));
|
|
if (w->ossdev >= 0) {
|
|
printf("\n");
|
|
return;
|
|
}
|
|
}
|
|
printf("\n");
|
|
|
|
for (i = 0; i < w->nconns; i++) {
|
|
if (w->connsenable[i] == 0)
|
|
continue;
|
|
cw = hdaa_widget_get(devinfo, w->conns[i]);
|
|
if (cw == NULL || cw->enable == 0 || cw->bindas == -1)
|
|
continue;
|
|
hdaa_dump_dst_nid(pdevinfo, w->conns[i], depth + 1);
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_dac(struct hdaa_pcm_devinfo *pdevinfo)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_audio_as *as;
|
|
struct hdaa_widget *w;
|
|
nid_t *nids;
|
|
int chid, i;
|
|
|
|
if (pdevinfo->playas < 0)
|
|
return;
|
|
|
|
device_printf(pdevinfo->dev, "Playback:\n");
|
|
|
|
chid = devinfo->as[pdevinfo->playas].chans[0];
|
|
hdaa_dump_audio_formats(pdevinfo->dev,
|
|
devinfo->chans[chid].supp_stream_formats,
|
|
devinfo->chans[chid].supp_pcm_size_rate);
|
|
for (i = 0; i < devinfo->as[pdevinfo->playas].num_chans; i++) {
|
|
chid = devinfo->as[pdevinfo->playas].chans[i];
|
|
device_printf(pdevinfo->dev, " DAC:");
|
|
for (nids = devinfo->chans[chid].io; *nids != -1; nids++)
|
|
printf(" %d", *nids);
|
|
printf("\n");
|
|
}
|
|
|
|
as = &devinfo->as[pdevinfo->playas];
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->pins[i] <= 0)
|
|
continue;
|
|
w = hdaa_widget_get(devinfo, as->pins[i]);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
device_printf(pdevinfo->dev, "\n");
|
|
hdaa_dump_dst_nid(pdevinfo, as->pins[i], 0);
|
|
}
|
|
device_printf(pdevinfo->dev, "\n");
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_adc(struct hdaa_pcm_devinfo *pdevinfo)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w;
|
|
nid_t *nids;
|
|
int chid, i;
|
|
|
|
if (pdevinfo->recas < 0)
|
|
return;
|
|
|
|
device_printf(pdevinfo->dev, "Record:\n");
|
|
|
|
chid = devinfo->as[pdevinfo->recas].chans[0];
|
|
hdaa_dump_audio_formats(pdevinfo->dev,
|
|
devinfo->chans[chid].supp_stream_formats,
|
|
devinfo->chans[chid].supp_pcm_size_rate);
|
|
for (i = 0; i < devinfo->as[pdevinfo->recas].num_chans; i++) {
|
|
chid = devinfo->as[pdevinfo->recas].chans[i];
|
|
device_printf(pdevinfo->dev, " ADC:");
|
|
for (nids = devinfo->chans[chid].io; *nids != -1; nids++)
|
|
printf(" %d", *nids);
|
|
printf("\n");
|
|
}
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT)
|
|
continue;
|
|
if (w->bindas != pdevinfo->recas)
|
|
continue;
|
|
device_printf(pdevinfo->dev, "\n");
|
|
hdaa_dump_dst_nid(pdevinfo, i, 0);
|
|
}
|
|
device_printf(pdevinfo->dev, "\n");
|
|
}
|
|
|
|
static void
|
|
hdaa_dump_mix(struct hdaa_pcm_devinfo *pdevinfo)
|
|
{
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_widget *w;
|
|
int i;
|
|
int printed = 0;
|
|
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0)
|
|
continue;
|
|
if (w->ossdev != SOUND_MIXER_IMIX)
|
|
continue;
|
|
if (w->bindas != pdevinfo->recas)
|
|
continue;
|
|
if (printed == 0) {
|
|
printed = 1;
|
|
device_printf(pdevinfo->dev, "Input Mix:\n");
|
|
}
|
|
device_printf(pdevinfo->dev, "\n");
|
|
hdaa_dump_dst_nid(pdevinfo, i, 0);
|
|
}
|
|
if (printed)
|
|
device_printf(pdevinfo->dev, "\n");
|
|
}
|
|
|
|
static void
|
|
hdaa_pindump(device_t dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
struct hdaa_widget *w;
|
|
uint32_t res, pincap, delay;
|
|
int i;
|
|
|
|
device_printf(dev, "Dumping AFG pins:\n");
|
|
device_printf(dev, "nid 0x as seq "
|
|
"device conn jack loc color misc\n");
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
hdaa_dump_pin_config(w, w->wclass.pin.config);
|
|
pincap = w->wclass.pin.cap;
|
|
device_printf(dev, " Caps: %2s %3s %2s %4s %4s",
|
|
HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)?"IN":"",
|
|
HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)?"OUT":"",
|
|
HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)?"HP":"",
|
|
HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)?"EAPD":"",
|
|
HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)?"VREF":"");
|
|
if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap) ||
|
|
HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) {
|
|
if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) {
|
|
delay = 0;
|
|
hda_command(dev,
|
|
HDA_CMD_SET_PIN_SENSE(0, w->nid, 0));
|
|
do {
|
|
res = hda_command(dev,
|
|
HDA_CMD_GET_PIN_SENSE(0, w->nid));
|
|
if (res != 0x7fffffff && res != 0xffffffff)
|
|
break;
|
|
DELAY(10);
|
|
} while (++delay < 10000);
|
|
} else {
|
|
delay = 0;
|
|
res = hda_command(dev, HDA_CMD_GET_PIN_SENSE(0,
|
|
w->nid));
|
|
}
|
|
printf(" Sense: 0x%08x (%sconnected%s)", res,
|
|
(res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ?
|
|
"" : "dis",
|
|
(HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap) &&
|
|
(res & HDA_CMD_GET_PIN_SENSE_ELD_VALID)) ?
|
|
", ELD valid" : "");
|
|
if (delay > 0)
|
|
printf(" delay %dus", delay * 10);
|
|
}
|
|
printf("\n");
|
|
}
|
|
device_printf(dev,
|
|
"NumGPIO=%d NumGPO=%d NumGPI=%d GPIWake=%d GPIUnsol=%d\n",
|
|
HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap),
|
|
HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap));
|
|
hdaa_dump_gpi(devinfo);
|
|
hdaa_dump_gpio(devinfo);
|
|
hdaa_dump_gpo(devinfo);
|
|
}
|
|
|
|
static void
|
|
hdaa_configure(device_t dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
struct hdaa_audio_ctl *ctl;
|
|
int i;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Applying built-in patches...\n");
|
|
);
|
|
hdaa_patch(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Applying local patches...\n");
|
|
);
|
|
hdaa_local_patch(devinfo);
|
|
hdaa_audio_postprocess(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Parsing Ctls...\n");
|
|
);
|
|
hdaa_audio_ctl_parse(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Disabling nonaudio...\n");
|
|
);
|
|
hdaa_audio_disable_nonaudio(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Disabling useless...\n");
|
|
);
|
|
hdaa_audio_disable_useless(devinfo);
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(dev, "Patched pins configuration:\n");
|
|
hdaa_dump_pin_configs(devinfo);
|
|
);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Parsing pin associations...\n");
|
|
);
|
|
hdaa_audio_as_parse(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Building AFG tree...\n");
|
|
);
|
|
hdaa_audio_build_tree(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Disabling unassociated "
|
|
"widgets...\n");
|
|
);
|
|
hdaa_audio_disable_unas(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Disabling nonselected "
|
|
"inputs...\n");
|
|
);
|
|
hdaa_audio_disable_notselected(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Disabling useless...\n");
|
|
);
|
|
hdaa_audio_disable_useless(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Disabling "
|
|
"crossassociatement connections...\n");
|
|
);
|
|
hdaa_audio_disable_crossas(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Disabling useless...\n");
|
|
);
|
|
hdaa_audio_disable_useless(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Binding associations to channels...\n");
|
|
);
|
|
hdaa_audio_bind_as(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Assigning names to signal sources...\n");
|
|
);
|
|
hdaa_audio_assign_names(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Preparing PCM devices...\n");
|
|
);
|
|
hdaa_prepare_pcms(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Assigning mixers to the tree...\n");
|
|
);
|
|
hdaa_audio_assign_mixers(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Preparing pin controls...\n");
|
|
);
|
|
hdaa_audio_prepare_pin_ctrl(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "AFG commit...\n");
|
|
);
|
|
hdaa_audio_commit(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Applying direct built-in patches...\n");
|
|
);
|
|
hdaa_patch_direct(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Pin sense init...\n");
|
|
);
|
|
hdaa_sense_init(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Creating PCM devices...\n");
|
|
);
|
|
hdaa_create_pcms(devinfo);
|
|
|
|
HDA_BOOTVERBOSE(
|
|
if (devinfo->quirks != 0) {
|
|
device_printf(dev, "FG config/quirks:");
|
|
for (i = 0; i < nitems(hdaa_quirks_tab); i++) {
|
|
if ((devinfo->quirks &
|
|
hdaa_quirks_tab[i].value) ==
|
|
hdaa_quirks_tab[i].value)
|
|
printf(" %s", hdaa_quirks_tab[i].key);
|
|
}
|
|
printf("\n");
|
|
}
|
|
);
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "\n");
|
|
device_printf(dev, "+-----------+\n");
|
|
device_printf(dev, "| HDA NODES |\n");
|
|
device_printf(dev, "+-----------+\n");
|
|
hdaa_dump_nodes(devinfo);
|
|
|
|
device_printf(dev, "\n");
|
|
device_printf(dev, "+----------------+\n");
|
|
device_printf(dev, "| HDA AMPLIFIERS |\n");
|
|
device_printf(dev, "+----------------+\n");
|
|
device_printf(dev, "\n");
|
|
i = 0;
|
|
while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) {
|
|
device_printf(dev, "%3d: nid %3d %s (%s) index %d", i,
|
|
(ctl->widget != NULL) ? ctl->widget->nid : -1,
|
|
(ctl->ndir == HDAA_CTL_IN)?"in ":"out",
|
|
(ctl->dir == HDAA_CTL_IN)?"in ":"out",
|
|
ctl->index);
|
|
if (ctl->childwidget != NULL)
|
|
printf(" cnid %3d", ctl->childwidget->nid);
|
|
else
|
|
printf(" ");
|
|
printf(" ossmask=0x%08x\n",
|
|
ctl->ossmask);
|
|
device_printf(dev,
|
|
" mute: %d step: %3d size: %3d off: %3d%s\n",
|
|
ctl->mute, ctl->step, ctl->size, ctl->offset,
|
|
(ctl->enable == 0) ? " [DISABLED]" :
|
|
((ctl->ossmask == 0) ? " [UNUSED]" : ""));
|
|
}
|
|
device_printf(dev, "\n");
|
|
);
|
|
}
|
|
|
|
static void
|
|
hdaa_unconfigure(device_t dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
struct hdaa_widget *w;
|
|
int i, j;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Pin sense deinit...\n");
|
|
);
|
|
hdaa_sense_deinit(devinfo);
|
|
free(devinfo->ctl, M_HDAA);
|
|
devinfo->ctl = NULL;
|
|
devinfo->ctlcnt = 0;
|
|
free(devinfo->as, M_HDAA);
|
|
devinfo->as = NULL;
|
|
devinfo->ascnt = 0;
|
|
free(devinfo->devs, M_HDAA);
|
|
devinfo->devs = NULL;
|
|
devinfo->num_devs = 0;
|
|
free(devinfo->chans, M_HDAA);
|
|
devinfo->chans = NULL;
|
|
devinfo->num_chans = 0;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL)
|
|
continue;
|
|
w->enable = 1;
|
|
w->selconn = -1;
|
|
w->pflags = 0;
|
|
w->bindas = -1;
|
|
w->bindseqmask = 0;
|
|
w->ossdev = -1;
|
|
w->ossmask = 0;
|
|
for (j = 0; j < w->nconns; j++)
|
|
w->connsenable[j] = 1;
|
|
if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
w->wclass.pin.config = w->wclass.pin.newconf;
|
|
if (w->eld != NULL) {
|
|
w->eld_len = 0;
|
|
free(w->eld, M_HDAA);
|
|
w->eld = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_gpi_state(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct hdaa_devinfo *devinfo = oidp->oid_arg1;
|
|
device_t dev = devinfo->dev;
|
|
char buf[256];
|
|
int n = 0, i, numgpi;
|
|
uint32_t data = 0;
|
|
|
|
buf[0] = 0;
|
|
hdaa_lock(devinfo);
|
|
numgpi = HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap);
|
|
if (numgpi > 0) {
|
|
data = hda_command(dev,
|
|
HDA_CMD_GET_GPI_DATA(0, devinfo->nid));
|
|
}
|
|
hdaa_unlock(devinfo);
|
|
for (i = 0; i < numgpi; i++) {
|
|
n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d",
|
|
n != 0 ? " " : "", i, ((data >> i) & 1));
|
|
}
|
|
return (sysctl_handle_string(oidp, buf, sizeof(buf), req));
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_gpio_state(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct hdaa_devinfo *devinfo = oidp->oid_arg1;
|
|
device_t dev = devinfo->dev;
|
|
char buf[256];
|
|
int n = 0, i, numgpio;
|
|
uint32_t data = 0, enable = 0, dir = 0;
|
|
|
|
buf[0] = 0;
|
|
hdaa_lock(devinfo);
|
|
numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap);
|
|
if (numgpio > 0) {
|
|
data = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_DATA(0, devinfo->nid));
|
|
enable = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid));
|
|
dir = hda_command(dev,
|
|
HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid));
|
|
}
|
|
hdaa_unlock(devinfo);
|
|
for (i = 0; i < numgpio; i++) {
|
|
n += snprintf(buf + n, sizeof(buf) - n, "%s%d=",
|
|
n != 0 ? " " : "", i);
|
|
if ((enable & (1 << i)) == 0) {
|
|
n += snprintf(buf + n, sizeof(buf) - n, "disabled");
|
|
continue;
|
|
}
|
|
n += snprintf(buf + n, sizeof(buf) - n, "%sput(%d)",
|
|
((dir >> i) & 1) ? "out" : "in", ((data >> i) & 1));
|
|
}
|
|
return (sysctl_handle_string(oidp, buf, sizeof(buf), req));
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_gpio_config(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct hdaa_devinfo *devinfo = oidp->oid_arg1;
|
|
char buf[256];
|
|
int error, n = 0, i, numgpio;
|
|
uint32_t gpio, x;
|
|
|
|
gpio = devinfo->newgpio;
|
|
numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap);
|
|
buf[0] = 0;
|
|
for (i = 0; i < numgpio; i++) {
|
|
x = (gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i);
|
|
n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s",
|
|
n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]);
|
|
}
|
|
error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
|
|
if (error != 0 || req->newptr == NULL)
|
|
return (error);
|
|
if (strncmp(buf, "0x", 2) == 0)
|
|
gpio = strtol(buf + 2, NULL, 16);
|
|
else
|
|
gpio = hdaa_gpio_patch(gpio, buf);
|
|
hdaa_lock(devinfo);
|
|
devinfo->newgpio = devinfo->gpio = gpio;
|
|
hdaa_gpio_commit(devinfo);
|
|
hdaa_unlock(devinfo);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_gpo_state(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct hdaa_devinfo *devinfo = oidp->oid_arg1;
|
|
device_t dev = devinfo->dev;
|
|
char buf[256];
|
|
int n = 0, i, numgpo;
|
|
uint32_t data = 0;
|
|
|
|
buf[0] = 0;
|
|
hdaa_lock(devinfo);
|
|
numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap);
|
|
if (numgpo > 0) {
|
|
data = hda_command(dev,
|
|
HDA_CMD_GET_GPO_DATA(0, devinfo->nid));
|
|
}
|
|
hdaa_unlock(devinfo);
|
|
for (i = 0; i < numgpo; i++) {
|
|
n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d",
|
|
n != 0 ? " " : "", i, ((data >> i) & 1));
|
|
}
|
|
return (sysctl_handle_string(oidp, buf, sizeof(buf), req));
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_gpo_config(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct hdaa_devinfo *devinfo = oidp->oid_arg1;
|
|
char buf[256];
|
|
int error, n = 0, i, numgpo;
|
|
uint32_t gpo, x;
|
|
|
|
gpo = devinfo->newgpo;
|
|
numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap);
|
|
buf[0] = 0;
|
|
for (i = 0; i < numgpo; i++) {
|
|
x = (gpo & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i);
|
|
n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s",
|
|
n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]);
|
|
}
|
|
error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
|
|
if (error != 0 || req->newptr == NULL)
|
|
return (error);
|
|
if (strncmp(buf, "0x", 2) == 0)
|
|
gpo = strtol(buf + 2, NULL, 16);
|
|
else
|
|
gpo = hdaa_gpio_patch(gpo, buf);
|
|
hdaa_lock(devinfo);
|
|
devinfo->newgpo = devinfo->gpo = gpo;
|
|
hdaa_gpo_commit(devinfo);
|
|
hdaa_unlock(devinfo);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_reconfig(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
device_t dev;
|
|
struct hdaa_devinfo *devinfo;
|
|
int error, val;
|
|
|
|
dev = oidp->oid_arg1;
|
|
devinfo = device_get_softc(dev);
|
|
if (devinfo == NULL)
|
|
return (EINVAL);
|
|
val = 0;
|
|
error = sysctl_handle_int(oidp, &val, 0, req);
|
|
if (error != 0 || req->newptr == NULL || val == 0)
|
|
return (error);
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Reconfiguration...\n");
|
|
);
|
|
if ((error = device_delete_children(dev)) != 0)
|
|
return (error);
|
|
hdaa_lock(devinfo);
|
|
hdaa_unconfigure(dev);
|
|
hdaa_configure(dev);
|
|
hdaa_unlock(devinfo);
|
|
bus_generic_attach(dev);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Reconfiguration done\n");
|
|
);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_suspend(device_t dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
int i;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Suspend...\n");
|
|
);
|
|
hdaa_lock(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Stop streams...\n");
|
|
);
|
|
for (i = 0; i < devinfo->num_chans; i++) {
|
|
if (devinfo->chans[i].flags & HDAA_CHN_RUNNING) {
|
|
devinfo->chans[i].flags |= HDAA_CHN_SUSPEND;
|
|
hdaa_channel_stop(&devinfo->chans[i]);
|
|
}
|
|
}
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Power down FG"
|
|
" nid=%d to the D3 state...\n",
|
|
devinfo->nid);
|
|
);
|
|
hda_command(devinfo->dev,
|
|
HDA_CMD_SET_POWER_STATE(0,
|
|
devinfo->nid, HDA_CMD_POWER_STATE_D3));
|
|
callout_stop(&devinfo->poll_jack);
|
|
hdaa_unlock(devinfo);
|
|
callout_drain(&devinfo->poll_jack);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Suspend done\n");
|
|
);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_resume(device_t dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
int i;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Resume...\n");
|
|
);
|
|
hdaa_lock(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Power up audio FG nid=%d...\n",
|
|
devinfo->nid);
|
|
);
|
|
hdaa_powerup(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "AFG commit...\n");
|
|
);
|
|
hdaa_audio_commit(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Applying direct built-in patches...\n");
|
|
);
|
|
hdaa_patch_direct(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Pin sense init...\n");
|
|
);
|
|
hdaa_sense_init(devinfo);
|
|
|
|
hdaa_unlock(devinfo);
|
|
for (i = 0; i < devinfo->num_devs; i++) {
|
|
struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i];
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(pdevinfo->dev,
|
|
"OSS mixer reinitialization...\n");
|
|
);
|
|
if (mixer_reinit(pdevinfo->dev) == -1)
|
|
device_printf(pdevinfo->dev,
|
|
"unable to reinitialize the mixer\n");
|
|
}
|
|
hdaa_lock(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Start streams...\n");
|
|
);
|
|
for (i = 0; i < devinfo->num_chans; i++) {
|
|
if (devinfo->chans[i].flags & HDAA_CHN_SUSPEND) {
|
|
devinfo->chans[i].flags &= ~HDAA_CHN_SUSPEND;
|
|
hdaa_channel_start(&devinfo->chans[i]);
|
|
}
|
|
}
|
|
hdaa_unlock(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Resume done\n");
|
|
);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_probe(device_t dev)
|
|
{
|
|
const char *pdesc;
|
|
char buf[128];
|
|
|
|
if (hda_get_node_type(dev) != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO)
|
|
return (ENXIO);
|
|
pdesc = device_get_desc(device_get_parent(dev));
|
|
snprintf(buf, sizeof(buf), "%.*s Audio Function Group",
|
|
(int)(strlen(pdesc) - 10), pdesc);
|
|
device_set_desc_copy(dev, buf);
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
static int
|
|
hdaa_attach(device_t dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
uint32_t res;
|
|
nid_t nid = hda_get_node_id(dev);
|
|
|
|
devinfo->dev = dev;
|
|
devinfo->lock = HDAC_GET_MTX(device_get_parent(dev), dev);
|
|
devinfo->nid = nid;
|
|
devinfo->newquirks = -1;
|
|
devinfo->newgpio = -1;
|
|
devinfo->newgpo = -1;
|
|
callout_init(&devinfo->poll_jack, 1);
|
|
devinfo->poll_ival = hz;
|
|
|
|
hdaa_lock(devinfo);
|
|
res = hda_command(dev,
|
|
HDA_CMD_GET_PARAMETER(0 , nid, HDA_PARAM_SUB_NODE_COUNT));
|
|
hdaa_unlock(devinfo);
|
|
|
|
devinfo->nodecnt = HDA_PARAM_SUB_NODE_COUNT_TOTAL(res);
|
|
devinfo->startnode = HDA_PARAM_SUB_NODE_COUNT_START(res);
|
|
devinfo->endnode = devinfo->startnode + devinfo->nodecnt;
|
|
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(dev, "Subsystem ID: 0x%08x\n",
|
|
hda_get_subsystem_id(dev));
|
|
);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev,
|
|
"Audio Function Group at nid=%d: %d subnodes %d-%d\n",
|
|
nid, devinfo->nodecnt,
|
|
devinfo->startnode, devinfo->endnode - 1);
|
|
);
|
|
|
|
if (devinfo->nodecnt > 0)
|
|
devinfo->widget = (struct hdaa_widget *)malloc(
|
|
sizeof(*(devinfo->widget)) * devinfo->nodecnt, M_HDAA,
|
|
M_WAITOK | M_ZERO);
|
|
else
|
|
devinfo->widget = NULL;
|
|
|
|
hdaa_lock(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Powering up...\n");
|
|
);
|
|
hdaa_powerup(devinfo);
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Parsing audio FG...\n");
|
|
);
|
|
hdaa_audio_parse(devinfo);
|
|
HDA_BOOTVERBOSE(
|
|
device_printf(dev, "Original pins configuration:\n");
|
|
hdaa_dump_pin_configs(devinfo);
|
|
);
|
|
hdaa_configure(dev);
|
|
hdaa_unlock(devinfo);
|
|
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE,
|
|
&devinfo->newquirks, 0, hdaa_sysctl_quirks, "A",
|
|
"Configuration options");
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"gpi_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
|
|
devinfo, 0, hdaa_sysctl_gpi_state, "A", "GPI state");
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"gpio_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
|
|
devinfo, 0, hdaa_sysctl_gpio_state, "A", "GPIO state");
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"gpio_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE,
|
|
devinfo, 0, hdaa_sysctl_gpio_config, "A", "GPIO configuration");
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"gpo_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
|
|
devinfo, 0, hdaa_sysctl_gpo_state, "A", "GPO state");
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"gpo_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE,
|
|
devinfo, 0, hdaa_sysctl_gpo_config, "A", "GPO configuration");
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"reconfig", CTLTYPE_INT | CTLFLAG_RW,
|
|
dev, 0, hdaa_sysctl_reconfig, "I", "Reprocess configuration");
|
|
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"init_clear", CTLFLAG_RW,
|
|
&devinfo->init_clear, 1,"Clear initial pin widget configuration");
|
|
bus_generic_attach(dev);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_detach(device_t dev)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
int error;
|
|
|
|
if ((error = device_delete_children(dev)) != 0)
|
|
return (error);
|
|
|
|
hdaa_lock(devinfo);
|
|
hdaa_unconfigure(dev);
|
|
devinfo->poll_ival = 0;
|
|
callout_stop(&devinfo->poll_jack);
|
|
hdaa_unlock(devinfo);
|
|
callout_drain(&devinfo->poll_jack);
|
|
|
|
free(devinfo->widget, M_HDAA);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_print_child(device_t dev, device_t child)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
struct hdaa_pcm_devinfo *pdevinfo =
|
|
(struct hdaa_pcm_devinfo *)device_get_ivars(child);
|
|
struct hdaa_audio_as *as;
|
|
int retval, first = 1, i;
|
|
|
|
retval = bus_print_child_header(dev, child);
|
|
retval += printf(" at nid ");
|
|
if (pdevinfo->playas >= 0) {
|
|
as = &devinfo->as[pdevinfo->playas];
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->pins[i] <= 0)
|
|
continue;
|
|
retval += printf("%s%d", first ? "" : ",", as->pins[i]);
|
|
first = 0;
|
|
}
|
|
}
|
|
if (pdevinfo->recas >= 0) {
|
|
if (pdevinfo->playas >= 0) {
|
|
retval += printf(" and ");
|
|
first = 1;
|
|
}
|
|
as = &devinfo->as[pdevinfo->recas];
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->pins[i] <= 0)
|
|
continue;
|
|
retval += printf("%s%d", first ? "" : ",", as->pins[i]);
|
|
first = 0;
|
|
}
|
|
}
|
|
retval += bus_print_child_footer(dev, child);
|
|
|
|
return (retval);
|
|
}
|
|
|
|
static int
|
|
hdaa_child_location_str(device_t dev, device_t child, char *buf,
|
|
size_t buflen)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
struct hdaa_pcm_devinfo *pdevinfo =
|
|
(struct hdaa_pcm_devinfo *)device_get_ivars(child);
|
|
struct hdaa_audio_as *as;
|
|
int first = 1, i, len = 0;
|
|
|
|
len += snprintf(buf + len, buflen - len, "nid=");
|
|
if (pdevinfo->playas >= 0) {
|
|
as = &devinfo->as[pdevinfo->playas];
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->pins[i] <= 0)
|
|
continue;
|
|
len += snprintf(buf + len, buflen - len,
|
|
"%s%d", first ? "" : ",", as->pins[i]);
|
|
first = 0;
|
|
}
|
|
}
|
|
if (pdevinfo->recas >= 0) {
|
|
as = &devinfo->as[pdevinfo->recas];
|
|
for (i = 0; i < 16; i++) {
|
|
if (as->pins[i] <= 0)
|
|
continue;
|
|
len += snprintf(buf + len, buflen - len,
|
|
"%s%d", first ? "" : ",", as->pins[i]);
|
|
first = 0;
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
hdaa_stream_intr(device_t dev, int dir, int stream)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
struct hdaa_chan *ch;
|
|
int i;
|
|
|
|
for (i = 0; i < devinfo->num_chans; i++) {
|
|
ch = &devinfo->chans[i];
|
|
if (!(ch->flags & HDAA_CHN_RUNNING))
|
|
continue;
|
|
if (ch->dir == ((dir == 1) ? PCMDIR_PLAY : PCMDIR_REC) &&
|
|
ch->sid == stream) {
|
|
hdaa_unlock(devinfo);
|
|
chn_intr(ch->c);
|
|
hdaa_lock(devinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hdaa_unsol_intr(device_t dev, uint32_t resp)
|
|
{
|
|
struct hdaa_devinfo *devinfo = device_get_softc(dev);
|
|
struct hdaa_widget *w;
|
|
int i, tag, flags;
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Unsolicited response %08x\n", resp);
|
|
);
|
|
tag = resp >> 26;
|
|
for (i = devinfo->startnode; i < devinfo->endnode; i++) {
|
|
w = hdaa_widget_get(devinfo, i);
|
|
if (w == NULL || w->enable == 0 || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
if (w->unsol != tag)
|
|
continue;
|
|
if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) ||
|
|
HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap))
|
|
flags = resp & 0x03;
|
|
else
|
|
flags = 0x01;
|
|
if (flags & 0x01)
|
|
hdaa_presence_handler(w);
|
|
if (flags & 0x02)
|
|
hdaa_eld_handler(w);
|
|
}
|
|
}
|
|
|
|
static device_method_t hdaa_methods[] = {
|
|
/* device interface */
|
|
DEVMETHOD(device_probe, hdaa_probe),
|
|
DEVMETHOD(device_attach, hdaa_attach),
|
|
DEVMETHOD(device_detach, hdaa_detach),
|
|
DEVMETHOD(device_suspend, hdaa_suspend),
|
|
DEVMETHOD(device_resume, hdaa_resume),
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_print_child, hdaa_print_child),
|
|
DEVMETHOD(bus_child_location_str, hdaa_child_location_str),
|
|
DEVMETHOD(hdac_stream_intr, hdaa_stream_intr),
|
|
DEVMETHOD(hdac_unsol_intr, hdaa_unsol_intr),
|
|
DEVMETHOD(hdac_pindump, hdaa_pindump),
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t hdaa_driver = {
|
|
"hdaa",
|
|
hdaa_methods,
|
|
sizeof(struct hdaa_devinfo),
|
|
};
|
|
|
|
static devclass_t hdaa_devclass;
|
|
|
|
DRIVER_MODULE(snd_hda, hdacc, hdaa_driver, hdaa_devclass, NULL, NULL);
|
|
|
|
static void
|
|
hdaa_chan_formula(struct hdaa_devinfo *devinfo, int asid,
|
|
char *buf, int buflen)
|
|
{
|
|
struct hdaa_audio_as *as;
|
|
int c;
|
|
|
|
as = &devinfo->as[asid];
|
|
c = devinfo->chans[as->chans[0]].channels;
|
|
if (c == 1)
|
|
snprintf(buf, buflen, "mono");
|
|
else if (c == 2) {
|
|
if (as->hpredir < 0)
|
|
buf[0] = 0;
|
|
else
|
|
snprintf(buf, buflen, "2.0");
|
|
} else if (as->pinset == 0x0003)
|
|
snprintf(buf, buflen, "3.1");
|
|
else if (as->pinset == 0x0005 || as->pinset == 0x0011)
|
|
snprintf(buf, buflen, "4.0");
|
|
else if (as->pinset == 0x0007 || as->pinset == 0x0013)
|
|
snprintf(buf, buflen, "5.1");
|
|
else if (as->pinset == 0x0017)
|
|
snprintf(buf, buflen, "7.1");
|
|
else
|
|
snprintf(buf, buflen, "%dch", c);
|
|
if (as->hpredir >= 0)
|
|
strlcat(buf, "+HP", buflen);
|
|
}
|
|
|
|
static int
|
|
hdaa_chan_type(struct hdaa_devinfo *devinfo, int asid)
|
|
{
|
|
struct hdaa_audio_as *as;
|
|
struct hdaa_widget *w;
|
|
int i, t = -1, t1;
|
|
|
|
as = &devinfo->as[asid];
|
|
for (i = 0; i < 16; i++) {
|
|
w = hdaa_widget_get(devinfo, as->pins[i]);
|
|
if (w == NULL || w->enable == 0 || w->type !=
|
|
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
|
|
continue;
|
|
t1 = HDA_CONFIG_DEFAULTCONF_DEVICE(w->wclass.pin.config);
|
|
if (t == -1)
|
|
t = t1;
|
|
else if (t != t1) {
|
|
t = -2;
|
|
break;
|
|
}
|
|
}
|
|
return (t);
|
|
}
|
|
|
|
static int
|
|
hdaa_sysctl_32bit(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct hdaa_audio_as *as = (struct hdaa_audio_as *)oidp->oid_arg1;
|
|
struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo;
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_chan *ch;
|
|
int error, val, i;
|
|
uint32_t pcmcap;
|
|
|
|
ch = &devinfo->chans[as->chans[0]];
|
|
val = (ch->bit32 == 4) ? 32 : ((ch->bit32 == 3) ? 24 :
|
|
((ch->bit32 == 2) ? 20 : 0));
|
|
error = sysctl_handle_int(oidp, &val, 0, req);
|
|
if (error != 0 || req->newptr == NULL)
|
|
return (error);
|
|
pcmcap = ch->supp_pcm_size_rate;
|
|
if (val == 32 && HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap))
|
|
ch->bit32 = 4;
|
|
else if (val == 24 && HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap))
|
|
ch->bit32 = 3;
|
|
else if (val == 20 && HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap))
|
|
ch->bit32 = 2;
|
|
else
|
|
return (EINVAL);
|
|
for (i = 1; i < as->num_chans; i++)
|
|
devinfo->chans[as->chans[i]].bit32 = ch->bit32;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_pcm_probe(device_t dev)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo =
|
|
(struct hdaa_pcm_devinfo *)device_get_ivars(dev);
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
const char *pdesc;
|
|
char chans1[8], chans2[8];
|
|
char buf[128];
|
|
int loc1, loc2, t1, t2;
|
|
|
|
if (pdevinfo->playas >= 0)
|
|
loc1 = devinfo->as[pdevinfo->playas].location;
|
|
else
|
|
loc1 = devinfo->as[pdevinfo->recas].location;
|
|
if (pdevinfo->recas >= 0)
|
|
loc2 = devinfo->as[pdevinfo->recas].location;
|
|
else
|
|
loc2 = loc1;
|
|
if (loc1 != loc2)
|
|
loc1 = -2;
|
|
if (loc1 >= 0 && HDA_LOCS[loc1][0] == '0')
|
|
loc1 = -2;
|
|
chans1[0] = 0;
|
|
chans2[0] = 0;
|
|
t1 = t2 = -1;
|
|
if (pdevinfo->playas >= 0) {
|
|
hdaa_chan_formula(devinfo, pdevinfo->playas,
|
|
chans1, sizeof(chans1));
|
|
t1 = hdaa_chan_type(devinfo, pdevinfo->playas);
|
|
}
|
|
if (pdevinfo->recas >= 0) {
|
|
hdaa_chan_formula(devinfo, pdevinfo->recas,
|
|
chans2, sizeof(chans2));
|
|
t2 = hdaa_chan_type(devinfo, pdevinfo->recas);
|
|
}
|
|
if (chans1[0] != 0 || chans2[0] != 0) {
|
|
if (chans1[0] == 0 && pdevinfo->playas >= 0)
|
|
snprintf(chans1, sizeof(chans1), "2.0");
|
|
else if (chans2[0] == 0 && pdevinfo->recas >= 0)
|
|
snprintf(chans2, sizeof(chans2), "2.0");
|
|
if (strcmp(chans1, chans2) == 0)
|
|
chans2[0] = 0;
|
|
}
|
|
if (t1 == -1)
|
|
t1 = t2;
|
|
else if (t2 == -1)
|
|
t2 = t1;
|
|
if (t1 != t2)
|
|
t1 = -2;
|
|
if (pdevinfo->digital)
|
|
t1 = -2;
|
|
pdesc = device_get_desc(device_get_parent(dev));
|
|
snprintf(buf, sizeof(buf), "%.*s (%s%s%s%s%s%s%s%s%s)",
|
|
(int)(strlen(pdesc) - 21), pdesc,
|
|
loc1 >= 0 ? HDA_LOCS[loc1] : "", loc1 >= 0 ? " " : "",
|
|
(pdevinfo->digital == 0x7)?"HDMI/DP":
|
|
((pdevinfo->digital == 0x5)?"DisplayPort":
|
|
((pdevinfo->digital == 0x3)?"HDMI":
|
|
((pdevinfo->digital)?"Digital":"Analog"))),
|
|
chans1[0] ? " " : "", chans1,
|
|
chans2[0] ? "/" : "", chans2,
|
|
t1 >= 0 ? " " : "", t1 >= 0 ? HDA_DEVS[t1] : "");
|
|
device_set_desc_copy(dev, buf);
|
|
return (BUS_PROBE_SPECIFIC);
|
|
}
|
|
|
|
static int
|
|
hdaa_pcm_attach(device_t dev)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo =
|
|
(struct hdaa_pcm_devinfo *)device_get_ivars(dev);
|
|
struct hdaa_devinfo *devinfo = pdevinfo->devinfo;
|
|
struct hdaa_audio_as *as;
|
|
struct snddev_info *d;
|
|
char status[SND_STATUSLEN];
|
|
int i;
|
|
|
|
pdevinfo->chan_size = pcm_getbuffersize(dev,
|
|
HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_MAX);
|
|
|
|
HDA_BOOTVERBOSE(
|
|
hdaa_dump_dac(pdevinfo);
|
|
hdaa_dump_adc(pdevinfo);
|
|
hdaa_dump_mix(pdevinfo);
|
|
hdaa_dump_ctls(pdevinfo, "Master Volume", SOUND_MASK_VOLUME);
|
|
hdaa_dump_ctls(pdevinfo, "PCM Volume", SOUND_MASK_PCM);
|
|
hdaa_dump_ctls(pdevinfo, "CD Volume", SOUND_MASK_CD);
|
|
hdaa_dump_ctls(pdevinfo, "Microphone Volume", SOUND_MASK_MIC);
|
|
hdaa_dump_ctls(pdevinfo, "Microphone2 Volume", SOUND_MASK_MONITOR);
|
|
hdaa_dump_ctls(pdevinfo, "Line-in Volume", SOUND_MASK_LINE);
|
|
hdaa_dump_ctls(pdevinfo, "Speaker/Beep Volume", SOUND_MASK_SPEAKER);
|
|
hdaa_dump_ctls(pdevinfo, "Recording Level", SOUND_MASK_RECLEV);
|
|
hdaa_dump_ctls(pdevinfo, "Input Mix Level", SOUND_MASK_IMIX);
|
|
hdaa_dump_ctls(pdevinfo, "Input Monitoring Level", SOUND_MASK_IGAIN);
|
|
hdaa_dump_ctls(pdevinfo, NULL, 0);
|
|
);
|
|
|
|
if (resource_int_value(device_get_name(dev),
|
|
device_get_unit(dev), "blocksize", &i) == 0 && i > 0) {
|
|
i &= HDA_BLK_ALIGN;
|
|
if (i < HDA_BLK_MIN)
|
|
i = HDA_BLK_MIN;
|
|
pdevinfo->chan_blkcnt = pdevinfo->chan_size / i;
|
|
i = 0;
|
|
while (pdevinfo->chan_blkcnt >> i)
|
|
i++;
|
|
pdevinfo->chan_blkcnt = 1 << (i - 1);
|
|
if (pdevinfo->chan_blkcnt < HDA_BDL_MIN)
|
|
pdevinfo->chan_blkcnt = HDA_BDL_MIN;
|
|
else if (pdevinfo->chan_blkcnt > HDA_BDL_MAX)
|
|
pdevinfo->chan_blkcnt = HDA_BDL_MAX;
|
|
} else
|
|
pdevinfo->chan_blkcnt = HDA_BDL_DEFAULT;
|
|
|
|
/*
|
|
* We don't register interrupt handler with snd_setup_intr
|
|
* in pcm device. Mark pcm device as MPSAFE manually.
|
|
*/
|
|
pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "OSS mixer initialization...\n");
|
|
);
|
|
if (mixer_init(dev, &hdaa_audio_ctl_ossmixer_class, pdevinfo) != 0)
|
|
device_printf(dev, "Can't register mixer\n");
|
|
|
|
HDA_BOOTHVERBOSE(
|
|
device_printf(dev, "Registering PCM channels...\n");
|
|
);
|
|
if (pcm_register(dev, pdevinfo, (pdevinfo->playas >= 0)?1:0,
|
|
(pdevinfo->recas >= 0)?1:0) != 0)
|
|
device_printf(dev, "Can't register PCM\n");
|
|
|
|
pdevinfo->registered++;
|
|
|
|
d = device_get_softc(dev);
|
|
if (pdevinfo->playas >= 0) {
|
|
as = &devinfo->as[pdevinfo->playas];
|
|
for (i = 0; i < as->num_chans; i++)
|
|
pcm_addchan(dev, PCMDIR_PLAY, &hdaa_channel_class,
|
|
&devinfo->chans[as->chans[i]]);
|
|
SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
|
|
SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO,
|
|
"32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE,
|
|
as, sizeof(as), hdaa_sysctl_32bit, "I",
|
|
"Resolution of 32bit samples (20/24/32bit)");
|
|
}
|
|
if (pdevinfo->recas >= 0) {
|
|
as = &devinfo->as[pdevinfo->recas];
|
|
for (i = 0; i < as->num_chans; i++)
|
|
pcm_addchan(dev, PCMDIR_REC, &hdaa_channel_class,
|
|
&devinfo->chans[as->chans[i]]);
|
|
SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
|
|
SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO,
|
|
"32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE,
|
|
as, sizeof(as), hdaa_sysctl_32bit, "I",
|
|
"Resolution of 32bit samples (20/24/32bit)");
|
|
pdevinfo->autorecsrc = 2;
|
|
resource_int_value(device_get_name(dev), device_get_unit(dev),
|
|
"rec.autosrc", &pdevinfo->autorecsrc);
|
|
SYSCTL_ADD_INT(&d->rec_sysctl_ctx,
|
|
SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO,
|
|
"autosrc", CTLFLAG_RW,
|
|
&pdevinfo->autorecsrc, 0,
|
|
"Automatic recording source selection");
|
|
}
|
|
|
|
if (pdevinfo->mixer != NULL) {
|
|
hdaa_audio_ctl_set_defaults(pdevinfo);
|
|
hdaa_lock(devinfo);
|
|
if (pdevinfo->playas >= 0) {
|
|
as = &devinfo->as[pdevinfo->playas];
|
|
hdaa_channels_handler(as);
|
|
}
|
|
if (pdevinfo->recas >= 0) {
|
|
as = &devinfo->as[pdevinfo->recas];
|
|
hdaa_autorecsrc_handler(as, NULL);
|
|
hdaa_channels_handler(as);
|
|
}
|
|
hdaa_unlock(devinfo);
|
|
}
|
|
|
|
snprintf(status, SND_STATUSLEN, "on %s %s",
|
|
device_get_nameunit(device_get_parent(dev)),
|
|
PCM_KLDSTRING(snd_hda));
|
|
pcm_setstatus(dev, status);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hdaa_pcm_detach(device_t dev)
|
|
{
|
|
struct hdaa_pcm_devinfo *pdevinfo =
|
|
(struct hdaa_pcm_devinfo *)device_get_ivars(dev);
|
|
int err;
|
|
|
|
if (pdevinfo->registered > 0) {
|
|
err = pcm_unregister(dev);
|
|
if (err != 0)
|
|
return (err);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t hdaa_pcm_methods[] = {
|
|
/* device interface */
|
|
DEVMETHOD(device_probe, hdaa_pcm_probe),
|
|
DEVMETHOD(device_attach, hdaa_pcm_attach),
|
|
DEVMETHOD(device_detach, hdaa_pcm_detach),
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t hdaa_pcm_driver = {
|
|
"pcm",
|
|
hdaa_pcm_methods,
|
|
PCM_SOFTC_SIZE,
|
|
};
|
|
|
|
DRIVER_MODULE(snd_hda_pcm, hdaa, hdaa_pcm_driver, pcm_devclass, NULL, NULL);
|
|
MODULE_DEPEND(snd_hda, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
|
|
MODULE_VERSION(snd_hda, 1);
|