Rewrite jack presence detection and implement automatic recording source

selection in snd_hda(4) driver.

Now driver tracks jack presence detection status for every CODEC pin. For
playback associations, when configured, that information, same as before,
can be used to automatically redirect audio to headphones. Also same as
before, these events are used to track digital display connection status
and fetch ELD. Now in addition to that driver uses that information to
automatically switch recording source of the mixer to the connected input.

When there are devices with no jack detection and with one both connected,
last ones will have the precedence. As result, on most laptops after boot
internal microphone should be automatically selected. But if external one
(for example, headset) connected, it will be selected automatically.
When external mic disconnected, internal one will be selected again.

Automatic recording source selection is enabled by default now to make
recording work out of the box without touching mixer. But it can be
disabled or limited only to attach time using hint.pcm.X.rec.autosrc loader
tunables or dev.pcm.X.rec.autosrc sysctls.

MFC after:	2 months
Sponsored by:	iXsystems, Inc.
This commit is contained in:
Alexander Motin 2012-01-25 20:46:10 +00:00
parent 45efc9b4aa
commit d9360bbfc4
3 changed files with 228 additions and 150 deletions

View File

@ -25,7 +25,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd January 11, 2012
.Dd January 25, 2012
.Dt SND_HDA 4
.Os
.Sh NAME
@ -182,6 +182,18 @@ May be specified as a 32-bit hexadecimal value with a leading
or as a set of space-separated
.Dq Ar option Ns = Ns Ar value
pairs.
.It Va hint.pcm.%d.rec.autosrc
Controls automatic recording source feature:
.Bl -tag -compact
.It 0
disabled,
.It 1
once on attach,
.It 2
enabled.
.El
When enabled, driver will automatically set recording source of the mixer to
connected input using jack presence detection statuses.
.El
.Pp
Pin configuration is the UAA driver's main source of information about codec
@ -357,6 +369,16 @@ Original pin configuration written by BIOS.
Setting this to a non-zero value makes driver to destroy existing pcm devices
and process new pins configuration set via
.Va dev.hdaa.%d.nid%d_config.
.It Va dev.pcm.%d.play.32bit , dev.pcm.%d.rec.32bit
HDA controller uses 32bit representation for all samples of more then 16 bits.
These variables allow to specify how many bits of these 32 should be
used by CODEC.
Depending on codec capabilities, possible values are 20, 24 and 32 bit.
The default value is 24.
.It Va dev.pcm.%d.rec.autosrc
Run-time equivalent of the
.Va hint.pcm.%d.rec.autosrc
tunable.
.El
.Sh EXAMPLES
Taking HP Compaq DX2300 with Realtek ALC888 HDA codec for example.

View File

@ -239,44 +239,30 @@ hdaa_audio_ctl_amp_get(struct hdaa_devinfo *devinfo, nid_t nid, int dir,
}
/*
* Jack detection (Speaker/HP redirection) event handler.
* Headphones redirection change handler.
*/
static void
hdaa_hp_switch_handler(struct hdaa_widget *w)
hdaa_hpredir_handler(struct hdaa_widget *w)
{
struct hdaa_devinfo *devinfo = w->devinfo;
struct hdaa_audio_as *as;
struct hdaa_audio_as *as = &devinfo->as[w->bindas];
struct hdaa_widget *w1;
struct hdaa_audio_ctl *ctl;
uint32_t val, res;
int j;
uint32_t val;
int j, connected = w->wclass.pin.connected;
if (w->enable == 0 || w->type !=
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
return;
res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid));
HDA_BOOTVERBOSE(
device_printf(devinfo->dev,
"Pin sense: nid=%d sence=0x%08x", w->nid, res);
device_printf((as->pdevinfo && as->pdevinfo->dev) ?
as->pdevinfo->dev : devinfo->dev,
"Redirect output to: %s\n",
connected ? "headphones": "main");
);
res = (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) != 0;
if (devinfo->quirks & HDAA_QUIRK_SENSEINV)
res ^= 1;
HDA_BOOTVERBOSE(
printf(" (%sconnected)\n", res == 0 ? "dis" : "");
);
as = &devinfo->as[w->bindas];
if (as->hpredir < 0 || as->pins[15] != w->nid)
return;
/* (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 = (res != 0) ? 0 : 1;
val = connected ? 0 : 1;
if (val != ctl->forcemute) {
ctl->forcemute = val;
hdaa_audio_ctl_amp_set(ctl,
@ -285,7 +271,7 @@ hdaa_hp_switch_handler(struct hdaa_widget *w)
}
} else {
/* If there is no muter - disable pin output. */
if (res != 0)
if (connected)
val = w->wclass.pin.ctrl |
HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE;
else
@ -306,7 +292,7 @@ hdaa_hp_switch_handler(struct hdaa_widget *w)
as->pins[j], HDAA_CTL_IN, -1, 1);
if (ctl != NULL && ctl->mute) {
/* If pin has muter - use it. */
val = (res != 0) ? 1 : 0;
val = connected ? 1 : 0;
if (val == ctl->forcemute)
continue;
ctl->forcemute = val;
@ -317,9 +303,8 @@ hdaa_hp_switch_handler(struct hdaa_widget *w)
}
/* If there is no muter - disable pin output. */
w1 = hdaa_widget_get(devinfo, as->pins[j]);
if (w1 != NULL && w1->type ==
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) {
if (res != 0)
if (w1 != NULL) {
if (connected)
val = w1->wclass.pin.ctrl &
~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE;
else
@ -336,7 +321,117 @@ hdaa_hp_switch_handler(struct hdaa_widget *w)
}
/*
* Callback for poll based jack detection.
* 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;
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;
if (connected == w->wclass.pin.connected)
return;
w->wclass.pin.connected = connected;
HDA_BOOTVERBOSE(
device_printf(devinfo->dev,
"Pin sense: nid=%d sence=0x%08x (%sconnected)\n",
w->nid, res, !w->wclass.pin.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)
hdaa_autorecsrc_handler(as, w);
}
/*
* Callback for poll based presence detection.
*/
static void
hdaa_jack_poll_callback(void *arg)
@ -357,91 +452,13 @@ hdaa_jack_poll_callback(void *arg)
if (w == NULL || w->enable == 0 || w->type !=
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
continue;
hdaa_hp_switch_handler(w);
hdaa_presence_handler(w);
}
callout_reset(&devinfo->poll_jack, devinfo->poll_ival,
hdaa_jack_poll_callback, devinfo);
hdaa_unlock(devinfo);
}
/*
* Jack detection initializer.
*/
static void
hdaa_hp_switch_init(struct hdaa_devinfo *devinfo)
{
struct hdaa_audio_as *as = devinfo->as;
struct hdaa_widget *w;
int i, poll = 0;
for (i = 0; i < devinfo->ascnt; i++) {
if (as[i].hpredir < 0)
continue;
w = hdaa_widget_get(devinfo, as[i].pins[15]);
if (w == NULL || w->enable == 0 || w->type !=
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
continue;
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 jack detection support at pin %d\n",
as[i].pins[15]);
continue;
}
if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap) &&
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));
}
if (w->unsol < 0)
poll = 1;
HDA_BOOTVERBOSE(
device_printf(devinfo->dev,
"Headphones redirection "
"for as=%d nid=%d using %s.\n",
i, w->nid,
(poll != 0) ? "polling" : "unsolicited responses");
);
hdaa_hp_switch_handler(w);
}
if (poll) {
callout_reset(&devinfo->poll_jack, 1,
hdaa_jack_poll_callback, devinfo);
}
}
static void
hdaa_hp_switch_deinit(struct hdaa_devinfo *devinfo)
{
struct hdaa_audio_as *as = devinfo->as;
struct hdaa_widget *w;
int i;
for (i = 0; i < devinfo->ascnt; i++) {
w = hdaa_widget_get(devinfo, as[i].pins[15]);
if (w == NULL || w->enable == 0 || w->type !=
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
continue;
if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) ||
HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap))
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;
}
}
static void
hdaa_eld_dump(struct hdaa_widget *w)
{
@ -534,13 +551,18 @@ hdaa_eld_handler(struct hdaa_widget *w)
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;
}
res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid));
HDA_BOOTVERBOSE(
device_printf(devinfo->dev,
"Pin sense: nid=%d sence=0x%08x "
@ -575,49 +597,73 @@ hdaa_eld_handler(struct hdaa_widget *w)
);
}
/*
* Pin sense initializer.
*/
static void
hdaa_eld_init(struct hdaa_devinfo *devinfo)
hdaa_sense_init(struct hdaa_devinfo *devinfo)
{
struct hdaa_widget *w;
int i;
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->type !=
if (w == NULL || w->enable == 0 || w->type !=
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
continue;
if (!HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) &&
!HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap))
continue;
if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap) &&
w->unsol < 0) {
w->unsol = HDAC_UNSOL_ALLOC(
device_get_parent(devinfo->dev), devinfo->dev,
w->nid);
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));
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",
as[i].pins[15]);
} 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,
(poll != 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_eld_deinit(struct hdaa_devinfo *devinfo)
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->type !=
if (w == NULL || w->enable == 0 || w->type !=
HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)
continue;
if (!HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) &&
!HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap))
continue;
if (w->unsol < 0)
continue;
hda_command(devinfo->dev,
@ -1191,6 +1237,10 @@ hdaa_widget_postprocess(struct hdaa_widget *w)
}
strlcat(w->name, HDA_CONNS[conn], sizeof(w->name));
strlcat(w->name, ")", sizeof(w->name));
if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 ||
(HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0)
w->wclass.pin.connected = 2;
}
}
@ -5778,13 +5828,9 @@ hdaa_configure(device_t dev)
);
hdaa_patch_direct(devinfo);
HDA_BOOTHVERBOSE(
device_printf(dev, "ELD init...\n");
device_printf(dev, "Pin sense init...\n");
);
hdaa_eld_init(devinfo);
HDA_BOOTHVERBOSE(
device_printf(dev, "HP switch init...\n");
);
hdaa_hp_switch_init(devinfo);
hdaa_sense_init(devinfo);
HDA_BOOTHVERBOSE(
device_printf(dev, "Creating PCM devices...\n");
);
@ -5849,13 +5895,9 @@ hdaa_unconfigure(device_t dev)
int i, j;
HDA_BOOTHVERBOSE(
device_printf(dev, "HP switch deinit...\n");
device_printf(dev, "Pin sense deinit...\n");
);
hdaa_hp_switch_deinit(devinfo);
HDA_BOOTHVERBOSE(
device_printf(dev, "ELD deinit...\n");
);
hdaa_eld_deinit(devinfo);
hdaa_sense_deinit(devinfo);
free(devinfo->ctl, M_HDAA);
devinfo->ctl = NULL;
devinfo->ctlcnt = 0;
@ -6084,10 +6126,6 @@ hdaa_suspend(device_t dev)
hdaa_channel_stop(&devinfo->chans[i]);
}
}
HDA_BOOTHVERBOSE(
device_printf(dev, "HP switch deinit...\n");
);
hdaa_hp_switch_deinit(devinfo);
HDA_BOOTHVERBOSE(
device_printf(dev, "Power down FG"
" nid=%d to the D3 state...\n",
@ -6129,9 +6167,9 @@ hdaa_resume(device_t dev)
);
hdaa_patch_direct(devinfo);
HDA_BOOTHVERBOSE(
device_printf(dev, "HP switch init...\n");
device_printf(dev, "Pin sense init...\n");
);
hdaa_hp_switch_init(devinfo);
hdaa_sense_init(devinfo);
hdaa_unlock(devinfo);
for (i = 0; i < devinfo->num_devs; i++) {
@ -6404,7 +6442,7 @@ hdaa_unsol_intr(device_t dev, uint32_t resp)
else
flags = 0x01;
if (flags & 0x01)
hdaa_hp_switch_handler(w);
hdaa_presence_handler(w);
if (flags & 0x02)
hdaa_eld_handler(w);
}
@ -6614,8 +6652,6 @@ hdaa_pcm_attach(device_t dev)
);
if (mixer_init(dev, &hdaa_audio_ctl_ossmixer_class, pdevinfo) != 0)
device_printf(dev, "Can't register mixer\n");
else
hdaa_audio_ctl_set_defaults(pdevinfo);
HDA_BOOTHVERBOSE(
device_printf(dev, "Registering PCM channels...\n");
@ -6648,6 +6684,24 @@ hdaa_pcm_attach(device_t dev)
"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), "autosrc", &pdevinfo->autorecsrc);
SYSCTL_ADD_INT(&d->rec_sysctl_ctx,
SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO,
"autosrc", CTLTYPE_INT | CTLFLAG_RW,
&pdevinfo->autorecsrc, 0,
"Automatic recording source selection");
}
if (pdevinfo->mixer != NULL) {
hdaa_audio_ctl_set_defaults(pdevinfo);
if (pdevinfo->recas >= 0) {
as = &devinfo->as[pdevinfo->recas];
hdaa_lock(devinfo);
hdaa_autorecsrc_handler(as, NULL);
hdaa_unlock(devinfo);
}
}
snprintf(status, SND_STATUSLEN, "on %s %s",

View File

@ -125,6 +125,7 @@ struct hdaa_widget {
uint32_t newconf;
uint32_t cap;
uint32_t ctrl;
int connected;
} pin;
struct {
uint8_t stripecap;
@ -180,6 +181,7 @@ struct hdaa_pcm_devinfo {
u_char digital;
uint32_t ossmask; /* Mask of supported OSS devices. */
uint32_t recsrc; /* Mask of supported OSS sources. */
int autorecsrc;
};
struct hdaa_devinfo {