2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* Universal Interface for Intel High Definition Audio Codec
|
|
|
|
*
|
|
|
|
* Generic widget tree parser
|
|
|
|
*
|
|
|
|
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
|
|
|
|
*
|
|
|
|
* This driver is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This driver is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/slab.h>
|
2011-09-22 13:34:58 +00:00
|
|
|
#include <linux/export.h>
|
2012-12-19 11:52:06 +00:00
|
|
|
#include <linux/sort.h>
|
2013-01-24 16:32:56 +00:00
|
|
|
#include <linux/delay.h>
|
2012-12-20 15:58:39 +00:00
|
|
|
#include <linux/ctype.h>
|
|
|
|
#include <linux/string.h>
|
2013-01-17 08:52:11 +00:00
|
|
|
#include <linux/bitops.h>
|
2013-11-18 11:03:56 +00:00
|
|
|
#include <linux/module.h>
|
ALSA: hda - Support led audio trigger
Now all relevant platform drivers are providing the LED audio trigger,
we can switch the mute LED control with the LED trigger, finally.
For the mic-mute LED trigger, a common fixup function,
snd_hda_gen_fixup_micmute_led(), is provided to be called for the
corresponding quirk entries. This sets up the capture sync hook with
ledtrig_audio_set() call appropriately.
For the mute LED trigger, which is done currently only for
thinkpad_acpi, the call is replaced with ledtrig_audio_set() as well.
Overall, the beauty of the new implementation is that the whole ugly
bindings with request_symbol() are dropped, and also that it provides
more flexibility to users.
One potential behavior change by this patch is that the mute LED enum
may be created on machines that actually have no LED device. In the
former code, we did test-call and abort binding if the test failed.
But with the LED-trigger binding, this test isn't possible, and the
actual check is done in the LED class device side. So it's the
downside of simpleness.
Also, note that the HD-audio codec driver doesn't select CONFIG_LEDS
and co by itself. It's supposed to be selected by the platform
drivers instead.
Acked-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
Acked-by: Pavel Machek <pavel@ucw.cz>
Acked-by: Pali Rohár <pali.rohar@gmail.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-11-26 16:47:46 +00:00
|
|
|
#include <linux/leds.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
#include <sound/core.h>
|
2012-12-19 11:52:06 +00:00
|
|
|
#include <sound/jack.h>
|
ALSA: hda - Add TLV_DB_SCALE_MUTE bit for relevant controls
The DACs on Sigmatel/IDT codecs do mute at the lowest volume level,
and in the earlier drivers, we passed TLV_DB_SCALE_MUTE bit for each
volume control element like Speaker and Headphone as well as Master.
Along with the translation to the generic parser, however, the TLV bit
was lost for the slave controls (e.g. Speaker) but set only to
Master. In theory this should have sufficed, but apps, particularly
PA, do care the slave volume bits, so we seem to see a regression in
the volume controls.
This patch adds a flag to hda_gen_spec to specify the DAC mute
feature, and adds the TLV bit properly for all relevant volume
controls. Also, the TLV bit for vmaster is set in hda_generic.c, so
that we can get rid of all tricks from the codec driver side.
As the similar hack is applied to Conexant 5051 stuff, we can get rid
of it as well.
BugLink: https://bugs.launchpad.net/bugs/1357928
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-09-01 08:07:04 +00:00
|
|
|
#include <sound/tlv.h>
|
2018-08-22 20:24:57 +00:00
|
|
|
#include <sound/hda_codec.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
#include "hda_local.h"
|
2012-12-19 11:52:06 +00:00
|
|
|
#include "hda_auto_parser.h"
|
|
|
|
#include "hda_jack.h"
|
2013-03-18 10:25:51 +00:00
|
|
|
#include "hda_beep.h"
|
2012-12-19 11:52:06 +00:00
|
|
|
#include "hda_generic.h"
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-09-06 12:03:14 +00:00
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_spec_init - initialize hda_gen_spec struct
|
|
|
|
* @spec: hda_gen_spec object to initialize
|
|
|
|
*
|
|
|
|
* Initialize the given hda_gen_spec object.
|
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
int snd_hda_gen_spec_init(struct hda_gen_spec *spec)
|
|
|
|
{
|
|
|
|
snd_array_init(&spec->kctls, sizeof(struct snd_kcontrol_new), 32);
|
|
|
|
snd_array_init(&spec->paths, sizeof(struct nid_path), 8);
|
2013-02-07 09:45:11 +00:00
|
|
|
snd_array_init(&spec->loopback_list, sizeof(struct hda_amp_list), 8);
|
2012-12-21 13:09:42 +00:00
|
|
|
mutex_init(&spec->pcm_mutex);
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_spec_init);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_add_kctl - Add a new kctl_new struct from the template
|
|
|
|
* @spec: hda_gen_spec object
|
|
|
|
* @name: name string to override the template, NULL if unchanged
|
|
|
|
* @temp: template for the new kctl
|
|
|
|
*
|
|
|
|
* Add a new kctl (actually snd_kcontrol_new to be instantiated later)
|
|
|
|
* element based on the given snd_kcontrol_new template @temp and the
|
|
|
|
* name string @name to the list in @spec.
|
|
|
|
* Returns the newly created object or NULL as error.
|
|
|
|
*/
|
2012-12-19 13:38:33 +00:00
|
|
|
struct snd_kcontrol_new *
|
|
|
|
snd_hda_gen_add_kctl(struct hda_gen_spec *spec, const char *name,
|
|
|
|
const struct snd_kcontrol_new *temp)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct snd_kcontrol_new *knew = snd_array_new(&spec->kctls);
|
|
|
|
if (!knew)
|
|
|
|
return NULL;
|
|
|
|
*knew = *temp;
|
|
|
|
if (name)
|
|
|
|
knew->name = kstrdup(name, GFP_KERNEL);
|
|
|
|
else if (knew->name)
|
|
|
|
knew->name = kstrdup(knew->name, GFP_KERNEL);
|
|
|
|
if (!knew->name)
|
|
|
|
return NULL;
|
|
|
|
return knew;
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_add_kctl);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static void free_kctls(struct hda_gen_spec *spec)
|
|
|
|
{
|
|
|
|
if (spec->kctls.list) {
|
|
|
|
struct snd_kcontrol_new *kctl = spec->kctls.list;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < spec->kctls.used; i++)
|
|
|
|
kfree(kctl[i].name);
|
|
|
|
}
|
|
|
|
snd_array_free(&spec->kctls);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2014-02-10 17:23:57 +00:00
|
|
|
static void snd_hda_gen_spec_free(struct hda_gen_spec *spec)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
if (!spec)
|
|
|
|
return;
|
|
|
|
free_kctls(spec);
|
|
|
|
snd_array_free(&spec->paths);
|
2013-02-07 09:45:11 +00:00
|
|
|
snd_array_free(&spec->loopback_list);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-01-11 16:48:22 +00:00
|
|
|
/*
|
|
|
|
* store user hints
|
|
|
|
*/
|
|
|
|
static void parse_user_hints(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int val;
|
|
|
|
|
|
|
|
val = snd_hda_get_bool_hint(codec, "jack_detect");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->no_jack_detect = !val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "inv_jack_detect");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->inv_jack_detect = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "trigger_sense");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->no_trigger_sense = !val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "inv_eapd");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->inv_eapd = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "pcm_format_first");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->pcm_format_first = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "sticky_stream");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->no_sticky_stream = !val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "spdif_status_reset");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->spdif_status_reset = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "pin_amp_workaround");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->pin_amp_workaround = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "single_adc_amp");
|
|
|
|
if (val >= 0)
|
|
|
|
codec->single_adc_amp = !!val;
|
2015-03-20 17:21:03 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "power_save_node");
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
if (val >= 0)
|
2015-03-20 17:21:03 +00:00
|
|
|
codec->power_save_node = !!val;
|
2013-01-11 16:48:22 +00:00
|
|
|
|
2013-01-16 17:20:07 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "auto_mute");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->suppress_auto_mute = !val;
|
2013-01-11 16:48:22 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "auto_mic");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->suppress_auto_mic = !val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "line_in_auto_switch");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->line_in_auto_switch = !!val;
|
2013-06-24 14:00:21 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "auto_mute_via_amp");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->auto_mute_via_amp = !!val;
|
2013-01-11 16:48:22 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "need_dac_fix");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->need_dac_fix = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "primary_hp");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->no_primary_hp = !val;
|
ALSA: hda - Fix invalid multi-io creation on VAIO-Z laptops
VAIO-Z laptops need to use the specific DAC for the speaker output
by some unknown reason although the codec itself supports the flexible
connection. So we implemented a workaround by a new flag,
no_primary_hp, for assigning the speaker pin first.
This worked until 3.8 kernel, but it got broken because the driver
learned for a better multi-io pin mapping, and not it can assign two
mic pins for multi-io. Since the multi-io requires to be the primary
output, the hp and two mic pins are assigned in prior to the speaker
in the end.
Although the machine has two mic pins, one of them is used as a noise-
canceling headphone, thus it's no real retaskable mic jack. Thus, at
best, we can disable the multi-io assignment and make the parser
behavior back to the state before the multi-io.
This patch adds again a new flag, no_multi_io, to indicate that the
device has no multi-io capability, and set it in the fixup for
VAIO-Z. The no_multi_io flag itself can be used generically, added
via a helper line, too.
Reported-by: Tormen <my.nl.abos@gmail.com>
Reported-by: Adam Williamson <awilliam@redhat.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-29 14:54:36 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "multi_io");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->no_multi_io = !val;
|
2013-01-11 16:48:22 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "multi_cap_vol");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->multi_cap_vol = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "inv_dmic_split");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->inv_dmic_split = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "indep_hp");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->indep_hp = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "add_stereo_mix_input");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->add_stereo_mix_input = !!val;
|
2013-03-07 17:32:59 +00:00
|
|
|
/* the following two are just for compatibility */
|
2013-01-11 16:48:22 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "add_out_jack_modes");
|
|
|
|
if (val >= 0)
|
2013-03-07 17:32:59 +00:00
|
|
|
spec->add_jack_modes = !!val;
|
2013-01-17 08:52:11 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "add_in_jack_modes");
|
|
|
|
if (val >= 0)
|
2013-03-07 17:32:59 +00:00
|
|
|
spec->add_jack_modes = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "add_jack_modes");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->add_jack_modes = !!val;
|
2013-01-24 16:32:56 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "power_down_unused");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->power_down_unused = !!val;
|
2013-02-19 16:12:42 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "add_hp_mic");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->hp_mic = !!val;
|
|
|
|
val = snd_hda_get_bool_hint(codec, "hp_mic_detect");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->suppress_hp_mic_detect = !val;
|
2017-04-10 15:37:34 +00:00
|
|
|
val = snd_hda_get_bool_hint(codec, "vmaster");
|
|
|
|
if (val >= 0)
|
|
|
|
spec->suppress_vmaster = !val;
|
2013-01-11 16:48:22 +00:00
|
|
|
|
|
|
|
if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
|
|
|
|
spec->mixer_nid = val;
|
|
|
|
}
|
|
|
|
|
2013-01-10 08:33:29 +00:00
|
|
|
/*
|
|
|
|
* pin control value accesses
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define update_pin_ctl(codec, pin, val) \
|
2018-06-27 09:43:09 +00:00
|
|
|
snd_hda_codec_write_cache(codec, pin, 0, \
|
2013-01-10 08:33:29 +00:00
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL, val)
|
|
|
|
|
|
|
|
/* restore the pinctl based on the cached value */
|
|
|
|
static inline void restore_pin_ctl(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
update_pin_ctl(codec, pin, snd_hda_codec_get_pin_target(codec, pin));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set the pinctl target value and write it if requested */
|
|
|
|
static void set_pin_target(struct hda_codec *codec, hda_nid_t pin,
|
|
|
|
unsigned int val, bool do_write)
|
|
|
|
{
|
|
|
|
if (!pin)
|
|
|
|
return;
|
|
|
|
val = snd_hda_correct_pin_ctl(codec, pin, val);
|
|
|
|
snd_hda_codec_set_pin_target(codec, pin, val);
|
|
|
|
if (do_write)
|
|
|
|
update_pin_ctl(codec, pin, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set pinctl target values for all given pins */
|
|
|
|
static void set_pin_targets(struct hda_codec *codec, int num_pins,
|
|
|
|
hda_nid_t *pins, unsigned int val)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < num_pins; i++)
|
|
|
|
set_pin_target(codec, pins[i], val, false);
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* parsing paths
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
|
2013-01-07 16:25:08 +00:00
|
|
|
/* return the position of NID in the list, or -1 if not found */
|
|
|
|
static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < nums; i++)
|
|
|
|
if (list[i] == nid)
|
|
|
|
return i;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return true if the given NID is contained in the path */
|
|
|
|
static bool is_nid_contained(struct nid_path *path, hda_nid_t nid)
|
|
|
|
{
|
|
|
|
return find_idx_in_nid_list(nid, path->path, path->depth) >= 0;
|
|
|
|
}
|
|
|
|
|
2013-01-04 12:19:55 +00:00
|
|
|
static struct nid_path *get_nid_path(struct hda_codec *codec,
|
|
|
|
hda_nid_t from_nid, hda_nid_t to_nid,
|
2013-01-07 16:25:08 +00:00
|
|
|
int anchor_nid)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2018-04-23 15:24:56 +00:00
|
|
|
struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2018-04-23 15:24:56 +00:00
|
|
|
snd_array_for_each(&spec->paths, i, path) {
|
2012-12-19 11:52:06 +00:00
|
|
|
if (path->depth <= 0)
|
|
|
|
continue;
|
|
|
|
if ((!from_nid || path->path[0] == from_nid) &&
|
2013-01-04 12:19:55 +00:00
|
|
|
(!to_nid || path->path[path->depth - 1] == to_nid)) {
|
2013-01-07 16:25:08 +00:00
|
|
|
if (!anchor_nid ||
|
|
|
|
(anchor_nid > 0 && is_nid_contained(path, anchor_nid)) ||
|
|
|
|
(anchor_nid < 0 && !is_nid_contained(path, anchor_nid)))
|
2013-01-04 12:19:55 +00:00
|
|
|
return path;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return NULL;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2013-01-04 12:19:55 +00:00
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_get_path_idx - get the index number corresponding to the path
|
|
|
|
* instance
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @path: nid_path object
|
|
|
|
*
|
|
|
|
* The returned index starts from 1, i.e. the actual array index with offset 1,
|
|
|
|
* and zero is handled as an invalid path
|
2013-01-04 14:01:40 +00:00
|
|
|
*/
|
|
|
|
int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct nid_path *array = spec->paths.list;
|
|
|
|
ssize_t idx;
|
|
|
|
|
|
|
|
if (!spec->paths.used)
|
|
|
|
return 0;
|
|
|
|
idx = path - array;
|
|
|
|
if (idx < 0 || idx >= spec->paths.used)
|
|
|
|
return 0;
|
|
|
|
return idx + 1;
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_get_path_idx);
|
2013-01-04 14:01:40 +00:00
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_get_path_from_idx - get the path instance corresponding to the
|
|
|
|
* given index number
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @idx: the path index
|
|
|
|
*/
|
2013-01-04 14:01:40 +00:00
|
|
|
struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (idx <= 0 || idx > spec->paths.used)
|
|
|
|
return NULL;
|
|
|
|
return snd_array_elem(&spec->paths, idx - 1);
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_get_path_from_idx);
|
2013-01-04 14:01:40 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* check whether the given DAC is already found in any existing paths */
|
|
|
|
static bool is_dac_already_used(struct hda_codec *codec, hda_nid_t nid)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2018-04-23 15:24:56 +00:00
|
|
|
const struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2018-04-23 15:24:56 +00:00
|
|
|
snd_array_for_each(&spec->paths, i, path) {
|
2012-12-19 11:52:06 +00:00
|
|
|
if (path->path[0] == nid)
|
|
|
|
return true;
|
2005-11-21 15:33:51 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return false;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* check whether the given two widgets can be connected */
|
|
|
|
static bool is_reachable_path(struct hda_codec *codec,
|
|
|
|
hda_nid_t from_nid, hda_nid_t to_nid)
|
|
|
|
{
|
|
|
|
if (!from_nid || !to_nid)
|
|
|
|
return false;
|
|
|
|
return snd_hda_get_conn_index(codec, to_nid, from_nid, true) >= 0;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* nid, dir and idx */
|
|
|
|
#define AMP_VAL_COMPARE_MASK (0xffff | (1U << 18) | (0x0f << 19))
|
|
|
|
|
|
|
|
/* check whether the given ctl is already assigned in any path elements */
|
|
|
|
static bool is_ctl_used(struct hda_codec *codec, unsigned int val, int type)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2018-04-23 15:24:56 +00:00
|
|
|
const struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
val &= AMP_VAL_COMPARE_MASK;
|
2018-04-23 15:24:56 +00:00
|
|
|
snd_array_for_each(&spec->paths, i, path) {
|
2012-12-19 11:52:06 +00:00
|
|
|
if ((path->ctls[type] & AMP_VAL_COMPARE_MASK) == val)
|
|
|
|
return true;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return false;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* check whether a control with the given (nid, dir, idx) was assigned */
|
|
|
|
static bool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid,
|
2013-01-18 10:01:33 +00:00
|
|
|
int dir, int idx, int type)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int val = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir);
|
2013-01-18 10:01:33 +00:00
|
|
|
return is_ctl_used(codec, val, type);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2014-02-25 11:21:03 +00:00
|
|
|
static void print_nid_path(struct hda_codec *codec,
|
|
|
|
const char *pfx, struct nid_path *path)
|
2012-12-20 16:54:22 +00:00
|
|
|
{
|
|
|
|
char buf[40];
|
2014-07-05 20:02:02 +00:00
|
|
|
char *pos = buf;
|
2012-12-20 16:54:22 +00:00
|
|
|
int i;
|
|
|
|
|
2014-07-05 20:02:02 +00:00
|
|
|
*pos = 0;
|
|
|
|
for (i = 0; i < path->depth; i++)
|
|
|
|
pos += scnprintf(pos, sizeof(buf) - (pos - buf), "%s%02x",
|
|
|
|
pos != buf ? ":" : "",
|
|
|
|
path->path[i]);
|
2012-12-20 16:54:22 +00:00
|
|
|
|
2014-07-05 20:02:02 +00:00
|
|
|
codec_dbg(codec, "%s path: depth=%d '%s'\n", pfx, path->depth, buf);
|
2012-12-20 16:54:22 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* called recursively */
|
|
|
|
static bool __parse_nid_path(struct hda_codec *codec,
|
|
|
|
hda_nid_t from_nid, hda_nid_t to_nid,
|
2013-01-07 16:25:08 +00:00
|
|
|
int anchor_nid, struct nid_path *path,
|
|
|
|
int depth)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
2013-01-03 14:25:11 +00:00
|
|
|
const hda_nid_t *conn;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i, nums;
|
|
|
|
|
2013-01-07 16:25:08 +00:00
|
|
|
if (to_nid == anchor_nid)
|
|
|
|
anchor_nid = 0; /* anchor passed */
|
|
|
|
else if (to_nid == (hda_nid_t)(-anchor_nid))
|
|
|
|
return false; /* hit the exclusive nid */
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-01-03 14:25:11 +00:00
|
|
|
nums = snd_hda_get_conn_list(codec, to_nid, &conn);
|
2012-12-19 11:52:06 +00:00
|
|
|
for (i = 0; i < nums; i++) {
|
|
|
|
if (conn[i] != from_nid) {
|
|
|
|
/* special case: when from_nid is 0,
|
|
|
|
* try to find an empty DAC
|
|
|
|
*/
|
|
|
|
if (from_nid ||
|
|
|
|
get_wcaps_type(get_wcaps(codec, conn[i])) != AC_WID_AUD_OUT ||
|
|
|
|
is_dac_already_used(codec, conn[i]))
|
|
|
|
continue;
|
|
|
|
}
|
2013-01-07 16:25:08 +00:00
|
|
|
/* anchor is not requested or already passed? */
|
|
|
|
if (anchor_nid <= 0)
|
2012-12-19 11:52:06 +00:00
|
|
|
goto found;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
if (depth >= MAX_NID_PATH_DEPTH)
|
|
|
|
return false;
|
|
|
|
for (i = 0; i < nums; i++) {
|
|
|
|
unsigned int type;
|
|
|
|
type = get_wcaps_type(get_wcaps(codec, conn[i]));
|
|
|
|
if (type == AC_WID_AUD_OUT || type == AC_WID_AUD_IN ||
|
|
|
|
type == AC_WID_PIN)
|
|
|
|
continue;
|
|
|
|
if (__parse_nid_path(codec, from_nid, conn[i],
|
2013-01-07 16:25:08 +00:00
|
|
|
anchor_nid, path, depth + 1))
|
2012-12-19 11:52:06 +00:00
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
|
|
|
|
found:
|
|
|
|
path->path[path->depth] = conn[i];
|
|
|
|
path->idx[path->depth + 1] = i;
|
|
|
|
if (nums > 1 && get_wcaps_type(get_wcaps(codec, to_nid)) != AC_WID_AUD_MIX)
|
|
|
|
path->multi[path->depth + 1] = 1;
|
|
|
|
path->depth++;
|
|
|
|
return true;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2015-12-08 10:48:39 +00:00
|
|
|
/*
|
2014-10-30 11:04:50 +00:00
|
|
|
* snd_hda_parse_nid_path - parse the widget path from the given nid to
|
|
|
|
* the target nid
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @from_nid: the NID where the path start from
|
|
|
|
* @to_nid: the NID where the path ends at
|
|
|
|
* @anchor_nid: the anchor indication
|
|
|
|
* @path: the path object to store the result
|
|
|
|
*
|
|
|
|
* Returns true if a matching path is found.
|
|
|
|
*
|
|
|
|
* The parsing behavior depends on parameters:
|
2012-12-19 11:52:06 +00:00
|
|
|
* when @from_nid is 0, try to find an empty DAC;
|
2013-01-07 16:25:08 +00:00
|
|
|
* when @anchor_nid is set to a positive value, only paths through the widget
|
|
|
|
* with the given value are evaluated.
|
|
|
|
* when @anchor_nid is set to a negative value, paths through the widget
|
|
|
|
* with the negative of given value are excluded, only other paths are chosen.
|
|
|
|
* when @anchor_nid is zero, no special handling about path selection.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2015-12-08 10:48:39 +00:00
|
|
|
static bool snd_hda_parse_nid_path(struct hda_codec *codec, hda_nid_t from_nid,
|
2013-01-07 16:25:08 +00:00
|
|
|
hda_nid_t to_nid, int anchor_nid,
|
2012-12-19 11:52:06 +00:00
|
|
|
struct nid_path *path)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2013-01-07 16:25:08 +00:00
|
|
|
if (__parse_nid_path(codec, from_nid, to_nid, anchor_nid, path, 1)) {
|
2012-12-19 11:52:06 +00:00
|
|
|
path->path[path->depth] = to_nid;
|
|
|
|
path->depth++;
|
|
|
|
return true;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return false;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_add_new_path - parse the path between the given NIDs and
|
|
|
|
* add to the path list
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @from_nid: the NID where the path start from
|
|
|
|
* @to_nid: the NID where the path ends at
|
|
|
|
* @anchor_nid: the anchor indication, see snd_hda_parse_nid_path()
|
|
|
|
*
|
|
|
|
* If no valid path is found, returns NULL.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
struct nid_path *
|
|
|
|
snd_hda_add_new_path(struct hda_codec *codec, hda_nid_t from_nid,
|
2013-01-07 16:25:08 +00:00
|
|
|
hda_nid_t to_nid, int anchor_nid)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct nid_path *path;
|
|
|
|
|
|
|
|
if (from_nid && to_nid && !is_reachable_path(codec, from_nid, to_nid))
|
|
|
|
return NULL;
|
|
|
|
|
2013-01-04 12:19:55 +00:00
|
|
|
/* check whether the path has been already added */
|
2013-01-07 16:25:08 +00:00
|
|
|
path = get_nid_path(codec, from_nid, to_nid, anchor_nid);
|
2013-01-04 12:19:55 +00:00
|
|
|
if (path)
|
|
|
|
return path;
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
path = snd_array_new(&spec->paths);
|
|
|
|
if (!path)
|
|
|
|
return NULL;
|
|
|
|
memset(path, 0, sizeof(*path));
|
2013-01-07 16:25:08 +00:00
|
|
|
if (snd_hda_parse_nid_path(codec, from_nid, to_nid, anchor_nid, path))
|
2012-12-19 11:52:06 +00:00
|
|
|
return path;
|
|
|
|
/* push back */
|
|
|
|
spec->paths.used--;
|
|
|
|
return NULL;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_add_new_path);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-01-09 08:28:20 +00:00
|
|
|
/* clear the given path as invalid so that it won't be picked up later */
|
|
|
|
static void invalidate_nid_path(struct hda_codec *codec, int idx)
|
|
|
|
{
|
|
|
|
struct nid_path *path = snd_hda_get_path_from_idx(codec, idx);
|
|
|
|
if (!path)
|
|
|
|
return;
|
|
|
|
memset(path, 0, sizeof(*path));
|
|
|
|
}
|
|
|
|
|
2013-12-10 16:29:26 +00:00
|
|
|
/* return a DAC if paired to the given pin by codec driver */
|
|
|
|
static hda_nid_t get_preferred_dac(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
const hda_nid_t *list = spec->preferred_dacs;
|
|
|
|
|
|
|
|
if (!list)
|
|
|
|
return 0;
|
|
|
|
for (; *list; list += 2)
|
|
|
|
if (*list == pin)
|
|
|
|
return list[1];
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* look for an empty DAC slot */
|
|
|
|
static hda_nid_t look_for_dac(struct hda_codec *codec, hda_nid_t pin,
|
|
|
|
bool is_digital)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
bool cap_digital;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < spec->num_all_dacs; i++) {
|
|
|
|
hda_nid_t nid = spec->all_dacs[i];
|
|
|
|
if (!nid || is_dac_already_used(codec, nid))
|
|
|
|
continue;
|
|
|
|
cap_digital = !!(get_wcaps(codec, nid) & AC_WCAP_DIGITAL);
|
|
|
|
if (is_digital != cap_digital)
|
|
|
|
continue;
|
|
|
|
if (is_reachable_path(codec, nid, pin))
|
|
|
|
return nid;
|
|
|
|
}
|
2007-08-10 15:09:26 +00:00
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* replace the channels in the composed amp value with the given number */
|
|
|
|
static unsigned int amp_val_replace_channels(unsigned int val, unsigned int chs)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
val &= ~(0x3U << 16);
|
|
|
|
val |= chs << 16;
|
|
|
|
return val;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2013-01-16 14:58:44 +00:00
|
|
|
static bool same_amp_caps(struct hda_codec *codec, hda_nid_t nid1,
|
|
|
|
hda_nid_t nid2, int dir)
|
|
|
|
{
|
|
|
|
if (!(get_wcaps(codec, nid1) & (1 << (dir + 1))))
|
|
|
|
return !(get_wcaps(codec, nid2) & (1 << (dir + 1)));
|
|
|
|
return (query_amp_caps(codec, nid1, dir) ==
|
|
|
|
query_amp_caps(codec, nid2, dir));
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* look for a widget suitable for assigning a mute switch in the path */
|
|
|
|
static hda_nid_t look_for_out_mute_nid(struct hda_codec *codec,
|
|
|
|
struct nid_path *path)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = path->depth - 1; i >= 0; i--) {
|
|
|
|
if (nid_has_mute(codec, path->path[i], HDA_OUTPUT))
|
|
|
|
return path->path[i];
|
|
|
|
if (i != path->depth - 1 && i != 0 &&
|
|
|
|
nid_has_mute(codec, path->path[i], HDA_INPUT))
|
|
|
|
return path->path[i];
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* look for a widget suitable for assigning a volume ctl in the path */
|
|
|
|
static hda_nid_t look_for_out_vol_nid(struct hda_codec *codec,
|
|
|
|
struct nid_path *path)
|
|
|
|
{
|
2013-11-04 15:32:01 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
for (i = path->depth - 1; i >= 0; i--) {
|
2013-11-04 15:32:01 +00:00
|
|
|
hda_nid_t nid = path->path[i];
|
|
|
|
if ((spec->out_vol_mask >> nid) & 1)
|
|
|
|
continue;
|
|
|
|
if (nid_has_volume(codec, nid, HDA_OUTPUT))
|
|
|
|
return nid;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* path activation / deactivation
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* can have the amp-in capability? */
|
|
|
|
static bool has_amp_in(struct hda_codec *codec, struct nid_path *path, int idx)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
hda_nid_t nid = path->path[idx];
|
|
|
|
unsigned int caps = get_wcaps(codec, nid);
|
|
|
|
unsigned int type = get_wcaps_type(caps);
|
|
|
|
|
|
|
|
if (!(caps & AC_WCAP_IN_AMP))
|
|
|
|
return false;
|
|
|
|
if (type == AC_WID_PIN && idx > 0) /* only for input pins */
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* can have the amp-out capability? */
|
|
|
|
static bool has_amp_out(struct hda_codec *codec, struct nid_path *path, int idx)
|
|
|
|
{
|
|
|
|
hda_nid_t nid = path->path[idx];
|
|
|
|
unsigned int caps = get_wcaps(codec, nid);
|
|
|
|
unsigned int type = get_wcaps_type(caps);
|
|
|
|
|
|
|
|
if (!(caps & AC_WCAP_OUT_AMP))
|
|
|
|
return false;
|
|
|
|
if (type == AC_WID_PIN && !idx) /* only for output pins */
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* check whether the given (nid,dir,idx) is active */
|
|
|
|
static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid,
|
2013-01-24 15:31:35 +00:00
|
|
|
unsigned int dir, unsigned int idx)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
int type = get_wcaps_type(get_wcaps(codec, nid));
|
2018-04-23 15:24:56 +00:00
|
|
|
const struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i, n;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2015-03-03 09:07:24 +00:00
|
|
|
if (nid == codec->core.afg)
|
2015-03-18 08:23:10 +00:00
|
|
|
return true;
|
|
|
|
|
2018-04-23 15:24:56 +00:00
|
|
|
snd_array_for_each(&spec->paths, n, path) {
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path->active)
|
2005-04-16 22:20:36 +00:00
|
|
|
continue;
|
2015-03-20 17:21:03 +00:00
|
|
|
if (codec->power_save_node) {
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
if (!path->stream_enabled)
|
|
|
|
continue;
|
|
|
|
/* ignore unplugged paths except for DAC/ADC */
|
2015-03-20 17:11:05 +00:00
|
|
|
if (!(path->pin_enabled || path->pin_fixed) &&
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
type != AC_WID_AUD_OUT && type != AC_WID_AUD_IN)
|
|
|
|
continue;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
for (i = 0; i < path->depth; i++) {
|
|
|
|
if (path->path[i] == nid) {
|
2015-08-24 08:45:27 +00:00
|
|
|
if (dir == HDA_OUTPUT || idx == -1 ||
|
|
|
|
path->idx[i] == idx)
|
2012-12-19 11:52:06 +00:00
|
|
|
return true;
|
|
|
|
break;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return false;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2013-05-14 10:58:47 +00:00
|
|
|
/* check whether the NID is referred by any active paths */
|
|
|
|
#define is_active_nid_for_any(codec, nid) \
|
2015-08-24 08:45:27 +00:00
|
|
|
is_active_nid(codec, nid, HDA_OUTPUT, -1)
|
2013-05-14 10:58:47 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* get the default amp value for the target state */
|
|
|
|
static int get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid,
|
2013-01-18 10:01:33 +00:00
|
|
|
int dir, unsigned int caps, bool enable)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int val = 0;
|
|
|
|
|
|
|
|
if (caps & AC_AMPCAP_NUM_STEPS) {
|
|
|
|
/* set to 0dB */
|
|
|
|
if (enable)
|
|
|
|
val = (caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2013-08-08 07:32:37 +00:00
|
|
|
if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) {
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!enable)
|
|
|
|
val |= HDA_AMP_MUTE;
|
|
|
|
}
|
|
|
|
return val;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2015-03-16 09:18:08 +00:00
|
|
|
/* is this a stereo widget or a stereo-to-mono mix? */
|
|
|
|
static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid, int dir)
|
|
|
|
{
|
|
|
|
unsigned int wcaps = get_wcaps(codec, nid);
|
|
|
|
hda_nid_t conn;
|
|
|
|
|
|
|
|
if (wcaps & AC_WCAP_STEREO)
|
|
|
|
return true;
|
|
|
|
if (dir != HDA_INPUT || get_wcaps_type(wcaps) != AC_WID_AUD_MIX)
|
|
|
|
return false;
|
|
|
|
if (snd_hda_get_num_conns(codec, nid) != 1)
|
|
|
|
return false;
|
|
|
|
if (snd_hda_get_connections(codec, nid, &conn, 1) < 0)
|
|
|
|
return false;
|
|
|
|
return !!(get_wcaps(codec, conn) & AC_WCAP_STEREO);
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* initialize the amp value (only at the first time) */
|
|
|
|
static void init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx)
|
|
|
|
{
|
2013-01-18 10:01:33 +00:00
|
|
|
unsigned int caps = query_amp_caps(codec, nid, dir);
|
|
|
|
int val = get_amp_val_to_activate(codec, nid, dir, caps, false);
|
2015-03-12 07:30:11 +00:00
|
|
|
|
2015-03-16 09:18:08 +00:00
|
|
|
if (is_stereo_amps(codec, nid, dir))
|
2015-03-12 07:30:11 +00:00
|
|
|
snd_hda_codec_amp_init_stereo(codec, nid, dir, idx, 0xff, val);
|
|
|
|
else
|
|
|
|
snd_hda_codec_amp_init(codec, nid, 0, dir, idx, 0xff, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update the amp, doing in stereo or mono depending on NID */
|
|
|
|
static int update_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx,
|
|
|
|
unsigned int mask, unsigned int val)
|
|
|
|
{
|
2015-03-16 09:18:08 +00:00
|
|
|
if (is_stereo_amps(codec, nid, dir))
|
2015-03-12 07:30:11 +00:00
|
|
|
return snd_hda_codec_amp_stereo(codec, nid, dir, idx,
|
|
|
|
mask, val);
|
|
|
|
else
|
|
|
|
return snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
|
|
|
|
mask, val);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-01-18 10:01:33 +00:00
|
|
|
/* calculate amp value mask we can modify;
|
|
|
|
* if the given amp is controlled by mixers, don't touch it
|
|
|
|
*/
|
|
|
|
static unsigned int get_amp_mask_to_modify(struct hda_codec *codec,
|
|
|
|
hda_nid_t nid, int dir, int idx,
|
|
|
|
unsigned int caps)
|
|
|
|
{
|
|
|
|
unsigned int mask = 0xff;
|
|
|
|
|
2013-08-08 07:32:37 +00:00
|
|
|
if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) {
|
2013-01-18 10:01:33 +00:00
|
|
|
if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_MUTE_CTL))
|
|
|
|
mask &= ~0x80;
|
|
|
|
}
|
|
|
|
if (caps & AC_AMPCAP_NUM_STEPS) {
|
|
|
|
if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) ||
|
|
|
|
is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL))
|
|
|
|
mask &= ~0x7f;
|
|
|
|
}
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static void activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir,
|
2013-01-18 10:01:33 +00:00
|
|
|
int idx, int idx_to_check, bool enable)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
2013-01-18 10:01:33 +00:00
|
|
|
unsigned int caps;
|
|
|
|
unsigned int mask, val;
|
|
|
|
|
|
|
|
caps = query_amp_caps(codec, nid, dir);
|
|
|
|
val = get_amp_val_to_activate(codec, nid, dir, caps, enable);
|
|
|
|
mask = get_amp_mask_to_modify(codec, nid, dir, idx_to_check, caps);
|
|
|
|
if (!mask)
|
2012-12-19 11:52:06 +00:00
|
|
|
return;
|
2013-01-18 10:01:33 +00:00
|
|
|
|
|
|
|
val &= mask;
|
2015-03-12 07:30:11 +00:00
|
|
|
update_amp(codec, nid, dir, idx, mask, val);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
static void check_and_activate_amp(struct hda_codec *codec, hda_nid_t nid,
|
|
|
|
int dir, int idx, int idx_to_check,
|
|
|
|
bool enable)
|
|
|
|
{
|
|
|
|
/* check whether the given amp is still used by others */
|
|
|
|
if (!enable && is_active_nid(codec, nid, dir, idx_to_check))
|
|
|
|
return;
|
|
|
|
activate_amp(codec, nid, dir, idx, idx_to_check, enable);
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static void activate_amp_out(struct hda_codec *codec, struct nid_path *path,
|
|
|
|
int i, bool enable)
|
|
|
|
{
|
|
|
|
hda_nid_t nid = path->path[i];
|
|
|
|
init_amp(codec, nid, HDA_OUTPUT, 0);
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
check_and_activate_amp(codec, nid, HDA_OUTPUT, 0, 0, enable);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void activate_amp_in(struct hda_codec *codec, struct nid_path *path,
|
|
|
|
int i, bool enable, bool add_aamix)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-03 14:25:11 +00:00
|
|
|
const hda_nid_t *conn;
|
2012-12-19 11:52:06 +00:00
|
|
|
int n, nums, idx;
|
|
|
|
int type;
|
|
|
|
hda_nid_t nid = path->path[i];
|
|
|
|
|
2013-01-03 14:25:11 +00:00
|
|
|
nums = snd_hda_get_conn_list(codec, nid, &conn);
|
2017-10-13 10:57:10 +00:00
|
|
|
if (nums < 0)
|
|
|
|
return;
|
2012-12-19 11:52:06 +00:00
|
|
|
type = get_wcaps_type(get_wcaps(codec, nid));
|
|
|
|
if (type == AC_WID_PIN ||
|
|
|
|
(type == AC_WID_AUD_IN && codec->single_adc_amp)) {
|
|
|
|
nums = 1;
|
|
|
|
idx = 0;
|
|
|
|
} else
|
|
|
|
idx = path->idx[i];
|
|
|
|
|
|
|
|
for (n = 0; n < nums; n++)
|
|
|
|
init_amp(codec, nid, HDA_INPUT, n);
|
|
|
|
|
|
|
|
/* here is a little bit tricky in comparison with activate_amp_out();
|
|
|
|
* when aa-mixer is available, we need to enable the path as well
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
for (n = 0; n < nums; n++) {
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
if (n != idx) {
|
|
|
|
if (conn[n] != spec->mixer_merge_nid)
|
|
|
|
continue;
|
|
|
|
/* when aamix is disabled, force to off */
|
|
|
|
if (!add_aamix) {
|
|
|
|
activate_amp(codec, nid, HDA_INPUT, n, n, false);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
check_and_activate_amp(codec, nid, HDA_INPUT, n, idx, enable);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
/* sync power of each widget in the the given path */
|
|
|
|
static hda_nid_t path_power_update(struct hda_codec *codec,
|
|
|
|
struct nid_path *path,
|
|
|
|
bool allow_powerdown)
|
|
|
|
{
|
|
|
|
hda_nid_t nid, changed = 0;
|
2016-04-17 07:39:41 +00:00
|
|
|
int i, state, power;
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
|
|
|
|
for (i = 0; i < path->depth; i++) {
|
|
|
|
nid = path->path[i];
|
2015-04-09 08:18:31 +00:00
|
|
|
if (!(get_wcaps(codec, nid) & AC_WCAP_POWER))
|
|
|
|
continue;
|
2015-03-03 09:07:24 +00:00
|
|
|
if (nid == codec->core.afg)
|
2015-03-18 08:23:10 +00:00
|
|
|
continue;
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
if (!allow_powerdown || is_active_nid_for_any(codec, nid))
|
|
|
|
state = AC_PWRST_D0;
|
|
|
|
else
|
|
|
|
state = AC_PWRST_D3;
|
2016-04-17 07:39:41 +00:00
|
|
|
power = snd_hda_codec_read(codec, nid, 0,
|
|
|
|
AC_VERB_GET_POWER_STATE, 0);
|
|
|
|
if (power != (state | (state << 4))) {
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
snd_hda_codec_write(codec, nid, 0,
|
|
|
|
AC_VERB_SET_POWER_STATE, state);
|
|
|
|
changed = nid;
|
2015-05-20 04:49:37 +00:00
|
|
|
/* all known codecs seem to be capable to handl
|
|
|
|
* widgets state even in D3, so far.
|
|
|
|
* if any new codecs need to restore the widget
|
|
|
|
* states after D0 transition, call the function
|
|
|
|
* below.
|
|
|
|
*/
|
|
|
|
#if 0 /* disabled */
|
2015-04-04 10:17:28 +00:00
|
|
|
if (state == AC_PWRST_D0)
|
|
|
|
snd_hdac_regmap_sync_node(&codec->core, nid);
|
2015-05-20 04:49:37 +00:00
|
|
|
#endif
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* do sync with the last power state change */
|
|
|
|
static void sync_power_state_change(struct hda_codec *codec, hda_nid_t nid)
|
|
|
|
{
|
|
|
|
if (nid) {
|
|
|
|
msleep(10);
|
|
|
|
snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_activate_path - activate or deactivate the given path
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @path: the path to activate/deactivate
|
|
|
|
* @enable: flag to activate or not
|
|
|
|
* @add_aamix: enable the input from aamix NID
|
|
|
|
*
|
|
|
|
* If @add_aamix is set, enable the input from aa-mix NID as well (if any).
|
2012-12-19 11:52:06 +00:00
|
|
|
*/
|
|
|
|
void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path,
|
|
|
|
bool enable, bool add_aamix)
|
|
|
|
{
|
2013-01-24 16:32:56 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i;
|
|
|
|
|
2015-08-24 08:52:06 +00:00
|
|
|
path->active = enable;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
/* make sure the widget is powered up */
|
2015-03-20 17:21:03 +00:00
|
|
|
if (enable && (spec->power_down_unused || codec->power_save_node))
|
|
|
|
path_power_update(codec, path, codec->power_save_node);
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
for (i = path->depth - 1; i >= 0; i--) {
|
2013-01-24 16:32:56 +00:00
|
|
|
hda_nid_t nid = path->path[i];
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (enable && path->multi[i])
|
2018-06-27 09:43:09 +00:00
|
|
|
snd_hda_codec_write_cache(codec, nid, 0,
|
2012-12-19 11:52:06 +00:00
|
|
|
AC_VERB_SET_CONNECT_SEL,
|
|
|
|
path->idx[i]);
|
|
|
|
if (has_amp_in(codec, path, i))
|
|
|
|
activate_amp_in(codec, path, i, enable, add_aamix);
|
|
|
|
if (has_amp_out(codec, path, i))
|
|
|
|
activate_amp_out(codec, path, i, enable);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_activate_path);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-24 16:32:56 +00:00
|
|
|
/* if the given path is inactive, put widgets into D3 (only if suitable) */
|
|
|
|
static void path_power_down_sync(struct hda_codec *codec, struct nid_path *path)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
2015-03-20 17:21:03 +00:00
|
|
|
if (!(spec->power_down_unused || codec->power_save_node) || path->active)
|
2013-01-24 16:32:56 +00:00
|
|
|
return;
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
sync_power_state_change(codec, path_power_update(codec, path, true));
|
2013-01-24 16:32:56 +00:00
|
|
|
}
|
|
|
|
|
2012-12-20 14:36:30 +00:00
|
|
|
/* turn on/off EAPD on the given pin */
|
|
|
|
static void set_pin_eapd(struct hda_codec *codec, hda_nid_t pin, bool enable)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
if (spec->own_eapd_ctl ||
|
|
|
|
!(snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_EAPD))
|
|
|
|
return;
|
2013-05-31 17:55:54 +00:00
|
|
|
if (spec->keep_eapd_on && !enable)
|
|
|
|
return;
|
2013-11-12 10:36:00 +00:00
|
|
|
if (codec->inv_eapd)
|
|
|
|
enable = !enable;
|
2018-06-27 09:43:09 +00:00
|
|
|
snd_hda_codec_write_cache(codec, pin, 0,
|
2012-12-20 14:36:30 +00:00
|
|
|
AC_VERB_SET_EAPD_BTLENABLE,
|
|
|
|
enable ? 0x02 : 0x00);
|
|
|
|
}
|
|
|
|
|
2013-01-23 16:07:23 +00:00
|
|
|
/* re-initialize the path specified by the given path index */
|
|
|
|
static void resume_path_from_idx(struct hda_codec *codec, int path_idx)
|
|
|
|
{
|
|
|
|
struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx);
|
|
|
|
if (path)
|
|
|
|
snd_hda_activate_path(codec, path, path->active, false);
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* Helper functions for creating mixer ctl elements
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
|
2013-06-24 14:00:21 +00:00
|
|
|
static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol);
|
2017-05-10 10:13:45 +00:00
|
|
|
static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol);
|
2013-08-09 13:05:03 +00:00
|
|
|
static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol);
|
2013-06-24 14:00:21 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
enum {
|
|
|
|
HDA_CTL_WIDGET_VOL,
|
|
|
|
HDA_CTL_WIDGET_MUTE,
|
|
|
|
HDA_CTL_BIND_MUTE,
|
|
|
|
};
|
|
|
|
static const struct snd_kcontrol_new control_templates[] = {
|
|
|
|
HDA_CODEC_VOLUME(NULL, 0, 0, 0),
|
2013-06-24 14:00:21 +00:00
|
|
|
/* only the put callback is replaced for handling the special mute */
|
|
|
|
{
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.subdevice = HDA_SUBDEV_AMP_FLAG,
|
|
|
|
.info = snd_hda_mixer_amp_switch_info,
|
|
|
|
.get = snd_hda_mixer_amp_switch_get,
|
|
|
|
.put = hda_gen_mixer_mute_put, /* replaced */
|
|
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0),
|
|
|
|
},
|
2013-08-09 13:05:03 +00:00
|
|
|
{
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.info = snd_hda_mixer_amp_switch_info,
|
2017-05-10 10:13:45 +00:00
|
|
|
.get = hda_gen_bind_mute_get,
|
2013-08-09 13:05:03 +00:00
|
|
|
.put = hda_gen_bind_mute_put, /* replaced */
|
|
|
|
.private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0),
|
|
|
|
},
|
2012-12-19 11:52:06 +00:00
|
|
|
};
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* add dynamic controls from template */
|
2013-01-18 13:01:14 +00:00
|
|
|
static struct snd_kcontrol_new *
|
|
|
|
add_control(struct hda_gen_spec *spec, int type, const char *name,
|
2012-12-19 11:52:06 +00:00
|
|
|
int cidx, unsigned long val)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct snd_kcontrol_new *knew;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 13:38:33 +00:00
|
|
|
knew = snd_hda_gen_add_kctl(spec, name, &control_templates[type]);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!knew)
|
2013-01-18 13:01:14 +00:00
|
|
|
return NULL;
|
2012-12-19 11:52:06 +00:00
|
|
|
knew->index = cidx;
|
|
|
|
if (get_amp_nid_(val))
|
|
|
|
knew->subdevice = HDA_SUBDEV_AMP_FLAG;
|
|
|
|
knew->private_value = val;
|
2013-01-18 13:01:14 +00:00
|
|
|
return knew;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int add_control_with_pfx(struct hda_gen_spec *spec, int type,
|
|
|
|
const char *pfx, const char *dir,
|
|
|
|
const char *sfx, int cidx, unsigned long val)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2013-06-28 09:56:49 +00:00
|
|
|
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
2012-12-19 11:52:06 +00:00
|
|
|
snprintf(name, sizeof(name), "%s %s %s", pfx, dir, sfx);
|
2013-01-18 13:01:14 +00:00
|
|
|
if (!add_control(spec, type, name, cidx, val))
|
|
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
#define add_pb_vol_ctrl(spec, type, pfx, val) \
|
|
|
|
add_control_with_pfx(spec, type, pfx, "Playback", "Volume", 0, val)
|
|
|
|
#define add_pb_sw_ctrl(spec, type, pfx, val) \
|
|
|
|
add_control_with_pfx(spec, type, pfx, "Playback", "Switch", 0, val)
|
|
|
|
#define __add_pb_vol_ctrl(spec, type, pfx, cidx, val) \
|
|
|
|
add_control_with_pfx(spec, type, pfx, "Playback", "Volume", cidx, val)
|
|
|
|
#define __add_pb_sw_ctrl(spec, type, pfx, cidx, val) \
|
|
|
|
add_control_with_pfx(spec, type, pfx, "Playback", "Switch", cidx, val)
|
|
|
|
|
|
|
|
static int add_vol_ctl(struct hda_codec *codec, const char *pfx, int cidx,
|
|
|
|
unsigned int chs, struct nid_path *path)
|
|
|
|
{
|
|
|
|
unsigned int val;
|
|
|
|
if (!path)
|
|
|
|
return 0;
|
|
|
|
val = path->ctls[NID_PATH_VOL_CTL];
|
|
|
|
if (!val)
|
|
|
|
return 0;
|
|
|
|
val = amp_val_replace_channels(val, chs);
|
|
|
|
return __add_pb_vol_ctrl(codec->spec, HDA_CTL_WIDGET_VOL, pfx, cidx, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return the channel bits suitable for the given path->ctls[] */
|
|
|
|
static int get_default_ch_nums(struct hda_codec *codec, struct nid_path *path,
|
|
|
|
int type)
|
|
|
|
{
|
|
|
|
int chs = 1; /* mono (left only) */
|
|
|
|
if (path) {
|
|
|
|
hda_nid_t nid = get_amp_nid_(path->ctls[type]);
|
|
|
|
if (nid && (get_wcaps(codec, nid) & AC_WCAP_STEREO))
|
|
|
|
chs = 3; /* stereo */
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return chs;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int add_stereo_vol(struct hda_codec *codec, const char *pfx, int cidx,
|
|
|
|
struct nid_path *path)
|
|
|
|
{
|
|
|
|
int chs = get_default_ch_nums(codec, path, NID_PATH_VOL_CTL);
|
|
|
|
return add_vol_ctl(codec, pfx, cidx, chs, path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create a mute-switch for the given mixer widget;
|
|
|
|
* if it has multiple sources (e.g. DAC and loopback), create a bind-mute
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
static int add_sw_ctl(struct hda_codec *codec, const char *pfx, int cidx,
|
|
|
|
unsigned int chs, struct nid_path *path)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int val;
|
|
|
|
int type = HDA_CTL_WIDGET_MUTE;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
val = path->ctls[NID_PATH_MUTE_CTL];
|
|
|
|
if (!val)
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
val = amp_val_replace_channels(val, chs);
|
|
|
|
if (get_amp_direction_(val) == HDA_INPUT) {
|
|
|
|
hda_nid_t nid = get_amp_nid_(val);
|
|
|
|
int nums = snd_hda_get_num_conns(codec, nid);
|
|
|
|
if (nums > 1) {
|
|
|
|
type = HDA_CTL_BIND_MUTE;
|
|
|
|
val |= nums << 19;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return __add_pb_sw_ctrl(codec->spec, type, pfx, cidx, val);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int add_stereo_sw(struct hda_codec *codec, const char *pfx,
|
|
|
|
int cidx, struct nid_path *path)
|
|
|
|
{
|
|
|
|
int chs = get_default_ch_nums(codec, path, NID_PATH_MUTE_CTL);
|
|
|
|
return add_sw_ctl(codec, pfx, cidx, chs, path);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-06-24 14:00:21 +00:00
|
|
|
/* playback mute control with the software mute bit check */
|
2013-08-09 13:05:03 +00:00
|
|
|
static void sync_auto_mute_bits(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
2013-06-24 14:00:21 +00:00
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->auto_mute_via_amp) {
|
|
|
|
hda_nid_t nid = get_amp_nid(kcontrol);
|
|
|
|
bool enabled = !((spec->mute_bits >> nid) & 1);
|
|
|
|
ucontrol->value.integer.value[0] &= enabled;
|
|
|
|
ucontrol->value.integer.value[1] &= enabled;
|
|
|
|
}
|
2013-08-09 13:05:03 +00:00
|
|
|
}
|
2013-06-24 14:00:21 +00:00
|
|
|
|
2013-08-09 13:05:03 +00:00
|
|
|
static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
sync_auto_mute_bits(kcontrol, ucontrol);
|
2013-06-24 14:00:21 +00:00
|
|
|
return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
|
|
|
|
}
|
|
|
|
|
2017-05-10 10:13:45 +00:00
|
|
|
/*
|
|
|
|
* Bound mute controls
|
|
|
|
*/
|
|
|
|
#define AMP_VAL_IDX_SHIFT 19
|
|
|
|
#define AMP_VAL_IDX_MASK (0x0f<<19)
|
|
|
|
|
|
|
|
static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
unsigned long pval;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
mutex_lock(&codec->control_mutex);
|
|
|
|
pval = kcontrol->private_value;
|
|
|
|
kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */
|
|
|
|
err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
|
|
|
|
kcontrol->private_value = pval;
|
|
|
|
mutex_unlock(&codec->control_mutex);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2013-08-09 13:05:03 +00:00
|
|
|
static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
2017-05-10 10:13:45 +00:00
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
unsigned long pval;
|
|
|
|
int i, indices, err = 0, change = 0;
|
|
|
|
|
2013-08-09 13:05:03 +00:00
|
|
|
sync_auto_mute_bits(kcontrol, ucontrol);
|
2017-05-10 10:13:45 +00:00
|
|
|
|
|
|
|
mutex_lock(&codec->control_mutex);
|
|
|
|
pval = kcontrol->private_value;
|
|
|
|
indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT;
|
|
|
|
for (i = 0; i < indices; i++) {
|
|
|
|
kcontrol->private_value = (pval & ~AMP_VAL_IDX_MASK) |
|
|
|
|
(i << AMP_VAL_IDX_SHIFT);
|
|
|
|
err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
|
|
|
|
if (err < 0)
|
|
|
|
break;
|
|
|
|
change |= err;
|
|
|
|
}
|
|
|
|
kcontrol->private_value = pval;
|
|
|
|
mutex_unlock(&codec->control_mutex);
|
|
|
|
return err < 0 ? err : change;
|
2013-08-09 13:05:03 +00:00
|
|
|
}
|
|
|
|
|
2013-01-17 15:18:11 +00:00
|
|
|
/* any ctl assigned to the path with the given index? */
|
|
|
|
static bool path_has_mixer(struct hda_codec *codec, int path_idx, int ctl_type)
|
|
|
|
{
|
|
|
|
struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx);
|
|
|
|
return path && path->ctls[ctl_type];
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static const char * const channel_name[4] = {
|
|
|
|
"Front", "Surround", "CLFE", "Side"
|
|
|
|
};
|
2006-03-21 10:29:07 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* give some appropriate ctl name prefix for the given line out channel */
|
2013-01-17 15:18:11 +00:00
|
|
|
static const char *get_line_out_pfx(struct hda_codec *codec, int ch,
|
|
|
|
int *index, int ctl_type)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
2013-01-17 15:18:11 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2012-12-19 11:52:06 +00:00
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
*index = 0;
|
|
|
|
if (cfg->line_outs == 1 && !spec->multi_ios &&
|
2017-04-10 15:12:33 +00:00
|
|
|
!codec->force_pin_prefix &&
|
2013-01-17 15:18:11 +00:00
|
|
|
!cfg->hp_outs && !cfg->speaker_outs)
|
2012-12-19 11:52:06 +00:00
|
|
|
return spec->vmaster_mute.hook ? "PCM" : "Master";
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* if there is really a single DAC used in the whole output paths,
|
|
|
|
* use it master (or "PCM" if a vmaster hook is present)
|
|
|
|
*/
|
|
|
|
if (spec->multiout.num_dacs == 1 && !spec->mixer_nid &&
|
2017-04-10 15:12:33 +00:00
|
|
|
!codec->force_pin_prefix &&
|
2012-12-19 11:52:06 +00:00
|
|
|
!spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0])
|
|
|
|
return spec->vmaster_mute.hook ? "PCM" : "Master";
|
|
|
|
|
2013-01-17 15:18:11 +00:00
|
|
|
/* multi-io channels */
|
|
|
|
if (ch >= cfg->line_outs)
|
|
|
|
return channel_name[ch];
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
switch (cfg->line_out_type) {
|
|
|
|
case AUTO_PIN_SPEAKER_OUT:
|
2013-01-17 15:18:11 +00:00
|
|
|
/* if the primary channel vol/mute is shared with HP volume,
|
|
|
|
* don't name it as Speaker
|
|
|
|
*/
|
|
|
|
if (!ch && cfg->hp_outs &&
|
|
|
|
!path_has_mixer(codec, spec->hp_paths[0], ctl_type))
|
|
|
|
break;
|
2012-12-19 11:52:06 +00:00
|
|
|
if (cfg->line_outs == 1)
|
|
|
|
return "Speaker";
|
|
|
|
if (cfg->line_outs == 2)
|
|
|
|
return ch ? "Bass Speaker" : "Speaker";
|
|
|
|
break;
|
|
|
|
case AUTO_PIN_HP_OUT:
|
2013-01-17 15:18:11 +00:00
|
|
|
/* if the primary channel vol/mute is shared with spk volume,
|
|
|
|
* don't name it as Headphone
|
|
|
|
*/
|
|
|
|
if (!ch && cfg->speaker_outs &&
|
|
|
|
!path_has_mixer(codec, spec->speaker_paths[0], ctl_type))
|
|
|
|
break;
|
2012-12-19 11:52:06 +00:00
|
|
|
/* for multi-io case, only the primary out */
|
|
|
|
if (ch && spec->multi_ios)
|
|
|
|
break;
|
|
|
|
*index = ch;
|
|
|
|
return "Headphone";
|
2014-10-16 13:33:45 +00:00
|
|
|
case AUTO_PIN_LINE_OUT:
|
|
|
|
/* This deals with the case where we have two DACs and
|
|
|
|
* one LO, one HP and one Speaker */
|
|
|
|
if (!ch && cfg->speaker_outs && cfg->hp_outs) {
|
|
|
|
bool hp_lo_shared = !path_has_mixer(codec, spec->hp_paths[0], ctl_type);
|
|
|
|
bool spk_lo_shared = !path_has_mixer(codec, spec->speaker_paths[0], ctl_type);
|
|
|
|
if (hp_lo_shared && spk_lo_shared)
|
|
|
|
return spec->vmaster_mute.hook ? "PCM" : "Master";
|
|
|
|
if (hp_lo_shared)
|
|
|
|
return "Headphone+LO";
|
|
|
|
if (spk_lo_shared)
|
|
|
|
return "Speaker+LO";
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2013-01-17 15:18:11 +00:00
|
|
|
|
|
|
|
/* for a single channel output, we don't have to name the channel */
|
|
|
|
if (cfg->line_outs == 1 && !spec->multi_ios)
|
2014-10-16 13:33:46 +00:00
|
|
|
return "Line Out";
|
2013-01-17 15:18:11 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (ch >= ARRAY_SIZE(channel_name)) {
|
|
|
|
snd_BUG();
|
|
|
|
return "PCM";
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return channel_name[ch];
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* Parse output paths
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* badness definition */
|
|
|
|
enum {
|
|
|
|
/* No primary DAC is found for the main output */
|
|
|
|
BAD_NO_PRIMARY_DAC = 0x10000,
|
|
|
|
/* No DAC is found for the extra output */
|
|
|
|
BAD_NO_DAC = 0x4000,
|
|
|
|
/* No possible multi-ios */
|
2013-02-13 13:17:32 +00:00
|
|
|
BAD_MULTI_IO = 0x120,
|
2012-12-19 11:52:06 +00:00
|
|
|
/* No individual DAC for extra output */
|
|
|
|
BAD_NO_EXTRA_DAC = 0x102,
|
|
|
|
/* No individual DAC for extra surrounds */
|
|
|
|
BAD_NO_EXTRA_SURR_DAC = 0x101,
|
|
|
|
/* Primary DAC shared with main surrounds */
|
|
|
|
BAD_SHARED_SURROUND = 0x100,
|
2013-03-21 16:20:12 +00:00
|
|
|
/* No independent HP possible */
|
2013-03-22 14:10:08 +00:00
|
|
|
BAD_NO_INDEP_HP = 0x10,
|
2012-12-19 11:52:06 +00:00
|
|
|
/* Primary DAC shared with main CLFE */
|
|
|
|
BAD_SHARED_CLFE = 0x10,
|
|
|
|
/* Primary DAC shared with extra surrounds */
|
|
|
|
BAD_SHARED_EXTRA_SURROUND = 0x10,
|
|
|
|
/* Volume widget is shared */
|
|
|
|
BAD_SHARED_VOL = 0x10,
|
|
|
|
};
|
|
|
|
|
2013-01-07 14:11:44 +00:00
|
|
|
/* look for widgets in the given path which are appropriate for
|
2012-12-19 11:52:06 +00:00
|
|
|
* volume and mute controls, and assign the values to ctls[].
|
|
|
|
*
|
|
|
|
* When no appropriate widget is found in the path, the badness value
|
|
|
|
* is incremented depending on the situation. The function returns the
|
|
|
|
* total badness for both volume and mute controls.
|
|
|
|
*/
|
2013-01-07 14:11:44 +00:00
|
|
|
static int assign_out_path_ctls(struct hda_codec *codec, struct nid_path *path)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
ALSA: hda - Add TLV_DB_SCALE_MUTE bit for relevant controls
The DACs on Sigmatel/IDT codecs do mute at the lowest volume level,
and in the earlier drivers, we passed TLV_DB_SCALE_MUTE bit for each
volume control element like Speaker and Headphone as well as Master.
Along with the translation to the generic parser, however, the TLV bit
was lost for the slave controls (e.g. Speaker) but set only to
Master. In theory this should have sufficed, but apps, particularly
PA, do care the slave volume bits, so we seem to see a regression in
the volume controls.
This patch adds a flag to hda_gen_spec to specify the DAC mute
feature, and adds the TLV bit properly for all relevant volume
controls. Also, the TLV bit for vmaster is set in hda_generic.c, so
that we can get rid of all tricks from the codec driver side.
As the similar hack is applied to Conexant 5051 stuff, we can get rid
of it as well.
BugLink: https://bugs.launchpad.net/bugs/1357928
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-09-01 08:07:04 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2012-12-19 11:52:06 +00:00
|
|
|
hda_nid_t nid;
|
|
|
|
unsigned int val;
|
|
|
|
int badness = 0;
|
|
|
|
|
|
|
|
if (!path)
|
|
|
|
return BAD_SHARED_VOL * 2;
|
2013-01-07 14:11:44 +00:00
|
|
|
|
|
|
|
if (path->ctls[NID_PATH_VOL_CTL] ||
|
|
|
|
path->ctls[NID_PATH_MUTE_CTL])
|
|
|
|
return 0; /* already evaluated */
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
nid = look_for_out_vol_nid(codec, path);
|
|
|
|
if (nid) {
|
|
|
|
val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
|
ALSA: hda - Add TLV_DB_SCALE_MUTE bit for relevant controls
The DACs on Sigmatel/IDT codecs do mute at the lowest volume level,
and in the earlier drivers, we passed TLV_DB_SCALE_MUTE bit for each
volume control element like Speaker and Headphone as well as Master.
Along with the translation to the generic parser, however, the TLV bit
was lost for the slave controls (e.g. Speaker) but set only to
Master. In theory this should have sufficed, but apps, particularly
PA, do care the slave volume bits, so we seem to see a regression in
the volume controls.
This patch adds a flag to hda_gen_spec to specify the DAC mute
feature, and adds the TLV bit properly for all relevant volume
controls. Also, the TLV bit for vmaster is set in hda_generic.c, so
that we can get rid of all tricks from the codec driver side.
As the similar hack is applied to Conexant 5051 stuff, we can get rid
of it as well.
BugLink: https://bugs.launchpad.net/bugs/1357928
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-09-01 08:07:04 +00:00
|
|
|
if (spec->dac_min_mute)
|
|
|
|
val |= HDA_AMP_VAL_MIN_MUTE;
|
2012-12-19 11:52:06 +00:00
|
|
|
if (is_ctl_used(codec, val, NID_PATH_VOL_CTL))
|
|
|
|
badness += BAD_SHARED_VOL;
|
|
|
|
else
|
|
|
|
path->ctls[NID_PATH_VOL_CTL] = val;
|
|
|
|
} else
|
|
|
|
badness += BAD_SHARED_VOL;
|
|
|
|
nid = look_for_out_mute_nid(codec, path);
|
|
|
|
if (nid) {
|
|
|
|
unsigned int wid_type = get_wcaps_type(get_wcaps(codec, nid));
|
|
|
|
if (wid_type == AC_WID_PIN || wid_type == AC_WID_AUD_OUT ||
|
|
|
|
nid_has_mute(codec, nid, HDA_OUTPUT))
|
|
|
|
val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
|
|
|
|
else
|
|
|
|
val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT);
|
|
|
|
if (is_ctl_used(codec, val, NID_PATH_MUTE_CTL))
|
|
|
|
badness += BAD_SHARED_VOL;
|
|
|
|
else
|
|
|
|
path->ctls[NID_PATH_MUTE_CTL] = val;
|
|
|
|
} else
|
|
|
|
badness += BAD_SHARED_VOL;
|
|
|
|
return badness;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-03-22 13:53:50 +00:00
|
|
|
const struct badness_table hda_main_out_badness = {
|
2012-12-19 11:52:06 +00:00
|
|
|
.no_primary_dac = BAD_NO_PRIMARY_DAC,
|
|
|
|
.no_dac = BAD_NO_DAC,
|
|
|
|
.shared_primary = BAD_NO_PRIMARY_DAC,
|
|
|
|
.shared_surr = BAD_SHARED_SURROUND,
|
|
|
|
.shared_clfe = BAD_SHARED_CLFE,
|
|
|
|
.shared_surr_main = BAD_SHARED_SURROUND,
|
|
|
|
};
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(hda_main_out_badness);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-03-22 13:53:50 +00:00
|
|
|
const struct badness_table hda_extra_out_badness = {
|
2012-12-19 11:52:06 +00:00
|
|
|
.no_primary_dac = BAD_NO_DAC,
|
|
|
|
.no_dac = BAD_NO_DAC,
|
|
|
|
.shared_primary = BAD_NO_EXTRA_DAC,
|
|
|
|
.shared_surr = BAD_SHARED_EXTRA_SURROUND,
|
|
|
|
.shared_clfe = BAD_SHARED_EXTRA_SURROUND,
|
|
|
|
.shared_surr_main = BAD_NO_EXTRA_SURR_DAC,
|
|
|
|
};
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(hda_extra_out_badness);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-07 08:50:52 +00:00
|
|
|
/* get the DAC of the primary output corresponding to the given array index */
|
|
|
|
static hda_nid_t get_primary_out(struct hda_codec *codec, int idx)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
|
|
|
|
if (cfg->line_outs > idx)
|
|
|
|
return spec->private_dac_nids[idx];
|
|
|
|
idx -= cfg->line_outs;
|
|
|
|
if (spec->multi_ios > idx)
|
|
|
|
return spec->multi_io[idx].dac;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return the DAC if it's reachable, otherwise zero */
|
|
|
|
static inline hda_nid_t try_dac(struct hda_codec *codec,
|
|
|
|
hda_nid_t dac, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
return is_reachable_path(codec, dac, pin) ? dac : 0;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* try to assign DACs to pins and return the resultant badness */
|
|
|
|
static int try_assign_dacs(struct hda_codec *codec, int num_outs,
|
|
|
|
const hda_nid_t *pins, hda_nid_t *dacs,
|
2013-01-04 14:01:40 +00:00
|
|
|
int *path_idx,
|
2012-12-19 11:52:06 +00:00
|
|
|
const struct badness_table *bad)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i, j;
|
|
|
|
int badness = 0;
|
|
|
|
hda_nid_t dac;
|
|
|
|
|
|
|
|
if (!num_outs)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (i = 0; i < num_outs; i++) {
|
2012-12-20 16:54:22 +00:00
|
|
|
struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
hda_nid_t pin = pins[i];
|
2013-01-04 11:56:52 +00:00
|
|
|
|
2013-01-07 14:11:44 +00:00
|
|
|
path = snd_hda_get_path_from_idx(codec, path_idx[i]);
|
|
|
|
if (path) {
|
|
|
|
badness += assign_out_path_ctls(codec, path);
|
2013-01-04 11:56:52 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-12-10 16:29:26 +00:00
|
|
|
dacs[i] = get_preferred_dac(codec, pin);
|
|
|
|
if (dacs[i]) {
|
|
|
|
if (is_dac_already_used(codec, dacs[i]))
|
|
|
|
badness += bad->shared_primary;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dacs[i])
|
|
|
|
dacs[i] = look_for_dac(codec, pin, false);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!dacs[i] && !i) {
|
2013-01-09 08:28:20 +00:00
|
|
|
/* try to steal the DAC of surrounds for the front */
|
2012-12-19 11:52:06 +00:00
|
|
|
for (j = 1; j < num_outs; j++) {
|
|
|
|
if (is_reachable_path(codec, dacs[j], pin)) {
|
|
|
|
dacs[0] = dacs[j];
|
|
|
|
dacs[j] = 0;
|
2013-01-09 08:28:20 +00:00
|
|
|
invalidate_nid_path(codec, path_idx[j]);
|
2013-01-04 14:01:40 +00:00
|
|
|
path_idx[j] = 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2006-08-23 16:34:06 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
dac = dacs[i];
|
|
|
|
if (!dac) {
|
2013-01-07 08:50:52 +00:00
|
|
|
if (num_outs > 2)
|
|
|
|
dac = try_dac(codec, get_primary_out(codec, i), pin);
|
|
|
|
if (!dac)
|
|
|
|
dac = try_dac(codec, dacs[0], pin);
|
|
|
|
if (!dac)
|
|
|
|
dac = try_dac(codec, get_primary_out(codec, i), pin);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (dac) {
|
|
|
|
if (!i)
|
|
|
|
badness += bad->shared_primary;
|
|
|
|
else if (i == 1)
|
|
|
|
badness += bad->shared_surr;
|
|
|
|
else
|
|
|
|
badness += bad->shared_clfe;
|
|
|
|
} else if (is_reachable_path(codec, spec->private_dac_nids[0], pin)) {
|
|
|
|
dac = spec->private_dac_nids[0];
|
|
|
|
badness += bad->shared_surr_main;
|
|
|
|
} else if (!i)
|
|
|
|
badness += bad->no_primary_dac;
|
|
|
|
else
|
|
|
|
badness += bad->no_dac;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2013-01-21 10:43:19 +00:00
|
|
|
if (!dac)
|
|
|
|
continue;
|
2013-01-07 16:25:08 +00:00
|
|
|
path = snd_hda_add_new_path(codec, dac, pin, -spec->mixer_nid);
|
2013-01-04 14:41:41 +00:00
|
|
|
if (!path && !i && spec->mixer_nid) {
|
2012-12-20 17:29:16 +00:00
|
|
|
/* try with aamix */
|
2013-01-07 16:25:08 +00:00
|
|
|
path = snd_hda_add_new_path(codec, dac, pin, 0);
|
2012-12-20 17:29:16 +00:00
|
|
|
}
|
2013-01-21 10:43:19 +00:00
|
|
|
if (!path) {
|
2012-12-19 11:52:06 +00:00
|
|
|
dac = dacs[i] = 0;
|
2013-01-21 10:43:19 +00:00
|
|
|
badness += bad->no_dac;
|
|
|
|
} else {
|
2014-02-25 11:21:03 +00:00
|
|
|
/* print_nid_path(codec, "output", path); */
|
2013-01-03 15:33:02 +00:00
|
|
|
path->active = true;
|
2013-01-04 14:01:40 +00:00
|
|
|
path_idx[i] = snd_hda_get_path_idx(codec, path);
|
2013-01-07 14:11:44 +00:00
|
|
|
badness += assign_out_path_ctls(codec, path);
|
2013-01-03 15:33:02 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return badness;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* return NID if the given pin has only a single connection to a certain DAC */
|
|
|
|
static hda_nid_t get_dac_if_single(struct hda_codec *codec, hda_nid_t pin)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
hda_nid_t nid_found = 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
for (i = 0; i < spec->num_all_dacs; i++) {
|
|
|
|
hda_nid_t nid = spec->all_dacs[i];
|
|
|
|
if (!nid || is_dac_already_used(codec, nid))
|
|
|
|
continue;
|
|
|
|
if (is_reachable_path(codec, nid, pin)) {
|
|
|
|
if (nid_found)
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
nid_found = nid;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
return nid_found;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* check whether the given pin can be a multi-io pin */
|
|
|
|
static bool can_be_multiio_pin(struct hda_codec *codec,
|
|
|
|
unsigned int location, hda_nid_t nid)
|
2007-08-10 15:21:45 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int defcfg, caps;
|
|
|
|
|
|
|
|
defcfg = snd_hda_codec_get_pincfg(codec, nid);
|
|
|
|
if (get_defcfg_connect(defcfg) != AC_JACK_PORT_COMPLEX)
|
|
|
|
return false;
|
|
|
|
if (location && get_defcfg_location(defcfg) != location)
|
|
|
|
return false;
|
|
|
|
caps = snd_hda_query_pin_caps(codec, nid);
|
|
|
|
if (!(caps & AC_PINCAP_OUT))
|
|
|
|
return false;
|
|
|
|
return true;
|
2007-08-10 15:21:45 +00:00
|
|
|
}
|
|
|
|
|
2013-01-04 13:50:04 +00:00
|
|
|
/* count the number of input pins that are capable to be multi-io */
|
|
|
|
static int count_multiio_pins(struct hda_codec *codec, hda_nid_t reference_pin)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin);
|
|
|
|
unsigned int location = get_defcfg_location(defcfg);
|
|
|
|
int type, i;
|
|
|
|
int num_pins = 0;
|
|
|
|
|
|
|
|
for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
if (cfg->inputs[i].type != type)
|
|
|
|
continue;
|
|
|
|
if (can_be_multiio_pin(codec, location,
|
|
|
|
cfg->inputs[i].pin))
|
|
|
|
num_pins++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return num_pins;
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* multi-io helper
|
|
|
|
*
|
|
|
|
* When hardwired is set, try to fill ony hardwired pins, and returns
|
|
|
|
* zero if any pins are filled, non-zero if nothing found.
|
|
|
|
* When hardwired is off, try to fill possible input pins, and returns
|
|
|
|
* the badness value.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
static int fill_multi_ios(struct hda_codec *codec,
|
|
|
|
hda_nid_t reference_pin,
|
2013-01-04 13:50:04 +00:00
|
|
|
bool hardwired)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
2013-01-04 13:50:04 +00:00
|
|
|
int type, i, j, num_pins, old_pins;
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin);
|
|
|
|
unsigned int location = get_defcfg_location(defcfg);
|
|
|
|
int badness = 0;
|
2013-01-07 14:11:44 +00:00
|
|
|
struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
old_pins = spec->multi_ios;
|
|
|
|
if (old_pins >= 2)
|
|
|
|
goto end_fill;
|
|
|
|
|
2013-01-04 13:50:04 +00:00
|
|
|
num_pins = count_multiio_pins(codec, reference_pin);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (num_pins < 2)
|
|
|
|
goto end_fill;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
hda_nid_t nid = cfg->inputs[i].pin;
|
|
|
|
hda_nid_t dac = 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (cfg->inputs[i].type != type)
|
|
|
|
continue;
|
|
|
|
if (!can_be_multiio_pin(codec, location, nid))
|
|
|
|
continue;
|
|
|
|
for (j = 0; j < spec->multi_ios; j++) {
|
|
|
|
if (nid == spec->multi_io[j].pin)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (j < spec->multi_ios)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (hardwired)
|
|
|
|
dac = get_dac_if_single(codec, nid);
|
|
|
|
else if (!dac)
|
|
|
|
dac = look_for_dac(codec, nid, false);
|
|
|
|
if (!dac) {
|
|
|
|
badness++;
|
|
|
|
continue;
|
|
|
|
}
|
2013-01-07 16:25:08 +00:00
|
|
|
path = snd_hda_add_new_path(codec, dac, nid,
|
|
|
|
-spec->mixer_nid);
|
2012-12-20 16:54:22 +00:00
|
|
|
if (!path) {
|
2012-12-19 11:52:06 +00:00
|
|
|
badness++;
|
|
|
|
continue;
|
|
|
|
}
|
2014-02-25 11:21:03 +00:00
|
|
|
/* print_nid_path(codec, "multiio", path); */
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->multi_io[spec->multi_ios].pin = nid;
|
|
|
|
spec->multi_io[spec->multi_ios].dac = dac;
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->out_paths[cfg->line_outs + spec->multi_ios] =
|
|
|
|
snd_hda_get_path_idx(codec, path);
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->multi_ios++;
|
|
|
|
if (spec->multi_ios >= 2)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end_fill:
|
|
|
|
if (badness)
|
|
|
|
badness = BAD_MULTI_IO;
|
|
|
|
if (old_pins == spec->multi_ios) {
|
|
|
|
if (hardwired)
|
|
|
|
return 1; /* nothing found */
|
|
|
|
else
|
|
|
|
return badness; /* no badness if nothing found */
|
|
|
|
}
|
|
|
|
if (!hardwired && spec->multi_ios < 2) {
|
|
|
|
/* cancel newly assigned paths */
|
|
|
|
spec->paths.used -= spec->multi_ios - old_pins;
|
|
|
|
spec->multi_ios = old_pins;
|
|
|
|
return badness;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* assign volume and mute controls */
|
2013-01-07 14:11:44 +00:00
|
|
|
for (i = old_pins; i < spec->multi_ios; i++) {
|
|
|
|
path = snd_hda_get_path_from_idx(codec, spec->out_paths[cfg->line_outs + i]);
|
|
|
|
badness += assign_out_path_ctls(codec, path);
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
return badness;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* map DACs for all pins in the list if they are single connections */
|
|
|
|
static bool map_singles(struct hda_codec *codec, int outs,
|
2013-01-04 14:01:40 +00:00
|
|
|
const hda_nid_t *pins, hda_nid_t *dacs, int *path_idx)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
2012-12-20 17:29:16 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i;
|
|
|
|
bool found = false;
|
|
|
|
for (i = 0; i < outs; i++) {
|
2012-12-20 16:54:22 +00:00
|
|
|
struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
hda_nid_t dac;
|
|
|
|
if (dacs[i])
|
|
|
|
continue;
|
|
|
|
dac = get_dac_if_single(codec, pins[i]);
|
|
|
|
if (!dac)
|
|
|
|
continue;
|
2013-01-07 16:25:08 +00:00
|
|
|
path = snd_hda_add_new_path(codec, dac, pins[i],
|
|
|
|
-spec->mixer_nid);
|
2013-01-04 14:41:41 +00:00
|
|
|
if (!path && !i && spec->mixer_nid)
|
2013-01-07 16:25:08 +00:00
|
|
|
path = snd_hda_add_new_path(codec, dac, pins[i], 0);
|
2012-12-20 16:54:22 +00:00
|
|
|
if (path) {
|
2012-12-19 11:52:06 +00:00
|
|
|
dacs[i] = dac;
|
|
|
|
found = true;
|
2014-02-25 11:21:03 +00:00
|
|
|
/* print_nid_path(codec, "output", path); */
|
2013-01-03 15:33:02 +00:00
|
|
|
path->active = true;
|
2013-01-04 14:01:40 +00:00
|
|
|
path_idx[i] = snd_hda_get_path_idx(codec, path);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
static inline bool has_aamix_out_paths(struct hda_gen_spec *spec)
|
|
|
|
{
|
|
|
|
return spec->aamix_out_paths[0] || spec->aamix_out_paths[1] ||
|
|
|
|
spec->aamix_out_paths[2];
|
|
|
|
}
|
|
|
|
|
2013-01-04 15:42:48 +00:00
|
|
|
/* create a new path including aamix if available, and return its index */
|
|
|
|
static int check_aamix_out_path(struct hda_codec *codec, int path_idx)
|
|
|
|
{
|
2013-01-07 16:25:08 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-04 15:42:48 +00:00
|
|
|
struct nid_path *path;
|
2013-04-16 12:16:54 +00:00
|
|
|
hda_nid_t path_dac, dac, pin;
|
2013-01-04 15:42:48 +00:00
|
|
|
|
|
|
|
path = snd_hda_get_path_from_idx(codec, path_idx);
|
2013-01-07 16:25:08 +00:00
|
|
|
if (!path || !path->depth ||
|
|
|
|
is_nid_contained(path, spec->mixer_nid))
|
2013-01-04 15:42:48 +00:00
|
|
|
return 0;
|
2013-04-16 12:16:54 +00:00
|
|
|
path_dac = path->path[0];
|
|
|
|
dac = spec->private_dac_nids[0];
|
2013-01-21 13:24:31 +00:00
|
|
|
pin = path->path[path->depth - 1];
|
|
|
|
path = snd_hda_add_new_path(codec, dac, pin, spec->mixer_nid);
|
|
|
|
if (!path) {
|
2013-04-16 12:16:54 +00:00
|
|
|
if (dac != path_dac)
|
|
|
|
dac = path_dac;
|
2013-01-21 13:24:31 +00:00
|
|
|
else if (spec->multiout.hp_out_nid[0])
|
|
|
|
dac = spec->multiout.hp_out_nid[0];
|
|
|
|
else if (spec->multiout.extra_out_nid[0])
|
|
|
|
dac = spec->multiout.extra_out_nid[0];
|
2013-04-16 12:16:54 +00:00
|
|
|
else
|
|
|
|
dac = 0;
|
2013-01-21 13:24:31 +00:00
|
|
|
if (dac)
|
|
|
|
path = snd_hda_add_new_path(codec, dac, pin,
|
|
|
|
spec->mixer_nid);
|
|
|
|
}
|
2013-01-04 15:42:48 +00:00
|
|
|
if (!path)
|
|
|
|
return 0;
|
2014-02-25 11:21:03 +00:00
|
|
|
/* print_nid_path(codec, "output-aamix", path); */
|
2013-01-04 15:42:48 +00:00
|
|
|
path->active = false; /* unused as default */
|
2015-03-20 17:11:05 +00:00
|
|
|
path->pin_fixed = true; /* static route */
|
2013-01-04 15:42:48 +00:00
|
|
|
return snd_hda_get_path_idx(codec, path);
|
|
|
|
}
|
|
|
|
|
2013-03-21 16:20:12 +00:00
|
|
|
/* check whether the independent HP is available with the current config */
|
|
|
|
static bool indep_hp_possible(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
struct nid_path *path;
|
|
|
|
int i, idx;
|
|
|
|
|
|
|
|
if (cfg->line_out_type == AUTO_PIN_HP_OUT)
|
|
|
|
idx = spec->out_paths[0];
|
|
|
|
else
|
|
|
|
idx = spec->hp_paths[0];
|
|
|
|
path = snd_hda_get_path_from_idx(codec, idx);
|
|
|
|
if (!path)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/* assume no path conflicts unless aamix is involved */
|
|
|
|
if (!spec->mixer_nid || !is_nid_contained(path, spec->mixer_nid))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* check whether output paths contain aamix */
|
|
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
|
|
if (spec->out_paths[i] == idx)
|
|
|
|
break;
|
|
|
|
path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]);
|
|
|
|
if (path && is_nid_contained(path, spec->mixer_nid))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (i = 0; i < cfg->speaker_outs; i++) {
|
|
|
|
path = snd_hda_get_path_from_idx(codec, spec->speaker_paths[i]);
|
|
|
|
if (path && is_nid_contained(path, spec->mixer_nid))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-01-07 15:44:06 +00:00
|
|
|
/* fill the empty entries in the dac array for speaker/hp with the
|
|
|
|
* shared dac pointed by the paths
|
|
|
|
*/
|
|
|
|
static void refill_shared_dacs(struct hda_codec *codec, int num_outs,
|
|
|
|
hda_nid_t *dacs, int *path_idx)
|
|
|
|
{
|
|
|
|
struct nid_path *path;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_outs; i++) {
|
|
|
|
if (dacs[i])
|
|
|
|
continue;
|
|
|
|
path = snd_hda_get_path_from_idx(codec, path_idx[i]);
|
|
|
|
if (!path)
|
|
|
|
continue;
|
|
|
|
dacs[i] = path->path[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* fill in the dac_nids table from the parsed pin configuration */
|
|
|
|
static int fill_and_eval_dacs(struct hda_codec *codec,
|
|
|
|
bool fill_hardwired,
|
|
|
|
bool fill_mio_first)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
int i, err, badness;
|
|
|
|
|
|
|
|
/* set num_dacs once to full for look_for_dac() */
|
|
|
|
spec->multiout.num_dacs = cfg->line_outs;
|
|
|
|
spec->multiout.dac_nids = spec->private_dac_nids;
|
|
|
|
memset(spec->private_dac_nids, 0, sizeof(spec->private_dac_nids));
|
|
|
|
memset(spec->multiout.hp_out_nid, 0, sizeof(spec->multiout.hp_out_nid));
|
|
|
|
memset(spec->multiout.extra_out_nid, 0, sizeof(spec->multiout.extra_out_nid));
|
|
|
|
spec->multi_ios = 0;
|
|
|
|
snd_array_free(&spec->paths);
|
2013-01-07 14:07:00 +00:00
|
|
|
|
|
|
|
/* clear path indices */
|
|
|
|
memset(spec->out_paths, 0, sizeof(spec->out_paths));
|
|
|
|
memset(spec->hp_paths, 0, sizeof(spec->hp_paths));
|
|
|
|
memset(spec->speaker_paths, 0, sizeof(spec->speaker_paths));
|
|
|
|
memset(spec->aamix_out_paths, 0, sizeof(spec->aamix_out_paths));
|
|
|
|
memset(spec->digout_paths, 0, sizeof(spec->digout_paths));
|
2013-01-07 16:09:26 +00:00
|
|
|
memset(spec->input_paths, 0, sizeof(spec->input_paths));
|
2013-01-07 14:07:00 +00:00
|
|
|
memset(spec->loopback_paths, 0, sizeof(spec->loopback_paths));
|
|
|
|
memset(&spec->digin_path, 0, sizeof(spec->digin_path));
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
badness = 0;
|
|
|
|
|
|
|
|
/* fill hard-wired DACs first */
|
|
|
|
if (fill_hardwired) {
|
|
|
|
bool mapped;
|
|
|
|
do {
|
|
|
|
mapped = map_singles(codec, cfg->line_outs,
|
|
|
|
cfg->line_out_pins,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->private_dac_nids,
|
|
|
|
spec->out_paths);
|
2012-12-19 11:52:06 +00:00
|
|
|
mapped |= map_singles(codec, cfg->hp_outs,
|
|
|
|
cfg->hp_pins,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->multiout.hp_out_nid,
|
|
|
|
spec->hp_paths);
|
2012-12-19 11:52:06 +00:00
|
|
|
mapped |= map_singles(codec, cfg->speaker_outs,
|
|
|
|
cfg->speaker_pins,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->multiout.extra_out_nid,
|
|
|
|
spec->speaker_paths);
|
ALSA: hda - Fix invalid multi-io creation on VAIO-Z laptops
VAIO-Z laptops need to use the specific DAC for the speaker output
by some unknown reason although the codec itself supports the flexible
connection. So we implemented a workaround by a new flag,
no_primary_hp, for assigning the speaker pin first.
This worked until 3.8 kernel, but it got broken because the driver
learned for a better multi-io pin mapping, and not it can assign two
mic pins for multi-io. Since the multi-io requires to be the primary
output, the hp and two mic pins are assigned in prior to the speaker
in the end.
Although the machine has two mic pins, one of them is used as a noise-
canceling headphone, thus it's no real retaskable mic jack. Thus, at
best, we can disable the multi-io assignment and make the parser
behavior back to the state before the multi-io.
This patch adds again a new flag, no_multi_io, to indicate that the
device has no multi-io capability, and set it in the fixup for
VAIO-Z. The no_multi_io flag itself can be used generically, added
via a helper line, too.
Reported-by: Tormen <my.nl.abos@gmail.com>
Reported-by: Adam Williamson <awilliam@redhat.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-29 14:54:36 +00:00
|
|
|
if (!spec->no_multi_io &&
|
|
|
|
fill_mio_first && cfg->line_outs == 1 &&
|
2012-12-19 11:52:06 +00:00
|
|
|
cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
|
2013-01-04 13:50:04 +00:00
|
|
|
err = fill_multi_ios(codec, cfg->line_out_pins[0], true);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!err)
|
|
|
|
mapped = true;
|
|
|
|
}
|
|
|
|
} while (mapped);
|
|
|
|
}
|
|
|
|
|
|
|
|
badness += try_assign_dacs(codec, cfg->line_outs, cfg->line_out_pins,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->private_dac_nids, spec->out_paths,
|
2013-03-22 13:53:50 +00:00
|
|
|
spec->main_out_badness);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
ALSA: hda - Fix invalid multi-io creation on VAIO-Z laptops
VAIO-Z laptops need to use the specific DAC for the speaker output
by some unknown reason although the codec itself supports the flexible
connection. So we implemented a workaround by a new flag,
no_primary_hp, for assigning the speaker pin first.
This worked until 3.8 kernel, but it got broken because the driver
learned for a better multi-io pin mapping, and not it can assign two
mic pins for multi-io. Since the multi-io requires to be the primary
output, the hp and two mic pins are assigned in prior to the speaker
in the end.
Although the machine has two mic pins, one of them is used as a noise-
canceling headphone, thus it's no real retaskable mic jack. Thus, at
best, we can disable the multi-io assignment and make the parser
behavior back to the state before the multi-io.
This patch adds again a new flag, no_multi_io, to indicate that the
device has no multi-io capability, and set it in the fixup for
VAIO-Z. The no_multi_io flag itself can be used generically, added
via a helper line, too.
Reported-by: Tormen <my.nl.abos@gmail.com>
Reported-by: Adam Williamson <awilliam@redhat.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-29 14:54:36 +00:00
|
|
|
if (!spec->no_multi_io && fill_mio_first &&
|
2012-12-19 11:52:06 +00:00
|
|
|
cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
|
|
|
|
/* try to fill multi-io first */
|
2013-01-04 13:50:04 +00:00
|
|
|
err = fill_multi_ios(codec, cfg->line_out_pins[0], false);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
/* we don't count badness at this stage yet */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
|
|
|
|
err = try_assign_dacs(codec, cfg->hp_outs, cfg->hp_pins,
|
|
|
|
spec->multiout.hp_out_nid,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->hp_paths,
|
2013-03-22 13:53:50 +00:00
|
|
|
spec->extra_out_badness);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
badness += err;
|
|
|
|
}
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
|
|
|
|
err = try_assign_dacs(codec, cfg->speaker_outs,
|
|
|
|
cfg->speaker_pins,
|
|
|
|
spec->multiout.extra_out_nid,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->speaker_paths,
|
2013-03-22 13:53:50 +00:00
|
|
|
spec->extra_out_badness);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
badness += err;
|
|
|
|
}
|
ALSA: hda - Fix invalid multi-io creation on VAIO-Z laptops
VAIO-Z laptops need to use the specific DAC for the speaker output
by some unknown reason although the codec itself supports the flexible
connection. So we implemented a workaround by a new flag,
no_primary_hp, for assigning the speaker pin first.
This worked until 3.8 kernel, but it got broken because the driver
learned for a better multi-io pin mapping, and not it can assign two
mic pins for multi-io. Since the multi-io requires to be the primary
output, the hp and two mic pins are assigned in prior to the speaker
in the end.
Although the machine has two mic pins, one of them is used as a noise-
canceling headphone, thus it's no real retaskable mic jack. Thus, at
best, we can disable the multi-io assignment and make the parser
behavior back to the state before the multi-io.
This patch adds again a new flag, no_multi_io, to indicate that the
device has no multi-io capability, and set it in the fixup for
VAIO-Z. The no_multi_io flag itself can be used generically, added
via a helper line, too.
Reported-by: Tormen <my.nl.abos@gmail.com>
Reported-by: Adam Williamson <awilliam@redhat.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-29 14:54:36 +00:00
|
|
|
if (!spec->no_multi_io &&
|
|
|
|
cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
|
2013-01-04 13:50:04 +00:00
|
|
|
err = fill_multi_ios(codec, cfg->line_out_pins[0], false);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
badness += err;
|
|
|
|
}
|
|
|
|
|
2013-01-04 15:42:48 +00:00
|
|
|
if (spec->mixer_nid) {
|
|
|
|
spec->aamix_out_paths[0] =
|
|
|
|
check_aamix_out_path(codec, spec->out_paths[0]);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT)
|
|
|
|
spec->aamix_out_paths[1] =
|
|
|
|
check_aamix_out_path(codec, spec->hp_paths[0]);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
|
|
|
|
spec->aamix_out_paths[2] =
|
|
|
|
check_aamix_out_path(codec, spec->speaker_paths[0]);
|
|
|
|
}
|
|
|
|
|
ALSA: hda - Fix invalid multi-io creation on VAIO-Z laptops
VAIO-Z laptops need to use the specific DAC for the speaker output
by some unknown reason although the codec itself supports the flexible
connection. So we implemented a workaround by a new flag,
no_primary_hp, for assigning the speaker pin first.
This worked until 3.8 kernel, but it got broken because the driver
learned for a better multi-io pin mapping, and not it can assign two
mic pins for multi-io. Since the multi-io requires to be the primary
output, the hp and two mic pins are assigned in prior to the speaker
in the end.
Although the machine has two mic pins, one of them is used as a noise-
canceling headphone, thus it's no real retaskable mic jack. Thus, at
best, we can disable the multi-io assignment and make the parser
behavior back to the state before the multi-io.
This patch adds again a new flag, no_multi_io, to indicate that the
device has no multi-io capability, and set it in the fixup for
VAIO-Z. The no_multi_io flag itself can be used generically, added
via a helper line, too.
Reported-by: Tormen <my.nl.abos@gmail.com>
Reported-by: Adam Williamson <awilliam@redhat.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-29 14:54:36 +00:00
|
|
|
if (!spec->no_multi_io &&
|
|
|
|
cfg->hp_outs && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
|
2013-01-04 13:50:04 +00:00
|
|
|
if (count_multiio_pins(codec, cfg->hp_pins[0]) >= 2)
|
|
|
|
spec->multi_ios = 1; /* give badness */
|
|
|
|
|
2013-01-07 15:44:06 +00:00
|
|
|
/* re-count num_dacs and squash invalid entries */
|
|
|
|
spec->multiout.num_dacs = 0;
|
|
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
|
|
if (spec->private_dac_nids[i])
|
|
|
|
spec->multiout.num_dacs++;
|
|
|
|
else {
|
|
|
|
memmove(spec->private_dac_nids + i,
|
|
|
|
spec->private_dac_nids + i + 1,
|
|
|
|
sizeof(hda_nid_t) * (cfg->line_outs - i - 1));
|
|
|
|
spec->private_dac_nids[cfg->line_outs - 1] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
spec->ext_channel_count = spec->min_channel_count =
|
2013-01-16 10:45:37 +00:00
|
|
|
spec->multiout.num_dacs * 2;
|
2013-01-07 15:44:06 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->multi_ios == 2) {
|
|
|
|
for (i = 0; i < 2; i++)
|
|
|
|
spec->private_dac_nids[spec->multiout.num_dacs++] =
|
|
|
|
spec->multi_io[i].dac;
|
|
|
|
} else if (spec->multi_ios) {
|
|
|
|
spec->multi_ios = 0;
|
|
|
|
badness += BAD_MULTI_IO;
|
|
|
|
}
|
|
|
|
|
2013-03-21 16:20:12 +00:00
|
|
|
if (spec->indep_hp && !indep_hp_possible(codec))
|
|
|
|
badness += BAD_NO_INDEP_HP;
|
|
|
|
|
2013-01-07 15:44:06 +00:00
|
|
|
/* re-fill the shared DAC for speaker / headphone */
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT)
|
|
|
|
refill_shared_dacs(codec, cfg->hp_outs,
|
|
|
|
spec->multiout.hp_out_nid,
|
|
|
|
spec->hp_paths);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
|
|
|
|
refill_shared_dacs(codec, cfg->speaker_outs,
|
|
|
|
spec->multiout.extra_out_nid,
|
|
|
|
spec->speaker_paths);
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return badness;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define DEBUG_BADNESS
|
|
|
|
|
|
|
|
#ifdef DEBUG_BADNESS
|
2014-07-05 20:02:02 +00:00
|
|
|
#define debug_badness(fmt, ...) \
|
|
|
|
codec_dbg(codec, fmt, ##__VA_ARGS__)
|
2012-12-19 11:52:06 +00:00
|
|
|
#else
|
2014-07-05 20:02:02 +00:00
|
|
|
#define debug_badness(fmt, ...) \
|
|
|
|
do { if (0) codec_dbg(codec, fmt, ##__VA_ARGS__); } while (0)
|
2012-12-19 11:52:06 +00:00
|
|
|
#endif
|
|
|
|
|
2013-01-21 09:43:18 +00:00
|
|
|
#ifdef DEBUG_BADNESS
|
|
|
|
static inline void print_nid_path_idx(struct hda_codec *codec,
|
|
|
|
const char *pfx, int idx)
|
|
|
|
{
|
|
|
|
struct nid_path *path;
|
|
|
|
|
|
|
|
path = snd_hda_get_path_from_idx(codec, idx);
|
|
|
|
if (path)
|
2014-02-25 11:21:03 +00:00
|
|
|
print_nid_path(codec, pfx, path);
|
2013-01-21 09:43:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void debug_show_configs(struct hda_codec *codec,
|
|
|
|
struct auto_pin_cfg *cfg)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
2013-01-21 09:43:18 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
static const char * const lo_type[3] = { "LO", "SP", "HP" };
|
|
|
|
int i;
|
|
|
|
|
|
|
|
debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x (type %s)\n",
|
2012-12-19 11:52:06 +00:00
|
|
|
cfg->line_out_pins[0], cfg->line_out_pins[1],
|
2012-12-20 16:56:57 +00:00
|
|
|
cfg->line_out_pins[2], cfg->line_out_pins[3],
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->multiout.dac_nids[0],
|
|
|
|
spec->multiout.dac_nids[1],
|
|
|
|
spec->multiout.dac_nids[2],
|
2013-01-21 09:43:18 +00:00
|
|
|
spec->multiout.dac_nids[3],
|
|
|
|
lo_type[cfg->line_out_type]);
|
|
|
|
for (i = 0; i < cfg->line_outs; i++)
|
|
|
|
print_nid_path_idx(codec, " out", spec->out_paths[i]);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->multi_ios > 0)
|
|
|
|
debug_badness("multi_ios(%d) = %x/%x : %x/%x\n",
|
|
|
|
spec->multi_ios,
|
|
|
|
spec->multi_io[0].pin, spec->multi_io[1].pin,
|
|
|
|
spec->multi_io[0].dac, spec->multi_io[1].dac);
|
2013-01-21 09:43:18 +00:00
|
|
|
for (i = 0; i < spec->multi_ios; i++)
|
|
|
|
print_nid_path_idx(codec, " mio",
|
|
|
|
spec->out_paths[cfg->line_outs + i]);
|
|
|
|
if (cfg->hp_outs)
|
|
|
|
debug_badness("hp_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
|
2012-12-19 11:52:06 +00:00
|
|
|
cfg->hp_pins[0], cfg->hp_pins[1],
|
2012-12-20 16:56:57 +00:00
|
|
|
cfg->hp_pins[2], cfg->hp_pins[3],
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->multiout.hp_out_nid[0],
|
|
|
|
spec->multiout.hp_out_nid[1],
|
|
|
|
spec->multiout.hp_out_nid[2],
|
|
|
|
spec->multiout.hp_out_nid[3]);
|
2013-01-21 09:43:18 +00:00
|
|
|
for (i = 0; i < cfg->hp_outs; i++)
|
|
|
|
print_nid_path_idx(codec, " hp ", spec->hp_paths[i]);
|
|
|
|
if (cfg->speaker_outs)
|
|
|
|
debug_badness("spk_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
|
2012-12-19 11:52:06 +00:00
|
|
|
cfg->speaker_pins[0], cfg->speaker_pins[1],
|
|
|
|
cfg->speaker_pins[2], cfg->speaker_pins[3],
|
|
|
|
spec->multiout.extra_out_nid[0],
|
|
|
|
spec->multiout.extra_out_nid[1],
|
|
|
|
spec->multiout.extra_out_nid[2],
|
|
|
|
spec->multiout.extra_out_nid[3]);
|
2013-01-21 09:43:18 +00:00
|
|
|
for (i = 0; i < cfg->speaker_outs; i++)
|
|
|
|
print_nid_path_idx(codec, " spk", spec->speaker_paths[i]);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
print_nid_path_idx(codec, " mix", spec->aamix_out_paths[i]);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2013-01-21 09:43:18 +00:00
|
|
|
#else
|
|
|
|
#define debug_show_configs(codec, cfg) /* NOP */
|
|
|
|
#endif
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* find all available DACs of the codec */
|
|
|
|
static void fill_all_dac_nids(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2015-03-03 09:07:24 +00:00
|
|
|
hda_nid_t nid;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
spec->num_all_dacs = 0;
|
|
|
|
memset(spec->all_dacs, 0, sizeof(spec->all_dacs));
|
2015-03-03 09:07:24 +00:00
|
|
|
for_each_hda_codec_node(nid, codec) {
|
2012-12-19 11:52:06 +00:00
|
|
|
if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_OUT)
|
|
|
|
continue;
|
|
|
|
if (spec->num_all_dacs >= ARRAY_SIZE(spec->all_dacs)) {
|
2014-02-25 11:21:03 +00:00
|
|
|
codec_err(codec, "Too many DACs!\n");
|
2012-12-19 11:52:06 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
spec->all_dacs[spec->num_all_dacs++] = nid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_output_paths(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
struct auto_pin_cfg *best_cfg;
|
2013-01-21 09:49:05 +00:00
|
|
|
unsigned int val;
|
2012-12-19 11:52:06 +00:00
|
|
|
int best_badness = INT_MAX;
|
|
|
|
int badness;
|
|
|
|
bool fill_hardwired = true, fill_mio_first = true;
|
|
|
|
bool best_wired = true, best_mio = true;
|
|
|
|
bool hp_spk_swapped = false;
|
|
|
|
|
|
|
|
best_cfg = kmalloc(sizeof(*best_cfg), GFP_KERNEL);
|
|
|
|
if (!best_cfg)
|
|
|
|
return -ENOMEM;
|
|
|
|
*best_cfg = *cfg;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
badness = fill_and_eval_dacs(codec, fill_hardwired,
|
|
|
|
fill_mio_first);
|
|
|
|
if (badness < 0) {
|
|
|
|
kfree(best_cfg);
|
|
|
|
return badness;
|
|
|
|
}
|
|
|
|
debug_badness("==> lo_type=%d, wired=%d, mio=%d, badness=0x%x\n",
|
|
|
|
cfg->line_out_type, fill_hardwired, fill_mio_first,
|
|
|
|
badness);
|
2013-01-21 09:43:18 +00:00
|
|
|
debug_show_configs(codec, cfg);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (badness < best_badness) {
|
|
|
|
best_badness = badness;
|
|
|
|
*best_cfg = *cfg;
|
|
|
|
best_wired = fill_hardwired;
|
|
|
|
best_mio = fill_mio_first;
|
|
|
|
}
|
|
|
|
if (!badness)
|
|
|
|
break;
|
|
|
|
fill_mio_first = !fill_mio_first;
|
|
|
|
if (!fill_mio_first)
|
|
|
|
continue;
|
|
|
|
fill_hardwired = !fill_hardwired;
|
|
|
|
if (!fill_hardwired)
|
|
|
|
continue;
|
|
|
|
if (hp_spk_swapped)
|
|
|
|
break;
|
|
|
|
hp_spk_swapped = true;
|
|
|
|
if (cfg->speaker_outs > 0 &&
|
|
|
|
cfg->line_out_type == AUTO_PIN_HP_OUT) {
|
|
|
|
cfg->hp_outs = cfg->line_outs;
|
|
|
|
memcpy(cfg->hp_pins, cfg->line_out_pins,
|
|
|
|
sizeof(cfg->hp_pins));
|
|
|
|
cfg->line_outs = cfg->speaker_outs;
|
|
|
|
memcpy(cfg->line_out_pins, cfg->speaker_pins,
|
|
|
|
sizeof(cfg->speaker_pins));
|
|
|
|
cfg->speaker_outs = 0;
|
|
|
|
memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins));
|
|
|
|
cfg->line_out_type = AUTO_PIN_SPEAKER_OUT;
|
|
|
|
fill_hardwired = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (cfg->hp_outs > 0 &&
|
|
|
|
cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
|
|
|
|
cfg->speaker_outs = cfg->line_outs;
|
|
|
|
memcpy(cfg->speaker_pins, cfg->line_out_pins,
|
|
|
|
sizeof(cfg->speaker_pins));
|
|
|
|
cfg->line_outs = cfg->hp_outs;
|
|
|
|
memcpy(cfg->line_out_pins, cfg->hp_pins,
|
|
|
|
sizeof(cfg->hp_pins));
|
|
|
|
cfg->hp_outs = 0;
|
|
|
|
memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
|
|
|
|
cfg->line_out_type = AUTO_PIN_HP_OUT;
|
|
|
|
fill_hardwired = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (badness) {
|
2012-12-20 16:54:22 +00:00
|
|
|
debug_badness("==> restoring best_cfg\n");
|
2012-12-19 11:52:06 +00:00
|
|
|
*cfg = *best_cfg;
|
|
|
|
fill_and_eval_dacs(codec, best_wired, best_mio);
|
|
|
|
}
|
|
|
|
debug_badness("==> Best config: lo_type=%d, wired=%d, mio=%d\n",
|
|
|
|
cfg->line_out_type, best_wired, best_mio);
|
2013-01-21 09:43:18 +00:00
|
|
|
debug_show_configs(codec, cfg);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
if (cfg->line_out_pins[0]) {
|
|
|
|
struct nid_path *path;
|
2013-01-04 14:01:40 +00:00
|
|
|
path = snd_hda_get_path_from_idx(codec, spec->out_paths[0]);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (path)
|
|
|
|
spec->vmaster_nid = look_for_out_vol_nid(codec, path);
|
ALSA: hda - Add TLV_DB_SCALE_MUTE bit for relevant controls
The DACs on Sigmatel/IDT codecs do mute at the lowest volume level,
and in the earlier drivers, we passed TLV_DB_SCALE_MUTE bit for each
volume control element like Speaker and Headphone as well as Master.
Along with the translation to the generic parser, however, the TLV bit
was lost for the slave controls (e.g. Speaker) but set only to
Master. In theory this should have sufficed, but apps, particularly
PA, do care the slave volume bits, so we seem to see a regression in
the volume controls.
This patch adds a flag to hda_gen_spec to specify the DAC mute
feature, and adds the TLV bit properly for all relevant volume
controls. Also, the TLV bit for vmaster is set in hda_generic.c, so
that we can get rid of all tricks from the codec driver side.
As the similar hack is applied to Conexant 5051 stuff, we can get rid
of it as well.
BugLink: https://bugs.launchpad.net/bugs/1357928
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-09-01 08:07:04 +00:00
|
|
|
if (spec->vmaster_nid) {
|
2013-01-17 09:25:15 +00:00
|
|
|
snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
|
|
|
|
HDA_OUTPUT, spec->vmaster_tlv);
|
ALSA: hda - Add TLV_DB_SCALE_MUTE bit for relevant controls
The DACs on Sigmatel/IDT codecs do mute at the lowest volume level,
and in the earlier drivers, we passed TLV_DB_SCALE_MUTE bit for each
volume control element like Speaker and Headphone as well as Master.
Along with the translation to the generic parser, however, the TLV bit
was lost for the slave controls (e.g. Speaker) but set only to
Master. In theory this should have sufficed, but apps, particularly
PA, do care the slave volume bits, so we seem to see a regression in
the volume controls.
This patch adds a flag to hda_gen_spec to specify the DAC mute
feature, and adds the TLV bit properly for all relevant volume
controls. Also, the TLV bit for vmaster is set in hda_generic.c, so
that we can get rid of all tricks from the codec driver side.
As the similar hack is applied to Conexant 5051 stuff, we can get rid
of it as well.
BugLink: https://bugs.launchpad.net/bugs/1357928
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-09-01 08:07:04 +00:00
|
|
|
if (spec->dac_min_mute)
|
2018-05-13 22:09:52 +00:00
|
|
|
spec->vmaster_tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] |= TLV_DB_SCALE_MUTE;
|
ALSA: hda - Add TLV_DB_SCALE_MUTE bit for relevant controls
The DACs on Sigmatel/IDT codecs do mute at the lowest volume level,
and in the earlier drivers, we passed TLV_DB_SCALE_MUTE bit for each
volume control element like Speaker and Headphone as well as Master.
Along with the translation to the generic parser, however, the TLV bit
was lost for the slave controls (e.g. Speaker) but set only to
Master. In theory this should have sufficed, but apps, particularly
PA, do care the slave volume bits, so we seem to see a regression in
the volume controls.
This patch adds a flag to hda_gen_spec to specify the DAC mute
feature, and adds the TLV bit properly for all relevant volume
controls. Also, the TLV bit for vmaster is set in hda_generic.c, so
that we can get rid of all tricks from the codec driver side.
As the similar hack is applied to Conexant 5051 stuff, we can get rid
of it as well.
BugLink: https://bugs.launchpad.net/bugs/1357928
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-09-01 08:07:04 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2013-01-21 09:49:05 +00:00
|
|
|
/* set initial pinctl targets */
|
|
|
|
if (spec->prefer_hp_amp || cfg->line_out_type == AUTO_PIN_HP_OUT)
|
|
|
|
val = PIN_HP;
|
|
|
|
else
|
|
|
|
val = PIN_OUT;
|
|
|
|
set_pin_targets(codec, cfg->line_outs, cfg->line_out_pins, val);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT)
|
|
|
|
set_pin_targets(codec, cfg->hp_outs, cfg->hp_pins, PIN_HP);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
|
|
|
|
val = spec->prefer_hp_amp ? PIN_HP : PIN_OUT;
|
|
|
|
set_pin_targets(codec, cfg->speaker_outs,
|
|
|
|
cfg->speaker_pins, val);
|
|
|
|
}
|
|
|
|
|
2013-03-21 16:20:12 +00:00
|
|
|
/* clear indep_hp flag if not available */
|
|
|
|
if (spec->indep_hp && !indep_hp_possible(codec))
|
|
|
|
spec->indep_hp = 0;
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
kfree(best_cfg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* add playback controls from the parsed DAC table */
|
|
|
|
static int create_multi_out_ctls(struct hda_codec *codec,
|
|
|
|
const struct auto_pin_cfg *cfg)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i, err, noutputs;
|
|
|
|
|
|
|
|
noutputs = cfg->line_outs;
|
|
|
|
if (spec->multi_ios > 0 && cfg->line_outs < 3)
|
|
|
|
noutputs += spec->multi_ios;
|
|
|
|
|
|
|
|
for (i = 0; i < noutputs; i++) {
|
|
|
|
const char *name;
|
|
|
|
int index;
|
|
|
|
struct nid_path *path;
|
|
|
|
|
2013-01-04 14:01:40 +00:00
|
|
|
path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
continue;
|
2013-01-17 15:18:11 +00:00
|
|
|
|
|
|
|
name = get_line_out_pfx(codec, i, &index, NID_PATH_VOL_CTL);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!name || !strcmp(name, "CLFE")) {
|
|
|
|
/* Center/LFE */
|
|
|
|
err = add_vol_ctl(codec, "Center", 0, 1, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = add_vol_ctl(codec, "LFE", 0, 2, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2013-01-17 15:18:11 +00:00
|
|
|
} else {
|
|
|
|
err = add_stereo_vol(codec, name, index, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
name = get_line_out_pfx(codec, i, &index, NID_PATH_MUTE_CTL);
|
|
|
|
if (!name || !strcmp(name, "CLFE")) {
|
2012-12-19 11:52:06 +00:00
|
|
|
err = add_sw_ctl(codec, "Center", 0, 1, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = add_sw_ctl(codec, "LFE", 0, 2, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
} else {
|
|
|
|
err = add_stereo_sw(codec, name, index, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
static int create_extra_out(struct hda_codec *codec, int path_idx,
|
2013-01-04 14:01:40 +00:00
|
|
|
const char *pfx, int cidx)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct nid_path *path;
|
|
|
|
int err;
|
|
|
|
|
2013-01-04 14:01:40 +00:00
|
|
|
path = snd_hda_get_path_from_idx(codec, path_idx);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
return 0;
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
err = add_stereo_vol(codec, pfx, cidx, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2012-12-19 11:52:06 +00:00
|
|
|
err = add_stereo_sw(codec, pfx, cidx, path);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* add playback controls for speaker and HP outputs */
|
|
|
|
static int create_extra_outs(struct hda_codec *codec, int num_pins,
|
2013-01-04 14:01:40 +00:00
|
|
|
const int *paths, const char *pfx)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
int i;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
for (i = 0; i < num_pins; i++) {
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
const char *name;
|
2013-06-28 09:56:49 +00:00
|
|
|
char tmp[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
int err, idx = 0;
|
|
|
|
|
|
|
|
if (num_pins == 2 && i == 1 && !strcmp(pfx, "Speaker"))
|
|
|
|
name = "Bass Speaker";
|
|
|
|
else if (num_pins >= 3) {
|
|
|
|
snprintf(tmp, sizeof(tmp), "%s %s",
|
2012-12-19 11:52:06 +00:00
|
|
|
pfx, channel_name[i]);
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
name = tmp;
|
2012-12-19 11:52:06 +00:00
|
|
|
} else {
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
name = pfx;
|
|
|
|
idx = i;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
ALSA: hda - Drop bind-volume workaround
The bind-volume workaround was introduced for simplifying the mixer
abstraction in the case where one or more pins of multiple outputs
lack of individual volume controls. This was essentially the case
like Acer Aspire 5935, which has 5.1 speakers and 5.1 (multi-io)
jacks although there are 5 DACs, so some of them must share a DAC.
However, the recent code rewrite changed the DAC assignment policy to
share with the same channel instead of binding to the front, thus
binding the volumes for all channels makes little sense now, rather
it's confusing. So in this patch, the ugly workaround is finally
dropped and simply create the volume control corresponding to the
parsed path position.
For dual headphones or 2.1 speakers with a shared volume control, it's
anyway bound to the same DAC if needed, so this change shouldn't bring
any practical difference.
And, as a good bonus, we can cut off the whole code handling the bind
volume elements.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-01-07 09:33:57 +00:00
|
|
|
err = create_extra_out(codec, paths[i], name, idx);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int create_hp_out_ctls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return create_extra_outs(codec, spec->autocfg.hp_outs,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->hp_paths,
|
2012-12-19 11:52:06 +00:00
|
|
|
"Headphone");
|
|
|
|
}
|
|
|
|
|
|
|
|
static int create_speaker_out_ctls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return create_extra_outs(codec, spec->autocfg.speaker_outs,
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->speaker_paths,
|
2012-12-19 11:52:06 +00:00
|
|
|
"Speaker");
|
|
|
|
}
|
|
|
|
|
2012-12-21 13:09:42 +00:00
|
|
|
/*
|
|
|
|
* independent HP controls
|
|
|
|
*/
|
|
|
|
|
2014-09-11 13:22:46 +00:00
|
|
|
static void call_hp_automute(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack);
|
2012-12-21 13:09:42 +00:00
|
|
|
static int indep_hp_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
return snd_hda_enum_bool_helper_info(kcontrol, uinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int indep_hp_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
ucontrol->value.enumerated.item[0] = spec->indep_hp_enabled;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-21 14:11:25 +00:00
|
|
|
static void update_aamix_paths(struct hda_codec *codec, bool do_mix,
|
|
|
|
int nomix_path_idx, int mix_path_idx,
|
|
|
|
int out_type);
|
|
|
|
|
2012-12-21 13:09:42 +00:00
|
|
|
static int indep_hp_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
unsigned int select = ucontrol->value.enumerated.item[0];
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&spec->pcm_mutex);
|
|
|
|
if (spec->active_streams) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spec->indep_hp_enabled != select) {
|
2013-01-21 14:11:25 +00:00
|
|
|
hda_nid_t *dacp;
|
|
|
|
if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
|
|
|
|
dacp = &spec->private_dac_nids[0];
|
|
|
|
else
|
|
|
|
dacp = &spec->multiout.hp_out_nid[0];
|
|
|
|
|
|
|
|
/* update HP aamix paths in case it conflicts with indep HP */
|
|
|
|
if (spec->have_aamix_ctl) {
|
|
|
|
if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
|
|
|
|
update_aamix_paths(codec, spec->aamix_mode,
|
|
|
|
spec->out_paths[0],
|
|
|
|
spec->aamix_out_paths[0],
|
|
|
|
spec->autocfg.line_out_type);
|
|
|
|
else
|
|
|
|
update_aamix_paths(codec, spec->aamix_mode,
|
|
|
|
spec->hp_paths[0],
|
|
|
|
spec->aamix_out_paths[1],
|
|
|
|
AUTO_PIN_HP_OUT);
|
|
|
|
}
|
|
|
|
|
2012-12-21 13:09:42 +00:00
|
|
|
spec->indep_hp_enabled = select;
|
|
|
|
if (spec->indep_hp_enabled)
|
2013-01-21 14:11:25 +00:00
|
|
|
*dacp = 0;
|
2012-12-21 13:09:42 +00:00
|
|
|
else
|
2013-01-21 14:11:25 +00:00
|
|
|
*dacp = spec->alt_dac_nid;
|
2013-01-22 06:46:31 +00:00
|
|
|
|
2013-05-31 13:20:31 +00:00
|
|
|
call_hp_automute(codec, NULL);
|
2012-12-21 13:09:42 +00:00
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&spec->pcm_mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new indep_hp_ctl = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Independent HP",
|
|
|
|
.info = indep_hp_info,
|
|
|
|
.get = indep_hp_get,
|
|
|
|
.put = indep_hp_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int create_indep_hp_ctls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-21 14:11:25 +00:00
|
|
|
hda_nid_t dac;
|
2012-12-21 13:09:42 +00:00
|
|
|
|
|
|
|
if (!spec->indep_hp)
|
|
|
|
return 0;
|
2013-01-21 14:11:25 +00:00
|
|
|
if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
|
|
|
|
dac = spec->multiout.dac_nids[0];
|
|
|
|
else
|
|
|
|
dac = spec->multiout.hp_out_nid[0];
|
|
|
|
if (!dac) {
|
2012-12-21 13:09:42 +00:00
|
|
|
spec->indep_hp = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
spec->indep_hp_enabled = false;
|
2013-01-21 14:11:25 +00:00
|
|
|
spec->alt_dac_nid = dac;
|
2012-12-21 13:09:42 +00:00
|
|
|
if (!snd_hda_gen_add_kctl(spec, NULL, &indep_hp_ctl))
|
|
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* channel mode enum control
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int ch_mode_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-07 15:44:06 +00:00
|
|
|
int chs;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.enumerated.items = spec->multi_ios + 1;
|
|
|
|
if (uinfo->value.enumerated.item > spec->multi_ios)
|
|
|
|
uinfo->value.enumerated.item = spec->multi_ios;
|
2013-01-07 15:44:06 +00:00
|
|
|
chs = uinfo->value.enumerated.item * 2 + spec->min_channel_count;
|
|
|
|
sprintf(uinfo->value.enumerated.name, "%dch", chs);
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ch_mode_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-07 15:44:06 +00:00
|
|
|
ucontrol->value.enumerated.item[0] =
|
|
|
|
(spec->ext_channel_count - spec->min_channel_count) / 2;
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-04 14:01:40 +00:00
|
|
|
static inline struct nid_path *
|
|
|
|
get_multiio_path(struct hda_codec *codec, int idx)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return snd_hda_get_path_from_idx(codec,
|
|
|
|
spec->out_paths[spec->autocfg.line_outs + idx]);
|
|
|
|
}
|
|
|
|
|
2013-01-16 17:08:55 +00:00
|
|
|
static void update_automute_all(struct hda_codec *codec);
|
|
|
|
|
2013-04-16 10:31:05 +00:00
|
|
|
/* Default value to be passed as aamix argument for snd_hda_activate_path();
|
|
|
|
* used for output paths
|
|
|
|
*/
|
|
|
|
static bool aamix_default(struct hda_gen_spec *spec)
|
|
|
|
{
|
|
|
|
return !spec->have_aamix_ctl || spec->aamix_mode;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int set_multi_io(struct hda_codec *codec, int idx, bool output)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
hda_nid_t nid = spec->multi_io[idx].pin;
|
|
|
|
struct nid_path *path;
|
|
|
|
|
2013-01-04 14:01:40 +00:00
|
|
|
path = get_multiio_path(codec, idx);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (path->active == output)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (output) {
|
2013-01-10 08:33:29 +00:00
|
|
|
set_pin_target(codec, nid, PIN_OUT, true);
|
2013-04-16 10:31:05 +00:00
|
|
|
snd_hda_activate_path(codec, path, true, aamix_default(spec));
|
2012-12-20 14:36:30 +00:00
|
|
|
set_pin_eapd(codec, nid, true);
|
2012-12-19 11:52:06 +00:00
|
|
|
} else {
|
2012-12-20 14:36:30 +00:00
|
|
|
set_pin_eapd(codec, nid, false);
|
2013-04-16 10:31:05 +00:00
|
|
|
snd_hda_activate_path(codec, path, false, aamix_default(spec));
|
2013-01-10 08:33:29 +00:00
|
|
|
set_pin_target(codec, nid, spec->multi_io[idx].ctl_in, true);
|
2013-01-24 16:32:56 +00:00
|
|
|
path_power_down_sync(codec, path);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2013-01-10 15:10:06 +00:00
|
|
|
|
|
|
|
/* update jack retasking in case it modifies any of them */
|
2013-01-16 17:08:55 +00:00
|
|
|
update_automute_all(codec);
|
2013-01-10 15:10:06 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ch_mode_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i, ch;
|
|
|
|
|
|
|
|
ch = ucontrol->value.enumerated.item[0];
|
|
|
|
if (ch < 0 || ch > spec->multi_ios)
|
|
|
|
return -EINVAL;
|
2013-01-07 15:44:06 +00:00
|
|
|
if (ch == (spec->ext_channel_count - spec->min_channel_count) / 2)
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
2013-01-07 15:44:06 +00:00
|
|
|
spec->ext_channel_count = ch * 2 + spec->min_channel_count;
|
2012-12-19 11:52:06 +00:00
|
|
|
for (i = 0; i < spec->multi_ios; i++)
|
|
|
|
set_multi_io(codec, i, i < ch);
|
|
|
|
spec->multiout.max_channels = max(spec->ext_channel_count,
|
|
|
|
spec->const_channel_count);
|
|
|
|
if (spec->need_dac_fix)
|
|
|
|
spec->multiout.num_dacs = spec->multiout.max_channels / 2;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new channel_mode_enum = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Channel Mode",
|
|
|
|
.info = ch_mode_info,
|
|
|
|
.get = ch_mode_get,
|
|
|
|
.put = ch_mode_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int create_multi_channel_mode(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->multi_ios > 0) {
|
2012-12-19 13:38:33 +00:00
|
|
|
if (!snd_hda_gen_add_kctl(spec, NULL, &channel_mode_enum))
|
2012-12-19 11:52:06 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-04 15:42:48 +00:00
|
|
|
/*
|
|
|
|
* aamix loopback enable/disable switch
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define loopback_mixing_info indep_hp_info
|
|
|
|
|
|
|
|
static int loopback_mixing_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
ucontrol->value.enumerated.item[0] = spec->aamix_mode;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_aamix_paths(struct hda_codec *codec, bool do_mix,
|
2013-01-21 14:11:25 +00:00
|
|
|
int nomix_path_idx, int mix_path_idx,
|
|
|
|
int out_type)
|
2013-01-04 15:42:48 +00:00
|
|
|
{
|
2013-01-21 14:11:25 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-04 15:42:48 +00:00
|
|
|
struct nid_path *nomix_path, *mix_path;
|
|
|
|
|
|
|
|
nomix_path = snd_hda_get_path_from_idx(codec, nomix_path_idx);
|
|
|
|
mix_path = snd_hda_get_path_from_idx(codec, mix_path_idx);
|
|
|
|
if (!nomix_path || !mix_path)
|
|
|
|
return;
|
2013-01-21 14:11:25 +00:00
|
|
|
|
|
|
|
/* if HP aamix path is driven from a different DAC and the
|
|
|
|
* independent HP mode is ON, can't turn on aamix path
|
|
|
|
*/
|
|
|
|
if (out_type == AUTO_PIN_HP_OUT && spec->indep_hp_enabled &&
|
|
|
|
mix_path->path[0] != spec->alt_dac_nid)
|
|
|
|
do_mix = false;
|
|
|
|
|
2013-01-04 15:42:48 +00:00
|
|
|
if (do_mix) {
|
|
|
|
snd_hda_activate_path(codec, nomix_path, false, true);
|
|
|
|
snd_hda_activate_path(codec, mix_path, true, true);
|
2013-01-24 16:32:56 +00:00
|
|
|
path_power_down_sync(codec, nomix_path);
|
2013-01-04 15:42:48 +00:00
|
|
|
} else {
|
2013-04-16 10:31:05 +00:00
|
|
|
snd_hda_activate_path(codec, mix_path, false, false);
|
|
|
|
snd_hda_activate_path(codec, nomix_path, true, false);
|
2013-01-24 16:32:56 +00:00
|
|
|
path_power_down_sync(codec, mix_path);
|
2013-01-04 15:42:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
/* re-initialize the output paths; only called from loopback_mixing_put() */
|
|
|
|
static void update_output_paths(struct hda_codec *codec, int num_outs,
|
|
|
|
const int *paths)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct nid_path *path;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_outs; i++) {
|
|
|
|
path = snd_hda_get_path_from_idx(codec, paths[i]);
|
|
|
|
if (path)
|
|
|
|
snd_hda_activate_path(codec, path, path->active,
|
|
|
|
spec->aamix_mode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-04 15:42:48 +00:00
|
|
|
static int loopback_mixing_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
const struct auto_pin_cfg *cfg = &spec->autocfg;
|
2013-01-04 15:42:48 +00:00
|
|
|
unsigned int val = ucontrol->value.enumerated.item[0];
|
|
|
|
|
|
|
|
if (val == spec->aamix_mode)
|
|
|
|
return 0;
|
|
|
|
spec->aamix_mode = val;
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
if (has_aamix_out_paths(spec)) {
|
|
|
|
update_aamix_paths(codec, val, spec->out_paths[0],
|
|
|
|
spec->aamix_out_paths[0],
|
|
|
|
cfg->line_out_type);
|
|
|
|
update_aamix_paths(codec, val, spec->hp_paths[0],
|
|
|
|
spec->aamix_out_paths[1],
|
|
|
|
AUTO_PIN_HP_OUT);
|
|
|
|
update_aamix_paths(codec, val, spec->speaker_paths[0],
|
|
|
|
spec->aamix_out_paths[2],
|
|
|
|
AUTO_PIN_SPEAKER_OUT);
|
|
|
|
} else {
|
|
|
|
update_output_paths(codec, cfg->line_outs, spec->out_paths);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT)
|
|
|
|
update_output_paths(codec, cfg->hp_outs, spec->hp_paths);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
|
|
|
|
update_output_paths(codec, cfg->speaker_outs,
|
|
|
|
spec->speaker_paths);
|
|
|
|
}
|
2013-01-04 15:42:48 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new loopback_mixing_enum = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Loopback Mixing",
|
|
|
|
.info = loopback_mixing_info,
|
|
|
|
.get = loopback_mixing_get,
|
|
|
|
.put = loopback_mixing_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int create_loopback_mixing_ctl(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (!spec->mixer_nid)
|
|
|
|
return 0;
|
|
|
|
if (!snd_hda_gen_add_kctl(spec, NULL, &loopback_mixing_enum))
|
|
|
|
return -ENOMEM;
|
2013-01-21 14:11:25 +00:00
|
|
|
spec->have_aamix_ctl = 1;
|
2013-01-04 15:42:48 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* shared headphone/mic handling
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void call_update_outputs(struct hda_codec *codec);
|
|
|
|
|
|
|
|
/* for shared I/O, change the pin-control accordingly */
|
2013-02-19 16:12:42 +00:00
|
|
|
static void update_hp_mic(struct hda_codec *codec, int adc_mux, bool force)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-02-19 16:12:42 +00:00
|
|
|
bool as_mic;
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int val;
|
2013-02-19 16:12:42 +00:00
|
|
|
hda_nid_t pin;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
pin = spec->hp_mic_pin;
|
|
|
|
as_mic = spec->cur_mux[adc_mux] == spec->hp_mic_mux_idx;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
if (!force) {
|
|
|
|
val = snd_hda_codec_get_pin_target(codec, pin);
|
|
|
|
if (as_mic) {
|
|
|
|
if (val & PIN_IN)
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
if (val & PIN_OUT)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val = snd_hda_get_default_vref(codec, pin);
|
|
|
|
/* if the HP pin doesn't support VREF and the codec driver gives an
|
|
|
|
* alternative pin, set up the VREF on that pin instead
|
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
if (val == AC_PINCTL_VREF_HIZ && spec->shared_mic_vref_pin) {
|
|
|
|
const hda_nid_t vref_pin = spec->shared_mic_vref_pin;
|
|
|
|
unsigned int vref_val = snd_hda_get_default_vref(codec, vref_pin);
|
|
|
|
if (vref_val != AC_PINCTL_VREF_HIZ)
|
2012-12-20 14:38:40 +00:00
|
|
|
snd_hda_set_pin_ctl_cache(codec, vref_pin,
|
2013-02-19 16:12:42 +00:00
|
|
|
PIN_IN | (as_mic ? vref_val : 0));
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2013-03-07 17:40:58 +00:00
|
|
|
if (!spec->hp_mic_jack_modes) {
|
|
|
|
if (as_mic)
|
|
|
|
val |= PIN_IN;
|
|
|
|
else
|
|
|
|
val = PIN_HP;
|
|
|
|
set_pin_target(codec, pin, val, true);
|
2013-05-31 13:20:31 +00:00
|
|
|
call_hp_automute(codec, NULL);
|
2013-03-07 17:40:58 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* create a shared input with the headphone out */
|
2013-02-19 16:12:42 +00:00
|
|
|
static int create_hp_mic(struct hda_codec *codec)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
unsigned int defcfg;
|
|
|
|
hda_nid_t nid;
|
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
if (!spec->hp_mic) {
|
|
|
|
if (spec->suppress_hp_mic_detect)
|
|
|
|
return 0;
|
|
|
|
/* automatic detection: only if no input or a single internal
|
|
|
|
* input pin is found, try to detect the shared hp/mic
|
|
|
|
*/
|
|
|
|
if (cfg->num_inputs > 1)
|
|
|
|
return 0;
|
|
|
|
else if (cfg->num_inputs == 1) {
|
|
|
|
defcfg = snd_hda_codec_get_pincfg(codec, cfg->inputs[0].pin);
|
|
|
|
if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
spec->hp_mic = 0; /* clear once */
|
|
|
|
if (cfg->num_inputs >= AUTO_CFG_MAX_INS)
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
nid = 0;
|
|
|
|
if (cfg->line_out_type == AUTO_PIN_HP_OUT && cfg->line_outs > 0)
|
|
|
|
nid = cfg->line_out_pins[0];
|
|
|
|
else if (cfg->hp_outs > 0)
|
|
|
|
nid = cfg->hp_pins[0];
|
|
|
|
if (!nid)
|
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_IN))
|
|
|
|
return 0; /* no input */
|
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
cfg->inputs[cfg->num_inputs].pin = nid;
|
|
|
|
cfg->inputs[cfg->num_inputs].type = AUTO_PIN_MIC;
|
2013-04-11 09:30:28 +00:00
|
|
|
cfg->inputs[cfg->num_inputs].is_headphone_mic = 1;
|
2013-02-19 16:12:42 +00:00
|
|
|
cfg->num_inputs++;
|
|
|
|
spec->hp_mic = 1;
|
|
|
|
spec->hp_mic_pin = nid;
|
|
|
|
/* we can't handle auto-mic together with HP-mic */
|
|
|
|
spec->suppress_auto_mic = 1;
|
2014-02-25 11:21:03 +00:00
|
|
|
codec_dbg(codec, "Enable shared I/O jack on NID 0x%x\n", nid);
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-10 15:57:58 +00:00
|
|
|
/*
|
|
|
|
* output jack mode
|
|
|
|
*/
|
2013-02-19 17:14:54 +00:00
|
|
|
|
|
|
|
static int create_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t pin);
|
|
|
|
|
|
|
|
static const char * const out_jack_texts[] = {
|
|
|
|
"Line Out", "Headphone Out",
|
|
|
|
};
|
|
|
|
|
2013-01-10 15:57:58 +00:00
|
|
|
static int out_jack_mode_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
2013-02-19 17:14:54 +00:00
|
|
|
return snd_hda_enum_helper_info(kcontrol, uinfo, 2, out_jack_texts);
|
2013-01-10 15:57:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int out_jack_mode_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
if (snd_hda_codec_get_pin_target(codec, nid) == PIN_HP)
|
|
|
|
ucontrol->value.enumerated.item[0] = 1;
|
|
|
|
else
|
|
|
|
ucontrol->value.enumerated.item[0] = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int out_jack_mode_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
unsigned int val;
|
|
|
|
|
|
|
|
val = ucontrol->value.enumerated.item[0] ? PIN_HP : PIN_OUT;
|
|
|
|
if (snd_hda_codec_get_pin_target(codec, nid) == val)
|
|
|
|
return 0;
|
|
|
|
snd_hda_set_pin_ctl_cache(codec, nid, val);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new out_jack_mode_enum = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.info = out_jack_mode_info,
|
|
|
|
.get = out_jack_mode_get,
|
|
|
|
.put = out_jack_mode_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2018-04-23 15:24:56 +00:00
|
|
|
const struct snd_kcontrol_new *kctl;
|
2013-01-10 15:57:58 +00:00
|
|
|
int i;
|
|
|
|
|
2018-04-23 15:24:56 +00:00
|
|
|
snd_array_for_each(&spec->kctls, i, kctl) {
|
2013-01-10 15:57:58 +00:00
|
|
|
if (!strcmp(kctl->name, name) && kctl->index == idx)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin,
|
|
|
|
char *name, size_t name_len)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int idx = 0;
|
|
|
|
|
|
|
|
snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx);
|
|
|
|
strlcat(name, " Jack Mode", name_len);
|
|
|
|
|
|
|
|
for (; find_kctl_name(codec, name, idx); idx++)
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2013-02-19 17:14:54 +00:00
|
|
|
static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-03-07 17:32:59 +00:00
|
|
|
if (spec->add_jack_modes) {
|
2013-02-19 17:14:54 +00:00
|
|
|
unsigned int pincap = snd_hda_query_pin_caps(codec, pin);
|
|
|
|
if ((pincap & AC_PINCAP_OUT) && (pincap & AC_PINCAP_HP_DRV))
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-01-10 15:57:58 +00:00
|
|
|
static int create_out_jack_modes(struct hda_codec *codec, int num_pins,
|
|
|
|
hda_nid_t *pins)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_pins; i++) {
|
|
|
|
hda_nid_t pin = pins[i];
|
2013-11-26 07:33:45 +00:00
|
|
|
if (pin == spec->hp_mic_pin)
|
2013-02-19 17:14:54 +00:00
|
|
|
continue;
|
|
|
|
if (get_out_jack_num_items(codec, pin) > 1) {
|
2013-01-10 15:57:58 +00:00
|
|
|
struct snd_kcontrol_new *knew;
|
2013-06-28 09:56:49 +00:00
|
|
|
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
2013-01-10 15:57:58 +00:00
|
|
|
get_jack_mode_name(codec, pin, name, sizeof(name));
|
|
|
|
knew = snd_hda_gen_add_kctl(spec, name,
|
|
|
|
&out_jack_mode_enum);
|
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
|
|
|
knew->private_value = pin;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-17 08:52:11 +00:00
|
|
|
/*
|
|
|
|
* input jack mode
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* from AC_PINCTL_VREF_HIZ to AC_PINCTL_VREF_100 */
|
|
|
|
#define NUM_VREFS 6
|
|
|
|
|
|
|
|
static const char * const vref_texts[NUM_VREFS] = {
|
|
|
|
"Line In", "Mic 50pc Bias", "Mic 0V Bias",
|
|
|
|
"", "Mic 80pc Bias", "Mic 100pc Bias"
|
|
|
|
};
|
|
|
|
|
|
|
|
static unsigned int get_vref_caps(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
unsigned int pincap;
|
|
|
|
|
|
|
|
pincap = snd_hda_query_pin_caps(codec, pin);
|
|
|
|
pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
|
|
|
|
/* filter out unusual vrefs */
|
|
|
|
pincap &= ~(AC_PINCAP_VREF_GRD | AC_PINCAP_VREF_100);
|
|
|
|
return pincap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* convert from the enum item index to the vref ctl index (0=HIZ, 1=50%...) */
|
|
|
|
static int get_vref_idx(unsigned int vref_caps, unsigned int item_idx)
|
|
|
|
{
|
|
|
|
unsigned int i, n = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < NUM_VREFS; i++) {
|
|
|
|
if (vref_caps & (1 << i)) {
|
|
|
|
if (n == item_idx)
|
|
|
|
return i;
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* convert back from the vref ctl index to the enum item index */
|
|
|
|
static int cvt_from_vref_idx(unsigned int vref_caps, unsigned int idx)
|
|
|
|
{
|
|
|
|
unsigned int i, n = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < NUM_VREFS; i++) {
|
|
|
|
if (i == idx)
|
|
|
|
return n;
|
|
|
|
if (vref_caps & (1 << i))
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int in_jack_mode_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
unsigned int vref_caps = get_vref_caps(codec, nid);
|
|
|
|
|
|
|
|
snd_hda_enum_helper_info(kcontrol, uinfo, hweight32(vref_caps),
|
|
|
|
vref_texts);
|
|
|
|
/* set the right text */
|
|
|
|
strcpy(uinfo->value.enumerated.name,
|
|
|
|
vref_texts[get_vref_idx(vref_caps, uinfo->value.enumerated.item)]);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int in_jack_mode_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
unsigned int vref_caps = get_vref_caps(codec, nid);
|
|
|
|
unsigned int idx;
|
|
|
|
|
|
|
|
idx = snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_VREFEN;
|
|
|
|
ucontrol->value.enumerated.item[0] = cvt_from_vref_idx(vref_caps, idx);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int in_jack_mode_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
unsigned int vref_caps = get_vref_caps(codec, nid);
|
|
|
|
unsigned int val, idx;
|
|
|
|
|
|
|
|
val = snd_hda_codec_get_pin_target(codec, nid);
|
|
|
|
idx = cvt_from_vref_idx(vref_caps, val & AC_PINCTL_VREFEN);
|
|
|
|
if (idx == ucontrol->value.enumerated.item[0])
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
val &= ~AC_PINCTL_VREFEN;
|
|
|
|
val |= get_vref_idx(vref_caps, ucontrol->value.enumerated.item[0]);
|
|
|
|
snd_hda_set_pin_ctl_cache(codec, nid, val);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new in_jack_mode_enum = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.info = in_jack_mode_info,
|
|
|
|
.get = in_jack_mode_get,
|
|
|
|
.put = in_jack_mode_put,
|
|
|
|
};
|
|
|
|
|
2013-02-19 17:14:54 +00:00
|
|
|
static int get_in_jack_num_items(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int nitems = 0;
|
2013-03-07 17:32:59 +00:00
|
|
|
if (spec->add_jack_modes)
|
2013-02-19 17:14:54 +00:00
|
|
|
nitems = hweight32(get_vref_caps(codec, pin));
|
|
|
|
return nitems ? nitems : 1;
|
|
|
|
}
|
|
|
|
|
2013-01-17 08:52:11 +00:00
|
|
|
static int create_in_jack_mode(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct snd_kcontrol_new *knew;
|
2013-06-28 09:56:49 +00:00
|
|
|
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
2013-02-19 17:14:54 +00:00
|
|
|
unsigned int defcfg;
|
|
|
|
|
2013-03-07 17:32:59 +00:00
|
|
|
if (pin == spec->hp_mic_pin)
|
|
|
|
return 0; /* already done in create_out_jack_mode() */
|
2013-01-17 08:52:11 +00:00
|
|
|
|
|
|
|
/* no jack mode for fixed pins */
|
|
|
|
defcfg = snd_hda_codec_get_pincfg(codec, pin);
|
|
|
|
if (snd_hda_get_input_pin_attr(defcfg) == INPUT_PIN_ATTR_INT)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* no multiple vref caps? */
|
2013-02-19 17:14:54 +00:00
|
|
|
if (get_in_jack_num_items(codec, pin) <= 1)
|
2013-01-17 08:52:11 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
get_jack_mode_name(codec, pin, name, sizeof(name));
|
|
|
|
knew = snd_hda_gen_add_kctl(spec, name, &in_jack_mode_enum);
|
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
|
|
|
knew->private_value = pin;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-19 17:14:54 +00:00
|
|
|
/*
|
|
|
|
* HP/mic shared jack mode
|
|
|
|
*/
|
|
|
|
static int hp_mic_jack_mode_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
int out_jacks = get_out_jack_num_items(codec, nid);
|
|
|
|
int in_jacks = get_in_jack_num_items(codec, nid);
|
|
|
|
const char *text = NULL;
|
|
|
|
int idx;
|
|
|
|
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.enumerated.items = out_jacks + in_jacks;
|
|
|
|
if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
|
|
|
|
uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
|
|
|
|
idx = uinfo->value.enumerated.item;
|
|
|
|
if (idx < out_jacks) {
|
|
|
|
if (out_jacks > 1)
|
|
|
|
text = out_jack_texts[idx];
|
|
|
|
else
|
|
|
|
text = "Headphone Out";
|
|
|
|
} else {
|
|
|
|
idx -= out_jacks;
|
|
|
|
if (in_jacks > 1) {
|
|
|
|
unsigned int vref_caps = get_vref_caps(codec, nid);
|
|
|
|
text = vref_texts[get_vref_idx(vref_caps, idx)];
|
|
|
|
} else
|
|
|
|
text = "Mic In";
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(uinfo->value.enumerated.name, text);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_cur_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t nid)
|
|
|
|
{
|
|
|
|
int out_jacks = get_out_jack_num_items(codec, nid);
|
|
|
|
int in_jacks = get_in_jack_num_items(codec, nid);
|
|
|
|
unsigned int val = snd_hda_codec_get_pin_target(codec, nid);
|
|
|
|
int idx = 0;
|
|
|
|
|
|
|
|
if (val & PIN_OUT) {
|
|
|
|
if (out_jacks > 1 && val == PIN_HP)
|
|
|
|
idx = 1;
|
|
|
|
} else if (val & PIN_IN) {
|
|
|
|
idx = out_jacks;
|
|
|
|
if (in_jacks > 1) {
|
|
|
|
unsigned int vref_caps = get_vref_caps(codec, nid);
|
|
|
|
val &= AC_PINCTL_VREFEN;
|
|
|
|
idx += cvt_from_vref_idx(vref_caps, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hp_mic_jack_mode_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
ucontrol->value.enumerated.item[0] =
|
|
|
|
get_cur_hp_mic_jack_mode(codec, nid);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hp_mic_jack_mode_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
hda_nid_t nid = kcontrol->private_value;
|
|
|
|
int out_jacks = get_out_jack_num_items(codec, nid);
|
|
|
|
int in_jacks = get_in_jack_num_items(codec, nid);
|
|
|
|
unsigned int val, oldval, idx;
|
|
|
|
|
|
|
|
oldval = get_cur_hp_mic_jack_mode(codec, nid);
|
|
|
|
idx = ucontrol->value.enumerated.item[0];
|
|
|
|
if (oldval == idx)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (idx < out_jacks) {
|
|
|
|
if (out_jacks > 1)
|
|
|
|
val = idx ? PIN_HP : PIN_OUT;
|
|
|
|
else
|
|
|
|
val = PIN_HP;
|
|
|
|
} else {
|
|
|
|
idx -= out_jacks;
|
|
|
|
if (in_jacks > 1) {
|
|
|
|
unsigned int vref_caps = get_vref_caps(codec, nid);
|
|
|
|
val = snd_hda_codec_get_pin_target(codec, nid);
|
2013-03-07 17:30:27 +00:00
|
|
|
val &= ~(AC_PINCTL_VREFEN | PIN_HP);
|
|
|
|
val |= get_vref_idx(vref_caps, idx) | PIN_IN;
|
2013-02-19 17:14:54 +00:00
|
|
|
} else
|
2013-11-26 07:44:26 +00:00
|
|
|
val = snd_hda_get_default_vref(codec, nid) | PIN_IN;
|
2013-02-19 17:14:54 +00:00
|
|
|
}
|
|
|
|
snd_hda_set_pin_ctl_cache(codec, nid, val);
|
2013-05-31 13:20:31 +00:00
|
|
|
call_hp_automute(codec, NULL);
|
2013-03-07 17:40:58 +00:00
|
|
|
|
2013-02-19 17:14:54 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new hp_mic_jack_mode_enum = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.info = hp_mic_jack_mode_info,
|
|
|
|
.get = hp_mic_jack_mode_get,
|
|
|
|
.put = hp_mic_jack_mode_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int create_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct snd_kcontrol_new *knew;
|
|
|
|
|
|
|
|
knew = snd_hda_gen_add_kctl(spec, "Headphone Mic Jack Mode",
|
|
|
|
&hp_mic_jack_mode_enum);
|
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
|
|
|
knew->private_value = pin;
|
2013-03-07 17:40:58 +00:00
|
|
|
spec->hp_mic_jack_modes = 1;
|
2013-02-19 17:14:54 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse input paths
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* add the powersave loopback-list entry */
|
2013-02-07 09:45:11 +00:00
|
|
|
static int add_loopback_list(struct hda_gen_spec *spec, hda_nid_t mix, int idx)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_amp_list *list;
|
|
|
|
|
2013-02-07 09:45:11 +00:00
|
|
|
list = snd_array_new(&spec->loopback_list);
|
|
|
|
if (!list)
|
|
|
|
return -ENOMEM;
|
2012-12-19 11:52:06 +00:00
|
|
|
list->nid = mix;
|
|
|
|
list->dir = HDA_INPUT;
|
|
|
|
list->idx = idx;
|
2013-02-07 09:45:11 +00:00
|
|
|
spec->loopback.amplist = spec->loopback_list.list;
|
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2013-11-28 10:05:28 +00:00
|
|
|
/* return true if either a volume or a mute amp is found for the given
|
|
|
|
* aamix path; the amp has to be either in the mixer node or its direct leaf
|
|
|
|
*/
|
|
|
|
static bool look_for_mix_leaf_ctls(struct hda_codec *codec, hda_nid_t mix_nid,
|
|
|
|
hda_nid_t pin, unsigned int *mix_val,
|
|
|
|
unsigned int *mute_val)
|
|
|
|
{
|
|
|
|
int idx, num_conns;
|
|
|
|
const hda_nid_t *list;
|
|
|
|
hda_nid_t nid;
|
|
|
|
|
|
|
|
idx = snd_hda_get_conn_index(codec, mix_nid, pin, true);
|
|
|
|
if (idx < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
*mix_val = *mute_val = 0;
|
|
|
|
if (nid_has_volume(codec, mix_nid, HDA_INPUT))
|
|
|
|
*mix_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT);
|
|
|
|
if (nid_has_mute(codec, mix_nid, HDA_INPUT))
|
|
|
|
*mute_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT);
|
|
|
|
if (*mix_val && *mute_val)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* check leaf node */
|
|
|
|
num_conns = snd_hda_get_conn_list(codec, mix_nid, &list);
|
|
|
|
if (num_conns < idx)
|
|
|
|
return false;
|
|
|
|
nid = list[idx];
|
2014-01-07 17:11:44 +00:00
|
|
|
if (!*mix_val && nid_has_volume(codec, nid, HDA_OUTPUT) &&
|
|
|
|
!is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_VOL_CTL))
|
2013-11-28 10:05:28 +00:00
|
|
|
*mix_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
|
2014-01-07 17:11:44 +00:00
|
|
|
if (!*mute_val && nid_has_mute(codec, nid, HDA_OUTPUT) &&
|
|
|
|
!is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_MUTE_CTL))
|
2013-11-28 10:05:28 +00:00
|
|
|
*mute_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
|
|
|
|
|
|
|
|
return *mix_val || *mute_val;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* create input playback/capture controls for the given pin */
|
2013-01-04 14:01:40 +00:00
|
|
|
static int new_analog_input(struct hda_codec *codec, int input_idx,
|
|
|
|
hda_nid_t pin, const char *ctlname, int ctlidx,
|
2012-12-19 11:52:06 +00:00
|
|
|
hda_nid_t mix_nid)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct nid_path *path;
|
2013-11-28 10:05:28 +00:00
|
|
|
unsigned int mix_val, mute_val;
|
2012-12-19 11:52:06 +00:00
|
|
|
int err, idx;
|
|
|
|
|
2013-11-28 10:05:28 +00:00
|
|
|
if (!look_for_mix_leaf_ctls(codec, mix_nid, pin, &mix_val, &mute_val))
|
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-07 16:25:08 +00:00
|
|
|
path = snd_hda_add_new_path(codec, pin, mix_nid, 0);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
return -EINVAL;
|
2014-02-25 11:21:03 +00:00
|
|
|
print_nid_path(codec, "loopback", path);
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->loopback_paths[input_idx] = snd_hda_get_path_idx(codec, path);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
idx = path->idx[path->depth - 1];
|
2013-11-28 10:05:28 +00:00
|
|
|
if (mix_val) {
|
|
|
|
err = __add_pb_vol_ctrl(spec, HDA_CTL_WIDGET_VOL, ctlname, ctlidx, mix_val);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2013-11-28 10:05:28 +00:00
|
|
|
path->ctls[NID_PATH_VOL_CTL] = mix_val;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2013-11-28 10:05:28 +00:00
|
|
|
if (mute_val) {
|
|
|
|
err = __add_pb_sw_ctrl(spec, HDA_CTL_WIDGET_MUTE, ctlname, ctlidx, mute_val);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2013-11-28 10:05:28 +00:00
|
|
|
path->ctls[NID_PATH_MUTE_CTL] = mute_val;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
path->active = true;
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
path->stream_enabled = true; /* no DAC/ADC involved */
|
2013-02-07 09:45:11 +00:00
|
|
|
err = add_loopback_list(spec, mix_nid, idx);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2013-01-23 16:00:31 +00:00
|
|
|
|
|
|
|
if (spec->mixer_nid != spec->mixer_merge_nid &&
|
|
|
|
!spec->loopback_merge_path) {
|
|
|
|
path = snd_hda_add_new_path(codec, spec->mixer_nid,
|
|
|
|
spec->mixer_merge_nid, 0);
|
|
|
|
if (path) {
|
2014-02-25 11:21:03 +00:00
|
|
|
print_nid_path(codec, "loopback-merge", path);
|
2013-01-23 16:00:31 +00:00
|
|
|
path->active = true;
|
2015-03-20 17:11:05 +00:00
|
|
|
path->pin_fixed = true; /* static route */
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
path->stream_enabled = true; /* no DAC/ADC involved */
|
2013-01-23 16:00:31 +00:00
|
|
|
spec->loopback_merge_path =
|
|
|
|
snd_hda_get_path_idx(codec, path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int is_input_pin(struct hda_codec *codec, hda_nid_t nid)
|
|
|
|
{
|
|
|
|
unsigned int pincap = snd_hda_query_pin_caps(codec, nid);
|
|
|
|
return (pincap & AC_PINCAP_IN) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse the codec tree and retrieve ADCs */
|
|
|
|
static int fill_adc_nids(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
hda_nid_t nid;
|
|
|
|
hda_nid_t *adc_nids = spec->adc_nids;
|
|
|
|
int max_nums = ARRAY_SIZE(spec->adc_nids);
|
2015-03-03 09:07:24 +00:00
|
|
|
int nums = 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2015-03-03 09:07:24 +00:00
|
|
|
for_each_hda_codec_node(nid, codec) {
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int caps = get_wcaps(codec, nid);
|
|
|
|
int type = get_wcaps_type(caps);
|
|
|
|
|
|
|
|
if (type != AC_WID_AUD_IN || (caps & AC_WCAP_DIGITAL))
|
|
|
|
continue;
|
|
|
|
adc_nids[nums] = nid;
|
|
|
|
if (++nums >= max_nums)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spec->num_adc_nids = nums;
|
2013-01-17 14:53:29 +00:00
|
|
|
|
|
|
|
/* copy the detected ADCs to all_adcs[] */
|
|
|
|
spec->num_all_adcs = nums;
|
|
|
|
memcpy(spec->all_adcs, spec->adc_nids, nums * sizeof(hda_nid_t));
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return nums;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* filter out invalid adc_nids that don't give all active input pins;
|
|
|
|
* if needed, check whether dynamic ADC-switching is available
|
|
|
|
*/
|
|
|
|
static int check_dyn_adc_switch(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct hda_input_mux *imux = &spec->input_mux;
|
2013-01-09 08:06:18 +00:00
|
|
|
unsigned int ok_bits;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i, n, nums;
|
|
|
|
|
|
|
|
nums = 0;
|
2013-01-09 08:06:18 +00:00
|
|
|
ok_bits = 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
for (n = 0; n < spec->num_adc_nids; n++) {
|
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
2013-01-09 08:06:18 +00:00
|
|
|
if (!spec->input_paths[i][n])
|
2012-12-19 11:52:06 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-01-09 08:06:18 +00:00
|
|
|
if (i >= imux->num_items) {
|
|
|
|
ok_bits |= (1 << n);
|
|
|
|
nums++;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2013-01-09 08:06:18 +00:00
|
|
|
if (!ok_bits) {
|
2012-12-19 11:52:06 +00:00
|
|
|
/* check whether ADC-switch is possible */
|
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
|
|
|
for (n = 0; n < spec->num_adc_nids; n++) {
|
2013-01-09 08:06:18 +00:00
|
|
|
if (spec->input_paths[i][n]) {
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->dyn_adc_idx[i] = n;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-25 11:21:03 +00:00
|
|
|
codec_dbg(codec, "enabling ADC switching\n");
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->dyn_adc_switch = 1;
|
|
|
|
} else if (nums != spec->num_adc_nids) {
|
2013-01-09 08:06:18 +00:00
|
|
|
/* shrink the invalid adcs and input paths */
|
|
|
|
nums = 0;
|
|
|
|
for (n = 0; n < spec->num_adc_nids; n++) {
|
|
|
|
if (!(ok_bits & (1 << n)))
|
|
|
|
continue;
|
|
|
|
if (n != nums) {
|
|
|
|
spec->adc_nids[nums] = spec->adc_nids[n];
|
2013-01-09 08:28:20 +00:00
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
|
|
|
invalidate_nid_path(codec,
|
|
|
|
spec->input_paths[i][nums]);
|
2013-01-09 08:06:18 +00:00
|
|
|
spec->input_paths[i][nums] =
|
|
|
|
spec->input_paths[i][n];
|
ALSA: hda - set input_path bitmap to zero after moving it to new place
Recently we met a problem, the codec has valid adcs and input pins,
and they can form valid input paths, but the driver does not build
valid controls for them like "Mic boost", "Capture Volume" and
"Capture Switch".
Through debugging, I found the driver needs to shrink the invalid
adcs and input paths for this machine, so it will move the whole
column bitmap value to the previous column, after moving it, the
driver forgets to set the original column bitmap value to zero, as a
result, the driver will invalidate the path whose index value is the
original colume bitmap value. After executing this function, all
valid input paths are invalidated by a mistake, there are no any
valid input paths, so the driver won't build controls for them.
Fixes: 3a65bcdc577a ("ALSA: hda - Fix inconsistent input_paths after ADC reduction")
Cc: <stable@vger.kernel.org>
Signed-off-by: Hui Wang <hui.wang@canonical.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2017-06-28 00:59:16 +00:00
|
|
|
spec->input_paths[i][n] = 0;
|
2013-01-09 08:28:20 +00:00
|
|
|
}
|
2013-01-09 08:06:18 +00:00
|
|
|
}
|
|
|
|
nums++;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->num_adc_nids = nums;
|
|
|
|
}
|
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
if (imux->num_items == 1 ||
|
|
|
|
(imux->num_items == 2 && spec->hp_mic)) {
|
2014-02-25 11:21:03 +00:00
|
|
|
codec_dbg(codec, "reducing to a single ADC\n");
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->num_adc_nids = 1; /* reduce to a single ADC */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* single index for individual volumes ctls */
|
|
|
|
if (!spec->dyn_adc_switch && spec->multi_cap_vol)
|
|
|
|
spec->num_adc_nids = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-09 08:14:23 +00:00
|
|
|
/* parse capture source paths from the given pin and create imux items */
|
|
|
|
static int parse_capture_source(struct hda_codec *codec, hda_nid_t pin,
|
2013-01-18 09:01:15 +00:00
|
|
|
int cfg_idx, int num_adcs,
|
|
|
|
const char *label, int anchor)
|
2013-01-09 08:14:23 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct hda_input_mux *imux = &spec->input_mux;
|
|
|
|
int imux_idx = imux->num_items;
|
|
|
|
bool imux_added = false;
|
|
|
|
int c;
|
|
|
|
|
|
|
|
for (c = 0; c < num_adcs; c++) {
|
|
|
|
struct nid_path *path;
|
|
|
|
hda_nid_t adc = spec->adc_nids[c];
|
|
|
|
|
|
|
|
if (!is_reachable_path(codec, pin, adc))
|
|
|
|
continue;
|
|
|
|
path = snd_hda_add_new_path(codec, pin, adc, anchor);
|
|
|
|
if (!path)
|
|
|
|
continue;
|
2014-02-25 11:21:03 +00:00
|
|
|
print_nid_path(codec, "input", path);
|
2013-01-09 08:14:23 +00:00
|
|
|
spec->input_paths[imux_idx][c] =
|
|
|
|
snd_hda_get_path_idx(codec, path);
|
|
|
|
|
|
|
|
if (!imux_added) {
|
2013-02-19 16:12:42 +00:00
|
|
|
if (spec->hp_mic_pin == pin)
|
|
|
|
spec->hp_mic_mux_idx = imux->num_items;
|
2013-01-09 08:14:23 +00:00
|
|
|
spec->imux_pins[imux->num_items] = pin;
|
2014-06-06 16:12:16 +00:00
|
|
|
snd_hda_add_imux_item(codec, imux, label, cfg_idx, NULL);
|
2013-01-09 08:14:23 +00:00
|
|
|
imux_added = true;
|
2013-12-09 15:02:24 +00:00
|
|
|
if (spec->dyn_adc_switch)
|
|
|
|
spec->dyn_adc_idx[imux_idx] = c;
|
2013-01-09 08:14:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* create playback/capture controls for input pins
|
|
|
|
*/
|
2013-01-18 09:01:15 +00:00
|
|
|
|
2013-01-18 09:17:30 +00:00
|
|
|
/* fill the label for each input at first */
|
|
|
|
static int fill_input_pin_labels(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
const struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
hda_nid_t pin = cfg->inputs[i].pin;
|
|
|
|
const char *label;
|
|
|
|
int j, idx;
|
|
|
|
|
|
|
|
if (!is_input_pin(codec, pin))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
label = hda_get_autocfg_input_label(codec, cfg, i);
|
|
|
|
idx = 0;
|
2013-01-18 14:43:02 +00:00
|
|
|
for (j = i - 1; j >= 0; j--) {
|
2013-01-18 09:17:30 +00:00
|
|
|
if (spec->input_labels[j] &&
|
|
|
|
!strcmp(spec->input_labels[j], label)) {
|
|
|
|
idx = spec->input_label_idxs[j] + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
spec->input_labels[i] = label;
|
|
|
|
spec->input_label_idxs[i] = idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-18 09:01:15 +00:00
|
|
|
#define CFG_IDX_MIX 99 /* a dummy cfg->input idx for stereo mix */
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int create_input_ctls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
const struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
hda_nid_t mixer = spec->mixer_nid;
|
|
|
|
int num_adcs;
|
2013-01-18 09:17:30 +00:00
|
|
|
int i, err;
|
2013-01-10 08:33:29 +00:00
|
|
|
unsigned int val;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
num_adcs = fill_adc_nids(codec);
|
|
|
|
if (num_adcs < 0)
|
|
|
|
return 0;
|
|
|
|
|
2013-01-18 09:17:30 +00:00
|
|
|
err = fill_input_pin_labels(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
hda_nid_t pin;
|
|
|
|
|
|
|
|
pin = cfg->inputs[i].pin;
|
|
|
|
if (!is_input_pin(codec, pin))
|
|
|
|
continue;
|
|
|
|
|
2013-01-10 08:33:29 +00:00
|
|
|
val = PIN_IN;
|
|
|
|
if (cfg->inputs[i].type == AUTO_PIN_MIC)
|
|
|
|
val |= snd_hda_get_default_vref(codec, pin);
|
2015-04-27 08:43:22 +00:00
|
|
|
if (pin != spec->hp_mic_pin &&
|
|
|
|
!snd_hda_codec_get_pin_target(codec, pin))
|
2013-03-11 08:48:43 +00:00
|
|
|
set_pin_target(codec, pin, val, false);
|
2013-01-10 08:33:29 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (mixer) {
|
|
|
|
if (is_reachable_path(codec, pin, mixer)) {
|
2013-01-04 14:01:40 +00:00
|
|
|
err = new_analog_input(codec, i, pin,
|
2013-01-18 09:17:30 +00:00
|
|
|
spec->input_labels[i],
|
|
|
|
spec->input_label_idxs[i],
|
|
|
|
mixer);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-18 09:17:30 +00:00
|
|
|
err = parse_capture_source(codec, pin, i, num_adcs,
|
|
|
|
spec->input_labels[i], -mixer);
|
2013-01-09 08:14:23 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2013-01-17 08:52:11 +00:00
|
|
|
|
2013-03-07 17:32:59 +00:00
|
|
|
if (spec->add_jack_modes) {
|
2013-01-17 08:52:11 +00:00
|
|
|
err = create_in_jack_mode(codec, pin);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
2013-01-09 08:14:23 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-12-09 15:02:24 +00:00
|
|
|
/* add stereo mix when explicitly enabled via hint */
|
2014-12-15 12:43:59 +00:00
|
|
|
if (mixer && spec->add_stereo_mix_input == HDA_HINT_STEREO_MIX_ENABLE) {
|
2013-01-18 09:01:15 +00:00
|
|
|
err = parse_capture_source(codec, mixer, CFG_IDX_MIX, num_adcs,
|
2013-01-09 08:14:23 +00:00
|
|
|
"Stereo Mix", 0);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2014-12-15 12:40:42 +00:00
|
|
|
else
|
|
|
|
spec->suppress_auto_mic = 1;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* input source mux
|
|
|
|
*/
|
|
|
|
|
2013-01-07 16:09:26 +00:00
|
|
|
/* get the input path specified by the given adc and imux indices */
|
|
|
|
static struct nid_path *get_input_path(struct hda_codec *codec, int adc_idx, int imux_idx)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-16 10:45:35 +00:00
|
|
|
if (imux_idx < 0 || imux_idx >= HDA_MAX_NUM_INPUTS) {
|
|
|
|
snd_BUG();
|
|
|
|
return NULL;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->dyn_adc_switch)
|
|
|
|
adc_idx = spec->dyn_adc_idx[imux_idx];
|
2013-01-18 14:43:01 +00:00
|
|
|
if (adc_idx < 0 || adc_idx >= AUTO_CFG_MAX_INS) {
|
2013-01-16 10:45:35 +00:00
|
|
|
snd_BUG();
|
|
|
|
return NULL;
|
|
|
|
}
|
2013-01-07 16:09:26 +00:00
|
|
|
return snd_hda_get_path_from_idx(codec, spec->input_paths[imux_idx][adc_idx]);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
|
|
|
|
unsigned int idx);
|
|
|
|
|
|
|
|
static int mux_enum_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return snd_hda_input_mux_info(&spec->input_mux, uinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mux_enum_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-18 15:23:25 +00:00
|
|
|
/* the ctls are created at once with multiple counts */
|
|
|
|
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mux_enum_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
2013-01-18 15:23:25 +00:00
|
|
|
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
2012-12-19 11:52:06 +00:00
|
|
|
return mux_select(codec, adc_idx,
|
|
|
|
ucontrol->value.enumerated.item[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new cap_src_temp = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Input Source",
|
|
|
|
.info = mux_enum_info,
|
|
|
|
.get = mux_enum_get,
|
|
|
|
.put = mux_enum_put,
|
|
|
|
};
|
|
|
|
|
2012-12-20 10:48:54 +00:00
|
|
|
/*
|
|
|
|
* capture volume and capture switch ctls
|
|
|
|
*/
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
typedef int (*put_call_t)(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol);
|
|
|
|
|
2012-12-20 10:48:54 +00:00
|
|
|
/* call the given amp update function for all amps in the imux list at once */
|
2012-12-19 11:52:06 +00:00
|
|
|
static int cap_put_caller(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol,
|
|
|
|
put_call_t func, int type)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
const struct hda_input_mux *imux;
|
|
|
|
struct nid_path *path;
|
|
|
|
int i, adc_idx, err = 0;
|
|
|
|
|
|
|
|
imux = &spec->input_mux;
|
2013-01-16 10:45:36 +00:00
|
|
|
adc_idx = kcontrol->id.index;
|
2012-12-19 11:52:06 +00:00
|
|
|
mutex_lock(&codec->control_mutex);
|
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
2013-01-07 16:09:26 +00:00
|
|
|
path = get_input_path(codec, adc_idx, i);
|
|
|
|
if (!path || !path->ctls[type])
|
2012-12-19 11:52:06 +00:00
|
|
|
continue;
|
|
|
|
kcontrol->private_value = path->ctls[type];
|
|
|
|
err = func(kcontrol, ucontrol);
|
|
|
|
if (err < 0)
|
ALSA: hda - Use regmap for command verb caches, too
Like the previous patches, this patch converts also to the regmap, at
this time, the cached verb writes are the target. But this conversion
needs a bit more caution than before.
- In the old code, we just record any verbs as is, and restore them at
resume. For the regmap scheme, this doesn't work, since a few verbs
like AMP or DIGI_CONVERT are asymmetrical. Such verbs are converted
either to the dedicated function (snd_hda_regmap_xxx_amp()) or
changed to the unified verb.
- Some verbs have to be declared as vendor-specific ones before
accessing via regmap.
Also, the minor optimization with codec->cached_write flag is dropped
in a few places, as this would confuse the operation. Further
optimizations will be brought in the later patches, if any.
This conversion ends up with a drop of significant amount of codes,
mostly the helper codes that are no longer used.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-02-26 11:34:49 +00:00
|
|
|
break;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
mutex_unlock(&codec->control_mutex);
|
|
|
|
if (err >= 0 && spec->cap_sync_hook)
|
2014-01-30 16:59:02 +00:00
|
|
|
spec->cap_sync_hook(codec, kcontrol, ucontrol);
|
2012-12-19 11:52:06 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* capture volume ctl callbacks */
|
|
|
|
#define cap_vol_info snd_hda_mixer_amp_volume_info
|
|
|
|
#define cap_vol_get snd_hda_mixer_amp_volume_get
|
|
|
|
#define cap_vol_tlv snd_hda_mixer_amp_tlv
|
|
|
|
|
|
|
|
static int cap_vol_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
return cap_put_caller(kcontrol, ucontrol,
|
|
|
|
snd_hda_mixer_amp_volume_put,
|
|
|
|
NID_PATH_VOL_CTL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new cap_vol_temp = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Capture Volume",
|
|
|
|
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK),
|
|
|
|
.info = cap_vol_info,
|
|
|
|
.get = cap_vol_get,
|
|
|
|
.put = cap_vol_put,
|
|
|
|
.tlv = { .c = cap_vol_tlv },
|
|
|
|
};
|
|
|
|
|
|
|
|
/* capture switch ctl callbacks */
|
|
|
|
#define cap_sw_info snd_ctl_boolean_stereo_info
|
|
|
|
#define cap_sw_get snd_hda_mixer_amp_switch_get
|
|
|
|
|
|
|
|
static int cap_sw_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
2013-01-18 13:10:00 +00:00
|
|
|
return cap_put_caller(kcontrol, ucontrol,
|
2012-12-19 11:52:06 +00:00
|
|
|
snd_hda_mixer_amp_switch_put,
|
|
|
|
NID_PATH_MUTE_CTL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new cap_sw_temp = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Capture Switch",
|
|
|
|
.info = cap_sw_info,
|
|
|
|
.get = cap_sw_get,
|
|
|
|
.put = cap_sw_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int parse_capvol_in_path(struct hda_codec *codec, struct nid_path *path)
|
|
|
|
{
|
|
|
|
hda_nid_t nid;
|
|
|
|
int i, depth;
|
|
|
|
|
|
|
|
path->ctls[NID_PATH_VOL_CTL] = path->ctls[NID_PATH_MUTE_CTL] = 0;
|
|
|
|
for (depth = 0; depth < 3; depth++) {
|
|
|
|
if (depth >= path->depth)
|
|
|
|
return -EINVAL;
|
|
|
|
i = path->depth - depth - 1;
|
|
|
|
nid = path->path[i];
|
|
|
|
if (!path->ctls[NID_PATH_VOL_CTL]) {
|
|
|
|
if (nid_has_volume(codec, nid, HDA_OUTPUT))
|
|
|
|
path->ctls[NID_PATH_VOL_CTL] =
|
|
|
|
HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
|
|
|
|
else if (nid_has_volume(codec, nid, HDA_INPUT)) {
|
|
|
|
int idx = path->idx[i];
|
|
|
|
if (!depth && codec->single_adc_amp)
|
|
|
|
idx = 0;
|
|
|
|
path->ctls[NID_PATH_VOL_CTL] =
|
|
|
|
HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_INPUT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!path->ctls[NID_PATH_MUTE_CTL]) {
|
|
|
|
if (nid_has_mute(codec, nid, HDA_OUTPUT))
|
|
|
|
path->ctls[NID_PATH_MUTE_CTL] =
|
|
|
|
HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
|
|
|
|
else if (nid_has_mute(codec, nid, HDA_INPUT)) {
|
|
|
|
int idx = path->idx[i];
|
|
|
|
if (!depth && codec->single_adc_amp)
|
|
|
|
idx = 0;
|
|
|
|
path->ctls[NID_PATH_MUTE_CTL] =
|
|
|
|
HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_INPUT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_inv_dmic_pin(struct hda_codec *codec, hda_nid_t nid)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
unsigned int val;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!spec->inv_dmic_split)
|
|
|
|
return false;
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
if (cfg->inputs[i].pin != nid)
|
|
|
|
continue;
|
|
|
|
if (cfg->inputs[i].type != AUTO_PIN_MIC)
|
|
|
|
return false;
|
|
|
|
val = snd_hda_codec_get_pincfg(codec, nid);
|
|
|
|
return snd_hda_get_input_pin_attr(val) == INPUT_PIN_ATTR_INT;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-01-18 13:10:00 +00:00
|
|
|
/* capture switch put callback for a single control with hook call */
|
2013-01-18 13:01:14 +00:00
|
|
|
static int cap_single_sw_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2013-01-18 13:10:00 +00:00
|
|
|
if (spec->cap_sync_hook)
|
2014-01-30 16:59:02 +00:00
|
|
|
spec->cap_sync_hook(codec, kcontrol, ucontrol);
|
2013-01-18 13:01:14 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int add_single_cap_ctl(struct hda_codec *codec, const char *label,
|
|
|
|
int idx, bool is_switch, unsigned int ctl,
|
|
|
|
bool inv_dmic)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-06-28 09:56:49 +00:00
|
|
|
char tmpname[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
2012-12-19 11:52:06 +00:00
|
|
|
int type = is_switch ? HDA_CTL_WIDGET_MUTE : HDA_CTL_WIDGET_VOL;
|
|
|
|
const char *sfx = is_switch ? "Switch" : "Volume";
|
|
|
|
unsigned int chs = inv_dmic ? 1 : 3;
|
2013-01-18 13:01:14 +00:00
|
|
|
struct snd_kcontrol_new *knew;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
if (!ctl)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (label)
|
|
|
|
snprintf(tmpname, sizeof(tmpname),
|
|
|
|
"%s Capture %s", label, sfx);
|
|
|
|
else
|
|
|
|
snprintf(tmpname, sizeof(tmpname),
|
|
|
|
"Capture %s", sfx);
|
2013-01-18 13:01:14 +00:00
|
|
|
knew = add_control(spec, type, tmpname, idx,
|
|
|
|
amp_val_replace_channels(ctl, chs));
|
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
2013-01-18 13:10:00 +00:00
|
|
|
if (is_switch)
|
2013-01-18 13:01:14 +00:00
|
|
|
knew->put = cap_single_sw_put;
|
|
|
|
if (!inv_dmic)
|
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* Make independent right kcontrol */
|
|
|
|
if (label)
|
|
|
|
snprintf(tmpname, sizeof(tmpname),
|
|
|
|
"Inverted %s Capture %s", label, sfx);
|
|
|
|
else
|
|
|
|
snprintf(tmpname, sizeof(tmpname),
|
|
|
|
"Inverted Capture %s", sfx);
|
2013-01-18 13:01:14 +00:00
|
|
|
knew = add_control(spec, type, tmpname, idx,
|
2012-12-19 11:52:06 +00:00
|
|
|
amp_val_replace_channels(ctl, 2));
|
2013-01-18 13:01:14 +00:00
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
2013-01-18 13:10:00 +00:00
|
|
|
if (is_switch)
|
2013-01-18 13:01:14 +00:00
|
|
|
knew->put = cap_single_sw_put;
|
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* create single (and simple) capture volume and switch controls */
|
|
|
|
static int create_single_cap_vol_ctl(struct hda_codec *codec, int idx,
|
|
|
|
unsigned int vol_ctl, unsigned int sw_ctl,
|
|
|
|
bool inv_dmic)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
err = add_single_cap_ctl(codec, NULL, idx, false, vol_ctl, inv_dmic);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = add_single_cap_ctl(codec, NULL, idx, true, sw_ctl, inv_dmic);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create bound capture volume and switch controls */
|
|
|
|
static int create_bind_cap_vol_ctl(struct hda_codec *codec, int idx,
|
|
|
|
unsigned int vol_ctl, unsigned int sw_ctl)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct snd_kcontrol_new *knew;
|
|
|
|
|
|
|
|
if (vol_ctl) {
|
2012-12-19 13:38:33 +00:00
|
|
|
knew = snd_hda_gen_add_kctl(spec, NULL, &cap_vol_temp);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
|
|
|
knew->index = idx;
|
|
|
|
knew->private_value = vol_ctl;
|
|
|
|
knew->subdevice = HDA_SUBDEV_AMP_FLAG;
|
|
|
|
}
|
|
|
|
if (sw_ctl) {
|
2012-12-19 13:38:33 +00:00
|
|
|
knew = snd_hda_gen_add_kctl(spec, NULL, &cap_sw_temp);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
|
|
|
knew->index = idx;
|
|
|
|
knew->private_value = sw_ctl;
|
|
|
|
knew->subdevice = HDA_SUBDEV_AMP_FLAG;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return the vol ctl when used first in the imux list */
|
|
|
|
static unsigned int get_first_cap_ctl(struct hda_codec *codec, int idx, int type)
|
|
|
|
{
|
|
|
|
struct nid_path *path;
|
|
|
|
unsigned int ctl;
|
|
|
|
int i;
|
|
|
|
|
2013-01-07 16:09:26 +00:00
|
|
|
path = get_input_path(codec, 0, idx);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
return 0;
|
|
|
|
ctl = path->ctls[type];
|
|
|
|
if (!ctl)
|
|
|
|
return 0;
|
|
|
|
for (i = 0; i < idx - 1; i++) {
|
2013-01-07 16:09:26 +00:00
|
|
|
path = get_input_path(codec, 0, i);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (path && path->ctls[type] == ctl)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return ctl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create individual capture volume and switch controls per input */
|
|
|
|
static int create_multi_cap_vol_ctl(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct hda_input_mux *imux = &spec->input_mux;
|
2013-01-18 09:17:30 +00:00
|
|
|
int i, err, type;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
|
|
|
bool inv_dmic;
|
2013-01-18 09:17:30 +00:00
|
|
|
int idx;
|
2013-01-18 09:01:15 +00:00
|
|
|
|
2013-01-18 09:17:30 +00:00
|
|
|
idx = imux->items[i].index;
|
|
|
|
if (idx >= spec->autocfg.num_inputs)
|
2013-01-18 09:01:15 +00:00
|
|
|
continue;
|
2012-12-19 11:52:06 +00:00
|
|
|
inv_dmic = is_inv_dmic_pin(codec, spec->imux_pins[i]);
|
|
|
|
|
|
|
|
for (type = 0; type < 2; type++) {
|
2013-01-18 09:17:30 +00:00
|
|
|
err = add_single_cap_ctl(codec,
|
|
|
|
spec->input_labels[idx],
|
|
|
|
spec->input_label_idxs[idx],
|
|
|
|
type,
|
2012-12-19 11:52:06 +00:00
|
|
|
get_first_cap_ctl(codec, i, type),
|
|
|
|
inv_dmic);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int create_capture_mixers(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct hda_input_mux *imux = &spec->input_mux;
|
|
|
|
int i, n, nums, err;
|
|
|
|
|
|
|
|
if (spec->dyn_adc_switch)
|
|
|
|
nums = 1;
|
|
|
|
else
|
|
|
|
nums = spec->num_adc_nids;
|
|
|
|
|
|
|
|
if (!spec->auto_mic && imux->num_items > 1) {
|
|
|
|
struct snd_kcontrol_new *knew;
|
2012-12-19 16:41:52 +00:00
|
|
|
const char *name;
|
|
|
|
name = nums > 1 ? "Input Source" : "Capture Source";
|
|
|
|
knew = snd_hda_gen_add_kctl(spec, name, &cap_src_temp);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!knew)
|
|
|
|
return -ENOMEM;
|
|
|
|
knew->count = nums;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (n = 0; n < nums; n++) {
|
|
|
|
bool multi = false;
|
2013-01-16 14:58:44 +00:00
|
|
|
bool multi_cap_vol = spec->multi_cap_vol;
|
2012-12-19 11:52:06 +00:00
|
|
|
bool inv_dmic = false;
|
|
|
|
int vol, sw;
|
|
|
|
|
|
|
|
vol = sw = 0;
|
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
|
|
|
struct nid_path *path;
|
2013-01-07 16:09:26 +00:00
|
|
|
path = get_input_path(codec, n, i);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
continue;
|
|
|
|
parse_capvol_in_path(codec, path);
|
|
|
|
if (!vol)
|
|
|
|
vol = path->ctls[NID_PATH_VOL_CTL];
|
2013-01-16 14:58:44 +00:00
|
|
|
else if (vol != path->ctls[NID_PATH_VOL_CTL]) {
|
2012-12-19 11:52:06 +00:00
|
|
|
multi = true;
|
2013-01-16 14:58:44 +00:00
|
|
|
if (!same_amp_caps(codec, vol,
|
|
|
|
path->ctls[NID_PATH_VOL_CTL], HDA_INPUT))
|
|
|
|
multi_cap_vol = true;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!sw)
|
|
|
|
sw = path->ctls[NID_PATH_MUTE_CTL];
|
2013-01-16 14:58:44 +00:00
|
|
|
else if (sw != path->ctls[NID_PATH_MUTE_CTL]) {
|
2012-12-19 11:52:06 +00:00
|
|
|
multi = true;
|
2013-01-16 14:58:44 +00:00
|
|
|
if (!same_amp_caps(codec, sw,
|
|
|
|
path->ctls[NID_PATH_MUTE_CTL], HDA_INPUT))
|
|
|
|
multi_cap_vol = true;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
if (is_inv_dmic_pin(codec, spec->imux_pins[i]))
|
|
|
|
inv_dmic = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!multi)
|
|
|
|
err = create_single_cap_vol_ctl(codec, n, vol, sw,
|
|
|
|
inv_dmic);
|
2013-10-14 08:16:22 +00:00
|
|
|
else if (!multi_cap_vol && !inv_dmic)
|
2012-12-19 11:52:06 +00:00
|
|
|
err = create_bind_cap_vol_ctl(codec, n, vol, sw);
|
|
|
|
else
|
|
|
|
err = create_multi_cap_vol_ctl(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* add mic boosts if needed
|
|
|
|
*/
|
2013-01-18 10:07:15 +00:00
|
|
|
|
|
|
|
/* check whether the given amp is feasible as a boost volume */
|
|
|
|
static bool check_boost_vol(struct hda_codec *codec, hda_nid_t nid,
|
|
|
|
int dir, int idx)
|
|
|
|
{
|
|
|
|
unsigned int step;
|
|
|
|
|
|
|
|
if (!nid_has_volume(codec, nid, dir) ||
|
|
|
|
is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) ||
|
|
|
|
is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
step = (query_amp_caps(codec, nid, dir) & AC_AMPCAP_STEP_SIZE)
|
|
|
|
>> AC_AMPCAP_STEP_SIZE_SHIFT;
|
|
|
|
if (step < 0x20)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* look for a boost amp in a widget close to the pin */
|
|
|
|
static unsigned int look_for_boost_amp(struct hda_codec *codec,
|
|
|
|
struct nid_path *path)
|
|
|
|
{
|
|
|
|
unsigned int val = 0;
|
|
|
|
hda_nid_t nid;
|
|
|
|
int depth;
|
|
|
|
|
|
|
|
for (depth = 0; depth < 3; depth++) {
|
|
|
|
if (depth >= path->depth - 1)
|
|
|
|
break;
|
|
|
|
nid = path->path[depth];
|
|
|
|
if (depth && check_boost_vol(codec, nid, HDA_OUTPUT, 0)) {
|
|
|
|
val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
|
|
|
|
break;
|
|
|
|
} else if (check_boost_vol(codec, nid, HDA_INPUT,
|
|
|
|
path->idx[depth])) {
|
|
|
|
val = HDA_COMPOSE_AMP_VAL(nid, 3, path->idx[depth],
|
|
|
|
HDA_INPUT);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int parse_mic_boost(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
2013-01-18 10:07:15 +00:00
|
|
|
struct hda_input_mux *imux = &spec->input_mux;
|
2013-01-18 13:01:14 +00:00
|
|
|
int i;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-18 10:07:15 +00:00
|
|
|
if (!spec->num_adc_nids)
|
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-18 10:07:15 +00:00
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
|
|
|
struct nid_path *path;
|
|
|
|
unsigned int val;
|
|
|
|
int idx;
|
2013-06-28 09:56:49 +00:00
|
|
|
char boost_label[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
2013-01-16 14:58:43 +00:00
|
|
|
|
2013-01-18 10:07:15 +00:00
|
|
|
idx = imux->items[i].index;
|
|
|
|
if (idx >= imux->num_items)
|
|
|
|
continue;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-18 10:07:15 +00:00
|
|
|
/* check only line-in and mic pins */
|
2013-01-18 13:37:16 +00:00
|
|
|
if (cfg->inputs[idx].type > AUTO_PIN_LINE_IN)
|
2013-01-18 10:07:15 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
path = get_input_path(codec, 0, i);
|
|
|
|
if (!path)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
val = look_for_boost_amp(codec, path);
|
|
|
|
if (!val)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* create a boost control */
|
|
|
|
snprintf(boost_label, sizeof(boost_label),
|
|
|
|
"%s Boost Volume", spec->input_labels[idx]);
|
2013-01-18 13:01:14 +00:00
|
|
|
if (!add_control(spec, HDA_CTL_WIDGET_VOL, boost_label,
|
|
|
|
spec->input_label_idxs[idx], val))
|
|
|
|
return -ENOMEM;
|
2013-01-18 10:07:15 +00:00
|
|
|
|
|
|
|
path->ctls[NID_PATH_BOOST_CTL] = val;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-06-18 15:26:12 +00:00
|
|
|
/*
|
|
|
|
* mic mute LED hook helpers
|
|
|
|
*/
|
|
|
|
enum {
|
|
|
|
MICMUTE_LED_ON,
|
|
|
|
MICMUTE_LED_OFF,
|
|
|
|
MICMUTE_LED_FOLLOW_CAPTURE,
|
|
|
|
MICMUTE_LED_FOLLOW_MUTE,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void call_micmute_led_update(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
unsigned int val;
|
|
|
|
|
|
|
|
switch (spec->micmute_led.led_mode) {
|
|
|
|
case MICMUTE_LED_ON:
|
|
|
|
val = 1;
|
|
|
|
break;
|
|
|
|
case MICMUTE_LED_OFF:
|
|
|
|
val = 0;
|
|
|
|
break;
|
|
|
|
case MICMUTE_LED_FOLLOW_CAPTURE:
|
2018-06-19 10:42:03 +00:00
|
|
|
val = !!spec->micmute_led.capture;
|
2018-06-18 15:26:12 +00:00
|
|
|
break;
|
|
|
|
case MICMUTE_LED_FOLLOW_MUTE:
|
|
|
|
default:
|
|
|
|
val = !spec->micmute_led.capture;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val == spec->micmute_led.led_value)
|
|
|
|
return;
|
|
|
|
spec->micmute_led.led_value = val;
|
|
|
|
if (spec->micmute_led.update)
|
|
|
|
spec->micmute_led.update(codec);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_micmute_led(struct hda_codec *codec,
|
|
|
|
struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2018-06-19 10:42:03 +00:00
|
|
|
unsigned int mask;
|
2018-06-18 15:26:12 +00:00
|
|
|
|
|
|
|
if (spec->micmute_led.old_hook)
|
|
|
|
spec->micmute_led.old_hook(codec, kcontrol, ucontrol);
|
|
|
|
|
|
|
|
if (!ucontrol)
|
|
|
|
return;
|
2018-06-19 10:42:03 +00:00
|
|
|
mask = 1U << snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
|
|
|
if (!strcmp("Capture Switch", ucontrol->id.name)) {
|
2018-06-18 15:26:12 +00:00
|
|
|
/* TODO: How do I verify if it's a mono or stereo here? */
|
2018-06-19 10:42:03 +00:00
|
|
|
if (ucontrol->value.integer.value[0] ||
|
|
|
|
ucontrol->value.integer.value[1])
|
|
|
|
spec->micmute_led.capture |= mask;
|
|
|
|
else
|
|
|
|
spec->micmute_led.capture &= ~mask;
|
2018-06-18 15:26:12 +00:00
|
|
|
call_micmute_led_update(codec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int micmute_led_mode_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
static const char * const texts[] = {
|
|
|
|
"On", "Off", "Follow Capture", "Follow Mute",
|
|
|
|
};
|
|
|
|
|
|
|
|
return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int micmute_led_mode_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
ucontrol->value.enumerated.item[0] = spec->micmute_led.led_mode;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int micmute_led_mode_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
unsigned int mode;
|
|
|
|
|
|
|
|
mode = ucontrol->value.enumerated.item[0];
|
|
|
|
if (mode > MICMUTE_LED_FOLLOW_MUTE)
|
|
|
|
mode = MICMUTE_LED_FOLLOW_MUTE;
|
|
|
|
if (mode == spec->micmute_led.led_mode)
|
|
|
|
return 0;
|
|
|
|
spec->micmute_led.led_mode = mode;
|
|
|
|
call_micmute_led_update(codec);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new micmute_led_mode_ctl = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Mic Mute-LED Mode",
|
|
|
|
.info = micmute_led_mode_info,
|
|
|
|
.get = micmute_led_mode_get,
|
|
|
|
.put = micmute_led_mode_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_hda_gen_add_micmute_led - helper for setting up mic mute LED hook
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @hook: the callback for updating LED
|
|
|
|
*
|
|
|
|
* Called from the codec drivers for offering the mic mute LED controls.
|
2018-06-19 10:42:03 +00:00
|
|
|
* When established, it sets up cap_sync_hook and triggers the callback at
|
|
|
|
* each time when the capture mixer switch changes. The callback is supposed
|
|
|
|
* to update the LED accordingly.
|
2018-06-18 15:26:12 +00:00
|
|
|
*
|
2018-06-19 10:42:03 +00:00
|
|
|
* Returns 0 if the hook is established or a negative error code.
|
2018-06-18 15:26:12 +00:00
|
|
|
*/
|
|
|
|
int snd_hda_gen_add_micmute_led(struct hda_codec *codec,
|
|
|
|
void (*hook)(struct hda_codec *))
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
spec->micmute_led.led_mode = MICMUTE_LED_FOLLOW_MUTE;
|
|
|
|
spec->micmute_led.capture = 0;
|
|
|
|
spec->micmute_led.led_value = 0;
|
|
|
|
spec->micmute_led.old_hook = spec->cap_sync_hook;
|
|
|
|
spec->micmute_led.update = hook;
|
|
|
|
spec->cap_sync_hook = update_micmute_led;
|
|
|
|
if (!snd_hda_gen_add_kctl(spec, NULL, &micmute_led_mode_ctl))
|
|
|
|
return -ENOMEM;
|
2018-06-19 10:42:03 +00:00
|
|
|
return 0;
|
2018-06-18 15:26:12 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_add_micmute_led);
|
|
|
|
|
ALSA: hda - Support led audio trigger
Now all relevant platform drivers are providing the LED audio trigger,
we can switch the mute LED control with the LED trigger, finally.
For the mic-mute LED trigger, a common fixup function,
snd_hda_gen_fixup_micmute_led(), is provided to be called for the
corresponding quirk entries. This sets up the capture sync hook with
ledtrig_audio_set() call appropriately.
For the mute LED trigger, which is done currently only for
thinkpad_acpi, the call is replaced with ledtrig_audio_set() as well.
Overall, the beauty of the new implementation is that the whole ugly
bindings with request_symbol() are dropped, and also that it provides
more flexibility to users.
One potential behavior change by this patch is that the mute LED enum
may be created on machines that actually have no LED device. In the
former code, we did test-call and abort binding if the test failed.
But with the LED-trigger binding, this test isn't possible, and the
actual check is done in the LED class device side. So it's the
downside of simpleness.
Also, note that the HD-audio codec driver doesn't select CONFIG_LEDS
and co by itself. It's supposed to be selected by the platform
drivers instead.
Acked-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
Acked-by: Pavel Machek <pavel@ucw.cz>
Acked-by: Pali Rohár <pali.rohar@gmail.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-11-26 16:47:46 +00:00
|
|
|
#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO)
|
|
|
|
static void call_ledtrig_micmute(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
ledtrig_audio_set(LED_AUDIO_MICMUTE,
|
|
|
|
spec->micmute_led.led_value ? LED_ON : LED_OFF);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_hda_gen_fixup_micmute_led - A fixup for mic-mute LED trigger
|
|
|
|
*
|
|
|
|
* Pass this function to the quirk entry if another driver supports the
|
|
|
|
* audio mic-mute LED trigger. Then this will bind the mixer capture switch
|
|
|
|
* change with the LED.
|
|
|
|
*
|
|
|
|
* Note that this fixup has to be called after other fixup that sets
|
|
|
|
* cap_sync_hook. Otherwise the chaining wouldn't work.
|
|
|
|
*/
|
|
|
|
void snd_hda_gen_fixup_micmute_led(struct hda_codec *codec,
|
|
|
|
const struct hda_fixup *fix, int action)
|
|
|
|
{
|
|
|
|
#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO)
|
|
|
|
if (action == HDA_FIXUP_ACT_PROBE)
|
|
|
|
snd_hda_gen_add_micmute_led(codec, call_ledtrig_micmute);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_fixup_micmute_led);
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* parse digital I/Os and set up NIDs in BIOS auto-parse mode
|
|
|
|
*/
|
|
|
|
static void parse_digital(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2012-12-20 16:54:22 +00:00
|
|
|
struct nid_path *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
int i, nums;
|
2013-01-10 08:33:29 +00:00
|
|
|
hda_nid_t dig_nid, pin;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* support multiple SPDIFs; the secondary is set up as a slave */
|
|
|
|
nums = 0;
|
|
|
|
for (i = 0; i < spec->autocfg.dig_outs; i++) {
|
2013-01-10 08:33:29 +00:00
|
|
|
pin = spec->autocfg.dig_out_pins[i];
|
2012-12-19 11:52:06 +00:00
|
|
|
dig_nid = look_for_dac(codec, pin, true);
|
|
|
|
if (!dig_nid)
|
|
|
|
continue;
|
2013-01-07 16:25:08 +00:00
|
|
|
path = snd_hda_add_new_path(codec, dig_nid, pin, 0);
|
2012-12-20 16:54:22 +00:00
|
|
|
if (!path)
|
2012-12-19 11:52:06 +00:00
|
|
|
continue;
|
2014-02-25 11:21:03 +00:00
|
|
|
print_nid_path(codec, "digout", path);
|
2013-01-03 15:33:02 +00:00
|
|
|
path->active = true;
|
2015-03-20 17:11:05 +00:00
|
|
|
path->pin_fixed = true; /* no jack detection */
|
2013-01-04 14:01:40 +00:00
|
|
|
spec->digout_paths[i] = snd_hda_get_path_idx(codec, path);
|
2013-01-10 08:33:29 +00:00
|
|
|
set_pin_target(codec, pin, PIN_OUT, false);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!nums) {
|
|
|
|
spec->multiout.dig_out_nid = dig_nid;
|
|
|
|
spec->dig_out_type = spec->autocfg.dig_out_type[0];
|
|
|
|
} else {
|
|
|
|
spec->multiout.slave_dig_outs = spec->slave_dig_outs;
|
|
|
|
if (nums >= ARRAY_SIZE(spec->slave_dig_outs) - 1)
|
2014-05-14 14:18:31 +00:00
|
|
|
break;
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->slave_dig_outs[nums - 1] = dig_nid;
|
|
|
|
}
|
|
|
|
nums++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spec->autocfg.dig_in_pin) {
|
2013-01-10 08:33:29 +00:00
|
|
|
pin = spec->autocfg.dig_in_pin;
|
2015-03-03 09:07:24 +00:00
|
|
|
for_each_hda_codec_node(dig_nid, codec) {
|
2012-12-19 11:52:06 +00:00
|
|
|
unsigned int wcaps = get_wcaps(codec, dig_nid);
|
|
|
|
if (get_wcaps_type(wcaps) != AC_WID_AUD_IN)
|
|
|
|
continue;
|
|
|
|
if (!(wcaps & AC_WCAP_DIGITAL))
|
|
|
|
continue;
|
2013-01-10 08:33:29 +00:00
|
|
|
path = snd_hda_add_new_path(codec, pin, dig_nid, 0);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (path) {
|
2014-02-25 11:21:03 +00:00
|
|
|
print_nid_path(codec, "digin", path);
|
2012-12-19 11:52:06 +00:00
|
|
|
path->active = true;
|
2015-03-20 17:11:05 +00:00
|
|
|
path->pin_fixed = true; /* no jack */
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->dig_in_nid = dig_nid;
|
2013-01-04 14:09:42 +00:00
|
|
|
spec->digin_path = snd_hda_get_path_idx(codec, path);
|
2013-01-10 08:33:29 +00:00
|
|
|
set_pin_target(codec, pin, PIN_IN, false);
|
2012-12-19 11:52:06 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* input MUX handling
|
|
|
|
*/
|
|
|
|
|
|
|
|
static bool dyn_adc_pcm_resetup(struct hda_codec *codec, int cur);
|
|
|
|
|
|
|
|
/* select the given imux item; either unmute exclusively or select the route */
|
|
|
|
static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
|
|
|
|
unsigned int idx)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
const struct hda_input_mux *imux;
|
2013-01-24 16:32:56 +00:00
|
|
|
struct nid_path *old_path, *path;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
imux = &spec->input_mux;
|
|
|
|
if (!imux->num_items)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (idx >= imux->num_items)
|
|
|
|
idx = imux->num_items - 1;
|
|
|
|
if (spec->cur_mux[adc_idx] == idx)
|
|
|
|
return 0;
|
|
|
|
|
2013-01-24 16:32:56 +00:00
|
|
|
old_path = get_input_path(codec, adc_idx, spec->cur_mux[adc_idx]);
|
|
|
|
if (!old_path)
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
2013-01-24 16:32:56 +00:00
|
|
|
if (old_path->active)
|
|
|
|
snd_hda_activate_path(codec, old_path, false, false);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
spec->cur_mux[adc_idx] = idx;
|
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
if (spec->hp_mic)
|
|
|
|
update_hp_mic(codec, adc_idx, false);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
if (spec->dyn_adc_switch)
|
|
|
|
dyn_adc_pcm_resetup(codec, idx);
|
|
|
|
|
2013-01-07 16:09:26 +00:00
|
|
|
path = get_input_path(codec, adc_idx, idx);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
return 0;
|
|
|
|
if (path->active)
|
|
|
|
return 0;
|
|
|
|
snd_hda_activate_path(codec, path, true, false);
|
|
|
|
if (spec->cap_sync_hook)
|
2014-01-30 16:59:02 +00:00
|
|
|
spec->cap_sync_hook(codec, NULL, NULL);
|
2013-01-24 16:32:56 +00:00
|
|
|
path_power_down_sync(codec, old_path);
|
2012-12-19 11:52:06 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
/* power up/down widgets in the all paths that match with the given NID
|
|
|
|
* as terminals (either start- or endpoint)
|
|
|
|
*
|
|
|
|
* returns the last changed NID, or zero if unchanged.
|
|
|
|
*/
|
|
|
|
static hda_nid_t set_path_power(struct hda_codec *codec, hda_nid_t nid,
|
|
|
|
int pin_state, int stream_state)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
hda_nid_t last, changed = 0;
|
|
|
|
struct nid_path *path;
|
|
|
|
int n;
|
|
|
|
|
2018-04-23 15:24:56 +00:00
|
|
|
snd_array_for_each(&spec->paths, n, path) {
|
2016-06-25 11:58:45 +00:00
|
|
|
if (!path->depth)
|
|
|
|
continue;
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
if (path->path[0] == nid ||
|
|
|
|
path->path[path->depth - 1] == nid) {
|
|
|
|
bool pin_old = path->pin_enabled;
|
|
|
|
bool stream_old = path->stream_enabled;
|
|
|
|
|
|
|
|
if (pin_state >= 0)
|
|
|
|
path->pin_enabled = pin_state;
|
|
|
|
if (stream_state >= 0)
|
|
|
|
path->stream_enabled = stream_state;
|
2015-03-20 17:11:05 +00:00
|
|
|
if ((!path->pin_fixed && path->pin_enabled != pin_old)
|
|
|
|
|| path->stream_enabled != stream_old) {
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
last = path_power_update(codec, path, true);
|
|
|
|
if (last)
|
|
|
|
changed = last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
2015-04-09 08:21:30 +00:00
|
|
|
/* check the jack status for power control */
|
|
|
|
static bool detect_pin_state(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
if (!is_jack_detectable(codec, pin))
|
|
|
|
return true;
|
|
|
|
return snd_hda_jack_detect_state(codec, pin) != HDA_JACK_NOT_PRESENT;
|
|
|
|
}
|
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
/* power up/down the paths of the given pin according to the jack state;
|
|
|
|
* power = 0/1 : only power up/down if it matches with the jack state,
|
|
|
|
* < 0 : force power up/down to follow the jack sate
|
|
|
|
*
|
|
|
|
* returns the last changed NID, or zero if unchanged.
|
|
|
|
*/
|
|
|
|
static hda_nid_t set_pin_power_jack(struct hda_codec *codec, hda_nid_t pin,
|
|
|
|
int power)
|
|
|
|
{
|
|
|
|
bool on;
|
|
|
|
|
2015-03-20 17:21:03 +00:00
|
|
|
if (!codec->power_save_node)
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
return 0;
|
|
|
|
|
2015-04-09 08:21:30 +00:00
|
|
|
on = detect_pin_state(codec, pin);
|
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
if (power >= 0 && on != power)
|
|
|
|
return 0;
|
|
|
|
return set_path_power(codec, pin, on, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pin_power_callback(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack,
|
|
|
|
bool on)
|
|
|
|
{
|
2016-02-09 09:23:52 +00:00
|
|
|
if (jack && jack->nid)
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
sync_power_state_change(codec,
|
2016-02-09 09:23:52 +00:00
|
|
|
set_pin_power_jack(codec, jack->nid, on));
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* callback only doing power up -- called at first */
|
|
|
|
static void pin_power_up_callback(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack)
|
|
|
|
{
|
|
|
|
pin_power_callback(codec, jack, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* callback only doing power down -- called at last */
|
|
|
|
static void pin_power_down_callback(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack)
|
|
|
|
{
|
|
|
|
pin_power_callback(codec, jack, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set up the power up/down callbacks */
|
|
|
|
static void add_pin_power_ctls(struct hda_codec *codec, int num_pins,
|
|
|
|
const hda_nid_t *pins, bool on)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
hda_jack_callback_fn cb =
|
|
|
|
on ? pin_power_up_callback : pin_power_down_callback;
|
|
|
|
|
|
|
|
for (i = 0; i < num_pins && pins[i]; i++) {
|
|
|
|
if (is_jack_detectable(codec, pins[i]))
|
|
|
|
snd_hda_jack_detect_enable_callback(codec, pins[i], cb);
|
|
|
|
else
|
|
|
|
set_path_power(codec, pins[i], true, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enabled power callback to each available I/O pin with jack detections;
|
|
|
|
* the digital I/O pins are excluded because of the unreliable detectsion
|
|
|
|
*/
|
|
|
|
static void add_all_pin_power_ctls(struct hda_codec *codec, bool on)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
int i;
|
|
|
|
|
2015-03-20 17:21:03 +00:00
|
|
|
if (!codec->power_save_node)
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
return;
|
|
|
|
add_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins, on);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT)
|
|
|
|
add_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins, on);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
|
|
|
|
add_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins, on);
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++)
|
|
|
|
add_pin_power_ctls(codec, 1, &cfg->inputs[i].pin, on);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sync path power up/down with the jack states of given pins */
|
|
|
|
static void sync_pin_power_ctls(struct hda_codec *codec, int num_pins,
|
|
|
|
const hda_nid_t *pins)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_pins && pins[i]; i++)
|
|
|
|
if (is_jack_detectable(codec, pins[i]))
|
|
|
|
set_pin_power_jack(codec, pins[i], -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sync path power up/down with pins; called at init and resume */
|
|
|
|
static void sync_all_pin_power_ctls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
int i;
|
|
|
|
|
2015-03-20 17:21:03 +00:00
|
|
|
if (!codec->power_save_node)
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
return;
|
|
|
|
sync_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT)
|
|
|
|
sync_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
|
|
|
|
sync_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins);
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++)
|
|
|
|
sync_pin_power_ctls(codec, 1, &cfg->inputs[i].pin);
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2015-03-18 08:23:10 +00:00
|
|
|
/* add fake paths if not present yet */
|
|
|
|
static int add_fake_paths(struct hda_codec *codec, hda_nid_t nid,
|
|
|
|
int num_pins, const hda_nid_t *pins)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct nid_path *path;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_pins; i++) {
|
|
|
|
if (!pins[i])
|
|
|
|
break;
|
|
|
|
if (get_nid_path(codec, nid, pins[i], 0))
|
|
|
|
continue;
|
|
|
|
path = snd_array_new(&spec->paths);
|
|
|
|
if (!path)
|
|
|
|
return -ENOMEM;
|
|
|
|
memset(path, 0, sizeof(*path));
|
|
|
|
path->depth = 2;
|
|
|
|
path->path[0] = nid;
|
|
|
|
path->path[1] = pins[i];
|
|
|
|
path->active = true;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create fake paths to all outputs from beep */
|
|
|
|
static int add_fake_beep_paths(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
hda_nid_t nid = spec->beep_nid;
|
|
|
|
int err;
|
|
|
|
|
2015-03-20 17:21:03 +00:00
|
|
|
if (!codec->power_save_node || !nid)
|
2015-03-18 08:23:10 +00:00
|
|
|
return 0;
|
|
|
|
err = add_fake_paths(codec, nid, cfg->line_outs, cfg->line_out_pins);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
|
|
|
|
err = add_fake_paths(codec, nid, cfg->hp_outs, cfg->hp_pins);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
|
|
|
|
err = add_fake_paths(codec, nid, cfg->speaker_outs,
|
|
|
|
cfg->speaker_pins);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* power up/down beep widget and its output paths */
|
|
|
|
static void beep_power_hook(struct hda_beep *beep, bool on)
|
|
|
|
{
|
|
|
|
set_path_power(beep->codec, beep->nid, -1, on);
|
|
|
|
}
|
|
|
|
|
2015-03-20 17:11:05 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_fix_pin_power - Fix the power of the given pin widget to D0
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @pin: NID of pin to fix
|
|
|
|
*/
|
|
|
|
int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct nid_path *path;
|
|
|
|
|
|
|
|
path = snd_array_new(&spec->paths);
|
|
|
|
if (!path)
|
|
|
|
return -ENOMEM;
|
|
|
|
memset(path, 0, sizeof(*path));
|
|
|
|
path->depth = 1;
|
|
|
|
path->path[0] = pin;
|
|
|
|
path->active = true;
|
|
|
|
path->pin_fixed = true;
|
|
|
|
path->stream_enabled = true;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_fix_pin_power);
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* Jack detections for HP auto-mute and mic-switch
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* check each pin in the given array; returns true if any of them is plugged */
|
|
|
|
static bool detect_jacks(struct hda_codec *codec, int num_pins, hda_nid_t *pins)
|
|
|
|
{
|
ALSA: hda - Add snd_hda_jack_detect_state() helper function
snd_hda_jack_detect() function returns a boolean value for a jack
plugged in or not, but it also returns always true when the
corresponding pin is phantom (i.e. fixed). This is OK in most cases,
but it makes the generic parser misbehaving about the auto-mute or
auto-mic switching, e.g. when one of headphone pins is a fixed.
Namely, the driver decides whether to mute the speaker or not, just
depending on the headphone plug state: if one of the headphone jacks
is seen as active, then the speaker is muted. Thus this will result
always in the muted speaker output.
So, the problem is the function returns a boolean, after all, although
we need to think of "phantom" jack. Now a new function,
snd_hda_jack_detect_state() is introduced to return these tristates.
The generic parser uses this function for checking the headphone or
mic jack states.
Meanwhile, the behavior of snd_hda_jack_detect() is kept as is, for
keeping compatibility in other driver codes.
Acked-by: David Henningsson <david.henningsson@canonical.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-19 14:59:46 +00:00
|
|
|
int i;
|
|
|
|
bool present = false;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
for (i = 0; i < num_pins; i++) {
|
|
|
|
hda_nid_t nid = pins[i];
|
|
|
|
if (!nid)
|
|
|
|
break;
|
2013-01-10 08:45:13 +00:00
|
|
|
/* don't detect pins retasked as inputs */
|
|
|
|
if (snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_IN_EN)
|
|
|
|
continue;
|
ALSA: hda - Add snd_hda_jack_detect_state() helper function
snd_hda_jack_detect() function returns a boolean value for a jack
plugged in or not, but it also returns always true when the
corresponding pin is phantom (i.e. fixed). This is OK in most cases,
but it makes the generic parser misbehaving about the auto-mute or
auto-mic switching, e.g. when one of headphone pins is a fixed.
Namely, the driver decides whether to mute the speaker or not, just
depending on the headphone plug state: if one of the headphone jacks
is seen as active, then the speaker is muted. Thus this will result
always in the muted speaker output.
So, the problem is the function returns a boolean, after all, although
we need to think of "phantom" jack. Now a new function,
snd_hda_jack_detect_state() is introduced to return these tristates.
The generic parser uses this function for checking the headphone or
mic jack states.
Meanwhile, the behavior of snd_hda_jack_detect() is kept as is, for
keeping compatibility in other driver codes.
Acked-by: David Henningsson <david.henningsson@canonical.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-19 14:59:46 +00:00
|
|
|
if (snd_hda_jack_detect_state(codec, nid) == HDA_JACK_PRESENT)
|
|
|
|
present = true;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
return present;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* standard HP/line-out auto-mute helper */
|
|
|
|
static void do_automute(struct hda_codec *codec, int num_pins, hda_nid_t *pins,
|
2013-08-12 12:44:59 +00:00
|
|
|
int *paths, bool mute)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_pins; i++) {
|
|
|
|
hda_nid_t nid = pins[i];
|
2013-02-19 16:12:42 +00:00
|
|
|
unsigned int val, oldval;
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!nid)
|
|
|
|
break;
|
2013-06-24 14:00:21 +00:00
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
oldval = snd_hda_codec_get_pin_target(codec, nid);
|
|
|
|
if (oldval & PIN_IN)
|
|
|
|
continue; /* no mute for inputs */
|
|
|
|
|
2013-06-24 14:00:21 +00:00
|
|
|
if (spec->auto_mute_via_amp) {
|
2013-08-12 12:44:59 +00:00
|
|
|
struct nid_path *path;
|
|
|
|
hda_nid_t mute_nid;
|
|
|
|
|
|
|
|
path = snd_hda_get_path_from_idx(codec, paths[i]);
|
|
|
|
if (!path)
|
|
|
|
continue;
|
|
|
|
mute_nid = get_amp_nid_(path->ctls[NID_PATH_MUTE_CTL]);
|
|
|
|
if (!mute_nid)
|
|
|
|
continue;
|
2013-06-24 14:00:21 +00:00
|
|
|
if (mute)
|
2013-08-12 12:44:59 +00:00
|
|
|
spec->mute_bits |= (1ULL << mute_nid);
|
2013-06-24 14:00:21 +00:00
|
|
|
else
|
2013-08-12 12:44:59 +00:00
|
|
|
spec->mute_bits &= ~(1ULL << mute_nid);
|
2013-06-24 14:00:21 +00:00
|
|
|
continue;
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
} else {
|
|
|
|
/* don't reset VREF value in case it's controlling
|
|
|
|
* the amp (see alc861_fixup_asus_amp_vref_0f())
|
|
|
|
*/
|
|
|
|
if (spec->keep_vref_in_automute)
|
|
|
|
val = oldval & ~PIN_HP;
|
|
|
|
else
|
|
|
|
val = 0;
|
|
|
|
if (!mute)
|
|
|
|
val |= oldval;
|
|
|
|
/* here we call update_pin_ctl() so that the pinctl is
|
|
|
|
* changed without changing the pinctl target value;
|
|
|
|
* the original target value will be still referred at
|
|
|
|
* the init / resume again
|
|
|
|
*/
|
|
|
|
update_pin_ctl(codec, nid, val);
|
2013-06-24 14:00:21 +00:00
|
|
|
}
|
|
|
|
|
2012-12-20 14:36:30 +00:00
|
|
|
set_pin_eapd(codec, nid, !mute);
|
2015-03-20 17:21:03 +00:00
|
|
|
if (codec->power_save_node) {
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
bool on = !mute;
|
|
|
|
if (on)
|
2015-04-09 08:21:30 +00:00
|
|
|
on = detect_pin_state(codec, nid);
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
set_path_power(codec, nid, on, -1);
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_update_outputs - Toggle outputs muting
|
|
|
|
* @codec: the HDA codec
|
|
|
|
*
|
|
|
|
* Update the mute status of all outputs based on the current jack states.
|
|
|
|
*/
|
2012-12-19 14:16:44 +00:00
|
|
|
void snd_hda_gen_update_outputs(struct hda_codec *codec)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-08-12 12:44:59 +00:00
|
|
|
int *paths;
|
2012-12-19 11:52:06 +00:00
|
|
|
int on;
|
|
|
|
|
|
|
|
/* Control HP pins/amps depending on master_mute state;
|
|
|
|
* in general, HP pins/amps control should be enabled in all cases,
|
|
|
|
* but currently set only for master_mute, just to be safe
|
|
|
|
*/
|
2013-08-12 12:44:59 +00:00
|
|
|
if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
|
|
|
|
paths = spec->out_paths;
|
|
|
|
else
|
|
|
|
paths = spec->hp_paths;
|
2013-02-19 16:12:42 +00:00
|
|
|
do_automute(codec, ARRAY_SIZE(spec->autocfg.hp_pins),
|
2013-08-12 12:44:59 +00:00
|
|
|
spec->autocfg.hp_pins, paths, spec->master_mute);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
if (!spec->automute_speaker)
|
|
|
|
on = 0;
|
|
|
|
else
|
|
|
|
on = spec->hp_jack_present | spec->line_jack_present;
|
|
|
|
on |= spec->master_mute;
|
2013-01-16 17:18:00 +00:00
|
|
|
spec->speaker_muted = on;
|
2013-08-12 12:44:59 +00:00
|
|
|
if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
|
|
|
|
paths = spec->out_paths;
|
|
|
|
else
|
|
|
|
paths = spec->speaker_paths;
|
2012-12-19 11:52:06 +00:00
|
|
|
do_automute(codec, ARRAY_SIZE(spec->autocfg.speaker_pins),
|
2013-08-12 12:44:59 +00:00
|
|
|
spec->autocfg.speaker_pins, paths, on);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* toggle line-out mutes if needed, too */
|
|
|
|
/* if LO is a copy of either HP or Speaker, don't need to handle it */
|
|
|
|
if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0] ||
|
|
|
|
spec->autocfg.line_out_pins[0] == spec->autocfg.speaker_pins[0])
|
|
|
|
return;
|
|
|
|
if (!spec->automute_lo)
|
|
|
|
on = 0;
|
|
|
|
else
|
|
|
|
on = spec->hp_jack_present;
|
|
|
|
on |= spec->master_mute;
|
2013-01-16 17:18:00 +00:00
|
|
|
spec->line_out_muted = on;
|
2013-08-12 12:44:59 +00:00
|
|
|
paths = spec->out_paths;
|
2012-12-19 11:52:06 +00:00
|
|
|
do_automute(codec, ARRAY_SIZE(spec->autocfg.line_out_pins),
|
2013-08-12 12:44:59 +00:00
|
|
|
spec->autocfg.line_out_pins, paths, on);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_update_outputs);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
static void call_update_outputs(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
if (spec->automute_hook)
|
|
|
|
spec->automute_hook(codec);
|
|
|
|
else
|
2012-12-19 14:16:44 +00:00
|
|
|
snd_hda_gen_update_outputs(codec);
|
2013-06-24 14:00:21 +00:00
|
|
|
|
|
|
|
/* sync the whole vmaster slaves to reflect the new auto-mute status */
|
|
|
|
if (spec->auto_mute_via_amp && !codec->bus->shutdown)
|
|
|
|
snd_ctl_sync_vmaster(spec->vmaster_mute.sw_kctl, false);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_hp_automute - standard HP-automute helper
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @jack: jack object, NULL for the whole
|
|
|
|
*/
|
2014-09-11 13:22:46 +00:00
|
|
|
void snd_hda_gen_hp_automute(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-22 06:46:31 +00:00
|
|
|
hda_nid_t *pins = spec->autocfg.hp_pins;
|
|
|
|
int num_pins = ARRAY_SIZE(spec->autocfg.hp_pins);
|
|
|
|
|
|
|
|
/* No detection for the first HP jack during indep-HP mode */
|
|
|
|
if (spec->indep_hp_enabled) {
|
|
|
|
pins++;
|
|
|
|
num_pins--;
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-22 06:46:31 +00:00
|
|
|
spec->hp_jack_present = detect_jacks(codec, num_pins, pins);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!spec->detect_hp || (!spec->automute_speaker && !spec->automute_lo))
|
|
|
|
return;
|
|
|
|
call_update_outputs(codec);
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_hp_automute);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_line_automute - standard line-out-automute helper
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @jack: jack object, NULL for the whole
|
|
|
|
*/
|
2014-09-11 13:22:46 +00:00
|
|
|
void snd_hda_gen_line_automute(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
|
|
|
|
return;
|
|
|
|
/* check LO jack only when it's different from HP */
|
|
|
|
if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0])
|
|
|
|
return;
|
|
|
|
|
|
|
|
spec->line_jack_present =
|
|
|
|
detect_jacks(codec, ARRAY_SIZE(spec->autocfg.line_out_pins),
|
|
|
|
spec->autocfg.line_out_pins);
|
|
|
|
if (!spec->automute_speaker || !spec->detect_lo)
|
|
|
|
return;
|
|
|
|
call_update_outputs(codec);
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_line_automute);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_mic_autoswitch - standard mic auto-switch helper
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @jack: jack object, NULL for the whole
|
|
|
|
*/
|
2014-09-11 13:22:46 +00:00
|
|
|
void snd_hda_gen_mic_autoswitch(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!spec->auto_mic)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = spec->am_num_entries - 1; i > 0; i--) {
|
2013-01-10 08:45:13 +00:00
|
|
|
hda_nid_t pin = spec->am_entry[i].pin;
|
|
|
|
/* don't detect pins retasked as outputs */
|
|
|
|
if (snd_hda_codec_get_pin_target(codec, pin) & AC_PINCTL_OUT_EN)
|
|
|
|
continue;
|
ALSA: hda - Add snd_hda_jack_detect_state() helper function
snd_hda_jack_detect() function returns a boolean value for a jack
plugged in or not, but it also returns always true when the
corresponding pin is phantom (i.e. fixed). This is OK in most cases,
but it makes the generic parser misbehaving about the auto-mute or
auto-mic switching, e.g. when one of headphone pins is a fixed.
Namely, the driver decides whether to mute the speaker or not, just
depending on the headphone plug state: if one of the headphone jacks
is seen as active, then the speaker is muted. Thus this will result
always in the muted speaker output.
So, the problem is the function returns a boolean, after all, although
we need to think of "phantom" jack. Now a new function,
snd_hda_jack_detect_state() is introduced to return these tristates.
The generic parser uses this function for checking the headphone or
mic jack states.
Meanwhile, the behavior of snd_hda_jack_detect() is kept as is, for
keeping compatibility in other driver codes.
Acked-by: David Henningsson <david.henningsson@canonical.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2013-07-19 14:59:46 +00:00
|
|
|
if (snd_hda_jack_detect_state(codec, pin) == HDA_JACK_PRESENT) {
|
2012-12-19 11:52:06 +00:00
|
|
|
mux_select(codec, 0, spec->am_entry[i].idx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mux_select(codec, 0, spec->am_entry[0].idx);
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_mic_autoswitch);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-05-31 12:10:03 +00:00
|
|
|
/* call appropriate hooks */
|
2014-09-11 13:22:46 +00:00
|
|
|
static void call_hp_automute(struct hda_codec *codec,
|
|
|
|
struct hda_jack_callback *jack)
|
2013-05-31 12:10:03 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
if (spec->hp_automute_hook)
|
|
|
|
spec->hp_automute_hook(codec, jack);
|
|
|
|
else
|
|
|
|
snd_hda_gen_hp_automute(codec, jack);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void call_line_automute(struct hda_codec *codec,
|
2014-09-11 13:22:46 +00:00
|
|
|
struct hda_jack_callback *jack)
|
2013-05-31 12:10:03 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
if (spec->line_automute_hook)
|
|
|
|
spec->line_automute_hook(codec, jack);
|
|
|
|
else
|
|
|
|
snd_hda_gen_line_automute(codec, jack);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void call_mic_autoswitch(struct hda_codec *codec,
|
2014-09-11 13:22:46 +00:00
|
|
|
struct hda_jack_callback *jack)
|
2013-05-31 12:10:03 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
if (spec->mic_autoswitch_hook)
|
|
|
|
spec->mic_autoswitch_hook(codec, jack);
|
|
|
|
else
|
|
|
|
snd_hda_gen_mic_autoswitch(codec, jack);
|
|
|
|
}
|
|
|
|
|
2013-05-31 13:20:31 +00:00
|
|
|
/* update jack retasking */
|
|
|
|
static void update_automute_all(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
call_hp_automute(codec, NULL);
|
|
|
|
call_line_automute(codec, NULL);
|
|
|
|
call_mic_autoswitch(codec, NULL);
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* Auto-Mute mode mixer enum support
|
|
|
|
*/
|
|
|
|
static int automute_mode_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
static const char * const texts3[] = {
|
|
|
|
"Disabled", "Speaker Only", "Line Out+Speaker"
|
|
|
|
};
|
|
|
|
|
|
|
|
if (spec->automute_speaker_possible && spec->automute_lo_possible)
|
|
|
|
return snd_hda_enum_helper_info(kcontrol, uinfo, 3, texts3);
|
|
|
|
return snd_hda_enum_bool_helper_info(kcontrol, uinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int automute_mode_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
unsigned int val = 0;
|
|
|
|
if (spec->automute_speaker)
|
|
|
|
val++;
|
|
|
|
if (spec->automute_lo)
|
|
|
|
val++;
|
|
|
|
|
|
|
|
ucontrol->value.enumerated.item[0] = val;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int automute_mode_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
switch (ucontrol->value.enumerated.item[0]) {
|
|
|
|
case 0:
|
|
|
|
if (!spec->automute_speaker && !spec->automute_lo)
|
|
|
|
return 0;
|
|
|
|
spec->automute_speaker = 0;
|
|
|
|
spec->automute_lo = 0;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (spec->automute_speaker_possible) {
|
|
|
|
if (!spec->automute_lo && spec->automute_speaker)
|
|
|
|
return 0;
|
|
|
|
spec->automute_speaker = 1;
|
|
|
|
spec->automute_lo = 0;
|
|
|
|
} else if (spec->automute_lo_possible) {
|
|
|
|
if (spec->automute_lo)
|
|
|
|
return 0;
|
|
|
|
spec->automute_lo = 1;
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
if (!spec->automute_lo_possible || !spec->automute_speaker_possible)
|
|
|
|
return -EINVAL;
|
|
|
|
if (spec->automute_speaker && spec->automute_lo)
|
|
|
|
return 0;
|
|
|
|
spec->automute_speaker = 1;
|
|
|
|
spec->automute_lo = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
call_update_outputs(codec);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new automute_mode_enum = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Auto-Mute Mode",
|
|
|
|
.info = automute_mode_info,
|
|
|
|
.get = automute_mode_get,
|
|
|
|
.put = automute_mode_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int add_automute_mode_enum(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
2012-12-19 13:38:33 +00:00
|
|
|
if (!snd_hda_gen_add_kctl(spec, NULL, &automute_mode_enum))
|
2012-12-19 11:52:06 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check the availability of HP/line-out auto-mute;
|
|
|
|
* Set up appropriately if really supported
|
|
|
|
*/
|
|
|
|
static int check_auto_mute_availability(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
int present = 0;
|
|
|
|
int i, err;
|
|
|
|
|
2013-01-16 17:20:07 +00:00
|
|
|
if (spec->suppress_auto_mute)
|
|
|
|
return 0;
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (cfg->hp_pins[0])
|
|
|
|
present++;
|
|
|
|
if (cfg->line_out_pins[0])
|
|
|
|
present++;
|
|
|
|
if (cfg->speaker_pins[0])
|
|
|
|
present++;
|
|
|
|
if (present < 2) /* need two different output types */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!cfg->speaker_pins[0] &&
|
|
|
|
cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
|
|
|
|
memcpy(cfg->speaker_pins, cfg->line_out_pins,
|
|
|
|
sizeof(cfg->speaker_pins));
|
|
|
|
cfg->speaker_outs = cfg->line_outs;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cfg->hp_pins[0] &&
|
|
|
|
cfg->line_out_type == AUTO_PIN_HP_OUT) {
|
|
|
|
memcpy(cfg->hp_pins, cfg->line_out_pins,
|
|
|
|
sizeof(cfg->hp_pins));
|
|
|
|
cfg->hp_outs = cfg->line_outs;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < cfg->hp_outs; i++) {
|
|
|
|
hda_nid_t nid = cfg->hp_pins[i];
|
|
|
|
if (!is_jack_detectable(codec, nid))
|
|
|
|
continue;
|
2014-02-25 11:21:03 +00:00
|
|
|
codec_dbg(codec, "Enable HP auto-muting on NID 0x%x\n", nid);
|
2014-09-11 12:06:53 +00:00
|
|
|
snd_hda_jack_detect_enable_callback(codec, nid,
|
2013-05-31 12:10:03 +00:00
|
|
|
call_hp_automute);
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->detect_hp = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg->line_out_type == AUTO_PIN_LINE_OUT && cfg->line_outs) {
|
|
|
|
if (cfg->speaker_outs)
|
|
|
|
for (i = 0; i < cfg->line_outs; i++) {
|
|
|
|
hda_nid_t nid = cfg->line_out_pins[i];
|
|
|
|
if (!is_jack_detectable(codec, nid))
|
|
|
|
continue;
|
2014-02-25 11:21:03 +00:00
|
|
|
codec_dbg(codec, "Enable Line-Out auto-muting on NID 0x%x\n", nid);
|
2012-12-19 11:52:06 +00:00
|
|
|
snd_hda_jack_detect_enable_callback(codec, nid,
|
2013-05-31 12:10:03 +00:00
|
|
|
call_line_automute);
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->detect_lo = 1;
|
|
|
|
}
|
|
|
|
spec->automute_lo_possible = spec->detect_hp;
|
|
|
|
}
|
|
|
|
|
|
|
|
spec->automute_speaker_possible = cfg->speaker_outs &&
|
|
|
|
(spec->detect_hp || spec->detect_lo);
|
|
|
|
|
|
|
|
spec->automute_lo = spec->automute_lo_possible;
|
|
|
|
spec->automute_speaker = spec->automute_speaker_possible;
|
|
|
|
|
|
|
|
if (spec->automute_speaker_possible || spec->automute_lo_possible) {
|
|
|
|
/* create a control for automute mode */
|
|
|
|
err = add_automute_mode_enum(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check whether all auto-mic pins are valid; setup indices if OK */
|
|
|
|
static bool auto_mic_check_imux(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
const struct hda_input_mux *imux;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
imux = &spec->input_mux;
|
|
|
|
for (i = 0; i < spec->am_num_entries; i++) {
|
|
|
|
spec->am_entry[i].idx =
|
|
|
|
find_idx_in_nid_list(spec->am_entry[i].pin,
|
|
|
|
spec->imux_pins, imux->num_items);
|
|
|
|
if (spec->am_entry[i].idx < 0)
|
|
|
|
return false; /* no corresponding imux */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we don't need the jack detection for the first pin */
|
|
|
|
for (i = 1; i < spec->am_num_entries; i++)
|
|
|
|
snd_hda_jack_detect_enable_callback(codec,
|
|
|
|
spec->am_entry[i].pin,
|
2013-05-31 12:10:03 +00:00
|
|
|
call_mic_autoswitch);
|
2012-12-19 11:52:06 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compare_attr(const void *ap, const void *bp)
|
|
|
|
{
|
|
|
|
const struct automic_entry *a = ap;
|
|
|
|
const struct automic_entry *b = bp;
|
|
|
|
return (int)(a->attr - b->attr);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* Check the availability of auto-mic switch;
|
|
|
|
* Set up if really supported
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
static int check_auto_mic_availability(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
unsigned int types;
|
|
|
|
int i, num_pins;
|
|
|
|
|
2013-01-07 15:32:11 +00:00
|
|
|
if (spec->suppress_auto_mic)
|
|
|
|
return 0;
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
types = 0;
|
|
|
|
num_pins = 0;
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
hda_nid_t nid = cfg->inputs[i].pin;
|
|
|
|
unsigned int attr;
|
|
|
|
attr = snd_hda_codec_get_pincfg(codec, nid);
|
|
|
|
attr = snd_hda_get_input_pin_attr(attr);
|
|
|
|
if (types & (1 << attr))
|
|
|
|
return 0; /* already occupied */
|
|
|
|
switch (attr) {
|
|
|
|
case INPUT_PIN_ATTR_INT:
|
|
|
|
if (cfg->inputs[i].type != AUTO_PIN_MIC)
|
|
|
|
return 0; /* invalid type */
|
|
|
|
break;
|
|
|
|
case INPUT_PIN_ATTR_UNUSED:
|
|
|
|
return 0; /* invalid entry */
|
|
|
|
default:
|
|
|
|
if (cfg->inputs[i].type > AUTO_PIN_LINE_IN)
|
|
|
|
return 0; /* invalid type */
|
|
|
|
if (!spec->line_in_auto_switch &&
|
|
|
|
cfg->inputs[i].type != AUTO_PIN_MIC)
|
|
|
|
return 0; /* only mic is allowed */
|
|
|
|
if (!is_jack_detectable(codec, nid))
|
|
|
|
return 0; /* no unsol support */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (num_pins >= MAX_AUTO_MIC_PINS)
|
|
|
|
return 0;
|
|
|
|
types |= (1 << attr);
|
|
|
|
spec->am_entry[num_pins].pin = nid;
|
|
|
|
spec->am_entry[num_pins].attr = attr;
|
|
|
|
num_pins++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num_pins < 2)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
spec->am_num_entries = num_pins;
|
|
|
|
/* sort the am_entry in the order of attr so that the pin with a
|
|
|
|
* higher attr will be selected when the jack is plugged.
|
|
|
|
*/
|
|
|
|
sort(spec->am_entry, num_pins, sizeof(spec->am_entry[0]),
|
|
|
|
compare_attr, NULL);
|
|
|
|
|
|
|
|
if (!auto_mic_check_imux(codec))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
spec->auto_mic = 1;
|
|
|
|
spec->num_adc_nids = 1;
|
|
|
|
spec->cur_mux[0] = spec->am_entry[0].idx;
|
2014-02-25 11:21:03 +00:00
|
|
|
codec_dbg(codec, "Enable auto-mic switch on NID 0x%x/0x%x/0x%x\n",
|
2012-12-19 11:52:06 +00:00
|
|
|
spec->am_entry[0].pin,
|
|
|
|
spec->am_entry[1].pin,
|
|
|
|
spec->am_entry[2].pin);
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_path_power_filter - power_filter hook to make inactive widgets
|
|
|
|
* into power down
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @nid: NID to evalute
|
|
|
|
* @power_state: target power state
|
|
|
|
*/
|
2014-01-13 15:09:57 +00:00
|
|
|
unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec,
|
2013-01-24 16:32:56 +00:00
|
|
|
hda_nid_t nid,
|
|
|
|
unsigned int power_state)
|
|
|
|
{
|
2015-04-09 08:25:03 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (!spec->power_down_unused && !codec->power_save_node)
|
|
|
|
return power_state;
|
2015-03-03 09:07:24 +00:00
|
|
|
if (power_state != AC_PWRST_D0 || nid == codec->core.afg)
|
2013-01-24 16:32:56 +00:00
|
|
|
return power_state;
|
|
|
|
if (get_wcaps_type(get_wcaps(codec, nid)) >= AC_WID_POWER)
|
|
|
|
return power_state;
|
2013-05-14 10:58:47 +00:00
|
|
|
if (is_active_nid_for_any(codec, nid))
|
2013-01-24 16:32:56 +00:00
|
|
|
return power_state;
|
|
|
|
return AC_PWRST_D3;
|
|
|
|
}
|
2014-01-13 15:09:57 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_path_power_filter);
|
2013-01-24 16:32:56 +00:00
|
|
|
|
2013-12-10 16:33:49 +00:00
|
|
|
/* mute all aamix inputs initially; parse up to the first leaves */
|
|
|
|
static void mute_all_mixer_nid(struct hda_codec *codec, hda_nid_t mix)
|
|
|
|
{
|
|
|
|
int i, nums;
|
|
|
|
const hda_nid_t *conn;
|
|
|
|
bool has_amp;
|
|
|
|
|
|
|
|
nums = snd_hda_get_conn_list(codec, mix, &conn);
|
|
|
|
has_amp = nid_has_mute(codec, mix, HDA_INPUT);
|
|
|
|
for (i = 0; i < nums; i++) {
|
|
|
|
if (has_amp)
|
2015-03-12 07:30:11 +00:00
|
|
|
update_amp(codec, mix, HDA_INPUT, i,
|
|
|
|
0xff, HDA_AMP_MUTE);
|
2013-12-10 16:33:49 +00:00
|
|
|
else if (nid_has_volume(codec, conn[i], HDA_OUTPUT))
|
2015-03-12 07:30:11 +00:00
|
|
|
update_amp(codec, conn[i], HDA_OUTPUT, 0,
|
|
|
|
0xff, HDA_AMP_MUTE);
|
2013-12-10 16:33:49 +00:00
|
|
|
}
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_stream_pm - Stream power management callback
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @nid: audio widget
|
|
|
|
* @on: power on/off flag
|
|
|
|
*
|
2015-03-20 17:21:03 +00:00
|
|
|
* Set this in patch_ops.stream_pm. Only valid with power_save_node flag.
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
*/
|
|
|
|
void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on)
|
|
|
|
{
|
2015-03-20 17:21:03 +00:00
|
|
|
if (codec->power_save_node)
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
set_path_power(codec, nid, -1, on);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_stream_pm);
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_parse_auto_config - Parse the given BIOS configuration and
|
|
|
|
* set up the hda_gen_spec
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @cfg: Parsed pin configuration
|
2012-12-19 13:41:21 +00:00
|
|
|
*
|
|
|
|
* return 1 if successful, 0 if the proper config is not found,
|
2012-12-19 11:52:06 +00:00
|
|
|
* or a negative error code
|
|
|
|
*/
|
|
|
|
int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
|
2012-12-19 13:41:21 +00:00
|
|
|
struct auto_pin_cfg *cfg)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int err;
|
|
|
|
|
2013-01-11 16:48:22 +00:00
|
|
|
parse_user_hints(codec);
|
|
|
|
|
2013-01-23 16:00:31 +00:00
|
|
|
if (spec->mixer_nid && !spec->mixer_merge_nid)
|
|
|
|
spec->mixer_merge_nid = spec->mixer_nid;
|
|
|
|
|
2012-12-19 13:41:21 +00:00
|
|
|
if (cfg != &spec->autocfg) {
|
|
|
|
spec->autocfg = *cfg;
|
|
|
|
cfg = &spec->autocfg;
|
|
|
|
}
|
|
|
|
|
2013-03-22 13:53:50 +00:00
|
|
|
if (!spec->main_out_badness)
|
|
|
|
spec->main_out_badness = &hda_main_out_badness;
|
|
|
|
if (!spec->extra_out_badness)
|
|
|
|
spec->extra_out_badness = &hda_extra_out_badness;
|
|
|
|
|
2013-01-16 14:58:45 +00:00
|
|
|
fill_all_dac_nids(codec);
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!cfg->line_outs) {
|
|
|
|
if (cfg->dig_outs || cfg->dig_in_pin) {
|
|
|
|
spec->multiout.max_channels = 2;
|
|
|
|
spec->no_analog = 1;
|
|
|
|
goto dig_only;
|
|
|
|
}
|
2013-12-06 08:30:14 +00:00
|
|
|
if (!cfg->num_inputs && !cfg->dig_in_pin)
|
|
|
|
return 0; /* can't find valid BIOS pin config */
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!spec->no_primary_hp &&
|
|
|
|
cfg->line_out_type == AUTO_PIN_SPEAKER_OUT &&
|
|
|
|
cfg->line_outs <= cfg->hp_outs) {
|
|
|
|
/* use HP as primary out */
|
|
|
|
cfg->speaker_outs = cfg->line_outs;
|
|
|
|
memcpy(cfg->speaker_pins, cfg->line_out_pins,
|
|
|
|
sizeof(cfg->speaker_pins));
|
|
|
|
cfg->line_outs = cfg->hp_outs;
|
|
|
|
memcpy(cfg->line_out_pins, cfg->hp_pins, sizeof(cfg->hp_pins));
|
|
|
|
cfg->hp_outs = 0;
|
|
|
|
memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
|
|
|
|
cfg->line_out_type = AUTO_PIN_HP_OUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = parse_output_paths(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = create_multi_channel_mode(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = create_multi_out_ctls(codec, cfg);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = create_hp_out_ctls(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = create_speaker_out_ctls(codec);
|
2012-12-21 13:09:42 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = create_indep_hp_ctls(codec);
|
2013-01-04 15:42:48 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = create_loopback_mixing_ctl(codec);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2013-02-19 16:12:42 +00:00
|
|
|
err = create_hp_mic(codec);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
err = create_input_ctls(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
/* add power-down pin callbacks at first */
|
|
|
|
add_all_pin_power_ctls(codec, false);
|
|
|
|
|
2013-01-07 15:44:06 +00:00
|
|
|
spec->const_channel_count = spec->ext_channel_count;
|
|
|
|
/* check the multiple speaker and headphone pins */
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
|
|
|
|
spec->const_channel_count = max(spec->const_channel_count,
|
|
|
|
cfg->speaker_outs * 2);
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT)
|
|
|
|
spec->const_channel_count = max(spec->const_channel_count,
|
|
|
|
cfg->hp_outs * 2);
|
|
|
|
spec->multiout.max_channels = max(spec->ext_channel_count,
|
|
|
|
spec->const_channel_count);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
err = check_auto_mute_availability(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = check_dyn_adc_switch(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2013-02-19 16:12:42 +00:00
|
|
|
err = check_auto_mic_availability(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-12-09 15:02:24 +00:00
|
|
|
/* add stereo mix if available and not enabled yet */
|
|
|
|
if (!spec->auto_mic && spec->mixer_nid &&
|
2014-12-15 12:43:59 +00:00
|
|
|
spec->add_stereo_mix_input == HDA_HINT_STEREO_MIX_AUTO &&
|
|
|
|
spec->input_mux.num_items > 1) {
|
2013-12-09 15:02:24 +00:00
|
|
|
err = parse_capture_source(codec, spec->mixer_nid,
|
|
|
|
CFG_IDX_MIX, spec->num_all_adcs,
|
|
|
|
"Stereo Mix", 0);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
err = create_capture_mixers(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2006-09-06 12:03:14 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
err = parse_mic_boost(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2013-11-26 07:33:45 +00:00
|
|
|
/* create "Headphone Mic Jack Mode" if no input selection is
|
|
|
|
* available (or user specifies add_jack_modes hint)
|
|
|
|
*/
|
|
|
|
if (spec->hp_mic_pin &&
|
|
|
|
(spec->auto_mic || spec->input_mux.num_items == 1 ||
|
|
|
|
spec->add_jack_modes)) {
|
|
|
|
err = create_hp_mic_jack_mode(codec, spec->hp_mic_pin);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2013-03-07 17:32:59 +00:00
|
|
|
if (spec->add_jack_modes) {
|
2013-01-10 15:57:58 +00:00
|
|
|
if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
|
|
|
|
err = create_out_jack_modes(codec, cfg->line_outs,
|
|
|
|
cfg->line_out_pins);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
|
|
|
|
err = create_out_jack_modes(codec, cfg->hp_outs,
|
|
|
|
cfg->hp_pins);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
/* add power-up pin callbacks at last */
|
|
|
|
add_all_pin_power_ctls(codec, true);
|
|
|
|
|
2013-12-10 16:33:49 +00:00
|
|
|
/* mute all aamix input initially */
|
|
|
|
if (spec->mixer_nid)
|
|
|
|
mute_all_mixer_nid(codec, spec->mixer_nid);
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
dig_only:
|
|
|
|
parse_digital(codec);
|
|
|
|
|
2015-05-27 06:37:19 +00:00
|
|
|
if (spec->power_down_unused || codec->power_save_node) {
|
2015-04-09 08:28:15 +00:00
|
|
|
if (!codec->power_filter)
|
|
|
|
codec->power_filter = snd_hda_gen_path_power_filter;
|
2015-05-27 06:37:19 +00:00
|
|
|
if (!codec->patch_ops.stream_pm)
|
|
|
|
codec->patch_ops.stream_pm = snd_hda_gen_stream_pm;
|
|
|
|
}
|
2013-01-24 16:32:56 +00:00
|
|
|
|
2013-03-18 10:25:51 +00:00
|
|
|
if (!spec->no_analog && spec->beep_nid) {
|
|
|
|
err = snd_hda_attach_beep_device(codec, spec->beep_nid);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2015-03-20 17:21:03 +00:00
|
|
|
if (codec->beep && codec->power_save_node) {
|
2015-03-18 08:23:10 +00:00
|
|
|
err = add_fake_beep_paths(codec);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
codec->beep->power_hook = beep_power_hook;
|
|
|
|
}
|
2013-03-18 10:25:51 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return 1;
|
2006-09-06 12:03:14 +00:00
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_parse_auto_config);
|
2006-09-06 12:03:14 +00:00
|
|
|
|
2006-08-23 16:34:06 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* Build control elements
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* slave controls for virtual master */
|
|
|
|
static const char * const slave_pfxs[] = {
|
|
|
|
"Front", "Surround", "Center", "LFE", "Side",
|
|
|
|
"Headphone", "Speaker", "Mono", "Line Out",
|
|
|
|
"CLFE", "Bass Speaker", "PCM",
|
2013-01-07 08:57:42 +00:00
|
|
|
"Speaker Front", "Speaker Surround", "Speaker CLFE", "Speaker Side",
|
|
|
|
"Headphone Front", "Headphone Surround", "Headphone CLFE",
|
2014-10-16 13:33:45 +00:00
|
|
|
"Headphone Side", "Headphone+LO", "Speaker+LO",
|
2012-12-19 11:52:06 +00:00
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_build_controls - Build controls from the parsed results
|
|
|
|
* @codec: the HDA codec
|
|
|
|
*
|
|
|
|
* Pass this to build_controls patch_ops.
|
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
int snd_hda_gen_build_controls(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int err;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 14:15:10 +00:00
|
|
|
if (spec->kctls.used) {
|
|
|
|
err = snd_hda_add_new_ctls(codec, spec->kctls.list);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
2006-08-23 16:34:06 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->multiout.dig_out_nid) {
|
|
|
|
err = snd_hda_create_dig_out_ctls(codec,
|
|
|
|
spec->multiout.dig_out_nid,
|
|
|
|
spec->multiout.dig_out_nid,
|
2015-02-27 16:43:19 +00:00
|
|
|
spec->pcm_rec[1]->pcm_type);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
if (!spec->no_analog) {
|
|
|
|
err = snd_hda_create_spdif_share_sw(codec,
|
|
|
|
&spec->multiout);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
spec->multiout.share_spdif = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (spec->dig_in_nid) {
|
|
|
|
err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
|
2006-08-23 16:34:06 +00:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* if we have no master control, let's create it */
|
2017-04-10 15:37:34 +00:00
|
|
|
if (!spec->no_analog && !spec->suppress_vmaster &&
|
2012-12-19 11:52:06 +00:00
|
|
|
!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
|
|
|
|
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
|
2013-01-17 09:25:15 +00:00
|
|
|
spec->vmaster_tlv, slave_pfxs,
|
2012-12-19 11:52:06 +00:00
|
|
|
"Playback Volume");
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
2017-04-10 15:37:34 +00:00
|
|
|
if (!spec->no_analog && !spec->suppress_vmaster &&
|
2012-12-19 11:52:06 +00:00
|
|
|
!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
|
|
|
|
err = __snd_hda_add_vmaster(codec, "Master Playback Switch",
|
|
|
|
NULL, slave_pfxs,
|
|
|
|
"Playback Switch",
|
|
|
|
true, &spec->vmaster_mute.sw_kctl);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2013-10-25 21:43:10 +00:00
|
|
|
if (spec->vmaster_mute.hook) {
|
2012-12-20 13:57:18 +00:00
|
|
|
snd_hda_add_vmaster_hook(codec, &spec->vmaster_mute,
|
|
|
|
spec->vmaster_mute_enum);
|
2013-10-25 21:43:10 +00:00
|
|
|
snd_hda_sync_vmaster_hook(&spec->vmaster_mute);
|
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2006-08-23 16:34:06 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
free_kctls(spec); /* no longer needed */
|
2006-08-23 16:34:06 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
err = snd_hda_jack_add_kctls(codec, &spec->autocfg);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_build_controls);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* PCM definitions
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
|
2013-01-07 10:54:34 +00:00
|
|
|
static void call_pcm_playback_hook(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream,
|
|
|
|
int action)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
if (spec->pcm_playback_hook)
|
|
|
|
spec->pcm_playback_hook(hinfo, codec, substream, action);
|
|
|
|
}
|
|
|
|
|
2013-01-17 14:57:10 +00:00
|
|
|
static void call_pcm_capture_hook(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream,
|
|
|
|
int action)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
if (spec->pcm_capture_hook)
|
|
|
|
spec->pcm_capture_hook(hinfo, codec, substream, action);
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/*
|
|
|
|
* Analog playback callbacks
|
|
|
|
*/
|
|
|
|
static int playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2012-12-21 13:09:42 +00:00
|
|
|
int err;
|
|
|
|
|
|
|
|
mutex_lock(&spec->pcm_mutex);
|
|
|
|
err = snd_hda_multi_out_analog_open(codec,
|
|
|
|
&spec->multiout, substream,
|
2012-12-19 11:52:06 +00:00
|
|
|
hinfo);
|
2013-01-07 10:54:34 +00:00
|
|
|
if (!err) {
|
2012-12-21 13:09:42 +00:00
|
|
|
spec->active_streams |= 1 << STREAM_MULTI_OUT;
|
2013-01-07 10:54:34 +00:00
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_OPEN);
|
|
|
|
}
|
2012-12-21 13:09:42 +00:00
|
|
|
mutex_unlock(&spec->pcm_mutex);
|
|
|
|
return err;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-07 10:54:34 +00:00
|
|
|
int err;
|
|
|
|
|
|
|
|
err = snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
|
|
|
|
stream_tag, format, substream);
|
|
|
|
if (!err)
|
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_PREPARE);
|
|
|
|
return err;
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2013-01-07 10:54:34 +00:00
|
|
|
int err;
|
|
|
|
|
|
|
|
err = snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
|
|
|
|
if (!err)
|
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_CLEANUP);
|
|
|
|
return err;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-21 13:09:42 +00:00
|
|
|
static int playback_pcm_close(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
mutex_lock(&spec->pcm_mutex);
|
|
|
|
spec->active_streams &= ~(1 << STREAM_MULTI_OUT);
|
2013-01-07 10:54:34 +00:00
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_CLOSE);
|
2012-12-21 13:09:42 +00:00
|
|
|
mutex_unlock(&spec->pcm_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-17 14:57:10 +00:00
|
|
|
static int capture_pcm_open(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_OPEN);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int capture_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
|
|
|
|
call_pcm_capture_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_PREPARE);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
snd_hda_codec_cleanup_stream(codec, hinfo->nid);
|
|
|
|
call_pcm_capture_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_CLEANUP);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int capture_pcm_close(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLOSE);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-21 13:09:42 +00:00
|
|
|
static int alt_playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
mutex_lock(&spec->pcm_mutex);
|
2015-07-08 07:22:10 +00:00
|
|
|
if (spec->indep_hp && !spec->indep_hp_enabled)
|
2012-12-21 13:09:42 +00:00
|
|
|
err = -EBUSY;
|
|
|
|
else
|
|
|
|
spec->active_streams |= 1 << STREAM_INDEP_HP;
|
2013-01-07 10:54:34 +00:00
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_OPEN);
|
2012-12-21 13:09:42 +00:00
|
|
|
mutex_unlock(&spec->pcm_mutex);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int alt_playback_pcm_close(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
mutex_lock(&spec->pcm_mutex);
|
|
|
|
spec->active_streams &= ~(1 << STREAM_INDEP_HP);
|
2013-01-07 10:54:34 +00:00
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_CLOSE);
|
2012-12-21 13:09:42 +00:00
|
|
|
mutex_unlock(&spec->pcm_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-07 10:54:34 +00:00
|
|
|
static int alt_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
|
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_PREPARE);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int alt_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
snd_hda_codec_cleanup_stream(codec, hinfo->nid);
|
|
|
|
call_pcm_playback_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_CLEANUP);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* Digital out
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
static int dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return snd_hda_multi_out_dig_open(codec, &spec->multiout);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
|
|
|
|
stream_tag, format, substream);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
return snd_hda_multi_out_dig_close(codec, &spec->multiout);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-12-19 11:52:06 +00:00
|
|
|
* Analog capture
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2013-01-17 14:57:10 +00:00
|
|
|
#define alt_capture_pcm_open capture_pcm_open
|
|
|
|
#define alt_capture_pcm_close capture_pcm_close
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int alt_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number + 1],
|
|
|
|
stream_tag, 0, format);
|
2013-01-17 14:57:10 +00:00
|
|
|
call_pcm_capture_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_PREPARE);
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int alt_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
snd_hda_codec_cleanup_stream(codec,
|
|
|
|
spec->adc_nids[substream->number + 1]);
|
2013-01-17 14:57:10 +00:00
|
|
|
call_pcm_capture_hook(hinfo, codec, substream,
|
|
|
|
HDA_GEN_PCM_ACT_CLEANUP);
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
static const struct hda_pcm_stream pcm_analog_playback = {
|
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 8,
|
|
|
|
/* NID is set in build_pcms */
|
|
|
|
.ops = {
|
|
|
|
.open = playback_pcm_open,
|
2012-12-21 13:09:42 +00:00
|
|
|
.close = playback_pcm_close,
|
2012-12-19 11:52:06 +00:00
|
|
|
.prepare = playback_pcm_prepare,
|
|
|
|
.cleanup = playback_pcm_cleanup
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct hda_pcm_stream pcm_analog_capture = {
|
2005-04-16 22:20:36 +00:00
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
2012-12-19 11:52:06 +00:00
|
|
|
/* NID is set in build_pcms */
|
2013-01-17 14:57:10 +00:00
|
|
|
.ops = {
|
|
|
|
.open = capture_pcm_open,
|
|
|
|
.close = capture_pcm_close,
|
|
|
|
.prepare = capture_pcm_prepare,
|
|
|
|
.cleanup = capture_pcm_cleanup
|
|
|
|
},
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static const struct hda_pcm_stream pcm_analog_alt_playback = {
|
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
/* NID is set in build_pcms */
|
2012-12-21 13:09:42 +00:00
|
|
|
.ops = {
|
|
|
|
.open = alt_playback_pcm_open,
|
2013-01-07 10:54:34 +00:00
|
|
|
.close = alt_playback_pcm_close,
|
|
|
|
.prepare = alt_playback_pcm_prepare,
|
|
|
|
.cleanup = alt_playback_pcm_cleanup
|
2012-12-21 13:09:42 +00:00
|
|
|
},
|
2012-12-19 11:52:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct hda_pcm_stream pcm_analog_alt_capture = {
|
|
|
|
.substreams = 2, /* can be overridden */
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
/* NID is set in build_pcms */
|
|
|
|
.ops = {
|
2013-01-17 14:57:10 +00:00
|
|
|
.open = alt_capture_pcm_open,
|
|
|
|
.close = alt_capture_pcm_close,
|
2012-12-19 11:52:06 +00:00
|
|
|
.prepare = alt_capture_pcm_prepare,
|
|
|
|
.cleanup = alt_capture_pcm_cleanup
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct hda_pcm_stream pcm_digital_playback = {
|
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
/* NID is set in build_pcms */
|
|
|
|
.ops = {
|
|
|
|
.open = dig_playback_pcm_open,
|
|
|
|
.close = dig_playback_pcm_close,
|
|
|
|
.prepare = dig_playback_pcm_prepare,
|
|
|
|
.cleanup = dig_playback_pcm_cleanup
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct hda_pcm_stream pcm_digital_capture = {
|
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
/* NID is set in build_pcms */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Used by build_pcms to flag that a PCM has no playback stream */
|
|
|
|
static const struct hda_pcm_stream pcm_null_stream = {
|
|
|
|
.substreams = 0,
|
|
|
|
.channels_min = 0,
|
|
|
|
.channels_max = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* dynamic changing ADC PCM streams
|
|
|
|
*/
|
|
|
|
static bool dyn_adc_pcm_resetup(struct hda_codec *codec, int cur)
|
2006-03-21 10:29:07 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
hda_nid_t new_adc = spec->adc_nids[spec->dyn_adc_idx[cur]];
|
|
|
|
|
|
|
|
if (spec->cur_adc && spec->cur_adc != new_adc) {
|
|
|
|
/* stream is running, let's swap the current ADC */
|
|
|
|
__snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1);
|
|
|
|
spec->cur_adc = new_adc;
|
|
|
|
snd_hda_codec_setup_stream(codec, new_adc,
|
|
|
|
spec->cur_adc_stream_tag, 0,
|
|
|
|
spec->cur_adc_format);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2006-03-21 10:29:07 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* analog capture with dynamic dual-adc changes */
|
|
|
|
static int dyn_adc_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
unsigned int stream_tag,
|
|
|
|
unsigned int format,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
spec->cur_adc = spec->adc_nids[spec->dyn_adc_idx[spec->cur_mux[0]]];
|
|
|
|
spec->cur_adc_stream_tag = stream_tag;
|
|
|
|
spec->cur_adc_format = format;
|
|
|
|
snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
|
2016-04-12 13:16:24 +00:00
|
|
|
call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_PREPARE);
|
2006-03-21 10:29:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static int dyn_adc_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
|
|
|
struct hda_codec *codec,
|
|
|
|
struct snd_pcm_substream *substream)
|
2006-03-21 10:29:07 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
|
|
|
|
spec->cur_adc = 0;
|
2016-04-12 13:16:24 +00:00
|
|
|
call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLEANUP);
|
2006-03-21 10:29:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static const struct hda_pcm_stream dyn_adc_pcm_analog_capture = {
|
|
|
|
.substreams = 1,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
.nid = 0, /* fill later */
|
|
|
|
.ops = {
|
|
|
|
.prepare = dyn_adc_capture_pcm_prepare,
|
|
|
|
.cleanup = dyn_adc_capture_pcm_cleanup
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2012-12-20 15:58:39 +00:00
|
|
|
static void fill_pcm_stream_name(char *str, size_t len, const char *sfx,
|
|
|
|
const char *chip_name)
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
if (*str)
|
|
|
|
return;
|
|
|
|
strlcpy(str, chip_name, len);
|
|
|
|
|
|
|
|
/* drop non-alnum chars after a space */
|
|
|
|
for (p = strchr(str, ' '); p; p = strchr(p + 1, ' ')) {
|
|
|
|
if (!isalnum(p[1])) {
|
|
|
|
*p = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
strlcat(str, sfx, len);
|
|
|
|
}
|
|
|
|
|
2015-03-16 22:34:34 +00:00
|
|
|
/* copy PCM stream info from @default_str, and override non-NULL entries
|
|
|
|
* from @spec_str and @nid
|
|
|
|
*/
|
|
|
|
static void setup_pcm_stream(struct hda_pcm_stream *str,
|
|
|
|
const struct hda_pcm_stream *default_str,
|
|
|
|
const struct hda_pcm_stream *spec_str,
|
|
|
|
hda_nid_t nid)
|
|
|
|
{
|
|
|
|
*str = *default_str;
|
|
|
|
if (nid)
|
|
|
|
str->nid = nid;
|
|
|
|
if (spec_str) {
|
|
|
|
if (spec_str->substreams)
|
|
|
|
str->substreams = spec_str->substreams;
|
|
|
|
if (spec_str->channels_min)
|
|
|
|
str->channels_min = spec_str->channels_min;
|
|
|
|
if (spec_str->channels_max)
|
|
|
|
str->channels_max = spec_str->channels_max;
|
|
|
|
if (spec_str->rates)
|
|
|
|
str->rates = spec_str->rates;
|
|
|
|
if (spec_str->formats)
|
|
|
|
str->formats = spec_str->formats;
|
|
|
|
if (spec_str->maxbps)
|
|
|
|
str->maxbps = spec_str->maxbps;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_build_pcms - build PCM streams based on the parsed results
|
|
|
|
* @codec: the HDA codec
|
|
|
|
*
|
|
|
|
* Pass this to build_pcms patch_ops.
|
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
int snd_hda_gen_build_pcms(struct hda_codec *codec)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2015-02-27 16:43:19 +00:00
|
|
|
struct hda_pcm *info;
|
2012-12-19 11:52:06 +00:00
|
|
|
bool have_multi_adcs;
|
|
|
|
|
|
|
|
if (spec->no_analog)
|
|
|
|
goto skip_analog;
|
|
|
|
|
2012-12-20 15:58:39 +00:00
|
|
|
fill_pcm_stream_name(spec->stream_name_analog,
|
|
|
|
sizeof(spec->stream_name_analog),
|
2015-03-03 09:07:24 +00:00
|
|
|
" Analog", codec->core.chip_name);
|
2015-02-27 16:43:19 +00:00
|
|
|
info = snd_hda_codec_pcm_new(codec, "%s", spec->stream_name_analog);
|
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
|
|
|
spec->pcm_rec[0] = info;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
if (spec->multiout.num_dacs > 0) {
|
2015-03-16 22:34:34 +00:00
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
|
|
|
|
&pcm_analog_playback,
|
|
|
|
spec->stream_analog_playback,
|
|
|
|
spec->multiout.dac_nids[0]);
|
2012-12-19 11:52:06 +00:00
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
|
|
|
|
spec->multiout.max_channels;
|
|
|
|
if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT &&
|
|
|
|
spec->autocfg.line_outs == 2)
|
|
|
|
info->stream[SNDRV_PCM_STREAM_PLAYBACK].chmap =
|
|
|
|
snd_pcm_2_1_chmaps;
|
|
|
|
}
|
|
|
|
if (spec->num_adc_nids) {
|
2015-03-16 22:34:34 +00:00
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
|
|
|
|
(spec->dyn_adc_switch ?
|
|
|
|
&dyn_adc_pcm_analog_capture : &pcm_analog_capture),
|
|
|
|
spec->stream_analog_capture,
|
|
|
|
spec->adc_nids[0]);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
skip_analog:
|
|
|
|
/* SPDIF for stream index #1 */
|
|
|
|
if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
|
2012-12-20 15:58:39 +00:00
|
|
|
fill_pcm_stream_name(spec->stream_name_digital,
|
|
|
|
sizeof(spec->stream_name_digital),
|
2015-03-03 09:07:24 +00:00
|
|
|
" Digital", codec->core.chip_name);
|
2015-02-27 16:43:19 +00:00
|
|
|
info = snd_hda_codec_pcm_new(codec, "%s",
|
|
|
|
spec->stream_name_digital);
|
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
2012-12-19 11:52:06 +00:00
|
|
|
codec->slave_dig_outs = spec->multiout.slave_dig_outs;
|
2015-02-27 16:43:19 +00:00
|
|
|
spec->pcm_rec[1] = info;
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->dig_out_type)
|
|
|
|
info->pcm_type = spec->dig_out_type;
|
|
|
|
else
|
|
|
|
info->pcm_type = HDA_PCM_TYPE_SPDIF;
|
2015-03-16 22:34:34 +00:00
|
|
|
if (spec->multiout.dig_out_nid)
|
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
|
|
|
|
&pcm_digital_playback,
|
|
|
|
spec->stream_digital_playback,
|
|
|
|
spec->multiout.dig_out_nid);
|
|
|
|
if (spec->dig_in_nid)
|
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
|
|
|
|
&pcm_digital_capture,
|
|
|
|
spec->stream_digital_capture,
|
|
|
|
spec->dig_in_nid);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->no_analog)
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* If the use of more than one ADC is requested for the current
|
|
|
|
* model, configure a second analog capture-only PCM.
|
|
|
|
*/
|
|
|
|
have_multi_adcs = (spec->num_adc_nids > 1) &&
|
|
|
|
!spec->dyn_adc_switch && !spec->auto_mic;
|
|
|
|
/* Additional Analaog capture for index #2 */
|
|
|
|
if (spec->alt_dac_nid || have_multi_adcs) {
|
2013-01-21 15:50:09 +00:00
|
|
|
fill_pcm_stream_name(spec->stream_name_alt_analog,
|
|
|
|
sizeof(spec->stream_name_alt_analog),
|
2015-03-03 09:07:24 +00:00
|
|
|
" Alt Analog", codec->core.chip_name);
|
2015-02-27 16:43:19 +00:00
|
|
|
info = snd_hda_codec_pcm_new(codec, "%s",
|
|
|
|
spec->stream_name_alt_analog);
|
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
|
|
|
spec->pcm_rec[2] = info;
|
2015-03-16 22:34:34 +00:00
|
|
|
if (spec->alt_dac_nid)
|
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
|
|
|
|
&pcm_analog_alt_playback,
|
|
|
|
spec->stream_analog_alt_playback,
|
|
|
|
spec->alt_dac_nid);
|
|
|
|
else
|
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
|
|
|
|
&pcm_null_stream, NULL, 0);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (have_multi_adcs) {
|
2015-03-16 22:34:34 +00:00
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
|
|
|
|
&pcm_analog_alt_capture,
|
|
|
|
spec->stream_analog_alt_capture,
|
|
|
|
spec->adc_nids[1]);
|
2012-12-19 11:52:06 +00:00
|
|
|
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
|
|
|
|
spec->num_adc_nids - 1;
|
|
|
|
} else {
|
2015-03-16 22:34:34 +00:00
|
|
|
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
|
|
|
|
&pcm_null_stream, NULL, 0);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_build_pcms);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Standard auto-parser initializations
|
|
|
|
*/
|
|
|
|
|
2013-01-07 09:08:02 +00:00
|
|
|
/* configure the given path as a proper output */
|
2013-01-10 08:33:29 +00:00
|
|
|
static void set_output_and_unmute(struct hda_codec *codec, int path_idx)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
struct nid_path *path;
|
2013-01-07 09:08:02 +00:00
|
|
|
hda_nid_t pin;
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2013-01-04 14:01:40 +00:00
|
|
|
path = snd_hda_get_path_from_idx(codec, path_idx);
|
2013-01-07 09:08:02 +00:00
|
|
|
if (!path || !path->depth)
|
2012-12-19 11:52:06 +00:00
|
|
|
return;
|
2013-01-07 09:08:02 +00:00
|
|
|
pin = path->path[path->depth - 1];
|
2013-01-10 08:33:29 +00:00
|
|
|
restore_pin_ctl(codec, pin);
|
2013-04-16 10:31:05 +00:00
|
|
|
snd_hda_activate_path(codec, path, path->active,
|
|
|
|
aamix_default(codec->spec));
|
2013-01-03 15:33:02 +00:00
|
|
|
set_pin_eapd(codec, pin, path->active);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize primary output paths */
|
|
|
|
static void init_multi_out(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
2013-01-07 09:08:02 +00:00
|
|
|
for (i = 0; i < spec->autocfg.line_outs; i++)
|
2013-01-10 08:33:29 +00:00
|
|
|
set_output_and_unmute(codec, spec->out_paths[i]);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2012-12-20 14:27:24 +00:00
|
|
|
|
2013-01-10 08:33:29 +00:00
|
|
|
static void __init_extra_out(struct hda_codec *codec, int num_outs, int *paths)
|
2012-12-19 11:52:06 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2013-01-07 09:08:02 +00:00
|
|
|
for (i = 0; i < num_outs; i++)
|
2013-01-10 08:33:29 +00:00
|
|
|
set_output_and_unmute(codec, paths[i]);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2012-12-20 14:27:24 +00:00
|
|
|
/* initialize hp and speaker paths */
|
|
|
|
static void init_extra_out(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->autocfg.line_out_type != AUTO_PIN_HP_OUT)
|
2013-01-10 08:33:29 +00:00
|
|
|
__init_extra_out(codec, spec->autocfg.hp_outs, spec->hp_paths);
|
2012-12-20 14:27:24 +00:00
|
|
|
if (spec->autocfg.line_out_type != AUTO_PIN_SPEAKER_OUT)
|
|
|
|
__init_extra_out(codec, spec->autocfg.speaker_outs,
|
2013-01-10 08:33:29 +00:00
|
|
|
spec->speaker_paths);
|
2012-12-20 14:27:24 +00:00
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* initialize multi-io paths */
|
|
|
|
static void init_multi_io(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < spec->multi_ios; i++) {
|
|
|
|
hda_nid_t pin = spec->multi_io[i].pin;
|
|
|
|
struct nid_path *path;
|
2013-01-04 14:01:40 +00:00
|
|
|
path = get_multiio_path(codec, i);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!path)
|
|
|
|
continue;
|
|
|
|
if (!spec->multi_io[i].ctl_in)
|
|
|
|
spec->multi_io[i].ctl_in =
|
2013-01-10 08:33:29 +00:00
|
|
|
snd_hda_codec_get_pin_target(codec, pin);
|
2013-04-16 10:31:05 +00:00
|
|
|
snd_hda_activate_path(codec, path, path->active,
|
|
|
|
aamix_default(spec));
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-03 09:51:37 +00:00
|
|
|
static void init_aamix_paths(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (!spec->have_aamix_ctl)
|
|
|
|
return;
|
ALSA: hda - Implement loopback control switch for Realtek and other codecs
Many codecs, typically found on Realtek codecs, have the analog
loopback path merged to the secondary input of the middle of the
output paths. Currently, we don't offer the dynamic switching in such
configuration but let each loopback path mute by itself.
This should work well in theory, but in reality, we often see that
such a dead loopback path causes some background noises even if all
the elements get muted. Such a problem has been fixed by adding the
quirk accordingly to disable aamix, and it's the right fix, per se.
The only problem is that it's not so trivial to achieve it; user needs
to pass a hint string via patch module option or sysfs.
This patch gives a bit improvement on the situation: it adds "Loopback
Mixing" control element for such codecs like other codecs (e.g. IDT or
VIA codecs) with the individual loopback paths. User can turn on/off
the loopback path simply via a mixer app.
For keeping the compatibility, the loopback is still enabled on these
codecs. But user can try to turn it off if experiencing a suspicious
background or click noise on the fly, then build a static fixup later
once after the problem is addressed.
Other than the addition of the loopback enable/disablement control,
there should be no changes.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-12-08 16:00:42 +00:00
|
|
|
if (!has_aamix_out_paths(spec))
|
|
|
|
return;
|
2013-12-03 09:51:37 +00:00
|
|
|
update_aamix_paths(codec, spec->aamix_mode, spec->out_paths[0],
|
|
|
|
spec->aamix_out_paths[0],
|
|
|
|
spec->autocfg.line_out_type);
|
|
|
|
update_aamix_paths(codec, spec->aamix_mode, spec->hp_paths[0],
|
|
|
|
spec->aamix_out_paths[1],
|
|
|
|
AUTO_PIN_HP_OUT);
|
|
|
|
update_aamix_paths(codec, spec->aamix_mode, spec->speaker_paths[0],
|
|
|
|
spec->aamix_out_paths[2],
|
|
|
|
AUTO_PIN_SPEAKER_OUT);
|
|
|
|
}
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* set up input pins and loopback paths */
|
|
|
|
static void init_analog_input(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct auto_pin_cfg *cfg = &spec->autocfg;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < cfg->num_inputs; i++) {
|
|
|
|
hda_nid_t nid = cfg->inputs[i].pin;
|
|
|
|
if (is_input_pin(codec, nid))
|
2013-01-10 08:33:29 +00:00
|
|
|
restore_pin_ctl(codec, nid);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
/* init loopback inputs */
|
|
|
|
if (spec->mixer_nid) {
|
2013-01-23 16:07:23 +00:00
|
|
|
resume_path_from_idx(codec, spec->loopback_paths[i]);
|
|
|
|
resume_path_from_idx(codec, spec->loopback_merge_path);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize ADC paths */
|
|
|
|
static void init_input_src(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
struct hda_input_mux *imux = &spec->input_mux;
|
|
|
|
struct nid_path *path;
|
|
|
|
int i, c, nums;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->dyn_adc_switch)
|
|
|
|
nums = 1;
|
|
|
|
else
|
|
|
|
nums = spec->num_adc_nids;
|
|
|
|
|
|
|
|
for (c = 0; c < nums; c++) {
|
|
|
|
for (i = 0; i < imux->num_items; i++) {
|
2013-01-07 16:09:26 +00:00
|
|
|
path = get_input_path(codec, c, i);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (path) {
|
|
|
|
bool active = path->active;
|
|
|
|
if (i == spec->cur_mux[c])
|
|
|
|
active = true;
|
|
|
|
snd_hda_activate_path(codec, path, active, false);
|
|
|
|
}
|
2006-03-21 10:29:07 +00:00
|
|
|
}
|
2013-02-19 16:12:42 +00:00
|
|
|
if (spec->hp_mic)
|
|
|
|
update_hp_mic(codec, c, true);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
|
|
|
|
if (spec->cap_sync_hook)
|
2014-01-30 16:59:02 +00:00
|
|
|
spec->cap_sync_hook(codec, NULL, NULL);
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* set right pin controls for digital I/O */
|
|
|
|
static void init_digital(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
int i;
|
|
|
|
hda_nid_t pin;
|
|
|
|
|
2013-01-07 09:08:02 +00:00
|
|
|
for (i = 0; i < spec->autocfg.dig_outs; i++)
|
2013-01-10 08:33:29 +00:00
|
|
|
set_output_and_unmute(codec, spec->digout_paths[i]);
|
2012-12-19 11:52:06 +00:00
|
|
|
pin = spec->autocfg.dig_in_pin;
|
2013-01-04 14:09:42 +00:00
|
|
|
if (pin) {
|
2013-01-10 08:33:29 +00:00
|
|
|
restore_pin_ctl(codec, pin);
|
2013-01-23 16:07:23 +00:00
|
|
|
resume_path_from_idx(codec, spec->digin_path);
|
2013-01-04 14:09:42 +00:00
|
|
|
}
|
2012-12-19 11:52:06 +00:00
|
|
|
}
|
|
|
|
|
2012-12-20 14:16:09 +00:00
|
|
|
/* clear unsol-event tags on unused pins; Conexant codecs seem to leave
|
|
|
|
* invalid unsol tags by some reason
|
|
|
|
*/
|
|
|
|
static void clear_unsol_on_unused_pins(struct hda_codec *codec)
|
|
|
|
{
|
2018-04-23 15:24:56 +00:00
|
|
|
const struct hda_pincfg *pin;
|
2012-12-20 14:16:09 +00:00
|
|
|
int i;
|
|
|
|
|
2018-04-23 15:24:56 +00:00
|
|
|
snd_array_for_each(&codec->init_pins, i, pin) {
|
2012-12-20 14:16:09 +00:00
|
|
|
hda_nid_t nid = pin->nid;
|
|
|
|
if (is_jack_detectable(codec, nid) &&
|
|
|
|
!snd_hda_jack_tbl_get(codec, nid))
|
2018-06-27 09:43:09 +00:00
|
|
|
snd_hda_codec_write_cache(codec, nid, 0,
|
2012-12-20 14:16:09 +00:00
|
|
|
AC_VERB_SET_UNSOLICITED_ENABLE, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_init - initialize the generic spec
|
|
|
|
* @codec: the HDA codec
|
|
|
|
*
|
|
|
|
* This can be put as patch_ops init function.
|
2013-01-07 11:52:16 +00:00
|
|
|
*/
|
2012-12-19 11:52:06 +00:00
|
|
|
int snd_hda_gen_init(struct hda_codec *codec)
|
|
|
|
{
|
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
|
|
|
|
|
|
|
if (spec->init_hook)
|
|
|
|
spec->init_hook(codec);
|
|
|
|
|
|
|
|
snd_hda_apply_verbs(codec);
|
|
|
|
|
|
|
|
init_multi_out(codec);
|
|
|
|
init_extra_out(codec);
|
|
|
|
init_multi_io(codec);
|
2013-12-03 09:51:37 +00:00
|
|
|
init_aamix_paths(codec);
|
2012-12-19 11:52:06 +00:00
|
|
|
init_analog_input(codec);
|
|
|
|
init_input_src(codec);
|
|
|
|
init_digital(codec);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-20 14:16:09 +00:00
|
|
|
clear_unsol_on_unused_pins(codec);
|
|
|
|
|
ALSA: hda - Support advanced power state controls
This patch enables the finer power state control of each widget
depending on the jack plug state and streaming state in addition to
the existing power_down_unused power optimization. The new feature is
enabled only when codec->power_mgmt flag is set.
Two new flags, pin_enabled and stream_enabled, are introduced in
nid_path struct for marking the two individual power states: the pin
plug/unplug and DAC/ADC stream, respectively. They can be set
statically in case they are static routes (e.g. some mixer paths),
too.
The power up and down events for each pin are triggered via the
standard hda_jack table. The call order is hard-coded, relying on the
current implementation of jack event chain (a la FILO/stack order).
One point to be dealt carefully is that DAC/ADC cannot be powered
on/off while streaming. They are pinned as long as the stream is
running. For controlling the power of DAC/ADC, a new patch_ops is
added. The generic parser provides the default callback for that.
As of this patch, only IDT/Sigmatel codec driver enables the flag.
The support on other codecs will follow.
An assumption we made in this code is that the widget state (e.g. amp,
pinctl, connections) remains after the widget power transition (not
about FG power transition). This is true for IDT codecs, at least.
But if the widget state is lost at widget power transition, we'd need
to implement additional code to sync the cached amp/verbs for the
specific NID.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-03-16 20:32:11 +00:00
|
|
|
sync_all_pin_power_ctls(codec);
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
/* call init functions of standard auto-mute helpers */
|
2013-01-16 17:08:55 +00:00
|
|
|
update_automute_all(codec);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
ALSA: hda - Use regmap for command verb caches, too
Like the previous patches, this patch converts also to the regmap, at
this time, the cached verb writes are the target. But this conversion
needs a bit more caution than before.
- In the old code, we just record any verbs as is, and restore them at
resume. For the regmap scheme, this doesn't work, since a few verbs
like AMP or DIGI_CONVERT are asymmetrical. Such verbs are converted
either to the dedicated function (snd_hda_regmap_xxx_amp()) or
changed to the unified verb.
- Some verbs have to be declared as vendor-specific ones before
accessing via regmap.
Also, the minor optimization with codec->cached_write flag is dropped
in a few places, as this would confuse the operation. Further
optimizations will be brought in the later patches, if any.
This conversion ends up with a drop of significant amount of codes,
mostly the helper codes that are no longer used.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-02-26 11:34:49 +00:00
|
|
|
regcache_sync(codec->core.regmap);
|
2012-12-20 10:50:58 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
if (spec->vmaster_mute.sw_kctl && spec->vmaster_mute.hook)
|
|
|
|
snd_hda_sync_vmaster_hook(&spec->vmaster_mute);
|
|
|
|
|
|
|
|
hda_call_check_power_status(codec, 0x01);
|
2005-04-16 22:20:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_init);
|
2012-12-19 11:52:06 +00:00
|
|
|
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_free - free the generic spec
|
|
|
|
* @codec: the HDA codec
|
|
|
|
*
|
|
|
|
* This can be put as patch_ops free function.
|
2013-01-07 11:52:16 +00:00
|
|
|
*/
|
2013-01-07 11:42:48 +00:00
|
|
|
void snd_hda_gen_free(struct hda_codec *codec)
|
|
|
|
{
|
2014-02-10 17:09:45 +00:00
|
|
|
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_FREE);
|
2013-01-07 11:42:48 +00:00
|
|
|
snd_hda_gen_spec_free(codec->spec);
|
|
|
|
kfree(codec->spec);
|
|
|
|
codec->spec = NULL;
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_free);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-08-24 16:38:08 +00:00
|
|
|
#ifdef CONFIG_PM
|
2014-10-30 11:04:50 +00:00
|
|
|
/**
|
|
|
|
* snd_hda_gen_check_power_status - check the loopback power save state
|
|
|
|
* @codec: the HDA codec
|
|
|
|
* @nid: NID to inspect
|
|
|
|
*
|
|
|
|
* This can be put as patch_ops check_power_status function.
|
2013-01-07 11:52:16 +00:00
|
|
|
*/
|
2013-01-07 11:42:48 +00:00
|
|
|
int snd_hda_gen_check_power_status(struct hda_codec *codec, hda_nid_t nid)
|
2007-08-10 15:21:45 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec = codec->spec;
|
2007-08-10 15:21:45 +00:00
|
|
|
return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
|
|
|
|
}
|
2013-12-18 06:45:52 +00:00
|
|
|
EXPORT_SYMBOL_GPL(snd_hda_gen_check_power_status);
|
2007-08-10 15:21:45 +00:00
|
|
|
#endif
|
|
|
|
|
2013-01-07 11:42:48 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* the generic codec support
|
|
|
|
*/
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
static const struct hda_codec_ops generic_patch_ops = {
|
|
|
|
.build_controls = snd_hda_gen_build_controls,
|
|
|
|
.build_pcms = snd_hda_gen_build_pcms,
|
|
|
|
.init = snd_hda_gen_init,
|
2013-01-07 11:42:48 +00:00
|
|
|
.free = snd_hda_gen_free,
|
2012-12-19 11:52:06 +00:00
|
|
|
.unsol_event = snd_hda_jack_unsol_event,
|
2012-08-24 16:38:08 +00:00
|
|
|
#ifdef CONFIG_PM
|
2013-01-07 11:42:48 +00:00
|
|
|
.check_power_status = snd_hda_gen_check_power_status,
|
2007-08-10 15:21:45 +00:00
|
|
|
#endif
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2015-02-17 14:25:37 +00:00
|
|
|
/*
|
2014-10-30 11:04:50 +00:00
|
|
|
* snd_hda_parse_generic_codec - Generic codec parser
|
|
|
|
* @codec: the HDA codec
|
|
|
|
*/
|
2015-02-17 14:25:37 +00:00
|
|
|
static int snd_hda_parse_generic_codec(struct hda_codec *codec)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-12-19 11:52:06 +00:00
|
|
|
struct hda_gen_spec *spec;
|
2005-04-16 22:20:36 +00:00
|
|
|
int err;
|
|
|
|
|
[ALSA] Replace with kzalloc() - pci stuff
AD1889 driver,ATIIXP driver,ATIIXP-modem driver,AZT3328 driver
BT87x driver,CMIPCI driver,CS4281 driver,ENS1370/1+ driver
ES1938 driver,ES1968 driver,FM801 driver,Intel8x0 driver
Intel8x0-modem driver,Maestro3 driver,SonicVibes driver,VIA82xx driver
VIA82xx-modem driver,AC97 Codec,AK4531 codec,au88x0 driver
CA0106 driver,CS46xx driver,EMU10K1/EMU10K2 driver,HDA Codec driver
HDA generic driver,HDA Intel driver,ICE1712 driver,ICE1724 driver
KORG1212 driver,MIXART driver,NM256 driver,Trident driver,YMFPCI driver
Replace kcalloc(1,..) with kzalloc().
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2005-09-09 12:21:46 +00:00
|
|
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (!spec)
|
2005-04-16 22:20:36 +00:00
|
|
|
return -ENOMEM;
|
2012-12-19 11:52:06 +00:00
|
|
|
snd_hda_gen_spec_init(spec);
|
2005-04-16 22:20:36 +00:00
|
|
|
codec->spec = spec;
|
|
|
|
|
2012-12-19 13:41:21 +00:00
|
|
|
err = snd_hda_parse_pin_defcfg(codec, &spec->autocfg, NULL, 0);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = snd_hda_gen_parse_auto_config(codec, &spec->autocfg);
|
2012-12-19 11:52:06 +00:00
|
|
|
if (err < 0)
|
2005-04-16 22:20:36 +00:00
|
|
|
goto error;
|
|
|
|
|
|
|
|
codec->patch_ops = generic_patch_ops;
|
|
|
|
return 0;
|
|
|
|
|
2012-12-19 11:52:06 +00:00
|
|
|
error:
|
2013-01-07 11:42:48 +00:00
|
|
|
snd_hda_gen_free(codec);
|
2005-04-16 22:20:36 +00:00
|
|
|
return err;
|
|
|
|
}
|
2015-02-17 14:25:37 +00:00
|
|
|
|
2015-10-01 14:20:04 +00:00
|
|
|
static const struct hda_device_id snd_hda_id_generic[] = {
|
|
|
|
HDA_CODEC_ENTRY(HDA_CODEC_ID_GENERIC, "Generic", snd_hda_parse_generic_codec),
|
2015-02-17 14:25:37 +00:00
|
|
|
{} /* terminator */
|
|
|
|
};
|
2015-10-01 14:20:04 +00:00
|
|
|
MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_generic);
|
2015-02-17 14:25:37 +00:00
|
|
|
|
|
|
|
static struct hda_codec_driver generic_driver = {
|
2015-10-01 14:20:04 +00:00
|
|
|
.id = snd_hda_id_generic,
|
2015-02-17 14:25:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
module_hda_codec_driver(generic_driver);
|
2013-11-18 11:03:56 +00:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("Generic HD-audio codec parser");
|