From owner-svn-src-all@FreeBSD.ORG Sun Jan 22 10:24:13 2012 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 4C0D81065691; Sun, 22 Jan 2012 10:24:13 +0000 (UTC) (envelope-from mav@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 399D88FC12; Sun, 22 Jan 2012 10:24:13 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id q0MAODHw071727; Sun, 22 Jan 2012 10:24:13 GMT (envelope-from mav@svn.freebsd.org) Received: (from mav@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id q0MAODT5071724; Sun, 22 Jan 2012 10:24:13 GMT (envelope-from mav@svn.freebsd.org) Message-Id: <201201221024.q0MAODT5071724@svn.freebsd.org> From: Alexander Motin Date: Sun, 22 Jan 2012 10:24:13 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r230451 - head/sys/dev/sound/pci/hda X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 22 Jan 2012 10:24:13 -0000 Author: mav Date: Sun Jan 22 10:24:12 2012 New Revision: 230451 URL: http://svn.freebsd.org/changeset/base/230451 Log: Complete rewrite of the snd_hda(4) volume control. Previous code was relatively dumb. During CODEC probe it was tracing signals and statically binding amplifier controls to the OSS mixer controls. To set volume it just set all bound amplifier controls proportionally to mixer level, not looking on their hierarchy and amplification levels/offsets. New code is much smarter. It also traces signals during probe, but mostly to find out possible amplification control rages in dB for each specific signal. To set volume it retraces each affected signal again and sets amplifiers controls recursively to reach desired amplification level in dB. It would be nice to export values in dB to user, but unluckily our OSS mixer API is too simple for that. As result of this change: - cascaded amplifiers will work together to reach maximal precision. If some input has 0/+40dB preamplifier with 10dB step and -10/+10dB mixer with 1dB step after it, new code will use both to provide 0/+40dB control with 1dB step! We could even get -10/+50dB range there, but that is intentionally blocked for now. - different channels of multichannel associations on non-uniform CODECs such as VIA VT1708S will have the same volume, not looking that control ranges are different. It was not good when fronts were 12dB louder. - for multiplexed recording, when we can record from only one source at a time, we can now use recording amplifier controls to set different volume levels for different inputs if they have no own controls of they are less precise. If recording source change, amplifiers will be reconfigured. To improve out-of-the-box behavior, ignore default volume levels set by sound(4) and use own, more reasonable: +20dB for mics, -10dB for analog output volume and 0dB for the rest of controls. sound(4) defaults of 75% mean absolutely random things for different controls of different CODECs because of very different control ranges. Together with further planned automatic recording source selection this should allow users to get fine playback and recording without touching mixer first. Note that existing users should delete /var/db/mixer*-state and reboot or trigger CODEC reconfiguration to get new default values. MFC after: 2 months Sponsored by: iXsystems, Inc. Modified: head/sys/dev/sound/pci/hda/hdaa.c head/sys/dev/sound/pci/hda/hdaa.h Modified: head/sys/dev/sound/pci/hda/hdaa.c ============================================================================== --- head/sys/dev/sound/pci/hda/hdaa.c Sun Jan 22 10:16:24 2012 (r230450) +++ head/sys/dev/sound/pci/hda/hdaa.c Sun Jan 22 10:24:12 2012 (r230451) @@ -175,6 +175,8 @@ static const struct { }; #define HDA_RATE_TAB_LEN (sizeof(hda_rate_tab) / sizeof(hda_rate_tab[0])) +const static char *ossnames[] = SOUND_DEVICE_NAMES; + /**************************************************************************** * Function prototypes ****************************************************************************/ @@ -193,7 +195,6 @@ static void hdaa_dump_pin_config(struct static char * hdaa_audio_ctl_ossmixer_mask2allname(uint32_t mask, char *buf, size_t len) { - static char *ossname[] = SOUND_DEVICE_NAMES; int i, first = 1; bzero(buf, len); @@ -201,7 +202,7 @@ hdaa_audio_ctl_ossmixer_mask2allname(uin if (mask & (1 << i)) { if (first == 0) strlcat(buf, ", ", len); - strlcat(buf, ossname[i], len); + strlcat(buf, ossnames[i], len); first = 0; } } @@ -1774,11 +1775,11 @@ hdaa_audio_ctl_ossmixer_init(struct snd_ struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; - struct hdaa_audio_ctl *ctl; uint32_t mask, recmask; - int i, j, softpcmvol; + 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++) { @@ -1786,11 +1787,10 @@ hdaa_audio_ctl_ossmixer_init(struct snd_ pdevinfo->right[i] = 100; } - mask = 0; - recmask = 0; - - /* Declate EAPD as ogain control. */ + /* 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) @@ -1802,23 +1802,36 @@ hdaa_audio_ctl_ossmixer_init(struct snd_ mask |= SOUND_MASK_OGAIN; break; } - } - /* Declare volume controls assigned to this association. */ - i = 0; - ctl = NULL; - while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { - if (ctl->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)) - mask |= ctl->ossmask; + /* 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) @@ -1841,66 +1854,273 @@ hdaa_audio_ctl_ossmixer_init(struct snd_ } } - /* Declare soft PCM volume if needed. */ - if (pdevinfo->playas >= 0) { - ctl = NULL; - if ((mask & SOUND_MASK_PCM) == 0 || - (devinfo->quirks & HDAA_QUIRK_SOFTPCMVOL)) { - softpcmvol = 1; - mask |= SOUND_MASK_PCM; - } else { - softpcmvol = 0; - i = 0; - while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { - if (ctl->enable == 0) - continue; - if (ctl->widget->bindas != pdevinfo->playas && - (ctl->widget->bindas != -2 || pdevinfo->index != 0)) - continue; - if (!(ctl->ossmask & SOUND_MASK_PCM)) - continue; - if (ctl->step > 0) - break; - } + 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 (softpcmvol == 1 || ctl == NULL) { - pcm_setflags(pdevinfo->dev, pcm_getflags(pdevinfo->dev) | SD_F_SOFTPCMVOL); - HDA_BOOTVERBOSE( - device_printf(pdevinfo->dev, - "%s Soft PCM volume\n", - (softpcmvol == 1) ? "Forcing" : "Enabling"); - ); + /* 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 it's 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; +} - /* Declare master volume if needed. */ - if (pdevinfo->playas >= 0) { - if ((mask & (SOUND_MASK_VOLUME | SOUND_MASK_PCM)) == - SOUND_MASK_PCM) { - mask |= SOUND_MASK_VOLUME; - mix_setparentchild(m, SOUND_MIXER_VOLUME, - SOUND_MASK_PCM); - mix_setrealdev(m, SOUND_MIXER_VOLUME, - SOUND_MIXER_NONE); - HDA_BOOTVERBOSE( - device_printf(pdevinfo->dev, - "Forcing master volume with PCM\n"); - ); +/* + * 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); } - recmask &= (1 << SOUND_MIXER_NRDEVICES) - 1; - mask &= (1 << SOUND_MIXER_NRDEVICES) - 1; + /* We must not traverse pin */ + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && + depth > 0) + return; - mix_setrecdevs(m, recmask); - mix_setdevs(m, mask); + 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); + } +} - hdaa_unlock(devinfo); +/* + * 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; - return (0); + 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 && pdevinfo->index != 0) + continue; + 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) @@ -1908,12 +2128,10 @@ hdaa_audio_ctl_ossmixer_set(struct snd_m struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; - struct hdaa_audio_ctl *ctl; - uint32_t mute; - int lvol, rvol; - int i, j; + int i; hdaa_lock(devinfo); + /* Save new values. */ pdevinfo->left[dev] = left; pdevinfo->right[dev] = right; @@ -1954,39 +2172,57 @@ hdaa_audio_ctl_ossmixer_set(struct snd_m } /* Recalculate all controls related to this OSS device. */ - i = 0; - while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { - if (ctl->enable == 0 || - !(ctl->ossmask & (1 << dev))) + 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 (!((pdevinfo->playas >= 0 && - ctl->widget->bindas == pdevinfo->playas) || - (pdevinfo->recas >= 0 && - ctl->widget->bindas == pdevinfo->recas) || - ctl->widget->bindas == -2)) + + /* 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; - lvol = 100; - rvol = 100; - for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { - if (ctl->ossmask & (1 << j)) { - lvol = lvol * pdevinfo->left[j] / 100; - rvol = rvol * pdevinfo->right[j] / 100; - } + 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); } - mute = (lvol == 0) ? HDAA_AMP_MUTE_LEFT : 0; - mute |= (rvol == 0) ? HDAA_AMP_MUTE_RIGHT : 0; - lvol = (lvol * ctl->step + 50) / 100; - rvol = (rvol * ctl->step + 50) / 100; - hdaa_audio_ctl_amp_set(ctl, mute, lvol, rvol); + mix_set(pdevinfo->mixer, dev, vol, vol); } - hdaa_unlock(devinfo); - - return (left | (right << 8)); } /* - * Commutate specified record source. + * 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) @@ -2069,6 +2305,7 @@ hdaa_audio_ctl_ossmixer_setrecsrc(struct 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; @@ -2097,9 +2334,47 @@ hdaa_audio_ctl_ossmixer_setrecsrc(struct 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 == 0xffffffff)? 0 : ret); + return (ret); } static kobj_method_t hdaa_audio_ctl_ossmixer_methods[] = { @@ -3782,31 +4057,31 @@ hdaa_audio_disable_crossas(struct hdaa_d } -#define HDA_CTL_GIVE(ctl) ((ctl)->step?1:0) - /* - * Find controls to control amplification for source. + * 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 need) + 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, rneed; + int i, j, conns = 0, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) - return (need); + return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) - return (need); + return (found); /* Count number of active inputs. */ if (depth > 0) { for (j = 0; j < w->nconns; j++) { - if (w->connsenable[j]) - conns++; + if (!w->connsenable[j]) + continue; + conns++; } } @@ -3817,81 +4092,96 @@ hdaa_audio_ctl_source_amp(struct hdaa_de ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, index, 1); if (ctl) { - if (HDA_CTL_GIVE(ctl) & need) - ctl->ossmask |= (1 << ossdev); - else - ctl->possmask |= (1 << ossdev); - need &= ~HDA_CTL_GIVE(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 it's own. */ if (w->ossdev >= 0 && depth > 0) - return (need); + 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 (need); + return (found); /* record that this widget exports such signal, */ w->ossmask |= (1 << ossdev); - /* If signals mixed, we can't assign controls farther. + /* + * If signals mixed, we can't assign controls farther. * Ignore this on depth zero. Caller must knows why. - * Ignore this for static selectors if this input selected. */ - if (conns > 1) + 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) { - if (HDA_CTL_GIVE(ctl) & need) - ctl->ossmask |= (1 << ossdev); - else - ctl->possmask |= (1 << ossdev); - need &= ~HDA_CTL_GIVE(ctl); + ctl->ossmask |= (1 << ossdev); + found++; + if (*minamp == *maxamp) { + *minamp += MINQDB(ctl); + *maxamp += MAXQDB(ctl); + } } } - rneed = 0; + 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) { - rneed |= hdaa_audio_ctl_source_amp(devinfo, - wc->nid, j, ossdev, ctlable, depth + 1, need); + 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); + } } } } - rneed &= need; - - return (rneed); + if (*minamp == *maxamp && cminamp < cmaxamp) { + *minamp += cminamp; + *maxamp += cmaxamp; + } + return (found); } /* - * Find controls to control amplification for destination. + * Find controls to control amplification for destination and calculate + * possible amplification range. */ -static void +static int hdaa_audio_ctl_dest_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index, - int ossdev, int depth, int need) + 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; + int i, j, consumers, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) - return; + return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) - return; + return (found); if (depth > 0) { /* If this node produce output for several consumers, @@ -3914,43 +4204,58 @@ hdaa_audio_ctl_dest_amp(struct hdaa_devi as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir || (w->bindseqmask & (1 << 15)) == 0)) || consumers > 2) - return; + return (found); /* Else use it's output mixer. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) { - if (HDA_CTL_GIVE(ctl) & need) - ctl->ossmask |= (1 << ossdev); - else - ctl->possmask |= (1 << ossdev); - need &= ~HDA_CTL_GIVE(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; + return (found); + cminamp = cmaxamp = 0; for (i = 0; i < w->nconns; i++) { - int tneed = need; 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) { - if (HDA_CTL_GIVE(ctl) & tneed) - ctl->ossmask |= (1 << ossdev); - else - ctl->possmask |= (1 << ossdev); - tneed &= ~HDA_CTL_GIVE(ctl); - } - hdaa_audio_ctl_dest_amp(devinfo, w->conns[i], -1, ossdev, - depth + 1, tneed); + 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); } /* @@ -4158,43 +4463,82 @@ retry: 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_audio_ctl *ctl; struct hdaa_widget *w, *cw; - int i, j; + 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; - hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, - w->ossdev, 1, 0, 1); + 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) { - hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, - SOUND_MIXER_RECLEV, 0, 1); + 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) { - hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, - SOUND_MIXER_VOLUME, 0, 1); + 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) { - if (hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, - w->ossdev, 1, 0, 1)) { + 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. */ - hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, - w->ossdev, 0, 1); + 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++) { @@ -4208,17 +4552,15 @@ hdaa_audio_assign_mixers(struct hdaa_dev if (cw->bindas >= 0 && as[cw->bindas].dir != HDAA_CTL_IN) continue; - hdaa_audio_ctl_dest_amp(devinfo, - w->nid, j, SOUND_MIXER_IGAIN, 0, 1); + 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); } } } - /* Treat unrequired as possible. */ - i = 0; - while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { - if (ctl->ossmask == 0) - ctl->ossmask = ctl->possmask; - } } static void @@ -4671,7 +5013,7 @@ hdaa_pcmchannel_setup(struct hdaa_chan * } static void -hdaa_create_pcms(struct hdaa_devinfo *devinfo) +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; @@ -4726,6 +5068,7 @@ hdaa_create_pcms(struct hdaa_devinfo *de 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]; @@ -4734,6 +5077,13 @@ hdaa_create_pcms(struct hdaa_devinfo *de 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]; @@ -4782,9 +5132,15 @@ hdaa_dump_ctls(struct hdaa_pcm_devinfo * } else { device_printf(pdevinfo->dev, "Unknown Ctl"); } - printf(" (OSS: %s)\n", + 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"); device_printf(pdevinfo->dev, " |\n"); printed = 1; } @@ -4797,8 +5153,8 @@ hdaa_dump_ctls(struct hdaa_pcm_devinfo * printf("): "); if (ctl->step > 0) { printf("%+d/%+ddB (%d steps)%s\n", - (0 - ctl->offset) * (ctl->size + 1) / 4, - (ctl->step - ctl->offset) * (ctl->size + 1) / 4, + MINQDB(ctl) / 4, + MAXQDB(ctl) / 4, ctl->step + 1, ctl->mute?" + mute":""); *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***