Re: [alsa-devel] HP-path handling on VIA codecs
2011/7/8 Takashi Iwai tiwai@suse.de:
Hi,
while testing with the gathered alsa-info.sh outputs, now I'm considering how to improve the handling of the headphone (and speaker) output paths.
On VIA codecs, it looks like that the loopback mixer always contains the input from the front DAC, and the front DAC is the only DAC that can be mixed with loopback. (It might be not always, but on many codecs, at least).
I mean, some codecs of other vendors have a connection like: Input-pin(s) -> Mix1 -> Mix2 -> Output1 DAC1 -> Mix2 -> Output1 Input-pin(s) -> Mix3 -> Mix4 -> Output2 DAC2 -> Mix4 -> Output2 .... For such devices, you can use DAC as the volume control of each output channel (HP, line-out, speaker, etc). So I started implementing in that way for VIA, too. It's found as two parsed output paths, spec->hp_out, and spec->hp_dep_out,
But later I found that VIA has only Input-pin(s) -> Mix1 -> Output1 DAC1 -> Mix1 -> Output1 or DAC1 -> Output1
Input-pin(s) -> Mix1 -> Output1 DAC1 -> Mix1 -> Output2 or DAC2 -> Output2 ...
Suppose the output2 is HP, it means that you can't have an individual volume control via DAC2 if you want to mix the loopback. Now we face a dilemma: usually an individual volume control is really nice. But, if we take the individual control, it looses the loopback, as these are mutual-exclusive for HP (or speaker) outputs.
Thus we need to decide a policy: either
A. HP has no volume unless independent mode
B. Don't mix loopback to HP or others (but front-out)
C. Allow to turn on/off aa-loopback mode; when loopback is enabled, switch the path to hp_dep without volume control (only for front). When loopback is disabled, switch to the direct DAC path.
A is the old driver behavior, I guess. B is somewhat similar to now. C would be smart, but need more implementation and add yet another mixer element.
What do you think?
thanks,
Takashi
Personally, if I'm not misunderstanding it, I'd vote for C. I had tried something similar for old implementation, and can tell it worked and was easy enough to implement, AFAICT (I can resend the code if found of any interest, since it seems to me my first mail about it went lost, at least that's not in this mailing list archives, though that's based on the old code, and could be useful only to exchange ideas).
It was quite simple: I used a few new fields in struct via_spec and two input mux controls, one for hp and one for front (to have a fine grained control and disable/enable aa path independently for those lines - and test if it could work this way), called "Direct Output" and "HP Direct Output" (I chose names thinking about the sound coming straight from analog inputs and going to analog outputs through the AA mixer, but could be, for instance, "({Front, HP}) Output Mode" (or "Output Source") with items labeled "Mixed with input" (or "Stereo mixer") and "Playback only", or the like). Corresponding controls callbacks worked on the mixer connected to each output jack and getting input from a dac (through an audio selector) and from the aa mixer, choosing the right mixer basing on the control they were invoked for, and the corresponding values in via_spec new fields: .get() returned the (negated) value of the mute bit of aa mixer connection in the chosen mixer connection list, and .put() muted/un-muted such a connection if in a different state (a better behavior would be to alternatively mute/unmute the connection to the dac and the connection to the loopback mixer, taking care of hp state (working on aa mixer connection only for hp mixer while in independent mode), and, of course, there could be a single switch/mux control to enable/disable the aa path globally).
My reason to test such a solution was some noise I constantly got (and still get with newer code, but for Independent HP in latest version) from my headset microphone to each such line out (front and hp), but I can think, for instance, of someone wishing to listen to something while recording something else from line-in and not aiming to mix both sounds in output.
But I guess some problems could arise in actual implementation, unless I'm overvaluing/misunderstanding them.
First of all, now there's a pair of PCM Playback controls (switch and volume) attached to aa mixer and I'm not sure if muting connections to it could affect those controls (specially the volume). In such a case, they could be moved to the front dac, but this problem, if existing, could be re-raised for hp path in independent mode (in which case front dac and attached controls would be out of the game). An alternative could be leaving those controls where they're now, but reimplementing callbacks so that they could use a different nid (and work on more than one nid at a time). This way:
- the .put callback for turning aa-loopback on/off for a certain nid (or all affected nids at once, if only one such control existed) would be responsible: 1. to record somewhere (accessible by via_spec) the nid(s) to work on (quite easy with separate controls attached to each affected mixer: the nid to gather would be just the actual one being unmuted in the two-entries connection list of passed-in kcontrol nid or the non loopback connection for hp mixer in independent mode), 2. to set the right volume (not just un-muting) for the non-loopback path(s) when switching to it (basing on pcm volume setting) and muting/unmuting the connection(s) to aa mixer as needed;
- the .put callback for adjusting pcm volume setting would work as now for aa mixer nid (both to let it work as expected when used as source for output and to gather correct values from it, but also for correct handling of capture from stereo mixer), while performing same operations for each alternative nid, gathered from a proper list and being non-null and different from aa mixer nid;
- the .get callback for "PCM Playback Volume" could be left untouched (to always gather infos from the 'base' nid the control is attacthed to).
Similar considerations could/should be done for the pcm switch, that (now, in my case) has the effect to mute/unmute front output (only).
However, I might be missing the point since I'm not sure how those controls are expected to work properly. Previously, with old code, I had no PCM Playback Switch and a "standalone" PCM Playback Volume, detached from any nid, and such seemed to work for all outputs, as far as I remember (I should re-install old implementation to make a test); alsa-info.sh reported following output for alsactl:
control.31 { iface MIXER name 'PCM Playback Volume' value.0 255 value.1 255 comment { access 'read write user' type INTEGER count 2 range '0 - 255' tlv '0000000100000008ffffec1400000014' dbmin -5100 dbmax 0 dbvalue.0 0 dbvalue.1 0 } }
Now I have both controls attached to 0x21, they seem to have effect on front path only (throughout aa mixer - previously its connection to front dac, for my codec, was just un-muted with an initialization verb) and alsa-info.sh reports:
control.9 { iface MIXER name 'PCM Playback Volume' value.0 22 value.1 22 comment { access 'read write' type INTEGER count 2 range '0 - 31' dbmin -3450 dbmax 1200 dbvalue.0 -150 dbvalue.1 -150 } } control.10 { iface MIXER name 'PCM Playback Switch' value.0 true value.1 true comment { access 'read write' type BOOLEAN count 2 } }
Similar concerns could be raised for Master Playback controls, given some codecs (but not mine - vt2020) holds them via their aa mixer nid.
Another, possibly minor, problem, as pointed out in Lydia's answer, could be the somewhat inconsistence introduced between newer codecs supporting solution C and older ones unable to mute their aa mixer without loosing their front dac (because their aa mixer is just between their front dac and their rear green jack, but also between front dac and hp jack when in redirected mode). Such could be simply disregarded, taken as a consequence of a different set of supported features; therefore, it should be possible to choose automatically whether to create the proper control(s) for turning on/off aa-loopback or not while parsing output paths, looking at the connection lists of nids in a certain path, starting from an output jack and moving towards a dac, and looking if an element (a mixer or the jack nid itself) is found with at least two connections, one of which should be the aa mixer nid and the other one either the front dac nid, or an audio selector connected to it. For hp path the non-loopback connection shouldn't be hp dac directly (not passing through a selector), or the implementation would conflict with redirected output if such a dac were shared, which is the case for several codecs, specially older ones (newer ones seem to use an audio selector, instead, for front and hp), though I'm not sure if that's the same for all such codecs - hp_indep_shared could tell that, so that a few older codecs could support solution C for hp only, but in such a case solution C would behave mostly as B in current implementation, but for redirected mode, given front stream would be 'cloned' for hp dac if not shared, so hp could get loopback mixed in or not at whim, even if such would be inconsistent with front being always mixed with loopback).
Regards,
Alex
At Sun, 10 Jul 2011 16:55:45 +0200, alex dot baldacchino dot alsasub at gmail dot com wrote:
2011/7/8 Takashi Iwai tiwai@suse.de:
Hi,
while testing with the gathered alsa-info.sh outputs, now I'm considering how to improve the handling of the headphone (and speaker) output paths.
On VIA codecs, it looks like that the loopback mixer always contains the input from the front DAC, and the front DAC is the only DAC that can be mixed with loopback. (It might be not always, but on many codecs, at least).
I mean, some codecs of other vendors have a connection like: Input-pin(s) -> Mix1 -> Mix2 -> Output1 DAC1 -> Mix2 -> Output1 Input-pin(s) -> Mix3 -> Mix4 -> Output2 DAC2 -> Mix4 -> Output2 .... For such devices, you can use DAC as the volume control of each output channel (HP, line-out, speaker, etc). So I started implementing in that way for VIA, too. It's found as two parsed output paths, spec->hp_out, and spec->hp_dep_out,
But later I found that VIA has only Input-pin(s) -> Mix1 -> Output1 DAC1 -> Mix1 -> Output1 or DAC1 -> Output1
Input-pin(s) -> Mix1 -> Output1 DAC1 -> Mix1 -> Output2 or DAC2 -> Output2 ...
Suppose the output2 is HP, it means that you can't have an individual volume control via DAC2 if you want to mix the loopback. Now we face a dilemma: usually an individual volume control is really nice. But, if we take the individual control, it looses the loopback, as these are mutual-exclusive for HP (or speaker) outputs.
Thus we need to decide a policy: either
A. HP has no volume unless independent mode
B. Don't mix loopback to HP or others (but front-out)
C. Allow to turn on/off aa-loopback mode; when loopback is enabled, switch the path to hp_dep without volume control (only for front). When loopback is disabled, switch to the direct DAC path.
A is the old driver behavior, I guess. B is somewhat similar to now. C would be smart, but need more implementation and add yet another mixer element.
What do you think?
thanks,
Takashi
Personally, if I'm not misunderstanding it, I'd vote for C. I had tried something similar for old implementation, and can tell it worked and was easy enough to implement, AFAICT (I can resend the code if found of any interest, since it seems to me my first mail about it went lost, at least that's not in this mailing list archives, though that's based on the old code, and could be useful only to exchange ideas).
A patch will be always useful, even for the older code.
Regarding ML archive: are you subscribed to ML? alsa-devel ML is basically only for subscribers, and posts from non-subscribers are postponed, manually approved. Sometimes the approval work delayed and the posts are canceled.
But, currently alsa-devel ML seems stopped by some reason, so it's anyway useless :)
It was quite simple: I used a few new fields in struct via_spec and two input mux controls, one for hp and one for front (to have a fine grained control and disable/enable aa path independently for those lines - and test if it could work this way), called "Direct Output" and "HP Direct Output" (I chose names thinking about the sound coming straight from analog inputs and going to analog outputs through the AA mixer, but could be, for instance, "({Front, HP}) Output Mode" (or "Output Source") with items labeled "Mixed with input" (or "Stereo mixer") and "Playback only", or the like). Corresponding controls callbacks worked on the mixer connected to each output jack and getting input from a dac (through an audio selector) and from the aa mixer, choosing the right mixer basing on the control they were invoked for, and the corresponding values in via_spec new fields: .get() returned the (negated) value of the mute bit of aa mixer connection in the chosen mixer connection list, and .put() muted/un-muted such a connection if in a different state (a better behavior would be to alternatively mute/unmute the connection to the dac and the connection to the loopback mixer, taking care of hp state (working on aa mixer connection only for hp mixer while in independent mode), and, of course, there could be a single switch/mux control to enable/disable the aa path globally).
My reason to test such a solution was some noise I constantly got (and still get with newer code, but for Independent HP in latest version) from my headset microphone to each such line out (front and hp), but I can think, for instance, of someone wishing to listen to something while recording something else from line-in and not aiming to mix both sounds in output.
Yes, I think there are both type of users, too.
But I guess some problems could arise in actual implementation, unless I'm overvaluing/misunderstanding them.
First of all, now there's a pair of PCM Playback controls (switch and volume) attached to aa mixer and I'm not sure if muting connections to it could affect those controls (specially the volume). In such a case, they could be moved to the front dac, but this problem, if existing, could be re-raised for hp path in independent mode (in which case front dac and attached controls would be out of the game). An alternative could be leaving those controls where they're now, but reimplementing callbacks so that they could use a different nid (and work on more than one nid at a time). This way:
- the .put callback for turning aa-loopback on/off for a certain nid
(or all affected nids at once, if only one such control existed) would be responsible: 1. to record somewhere (accessible by via_spec) the nid(s) to work on (quite easy with separate controls attached to each affected mixer: the nid to gather would be just the actual one being unmuted in the two-entries connection list of passed-in kcontrol nid or the non loopback connection for hp mixer in independent mode), 2. to set the right volume (not just un-muting) for the non-loopback path(s) when switching to it (basing on pcm volume setting) and muting/unmuting the connection(s) to aa mixer as needed;
- the .put callback for adjusting pcm volume setting would work as now
for aa mixer nid (both to let it work as expected when used as source for output and to gather correct values from it, but also for correct handling of capture from stereo mixer), while performing same operations for each alternative nid, gathered from a proper list and being non-null and different from aa mixer nid;
- the .get callback for "PCM Playback Volume" could be left untouched
(to always gather infos from the 'base' nid the control is attacthed to).
Similar considerations could/should be done for the pcm switch, that (now, in my case) has the effect to mute/unmute front output (only).
However, I might be missing the point since I'm not sure how those controls are expected to work properly. Previously, with old code, I had no PCM Playback Switch and a "standalone" PCM Playback Volume, detached from any nid, and such seemed to work for all outputs, as far as I remember (I should re-install old implementation to make a test); alsa-info.sh reported following output for alsactl:
control.31 { iface MIXER name 'PCM Playback Volume' value.0 255 value.1 255 comment { access 'read write user' type INTEGER count 2 range '0 - 255' tlv '0000000100000008ffffec1400000014' dbmin -5100 dbmax 0 dbvalue.0 0 dbvalue.1 0 } }
Now I have both controls attached to 0x21, they seem to have effect on front path only (throughout aa mixer - previously its connection to front dac, for my codec, was just un-muted with an initialization verb) and alsa-info.sh reports:
control.9 { iface MIXER name 'PCM Playback Volume' value.0 22 value.1 22 comment { access 'read write' type INTEGER count 2 range '0 - 31' dbmin -3450 dbmax 1200 dbvalue.0 -150 dbvalue.1 -150 } } control.10 { iface MIXER name 'PCM Playback Switch' value.0 true value.1 true comment { access 'read write' type BOOLEAN count 2 } }
Similar concerns could be raised for Master Playback controls, given some codecs (but not mine - vt2020) holds them via their aa mixer nid.
Indeed how to name the control element in such a case is a difficult problem. Since the role of the mixer element changes depending on the mode, it can't be simply named. The current naming with "PCM" is actually confusing.
Once when we implement the loopback mode-switch, I'd name it like "Loopback PCM" or such, to make clear that it belongs only to a loopback path.
Another, possibly minor, problem, as pointed out in Lydia's answer, could be the somewhat inconsistence introduced between newer codecs supporting solution C and older ones unable to mute their aa mixer without loosing their front dac (because their aa mixer is just between their front dac and their rear green jack, but also between front dac and hp jack when in redirected mode). Such could be simply disregarded, taken as a consequence of a different set of supported features; therefore, it should be possible to choose automatically whether to create the proper control(s) for turning on/off aa-loopback or not while parsing output paths, looking at the connection lists of nids in a certain path, starting from an output jack and moving towards a dac, and looking if an element (a mixer or the jack nid itself) is found with at least two connections, one of which should be the aa mixer nid and the other one either the front dac nid, or an audio selector connected to it. For hp path the non-loopback connection shouldn't be hp dac directly (not passing through a selector), or the implementation would conflict with redirected output if such a dac were shared, which is the case for several codecs, specially older ones (newer ones seem to use an audio selector, instead, for front and hp), though I'm not sure if that's the same for all such codecs - hp_indep_shared could tell that, so that a few older codecs could support solution C for hp only, but in such a case solution C would behave mostly as B in current implementation, but for redirected mode, given front stream would be 'cloned' for hp dac if not shared, so hp could get loopback mixed in or not at whim, even if such would be inconsistent with front being always mixed with loopback).
Yeah, more small glitches may come out. Let's see step by step :)
thanks,
Takashi
2011/7/11 Takashi Iwai tiwai@suse.de:
At Sun, 10 Jul 2011 16:55:45 +0200, alex dot baldacchino dot alsasub at gmail dot com wrote:
[...]
Personally, if I'm not misunderstanding it, I'd vote for C. I had tried something similar for old implementation, and can tell it worked and was easy enough to implement, AFAICT (I can resend the code if found of any interest, since it seems to me my first mail about it went lost, at least that's not in this mailing list archives, though that's based on the old code, and could be useful only to exchange ideas).
A patch will be always useful, even for the older code.
Since early May I've been using the same snapshot from tarballs, until switching to the reworked implementation. However, by the time I've made several tries and there's more stuff there than loopback switching. Part of that staff is either useless (such as a few calls to printk() I used to clear out a few doubts, some of which I had left behind) or mistaken/unneeded (such as validating the result of snd_ctl_get_ioffidx() in via_mux_enum_{get,put}), or in a too early stage/untested (for instance, I was trying to replace 'hard-coded' and (partly) duplicated switch statements in is_aa_path_mute() and mute_aa_path() with calls to a function, to unify and simplify both functions and make maintenance a bit easier, specially if adding a new codec, but found those switches were different, and wasn't sure whether they should have been identical - as I thought and still think - or if there were really some codecs providing infos on actual state but unable to set the mute bit, and vice-versa, then the implementation changed radically, thus I gave up with that - and anyway I wasn't able to test it properly since I have no smart51 control). In general, most of that code is meaningless, because superseded by actual code, even when it was (seemingly) working (for instance, I was trying to make part of hp handling more uniform by creating the same input mux for all codecs, adding a field named 'hp_redirected_mode_index' to struct via_spec and computing its value basing on a simple assumption: main control 'Independent HP' was attached either to hp jack nid (for older codecs) or to an audio selector(for newer ones); in the former case, hp jack is connected to hp dac and to aa mixer, in turn connected to front dac, in the latter the audio selector is connected to front and hp dac directly, thus I was using a function, called by via_hp_build, to scan connection lists in search of front dac, up to one level in depth, starting at the control nid - such would have been easier to implement by setting the right index in each patch_vtxxx() but I didn't find alsa-info.sh data for all VIA codecs - this way, via_hp_put had just to choose between independent and redirected index (beside the rest of its implementation) for all codecs and create_hp_imux() was usable for VT1702 as well as all other codecs).
Actually, I'm basing my (kind of reference) patch on code resulting after commit ad2409413d09fca763be1ac5161f2a9d82367903, which seems to be the last version before major changes in parsing and still close to the implementation I was basing on, so I think I can use it both to produce a more readable patch (to show my implementation of loopback switching in this form) and to take apart the other stuff (part of which I can send as a separate patch).
Anyway, my implementation of loopback mode switching was aimed to those newer codecs using an intermediate mixer to connect to both aa-mixer and to dacs (via an audio selector).
Regarding ML archive: are you subscribed to ML? alsa-devel ML is basically only for subscribers, and posts from non-subscribers are postponed, manually approved. Sometimes the approval work delayed and the posts are canceled.
But, currently alsa-devel ML seems stopped by some reason, so it's anyway useless :)
Yes, I've created this mail account for subscribing to alsa-devel ML, after failing to send a mail as non-subscriber from a different account (and provider).
[...] My reason to test such a solution was some noise I constantly got (and still get with newer code, but for Independent HP in latest version) from my headset microphone to each such line out (front and hp), but I can think, for instance, of someone wishing to listen to something while recording something else from line-in and not aiming to mix both sounds in output.
Yes, I think there are both type of users, too.
And a 'loopback mode' for playback would be consistent with capture options allowing to record from 'stereo mixer' or not.
[...]
Indeed how to name the control element in such a case is a difficult problem. Since the role of the mixer element changes depending on the mode, it can't be simply named. The current naming with "PCM" is actually confusing.
Once when we implement the loopback mode-switch, I'd name it like "Loopback PCM" or such, to make clear that it belongs only to a loopback path.
Understood. Perhaps I don't remember well and the old (stand alone) PCM Volume, in old implementation, just influenced front/aa mixer gains. And, perhaps, the names of inputs' switch and volume controls attached to aa mixer could be misleading for someone, since they only affect this mixer connections and not input lines themselves, while an average user could think those controls can set values also for recording from each single input and not only for stereo mixer (in fact, each of those switches could be used as a 'tricky' mean to change loopback mode for each single connection to aa mixer while preserving the possibility to record from a single input line, but such would compromise/limit the full effectiveness of stereo mixer capturing).
Attaching a tarball with two diff files. File 'patch_via.aamode.diff' contains only my attempt to implement a mode switch for aa path (two input muxs for front and hp, working independently), but for some lines shared with the other patch and for a few fields in struct via_spec, and is based on commit ad2409413d09fca763be1ac5161f2a9d82367903. File 'patch_via.other.diff' contains other stuff, with some pieces of code that were suggested and/or inspired by Raymond Yau (like testing effectiveness of nid 0x0b as hp dac), and is based on aforementioned commit with patch_via.aamode.diff applied to it.
thanks,
Takashi
Thanks,
Alex
Hi,
here are some updates for the new HP-path handling.
Thanks to Lydia's debugging effort, the unexpected mute of the front jack was fixed now.
And now I also changed the hp-indep HP setup more dynamically, i.e. the DAC is switched at the time when the mixer enum is changed on the fly. In this way, you'll get no longer -EBUSY.
The patches will follow after this mail.
Both patches are now committed to topic/hda branch, and the latest alsa-driver snapshot should include these.
thanks,
Takashi
This patch adds the dynamic control of analog-loopback for VIA codecs.
When the loopback is enabled, the inputs from line-ins and mics are mixed with the front DAC, and sent to the front outputs. The very same input is routed to the headhpones and speakers in loopback mode. However, since the loopback mix can't take other than the front DAC, there is no longer individual volume controls for headphones and speakers. Once when the loopback control is off, these volumes take effect.
Since the individual volumes are more desired in general use caess, the loopback mode is set to off as default for now.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/patch_via.c | 400 +++++++++++++++++++++++++++++---------------- 1 files changed, 260 insertions(+), 140 deletions(-)
diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index 5232abc..76c6884 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -130,13 +130,28 @@ struct via_spec { struct hda_multi_out multiout; hda_nid_t slave_dig_outs[2]; hda_nid_t hp_dac_nid; - bool hp_indep_shared; /* indep HP-DAC is shared with side ch */ + hda_nid_t speaker_dac_nid; + int hp_indep_shared; /* indep HP-DAC is shared with side ch */ int num_active_streams; - + int aamix_mode; /* loopback is enabled for output-path? */ + + /* Output-paths: + * There are different output-paths depending on the setup. + * out_path, hp_path and speaker_path are primary paths. If both + * direct DAC and aa-loopback routes are available, these contain + * the former paths. Meanwhile *_mix_path contain the paths with + * loopback mixer. (Since the loopback is only for front channel, + * no out_mix_path for surround channels.) + * The HP output has another path, hp_indep_path, which is used in + * the independent-HP mode. + */ struct nid_path out_path[HDA_SIDE + 1]; + struct nid_path out_mix_path; struct nid_path hp_path; - struct nid_path hp_dep_path; + struct nid_path hp_mix_path; + struct nid_path hp_indep_path; struct nid_path speaker_path; + struct nid_path speaker_mix_path;
/* capture */ unsigned int num_adc_nids; @@ -437,50 +452,20 @@ static bool check_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, #define have_mute(codec, nid, dir) \ check_amp_caps(codec, nid, dir, AC_AMPCAP_MUTE)
-static bool is_node_in_path(struct nid_path *path, hda_nid_t nid) -{ - int i; - if (!nid) - return false; - for (i = 0; i < path->depth; i++) { - if (path->path[i] == nid) - return true; - } - return false; -} - /* enable/disable the output-route mixers */ static void activate_output_mix(struct hda_codec *codec, struct nid_path *path, - hda_nid_t mix_nid, int aa_mix_idx, bool enable) + hda_nid_t mix_nid, int idx, bool enable) { int i, num, val; - bool hp_path, front_path; - struct via_spec *spec = codec->spec;
if (!path) return; num = snd_hda_get_conn_list(codec, mix_nid, NULL); - hp_path = is_node_in_path(path, spec->hp_dac_nid); - front_path = is_node_in_path(path, spec->multiout.dac_nids[0]); - for (i = 0; i < num; i++) { - if (i == aa_mix_idx) { - if (hp_path) - val = enable ? AMP_IN_MUTE(i) : - AMP_IN_UNMUTE(i); - else if (front_path) - val = AMP_IN_UNMUTE(i); - else - val = AMP_IN_MUTE(i); - } else { - if (hp_path) - val = enable ? AMP_IN_UNMUTE(i) : - AMP_IN_MUTE(i); - else if (front_path) - val = AMP_IN_MUTE(i); - else - val = AMP_IN_UNMUTE(i); - } + if (i == idx) + val = AMP_IN_UNMUTE(i); + else + val = AMP_IN_MUTE(i); snd_hda_codec_write(codec, mix_nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, val); } @@ -490,9 +475,8 @@ static void activate_output_mix(struct hda_codec *codec, struct nid_path *path, static void activate_output_path(struct hda_codec *codec, struct nid_path *path, bool enable, bool force) { - int i, val; struct via_spec *spec = codec->spec; - hda_nid_t aa_mix_nid = spec->aa_mix_nid; + int i; for (i = 0; i < path->depth; i++) { hda_nid_t src, dst; int idx = path->idx[i]; @@ -504,25 +488,10 @@ static void activate_output_path(struct hda_codec *codec, struct nid_path *path, if (enable && path->multi[i]) snd_hda_codec_write(codec, dst, 0, AC_VERB_SET_CONNECT_SEL, idx); - if (!force - && get_wcaps_type(get_wcaps(codec, src)) == AC_WID_AUD_OUT - && get_wcaps_type(get_wcaps(codec, dst)) == AC_WID_AUD_MIX) + if (!force && (dst == spec->aa_mix_nid)) continue; - if (have_mute(codec, dst, HDA_INPUT)) { - if (dst == aa_mix_nid) { - val = enable ? AMP_IN_UNMUTE(idx) : - AMP_IN_MUTE(idx); - snd_hda_codec_write(codec, dst, 0, - AC_VERB_SET_AMP_GAIN_MUTE, val); - } else { - idx = get_connection_index(codec, dst, - aa_mix_nid); - if (idx >= 0) { - activate_output_mix(codec, path, - dst, idx, enable); - } - } - } + if (have_mute(codec, dst, HDA_INPUT)) + activate_output_mix(codec, path, dst, idx, enable); if (!force && (src == path->vol_ctl || src == path->mute_ctl)) continue; if (have_mute(codec, src, HDA_OUTPUT)) { @@ -548,9 +517,8 @@ static void init_output_pin(struct hda_codec *codec, hda_nid_t pin,
static void via_auto_init_output(struct hda_codec *codec, struct nid_path *path, int pin_type, - bool with_aa_mix, bool force) + bool force) { - struct via_spec *spec = codec->spec; unsigned int caps; hda_nid_t pin;
@@ -566,41 +534,45 @@ static void via_auto_init_output(struct hda_codec *codec, snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE | val); } - - /* initialize the AA-path */ - if (!spec->aa_mix_nid) - return; activate_output_path(codec, path, true, force); }
static void via_auto_init_multi_out(struct hda_codec *codec) { struct via_spec *spec = codec->spec; + struct nid_path *path; int i;
- for (i = 0; i < spec->autocfg.line_outs + spec->smart51_nums; i++) - /* enable aa-mute only for the front channel */ - via_auto_init_output(codec, &spec->out_path[i], PIN_OUT, - i == 0, true); + for (i = 0; i < spec->autocfg.line_outs + spec->smart51_nums; i++) { + path = &spec->out_path[i]; + if (!i && spec->aamix_mode && spec->out_mix_path.depth) + path = &spec->out_mix_path; + via_auto_init_output(codec, path, PIN_OUT, true); + } }
static void via_auto_init_hp_out(struct hda_codec *codec) { struct via_spec *spec = codec->spec; + int shared = spec->hp_indep_shared;
- if (!spec->hp_dac_nid) { - via_auto_init_output(codec, &spec->hp_dep_path, PIN_HP, - true, true); + if (!spec->hp_path.depth) { + via_auto_init_output(codec, &spec->hp_mix_path, PIN_HP, true); return; } if (spec->hp_independent_mode) { - activate_output_path(codec, &spec->hp_dep_path, false, false); - via_auto_init_output(codec, &spec->hp_path, PIN_HP, - true, true); - } else { activate_output_path(codec, &spec->hp_path, false, false); - via_auto_init_output(codec, &spec->hp_dep_path, PIN_HP, - true, true); + activate_output_path(codec, &spec->hp_mix_path, false, false); + if (shared) + activate_output_path(codec, &spec->out_path[shared], + false, false); + via_auto_init_output(codec, &spec->hp_indep_path, PIN_HP, true); + } else if (spec->aamix_mode) { + activate_output_path(codec, &spec->hp_path, false, false); + via_auto_init_output(codec, &spec->hp_mix_path, PIN_HP, true); + } else { + activate_output_path(codec, &spec->hp_mix_path, false, false); + via_auto_init_output(codec, &spec->hp_path, PIN_HP, true); } }
@@ -608,9 +580,23 @@ static void via_auto_init_speaker_out(struct hda_codec *codec) { struct via_spec *spec = codec->spec;
- if (spec->autocfg.speaker_outs) + if (!spec->autocfg.speaker_outs) + return; + if (!spec->speaker_path.depth) { + via_auto_init_output(codec, &spec->speaker_mix_path, PIN_OUT, + true); + return; + } + if (!spec->aamix_mode) { + activate_output_path(codec, &spec->speaker_mix_path, + false, false); via_auto_init_output(codec, &spec->speaker_path, PIN_OUT, - true, true); + true); + } else { + activate_output_path(codec, &spec->speaker_path, false, false); + via_auto_init_output(codec, &spec->speaker_mix_path, PIN_OUT, + true); + } }
static bool is_smart51_pins(struct hda_codec *codec, hda_nid_t pin); @@ -775,7 +761,7 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol, { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct via_spec *spec = codec->spec; - int cur; + int cur, shared;
/* no independent-hp status change during PCM playback is running */ if (spec->num_active_streams) @@ -785,18 +771,19 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol, if (spec->hp_independent_mode == cur) return 0; spec->hp_independent_mode = cur; + shared = spec->hp_indep_shared; if (cur) { - activate_output_path(codec, &spec->hp_dep_path, false, false); - activate_output_path(codec, &spec->hp_path, true, false); - if (spec->hp_indep_shared) - activate_output_path(codec, &spec->out_path[HDA_SIDE], + activate_output_path(codec, &spec->hp_mix_path, false, false); + if (shared) + activate_output_path(codec, &spec->out_path[shared], false, false); + activate_output_path(codec, &spec->hp_path, true, false); } else { activate_output_path(codec, &spec->hp_path, false, false); - activate_output_path(codec, &spec->hp_dep_path, true, false); - if (spec->hp_indep_shared) - activate_output_path(codec, &spec->out_path[HDA_SIDE], + if (shared) + activate_output_path(codec, &spec->out_path[shared], true, false); + activate_output_path(codec, &spec->hp_mix_path, true, false); }
/* update jack power state */ @@ -1671,29 +1658,38 @@ static bool is_empty_dac(struct hda_codec *codec, hda_nid_t dac) }
static bool __parse_output_path(struct hda_codec *codec, hda_nid_t nid, - hda_nid_t target_dac, struct nid_path *path, - int depth, int wid_type) + hda_nid_t target_dac, int with_aa_mix, + struct nid_path *path, int depth) { + struct via_spec *spec = codec->spec; hda_nid_t conn[8]; int i, nums;
+ if (nid == spec->aa_mix_nid) { + if (!with_aa_mix) + return false; + with_aa_mix = 2; /* mark aa-mix is included */ + } + nums = snd_hda_get_connections(codec, nid, conn, ARRAY_SIZE(conn)); for (i = 0; i < nums; i++) { if (get_wcaps_type(get_wcaps(codec, conn[i])) != AC_WID_AUD_OUT) continue; - if (conn[i] == target_dac || is_empty_dac(codec, conn[i])) - goto found; + if (conn[i] == target_dac || is_empty_dac(codec, conn[i])) { + /* aa-mix is requested but not included? */ + if (!(spec->aa_mix_nid && with_aa_mix == 1)) + goto found; + } } 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 || - (wid_type != -1 && type != wid_type)) + if (type == AC_WID_AUD_OUT) continue; if (__parse_output_path(codec, conn[i], target_dac, - path, depth + 1, AC_WID_AUD_SEL)) + with_aa_mix, path, depth + 1)) goto found; } return false; @@ -1708,11 +1704,15 @@ static bool __parse_output_path(struct hda_codec *codec, hda_nid_t nid, }
static bool parse_output_path(struct hda_codec *codec, hda_nid_t nid, - hda_nid_t target_dac, struct nid_path *path) + hda_nid_t target_dac, int with_aa_mix, + struct nid_path *path) { - if (__parse_output_path(codec, nid, target_dac, path, 1, -1)) { + if (__parse_output_path(codec, nid, target_dac, with_aa_mix, path, 1)) { path->path[path->depth] = nid; path->depth++; + snd_printdd("output-path: depth=%d, %02x/%02x/%02x/%02x/%02x\n", + path->depth, path->path[0], path->path[1], + path->path[2], path->path[3], path->path[4]); return true; } return false; @@ -1728,14 +1728,24 @@ static int via_auto_fill_dac_nids(struct hda_codec *codec) spec->multiout.dac_nids = spec->private_dac_nids; dac_num = 0; for (i = 0; i < cfg->line_outs; i++) { + hda_nid_t dac = 0; nid = cfg->line_out_pins[i]; if (!nid) continue; - if (parse_output_path(codec, nid, 0, &spec->out_path[i])) { - spec->private_dac_nids[i] = spec->out_path[i].path[0]; + if (parse_output_path(codec, nid, 0, 0, &spec->out_path[i])) + dac = spec->out_path[i].path[0]; + if (!i && parse_output_path(codec, nid, dac, 1, + &spec->out_mix_path)) + dac = spec->out_mix_path.path[0]; + if (dac) { + spec->private_dac_nids[i] = dac; dac_num++; } } + if (!spec->out_path[0].depth && spec->out_mix_path.depth) { + spec->out_path[0] = spec->out_mix_path; + spec->out_mix_path.depth = 0; + } spec->multiout.num_dacs = dac_num; return 0; } @@ -1832,6 +1842,7 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec) { struct via_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; + struct nid_path *path; static const char * const chname[4] = { "Front", "Surround", "C/LFE", "Side" }; @@ -1857,13 +1868,12 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec) dac = spec->multiout.dac_nids[i]; if (!pin || !dac) continue; + path = spec->out_path + i; if (i == HDA_CLFE) { - err = create_ch_ctls(codec, "Center", 1, true, - &spec->out_path[i]); + err = create_ch_ctls(codec, "Center", 1, true, path); if (err < 0) return err; - err = create_ch_ctls(codec, "LFE", 2, true, - &spec->out_path[i]); + err = create_ch_ctls(codec, "LFE", 2, true, path); if (err < 0) return err; } else { @@ -1871,25 +1881,35 @@ static int via_auto_create_multi_out_ctls(struct hda_codec *codec) if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT && cfg->line_outs == 1) pfx = "Speaker"; - err = create_ch_ctls(codec, pfx, 3, true, - &spec->out_path[i]); + err = create_ch_ctls(codec, pfx, 3, true, path); if (err < 0) return err; } + if (path != spec->out_path + i) { + spec->out_path[i].vol_ctl = path->vol_ctl; + spec->out_path[i].mute_ctl = path->mute_ctl; + } + if (path == spec->out_path && spec->out_mix_path.depth) { + spec->out_mix_path.vol_ctl = path->vol_ctl; + spec->out_mix_path.mute_ctl = path->mute_ctl; + } }
idx = get_connection_index(codec, spec->aa_mix_nid, spec->multiout.dac_nids[0]); if (idx >= 0) { /* add control to mixer */ - err = via_add_control(spec, VIA_CTL_WIDGET_VOL, - "PCM Playback Volume", + const char *name; + name = spec->out_mix_path.depth ? + "PCM Loopback Playback Volume" : "PCM Playback Volume"; + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, HDA_COMPOSE_AMP_VAL(spec->aa_mix_nid, 3, idx, HDA_INPUT)); if (err < 0) return err; - err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, - "PCM Playback Switch", + name = spec->out_mix_path.depth ? + "PCM Loopback Playback Switch" : "PCM Playback Switch"; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, HDA_COMPOSE_AMP_VAL(spec->aa_mix_nid, 3, idx, HDA_INPUT)); if (err < 0) @@ -1906,70 +1926,167 @@ static int via_auto_create_hp_ctls(struct hda_codec *codec, hda_nid_t pin) struct via_spec *spec = codec->spec; struct nid_path *path; bool check_dac; - int err; + int i, err;
if (!pin) return 0;
- if (parse_output_path(codec, pin, 0, &spec->hp_path)) - spec->hp_dac_nid = spec->hp_path.path[0]; - else if (spec->multiout.dac_nids[HDA_SIDE] && - parse_output_path(codec, pin, - spec->multiout.dac_nids[HDA_SIDE], - &spec->hp_path)) { - spec->hp_dac_nid = spec->hp_path.path[0]; - spec->hp_indep_shared = true; - } else if (spec->multiout.dac_nids[HDA_CLFE] && - parse_output_path(codec, pin, - spec->multiout.dac_nids[HDA_CLFE], - &spec->hp_path)) { - spec->hp_dac_nid = spec->hp_path.path[0]; - spec->hp_indep_shared = true; + if (!parse_output_path(codec, pin, 0, 0, &spec->hp_indep_path)) { + for (i = HDA_SIDE; i >= HDA_CLFE; i--) { + if (i < spec->multiout.num_dacs && + parse_output_path(codec, pin, + spec->multiout.dac_nids[i], 0, + &spec->hp_indep_path)) { + spec->hp_indep_shared = i; + break; + } + } } + if (spec->hp_indep_path.depth) { + spec->hp_dac_nid = spec->hp_indep_path.path[0]; + if (!spec->hp_indep_shared) + spec->hp_path = spec->hp_indep_path; + } + /* optionally check front-path w/o AA-mix */ + if (!spec->hp_path.depth) + parse_output_path(codec, pin, + spec->multiout.dac_nids[HDA_FRONT], 0, + &spec->hp_path);
if (!parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT], - &spec->hp_dep_path) && - !spec->hp_dac_nid) + 1, &spec->hp_mix_path) && !spec->hp_path.depth) return 0;
- if (spec->hp_dac_nid && !spec->hp_indep_shared) { + if (spec->hp_path.depth) { path = &spec->hp_path; check_dac = true; } else { - path = &spec->hp_dep_path; + path = &spec->hp_mix_path; check_dac = false; } err = create_ch_ctls(codec, "Headphone", 3, check_dac, path); if (err < 0) return err; - if (spec->hp_dac_nid) { - spec->hp_dep_path.vol_ctl = spec->hp_path.vol_ctl; - spec->hp_dep_path.mute_ctl = spec->hp_path.mute_ctl; + if (check_dac) { + spec->hp_mix_path.vol_ctl = path->vol_ctl; + spec->hp_mix_path.mute_ctl = path->mute_ctl; + } else { + spec->hp_path.vol_ctl = path->vol_ctl; + spec->hp_path.mute_ctl = path->mute_ctl; } - return 0; }
static int via_auto_create_speaker_ctls(struct hda_codec *codec) { struct via_spec *spec = codec->spec; + struct nid_path *path; + bool check_dac; hda_nid_t pin, dac; + int err;
pin = spec->autocfg.speaker_pins[0]; if (!spec->autocfg.speaker_outs || !pin) return 0;
- if (parse_output_path(codec, pin, 0, &spec->speaker_path)) { + if (parse_output_path(codec, pin, 0, 0, &spec->speaker_path)) dac = spec->speaker_path.path[0]; - spec->multiout.extra_out_nid[0] = dac; - return create_ch_ctls(codec, "Speaker", 3, true, - &spec->speaker_path); + if (!dac) + parse_output_path(codec, pin, + spec->multiout.dac_nids[HDA_FRONT], 0, + &spec->speaker_path); + if (!parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT], + 1, &spec->speaker_mix_path) && !dac) + return 0; + + /* no AA-path for front? */ + if (!spec->out_mix_path.depth && spec->speaker_mix_path.depth) + dac = 0; + + spec->speaker_dac_nid = dac; + spec->multiout.extra_out_nid[0] = dac; + if (dac) { + path = &spec->speaker_path; + check_dac = true; + } else { + path = &spec->speaker_mix_path; + check_dac = false; + } + err = create_ch_ctls(codec, "Speaker", 3, check_dac, path); + if (err < 0) + return err; + if (check_dac) { + spec->speaker_mix_path.vol_ctl = path->vol_ctl; + spec->speaker_mix_path.mute_ctl = path->mute_ctl; + } else { + spec->speaker_path.vol_ctl = path->vol_ctl; + spec->speaker_path.mute_ctl = path->mute_ctl; } - if (parse_output_path(codec, pin, spec->multiout.dac_nids[HDA_FRONT], - &spec->speaker_path)) - return create_ch_ctls(codec, "Speaker", 3, false, - &spec->speaker_path); + return 0; +} + +#define via_aamix_ctl_info via_pin_power_ctl_info
+static int via_aamix_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + ucontrol->value.enumerated.item[0] = spec->aamix_mode; + return 0; +} + +static void update_aamix_paths(struct hda_codec *codec, int do_mix, + struct nid_path *nomix, struct nid_path *mix) +{ + if (do_mix) { + activate_output_path(codec, nomix, false, false); + activate_output_path(codec, mix, true, false); + } else { + activate_output_path(codec, mix, false, false); + activate_output_path(codec, nomix, true, false); + } +} + +static int via_aamix_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val == spec->aamix_mode) + return 0; + spec->aamix_mode = val; + /* update front path */ + update_aamix_paths(codec, val, &spec->out_path[0], &spec->out_mix_path); + /* update HP path */ + if (!spec->hp_independent_mode) { + update_aamix_paths(codec, val, &spec->hp_path, + &spec->hp_mix_path); + } + /* update speaker path */ + update_aamix_paths(codec, val, &spec->speaker_path, + &spec->speaker_mix_path); + return 1; +} + +static const struct snd_kcontrol_new via_aamix_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Mixing", + .info = via_aamix_ctl_info, + .get = via_aamix_ctl_get, + .put = via_aamix_ctl_put, +}; + +static int via_auto_create_loopback_switch(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + + if (!spec->aa_mix_nid || !spec->out_mix_path.depth) + return 0; /* no loopback switching available */ + if (!via_clone_control(spec, &via_aamix_ctl_enum)) + return -ENOMEM; return 0; }
@@ -2440,6 +2557,9 @@ static int via_parse_auto_config(struct hda_codec *codec) err = via_auto_create_speaker_ctls(codec); if (err < 0) return err; + err = via_auto_create_loopback_switch(codec); + if (err < 0) + return err; err = via_auto_create_analog_input_ctls(codec); if (err < 0) return err; @@ -2453,7 +2573,7 @@ static int via_parse_auto_config(struct hda_codec *codec) spec->mixers[spec->num_mixers++] = spec->kctls.list;
- if (spec->hp_dac_nid && spec->hp_dep_path.depth) { + if (spec->hp_dac_nid && spec->hp_mix_path.depth) { err = via_hp_build(codec); if (err < 0) return err;
This patch changes the behavior of independent-HP enum switch. Now instead of returning a busy error, the driver switches dynamically the stream of the HP (and shared) DACs according to the current mode. The logic is similar like the dual-mic ADC switch, but a bit more complicated because of the presence of shared DAC.
Together with the change, a mutex is introduced to protect against the possible races for the indep-HP mode setting.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/patch_via.c | 159 +++++++++++++++++++++++++++++++++++---------- 1 files changed, 125 insertions(+), 34 deletions(-)
diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index 76c6884..5b03426 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -109,6 +109,11 @@ struct via_input {
#define VIA_MAX_ADCS 3
+enum { + STREAM_MULTI_OUT = (1 << 0), + STREAM_INDEP_HP = (1 << 1), +}; + struct via_spec { /* codec parameterization */ const struct snd_kcontrol_new *mixers[6]; @@ -132,7 +137,8 @@ struct via_spec { hda_nid_t hp_dac_nid; hda_nid_t speaker_dac_nid; int hp_indep_shared; /* indep HP-DAC is shared with side ch */ - int num_active_streams; + int opened_streams; /* STREAM_* bits */ + int active_streams; /* STREAM_* bits */ int aamix_mode; /* loopback is enabled for output-path? */
/* Output-paths: @@ -166,6 +172,12 @@ struct via_spec { struct via_input inputs[AUTO_CFG_MAX_INS + 1]; unsigned int cur_mux[VIA_MAX_ADCS];
+ /* dynamic DAC switching */ + unsigned int cur_dac_stream_tag; + unsigned int cur_dac_format; + unsigned int cur_hp_stream_tag; + unsigned int cur_hp_format; + /* dynamic ADC switching */ hda_nid_t cur_adc; unsigned int cur_adc_stream_tag; @@ -207,6 +219,8 @@ struct via_spec { /* bind capture-volume */ struct hda_bind_ctls *bind_cap_vol; struct hda_bind_ctls *bind_cap_sw; + + struct mutex config_mutex; };
static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec); @@ -218,6 +232,7 @@ static struct via_spec * via_new_spec(struct hda_codec *codec) if (spec == NULL) return NULL;
+ mutex_init(&spec->config_mutex); codec->spec = spec; spec->codec = codec; spec->codec_type = get_codec_type(codec); @@ -756,6 +771,67 @@ static int via_independent_hp_get(struct snd_kcontrol *kcontrol, return 0; }
+/* adjust spec->multiout setup according to the current flags */ +static void setup_playback_multi_pcm(struct via_spec *spec) +{ + const struct auto_pin_cfg *cfg = &spec->autocfg; + spec->multiout.num_dacs = cfg->line_outs + spec->smart51_nums; + spec->multiout.hp_nid = 0; + if (!spec->hp_independent_mode) { + if (!spec->hp_indep_shared) + spec->multiout.hp_nid = spec->hp_dac_nid; + } else { + if (spec->hp_indep_shared) + spec->multiout.num_dacs = cfg->line_outs - 1; + } +} + +/* update DAC setups according to indep-HP switch; + * this function is called only when indep-HP is modified + */ +static void switch_indep_hp_dacs(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int shared = spec->hp_indep_shared; + hda_nid_t shared_dac, hp_dac; + + if (!spec->opened_streams) + return; + + shared_dac = shared ? spec->multiout.dac_nids[shared] : 0; + hp_dac = spec->hp_dac_nid; + if (spec->hp_independent_mode) { + /* switch to indep-HP mode */ + if (spec->active_streams & STREAM_MULTI_OUT) { + __snd_hda_codec_cleanup_stream(codec, hp_dac, 1); + __snd_hda_codec_cleanup_stream(codec, shared_dac, 1); + } + if (spec->active_streams & STREAM_INDEP_HP) + snd_hda_codec_setup_stream(codec, hp_dac, + spec->cur_hp_stream_tag, 0, + spec->cur_hp_format); + } else { + /* back to HP or shared-DAC */ + if (spec->active_streams & STREAM_INDEP_HP) + __snd_hda_codec_cleanup_stream(codec, hp_dac, 1); + if (spec->active_streams & STREAM_MULTI_OUT) { + hda_nid_t dac; + int ch; + if (shared_dac) { /* reset mutli-ch DAC */ + dac = shared_dac; + ch = shared * 2; + } else { /* reset HP DAC */ + dac = hp_dac; + ch = 0; + } + snd_hda_codec_setup_stream(codec, dac, + spec->cur_dac_stream_tag, ch, + spec->cur_dac_format); + } + } + setup_playback_multi_pcm(spec); +} + static int via_independent_hp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -763,13 +839,12 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol, struct via_spec *spec = codec->spec; int cur, shared;
- /* no independent-hp status change during PCM playback is running */ - if (spec->num_active_streams) - return -EBUSY; - + mutex_lock(&spec->config_mutex); cur = !!ucontrol->value.enumerated.item[0]; - if (spec->hp_independent_mode == cur) + if (spec->hp_independent_mode == cur) { + mutex_unlock(&spec->config_mutex); return 0; + } spec->hp_independent_mode = cur; shared = spec->hp_indep_shared; if (cur) { @@ -786,6 +861,9 @@ static int via_independent_hp_put(struct snd_kcontrol *kcontrol, activate_output_path(codec, &spec->hp_mix_path, true, false); }
+ switch_indep_hp_dacs(codec); + mutex_unlock(&spec->config_mutex); + /* update jack power state */ set_widgets_power_state(codec); via_hp_automute(codec); @@ -948,7 +1026,7 @@ static void analog_low_current_mode(struct hda_codec *codec) bool enable; unsigned int verb, parm;
- enable = is_aa_path_mute(codec) && (spec->num_active_streams > 0); + enable = is_aa_path_mute(codec) && (spec->opened_streams != 0);
/* decide low current mode's verb & parameter */ switch (spec->codec_type) { @@ -989,14 +1067,14 @@ static const struct hda_verb vt1708_init_verbs[] = { { } };
-static void set_stream_active(struct hda_codec *codec, bool active) +static void set_stream_open(struct hda_codec *codec, int bit, bool active) { struct via_spec *spec = codec->spec;
if (active) - spec->num_active_streams++; + spec->opened_streams |= bit; else - spec->num_active_streams--; + spec->opened_streams &= ~bit; analog_low_current_mode(codec); }
@@ -1008,22 +1086,13 @@ static int via_playback_multi_pcm_open(struct hda_pcm_stream *hinfo, const struct auto_pin_cfg *cfg = &spec->autocfg; int err;
- spec->multiout.hp_nid = 0; spec->multiout.num_dacs = cfg->line_outs + spec->smart51_nums; - if (!spec->hp_independent_mode) { - if (!spec->hp_indep_shared) - spec->multiout.hp_nid = spec->hp_dac_nid; - } else { - if (spec->hp_indep_shared) - spec->multiout.num_dacs = cfg->line_outs - 1; - } spec->multiout.max_channels = spec->multiout.num_dacs * 2; - set_stream_active(codec, true); + set_stream_open(codec, STREAM_MULTI_OUT, true); err = snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, hinfo); if (err < 0) { - spec->multiout.hp_nid = 0; - set_stream_active(codec, false); + set_stream_open(codec, STREAM_MULTI_OUT, false); return err; } return 0; @@ -1033,10 +1102,7 @@ static int via_playback_multi_pcm_close(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - struct via_spec *spec = codec->spec; - - spec->multiout.hp_nid = 0; - set_stream_active(codec, false); + set_stream_open(codec, STREAM_MULTI_OUT, false); return 0; }
@@ -1048,9 +1114,7 @@ static int via_playback_hp_pcm_open(struct hda_pcm_stream *hinfo,
if (snd_BUG_ON(!spec->hp_dac_nid)) return -EINVAL; - if (!spec->hp_independent_mode || spec->multiout.hp_nid) - return -EBUSY; - set_stream_active(codec, true); + set_stream_open(codec, STREAM_INDEP_HP, true); return 0; }
@@ -1058,7 +1122,7 @@ static int via_playback_hp_pcm_close(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - set_stream_active(codec, false); + set_stream_open(codec, STREAM_INDEP_HP, false); return 0; }
@@ -1070,8 +1134,15 @@ static int via_playback_multi_pcm_prepare(struct hda_pcm_stream *hinfo, { struct via_spec *spec = codec->spec;
+ mutex_lock(&spec->config_mutex); + setup_playback_multi_pcm(spec); snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag, format, substream); + /* remember for dynamic DAC switch with indep-HP */ + spec->active_streams |= STREAM_MULTI_OUT; + spec->cur_dac_stream_tag = stream_tag; + spec->cur_dac_format = format; + mutex_unlock(&spec->config_mutex); vt1708_start_hp_work(spec); return 0; } @@ -1084,8 +1155,14 @@ static int via_playback_hp_pcm_prepare(struct hda_pcm_stream *hinfo, { struct via_spec *spec = codec->spec;
- snd_hda_codec_setup_stream(codec, spec->hp_dac_nid, - stream_tag, 0, format); + mutex_lock(&spec->config_mutex); + if (spec->hp_independent_mode) + snd_hda_codec_setup_stream(codec, spec->hp_dac_nid, + stream_tag, 0, format); + spec->active_streams |= STREAM_INDEP_HP; + spec->cur_hp_stream_tag = stream_tag; + spec->cur_hp_format = format; + mutex_unlock(&spec->config_mutex); vt1708_start_hp_work(spec); return 0; } @@ -1096,7 +1173,10 @@ static int via_playback_multi_pcm_cleanup(struct hda_pcm_stream *hinfo, { struct via_spec *spec = codec->spec;
+ mutex_lock(&spec->config_mutex); snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); + spec->active_streams &= ~STREAM_MULTI_OUT; + mutex_unlock(&spec->config_mutex); vt1708_stop_hp_work(spec); return 0; } @@ -1107,7 +1187,11 @@ static int via_playback_hp_pcm_cleanup(struct hda_pcm_stream *hinfo, { struct via_spec *spec = codec->spec;
- snd_hda_codec_setup_stream(codec, spec->hp_dac_nid, 0, 0, 0); + mutex_lock(&spec->config_mutex); + if (spec->hp_independent_mode) + snd_hda_codec_setup_stream(codec, spec->hp_dac_nid, 0, 0, 0); + spec->active_streams &= ~STREAM_INDEP_HP; + mutex_unlock(&spec->config_mutex); vt1708_stop_hp_work(spec); return 0; } @@ -1186,10 +1270,12 @@ static int via_dyn_adc_capture_pcm_prepare(struct hda_pcm_stream *hinfo, struct via_spec *spec = codec->spec; int adc_idx = spec->inputs[spec->cur_mux[0]].adc_idx;
+ mutex_lock(&spec->config_mutex); spec->cur_adc = spec->adc_nids[adc_idx]; 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); + mutex_unlock(&spec->config_mutex); return 0; }
@@ -1199,8 +1285,10 @@ static int via_dyn_adc_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, { struct via_spec *spec = codec->spec;
+ mutex_lock(&spec->config_mutex); snd_hda_codec_cleanup_stream(codec, spec->cur_adc); spec->cur_adc = 0; + mutex_unlock(&spec->config_mutex); return 0; }
@@ -1210,7 +1298,9 @@ static bool via_dyn_adc_pcm_resetup(struct hda_codec *codec, int cur) struct via_spec *spec = codec->spec; int adc_idx = spec->inputs[cur].adc_idx; hda_nid_t adc = spec->adc_nids[adc_idx]; + bool ret = false;
+ mutex_lock(&spec->config_mutex); if (spec->cur_adc && spec->cur_adc != adc) { /* stream is running, let's swap the current ADC */ __snd_hda_codec_cleanup_stream(codec, spec->cur_adc, 1); @@ -1218,9 +1308,10 @@ static bool via_dyn_adc_pcm_resetup(struct hda_codec *codec, int cur) snd_hda_codec_setup_stream(codec, adc, spec->cur_adc_stream_tag, 0, spec->cur_adc_format); - return true; + ret = true; } - return false; + mutex_unlock(&spec->config_mutex); + return ret; }
static const struct hda_pcm_stream via_pcm_analog_playback = {
participants (2)
-
alex dot baldacchino dot alsasub at gmail dot com
-
Takashi Iwai