ALSA: hda/realtek - Add path active flag

... and rewrite the initialization of output paths as a generic
function that is applicable for both i/o directions.

The new flag, active, is introduced to each nid_path entry.  This
indicates whether the given path is active, and it's used for checking
whether a certain widget can be turned off or changed when a path is
no longer used or newly enabled.

It's still used only in the output paths.  More wider adaption for
input and loopback paths will be achieved in the later patch.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2012-12-14 16:09:29 +01:00
parent b8a47c79b2
commit 130e5f0642
2 changed files with 178 additions and 90 deletions

View File

@ -598,7 +598,8 @@ int snd_hda_check_amp_list_power(struct hda_codec *codec,
#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3) #define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
#define get_amp_direction_(pv) (((pv) >> 18) & 0x1) #define get_amp_direction_(pv) (((pv) >> 18) & 0x1)
#define get_amp_direction(kc) get_amp_direction_((kc)->private_value) #define get_amp_direction(kc) get_amp_direction_((kc)->private_value)
#define get_amp_index(kc) (((kc)->private_value >> 19) & 0xf) #define get_amp_index_(pv) (((pv) >> 19) & 0xf)
#define get_amp_index(kc) get_amp_index_((kc)->private_value)
#define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f) #define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f)
#define get_amp_min_mute(kc) (((kc)->private_value >> 29) & 0x1) #define get_amp_min_mute(kc) (((kc)->private_value >> 29) & 0x1)

View File

@ -112,6 +112,7 @@ struct nid_path {
unsigned char idx[MAX_NID_PATH_DEPTH]; unsigned char idx[MAX_NID_PATH_DEPTH];
unsigned char multi[MAX_NID_PATH_DEPTH]; unsigned char multi[MAX_NID_PATH_DEPTH];
unsigned int ctls[2]; /* 0 = volume, 1 = mute */ unsigned int ctls[2]; /* 0 = volume, 1 = mute */
bool active;
}; };
enum { NID_PATH_VOL_CTL = 0, NID_PATH_MUTE_CTL = 1 }; enum { NID_PATH_VOL_CTL = 0, NID_PATH_MUTE_CTL = 1 };
@ -2853,16 +2854,6 @@ static int alc_auto_create_shared_input(struct hda_codec *codec)
return 0; return 0;
} }
static void alc_set_pin_output(struct hda_codec *codec, hda_nid_t nid,
unsigned int pin_type)
{
snd_hda_set_pin_ctl(codec, nid, pin_type);
/* unmute pin */
if (nid_has_mute(codec, nid, HDA_OUTPUT))
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
AMP_OUT_UNMUTE);
}
static int get_pin_type(int line_out_type) static int get_pin_type(int line_out_type)
{ {
if (line_out_type == AUTO_PIN_HP_OUT) if (line_out_type == AUTO_PIN_HP_OUT)
@ -3824,23 +3815,21 @@ static int alc_auto_create_speaker_out(struct hda_codec *codec)
"Speaker"); "Speaker");
} }
/* is a volume or mute control already present? */ static bool is_ctl_associated_in_list(struct snd_array *array, hda_nid_t nid,
static bool __is_out_ctl_present(struct hda_codec *codec, int dir, int idx, int types)
struct nid_path *exclude_path,
hda_nid_t nid, int dir, int types)
{ {
struct alc_spec *spec = codec->spec;
int i, type; int i, type;
for (i = 0; i < spec->out_path.used; i++) { for (i = 0; i < array->used; i++) {
struct nid_path *p = snd_array_elem(&spec->out_path, i); struct nid_path *p = snd_array_elem(array, i);
if (p == exclude_path || p->depth <= 0) if (p->depth <= 0)
continue; continue;
for (type = 0; type < 2; type++) { for (type = 0; type < 2; type++) {
if (types & (1 << type)) { if (types & (1 << type)) {
unsigned int val = p->ctls[type]; unsigned int val = p->ctls[type];
if (get_amp_nid_(val) == nid && if (get_amp_nid_(val) == nid &&
get_amp_direction_(val) == dir) get_amp_direction_(val) == dir &&
get_amp_index_(val) == idx)
return true; return true;
} }
} }
@ -3848,85 +3837,183 @@ static bool __is_out_ctl_present(struct hda_codec *codec,
return false; return false;
} }
#define is_out_ctl_present(codec, path, nid, dir) \ /* check whether a control with the given (nid, dir, idx) was assigned */
__is_out_ctl_present(codec, path, nid, dir, 3) /* check both types */ static bool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid,
#define is_out_vol_ctl_present(codec, nid, dir) \ int dir, int idx)
__is_out_ctl_present(codec, NULL, nid, dir, 1 << NID_PATH_VOL_CTL)
#define is_out_mute_ctl_present(codec, nid, dir) \
__is_out_ctl_present(codec, NULL, nid, dir, 1 << NID_PATH_MUTE_CTL)
static int get_default_amp_val(struct hda_codec *codec, hda_nid_t nid, int dir)
{ {
unsigned int caps, offset; struct alc_spec *spec = codec->spec;
return is_ctl_associated_in_list(&spec->out_path, nid, dir, idx, 3) ||
is_ctl_associated_in_list(&spec->in_path, nid, dir, idx, 3) ||
is_ctl_associated_in_list(&spec->loopback_path, nid, dir, idx, 3);
}
/* can have the amp-in capability? */
static bool has_amp_in(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_IN_AMP))
return false;
if (type == AC_WID_PIN && idx > 0) /* only for input pins */
return false;
return true;
}
/* 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;
}
static bool is_active_in_list(struct hda_codec *codec, struct snd_array *array,
hda_nid_t nid, int dir, int idx)
{
int i, n;
for (n = 0; n < array->used; n++) {
struct nid_path *path = snd_array_elem(array, n);
if (!path->active)
continue;
for (i = 0; i < path->depth; i++) {
if (path->path[i] == nid) {
if (dir == HDA_OUTPUT || path->idx[i] == idx)
return true;
break;
}
}
}
return false;
}
/* check whether the given (nid,dir,idx) is active */
static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid,
unsigned int idx, unsigned int dir)
{
struct alc_spec *spec = codec->spec;
return is_active_in_list(codec, &spec->out_path, nid, idx, dir) ||
is_active_in_list(codec, &spec->in_path, nid, idx, dir) ||
is_active_in_list(codec, &spec->loopback_path, nid, idx, dir);
}
/* get the default amp value for the target state */
static int get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid,
int dir, bool enable)
{
unsigned int caps;
unsigned int val = 0; unsigned int val = 0;
caps = query_amp_caps(codec, nid, dir); caps = query_amp_caps(codec, nid, dir);
if (caps & AC_AMPCAP_NUM_STEPS) { if (caps & AC_AMPCAP_NUM_STEPS) {
offset = (caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT; /* set to 0dB */
/* if a volume control is assigned, set the lowest level if (enable)
* as default; otherwise set to 0dB val = (caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
*/
if (is_out_vol_ctl_present(codec, nid, dir))
val = 0;
else
val = offset;
} }
if (caps & AC_AMPCAP_MUTE) { if (caps & AC_AMPCAP_MUTE) {
/* if a mute control is assigned, mute as default */ if (!enable)
if (is_out_mute_ctl_present(codec, nid, dir))
val |= HDA_AMP_MUTE; val |= HDA_AMP_MUTE;
} }
return val; return val;
} }
/* 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)
{
int val = get_amp_val_to_activate(codec, nid, dir, false);
snd_hda_codec_amp_init_stereo(codec, nid, dir, idx, 0xff, val);
}
static void activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir,
int idx, bool enable)
{
int val;
if (is_ctl_associated(codec, nid, dir, idx) ||
is_active_nid(codec, nid, dir, idx))
return;
val = get_amp_val_to_activate(codec, nid, dir, enable);
snd_hda_codec_amp_stereo(codec, nid, dir, idx, 0xff, val);
}
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);
activate_amp(codec, nid, HDA_OUTPUT, 0, enable);
}
static void activate_amp_in(struct hda_codec *codec, struct nid_path *path,
int i, bool enable)
{
struct alc_spec *spec = codec->spec;
hda_nid_t conn[16];
int n, nums;
hda_nid_t nid = path->path[i];
nums = snd_hda_get_connections(codec, nid, conn, ARRAY_SIZE(conn));
for (n = 0; n < nums; n++)
init_amp(codec, nid, HDA_INPUT, n);
if (is_ctl_associated(codec, nid, HDA_INPUT, path->idx[i]))
return;
/* 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
*/
for (n = 0; n < nums; n++) {
if (n != path->idx[i] && conn[n] != spec->mixer_nid)
continue;
activate_amp(codec, nid, HDA_INPUT, n, enable);
}
}
static void activate_path(struct hda_codec *codec, struct nid_path *path,
bool enable)
{
int i;
if (path->active == enable)
return;
if (!enable)
path->active = false;
for (i = path->depth - 1; i >= 0; i--) {
if (path->multi[i])
snd_hda_codec_write_cache(codec, path->path[i], 0,
AC_VERB_SET_CONNECT_SEL,
path->idx[i]);
if (has_amp_in(codec, path, i))
activate_amp_in(codec, path, i, enable);
if (has_amp_out(codec, path, i))
activate_amp_out(codec, path, i, enable);
}
if (enable)
path->active = true;
}
/* configure the path from the given dac to the pin as the proper output */ /* configure the path from the given dac to the pin as the proper output */
static void alc_auto_set_output_and_unmute(struct hda_codec *codec, static void alc_auto_set_output_and_unmute(struct hda_codec *codec,
hda_nid_t pin, int pin_type, hda_nid_t pin, int pin_type,
hda_nid_t dac, bool force) hda_nid_t dac)
{ {
struct alc_spec *spec = codec->spec;
int i, val;
struct nid_path *path; struct nid_path *path;
alc_set_pin_output(codec, pin, pin_type); snd_hda_set_pin_ctl_cache(codec, pin, pin_type);
path = get_out_path(codec, pin, dac); path = get_out_path(codec, pin, dac);
if (!path) if (!path)
return; return;
activate_path(codec, path, true);
for (i = path->depth - 1; i >= 0; i--) {
hda_nid_t nid = path->path[i];
if (path->multi[i])
snd_hda_codec_write(codec, nid, 0,
AC_VERB_SET_CONNECT_SEL,
path->idx[i]);
if (i != 0 && i != path->depth - 1 &&
(get_wcaps(codec, nid) & AC_WCAP_IN_AMP) &&
(force || !is_out_ctl_present(codec, path, nid,
HDA_INPUT))) {
hda_nid_t conn[16];
int n, nums;
nums = snd_hda_get_connections(codec, nid, conn,
ARRAY_SIZE(conn));
val = get_default_amp_val(codec, nid, HDA_INPUT);
for (n = 0; n < nums; n++) {
if (n != path->idx[i] &&
conn[n] != spec->mixer_nid)
continue;
snd_hda_codec_write(codec, nid, 0,
AC_VERB_SET_AMP_GAIN_MUTE,
AMP_IN_UNMUTE(n) | val);
}
}
if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) &&
(force || !is_out_ctl_present(codec, path, nid,
HDA_OUTPUT))) {
val = get_default_amp_val(codec, nid, HDA_OUTPUT);
snd_hda_codec_write(codec, nid, 0,
AC_VERB_SET_AMP_GAIN_MUTE,
AMP_OUT_UNMUTE | val);
}
}
} }
static void alc_auto_init_multi_out(struct hda_codec *codec) static void alc_auto_init_multi_out(struct hda_codec *codec)
@ -3939,7 +4026,7 @@ static void alc_auto_init_multi_out(struct hda_codec *codec)
hda_nid_t nid = spec->autocfg.line_out_pins[i]; hda_nid_t nid = spec->autocfg.line_out_pins[i];
if (nid) if (nid)
alc_auto_set_output_and_unmute(codec, nid, pin_type, alc_auto_set_output_and_unmute(codec, nid, pin_type,
spec->multiout.dac_nids[i], true); spec->multiout.dac_nids[i]);
} }
} }
@ -3963,7 +4050,7 @@ static void alc_auto_init_extra_out(struct hda_codec *codec)
else else
dac = spec->multiout.dac_nids[0]; dac = spec->multiout.dac_nids[0];
} }
alc_auto_set_output_and_unmute(codec, pin, PIN_HP, dac, true); alc_auto_set_output_and_unmute(codec, pin, PIN_HP, dac);
} }
for (i = 0; i < spec->autocfg.speaker_outs; i++) { for (i = 0; i < spec->autocfg.speaker_outs; i++) {
if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
@ -3978,7 +4065,7 @@ static void alc_auto_init_extra_out(struct hda_codec *codec)
else else
dac = spec->multiout.dac_nids[0]; dac = spec->multiout.dac_nids[0];
} }
alc_auto_set_output_and_unmute(codec, pin, PIN_OUT, dac, true); alc_auto_set_output_and_unmute(codec, pin, PIN_OUT, dac);
} }
} }
@ -4129,22 +4216,22 @@ static int alc_set_multi_io(struct hda_codec *codec, int idx, bool output)
{ {
struct alc_spec *spec = codec->spec; struct alc_spec *spec = codec->spec;
hda_nid_t nid = spec->multi_io[idx].pin; hda_nid_t nid = spec->multi_io[idx].pin;
struct nid_path *path;
path = get_out_path(codec, nid, spec->multi_io[idx].dac);
if (!path)
return -EINVAL;
if (!spec->multi_io[idx].ctl_in) if (!spec->multi_io[idx].ctl_in)
spec->multi_io[idx].ctl_in = spec->multi_io[idx].ctl_in =
snd_hda_codec_read(codec, nid, 0, snd_hda_codec_update_cache(codec, nid, 0,
AC_VERB_GET_PIN_WIDGET_CONTROL, 0); AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
if (output) { if (output) {
snd_hda_set_pin_ctl_cache(codec, nid, PIN_OUT); snd_hda_set_pin_ctl_cache(codec, nid, PIN_OUT);
if (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) activate_path(codec, path, true);
snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
HDA_AMP_MUTE, 0);
alc_auto_set_output_and_unmute(codec, nid, PIN_OUT,
spec->multi_io[idx].dac, false);
} else { } else {
if (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) activate_path(codec, path, false);
snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0,
HDA_AMP_MUTE, HDA_AMP_MUTE);
snd_hda_set_pin_ctl_cache(codec, nid, snd_hda_set_pin_ctl_cache(codec, nid,
spec->multi_io[idx].ctl_in); spec->multi_io[idx].ctl_in);
} }