There are three ultimate propositions in Philosophy: Who are you? From where? Where to go? For the dapm mechanism, we can ask: What is dapm and what is its function? How is dapm created? How is dapm triggered? The following is an analysis of these three problems.
First, what is dapm? This is mentioned in the overview of dapm, that is, the dynamic management of audio power supply. I believe that power management is not unfamiliar to everyone. The purpose of the dapm design is to enable the necessary widgets only when necessary, and disable the components when not needed to save power, which is very important in portable devices. Here, "need" refers to operations such as audio playback, and "components" refer to ADC, DAC, PGA, mixer, and SPK. In addition, dapm also plays an important role, namely the audio path. In this regard, we believe that dapm 2: audio paths and dapm kcontrol are described in sufficient detail. Therefore, this article only analyzes the power supply management.
First, we analyze some important functions in the dapm module (source code soc-dapm.c:
1. dapm_update_bits
Update the reg value in a widget. Generally, you can consider it as a control widget.
Ii. snd_soc_update_bits
Update the value of the specified register Reg and position the mask.
3. dapm_set_pga
This function is called when dapm is triggered to reduce pop sound generated by power-on/power-off PGA. This function is analyzed in detail as follows:
/* Ramps the volume up or down to minimize POPs before or after a * dapm power event */static int dapm_set_pga (struct snd_soc_dapm_widget * widget, int power) {// get the kcontrol of the widget. This kcontrol should be used to adjust the volume of the PGA. Pay attention to this. // For PGA widgets, it is generally a single input, single output, with gain adjustment. Const struct snd_kcontrol_new * k = widget-> kcontrols; // check whether the widget is muted. As shown below, when the widget powers up, set muted to 0, otherwise, set 1if (widget-> muted &&! Power) return 0; If (! Widget-> muted & Power) return 0; If (widget-> num_kcontrols & K) {// perform the following operations to obtain the reg, shift, and mask of kcontrol, used to adjust the gain of the PGA widget. Struct soc_mixer_control * MC = (struct soc_mixer_control *) K-> private_value; unsigned int Reg = mc-> reg; unsigned int shift = mc-> shift; int max = MC-> MAX; unsigned int mask = (1 <FLS (max)-1; unsigned int invert = mc-> invert; If (power) {int I;/* power up has happended, increase volume to last level * // power up widget, the SAVE value is restored to the widget. Note: This is a step-by-step process. // It is gradually restored from 0 to the saved value saved_value. I think this is to reduce pop. If (invert) {for (I = max; I> widget-> saved_value; I --) snd_soc_update_bits (widget-> codec, Reg, mask, I );} else {for (I = 0; I <widget-> saved_value; I ++) snd_soc_update_bits (widget-> codec, Reg, mask, I );} widget-> muted = 0;} else {/* power down is about to occur, decrease volume to mute * // power down widget, save the current gain value of the widget, and gradually reduce the volume of the widget until 0. Int val = snd_soc_read (widget-> codec, Reg); int I = widget-> saved_value = (Val> shift) & mask; If (invert) {(; I <mask; I ++) snd_soc_update_bits (widget-> codec, Reg, mask, I) ;}else {for (; I> 0; I --) snd_soc_update_bits (widget-> codec, Reg, mask, I);} widget-> muted = 1 ;}} return 0 ;}
Here is a suggestion: When the PGA component is encountered, it is best to write this dapm widget according to the specification. The widget should contain the gain adjusted kcontrol, in this way, the system will reduce the pop sound that may be caused by power-on/power-off.
Conclusion: When triggered by dapm, dapm_set_pga is called to gradually increase/decrease the volume of PGA widgets, in order to reduce the pop sound that may be caused by power-on or power-off.
4. is_connected_output_ep
1. is_connected_output_ep and is_connected_input_ep, and their endpoints
Is_connected_output_ep is interesting. The widget goes all the way back until it reaches the output endpoint and counts the number of paths from the widget to endpoints. Similarly, is_connected_input_ep is used to obtain the number of paths for the widget to connect to the input endpoint, but the direction is the opposite. The purpose is to check the integrity of the path and ensure that the channel runs through the input endpoint and output endpoint.
What is output endpoint? Snd_soc_dapm_adc, snd_soc_dapm_aif_out, snd_soc_dapm_output, snd_soc_dapm_hp, snd_soc_dapm_spk, etc.
What is input endpoint? Snd_soc_dapm_dac, snd_soc_dapm_aif_in, snd_soc_dapm_input, snd_soc_dapm_vmid, and snd_soc_dapm_mic.
If the number of connections to the output endpoint is 0 or the number of connections to the input endpoint is 0, the widget does not constitute any valid and complete audio path. Therefore, you should disable the widget; if the widget is connected, open the widget. Detailed analysis in dapm_power_widgets. [To be honest, this is the code comment, but it is too complicated to go down to dapm_power_widgets. I have not completely analyzed it for the moment .]
2. Path-> connect
The connection status is determined by path-> connect. There are two places to change the path-> connect of the mixer/MUX component:
(1) When dapm kcontrols is created, the connection status of mixer/MUX will be checked. For details, see dapm_set_path_status: Determine whether the source corresponding to this path is selected, if yes, Set P-> connect = 1; otherwise, Set P-> connect = 0. This process is described in <dapm 2: audio paths and dapm kcontrol>.
(2) When the upper layer uses alsa_amixer or other methods to operate dapm kcontrols, the connection status path-> connect of the widget path will be updated. The simple process is as follows:
Amixer-application layer [alsa_amixer cset name = 'left output mixer left input mixer Switch '1] |-> snd_ctl_ioctl-System Call |-> snd_ctl_elem_write_user-kernel hook function |-> kernel-| -> snd_ctl_find_id-traverse the kcontrol linked list to find the kctl that matches the name field |-> kctl-> put () -Call the kctl member function put () |-> snd_soc_dapm_put_volsw |-> dapm_mixer_update_power to update path-> connect
PGA, ADC, DAC, input, output, and other components initialize path-> connect = 1, HP, SPK, mic, and other components initialize path-> connect = 0. For details, see snd_soc_dapm_add_route function.
3. Linked List of Path
Each mixer/MUX widget has several paths. The number of paths is consistent with the number of kcontrols of mixer/MUX Widgets. With n kcontrols, N paths can be formed. Each path is added to the linked list codec-> dapm_paths through list_add (& Path-> list, & codec-> dapm_paths). Each path is added to the linked list through init_list_head (& Path-> list_sink) create a path-> list_sink node, and add the node to the linked list wsink-> sources through list_add (& Path-> list_sink, & wsink-> sources). Each path, create a path-> list_source node through init_list_head (& Path-> list_source), and then use list_add (& Path-> list_source, & wsource-> sinks) add the node to the linked list wsource-> sink. Each path is mounted to the linked list on the source widget and sink widget. Therefore, you can find this widget as all paths of sources through widgets-> sources, click widgets-> sinks to find all the paths of the widget as sinks.
For more information about the process of creating a path, see dapm 2: audio paths and dapm kcontrol. In fact, these should be put in the previous chapter, which is a bit messy, and a organized document will be sorted out later.
4. is_connected_output_ep Process
To understand the above three points, we should be able to discuss the is_connected_output_ep process. Is_connected_input_ep is similar. Illustration:
In the diagram, the light green is the input endpoint, and the light blue is the output endpoint. For example, how does a red line find the path to the output endpoint for input mixer?
(1) first, we will find the input mixer-> sinks and find two types: ADC and output mixer;
(2) check whether the path from input mixer to ADC is connected. Then, check that the ADC is a widget of the snd_soc_dapm_adc type. 1 is returned, this widget belongs to the output endpoint type and is connected;
(3) check whether the path from input mixer to output mixer is connected. If not, the system jumps out. If yes, the system continues to go down. Because the widget does not belong to the output endpoint, continue to output mixer-> sinks. There are two types: HP and SPK;
(4) Check that HP is connected to the output mixer, and HP also belongs to the output endpoint. Therefore, the input mixer-> output mixer-> HP channel also runs through the output endpoint.
The above is the is_connected_output_ep process. As for is_connected_input_ep, The traversal direction is the opposite (that is, the widget-> sources), traversing forward to the mic (input endpoint ). Of course, the path that has passed will be set to the identified Ed and will not be checked again.
Only when is_connected_output_ep and is_connected_input_ep are not 0 at the same time (that is, they can go forward to the input endpoint and then to the output endpoint) can they think that the widget is located in a complete and valid audio channel.
/** Recursively check for a completed path to an active or physically connected * output widget. returns number of complete paths. */static int is_connected_output_ep (struct snd_soc_dapm_widget * widget) {struct snd_soc_dapm_path * path; int con = 0; If (widget-> id = snd_soc_dapm_supply) return 0; // If the widget type is ADC or aif_out, it reaches the endpoint, And the widget-> endpoint path is valid. Switch (widget-> ID) {Case snd_soc_dapm_adc: Case snd_soc_dapm_aif_out: If (widget-> active) return 1; default: break ;} // If the widget type is output, HP, or SPK, it will arrive at the endpoint. The widget-> endpoint path is valid. If (widget-> connected) {/* connected pin? */If (widget-> id = snd_soc_dapm_output &&! Widget-> ext) return 1;/* connected Jack or SPK? */If (widget-> id = snd_soc_dapm_hp | widget-> id = snd_soc_dapm_spk | (widget-> id = snd_soc_dapm_line &&! List_empty (& widget-> sources) return 1;} // it is hard to understand here. The linked list of dapm is very complicated. I will mention it later, but it cannot be clearly expressed. // Recursively traverse the widget-> sink here until an endpoint is reached or the traversal is complete. Check the path connection status during the process. // Is_connected_output_ep recursively finds the output endpoint as the source. // is_connected_input_ep recursively finds the input endpoint as the sink. List_for_each_entry (path, & widget-> sinks, list_source) {// This path has been passed. Find the next patlif (path-> stored ed) continue; if (path-> Sink & Path-> Connect) {path-> stored ED = 1; con + = is_connected_output_ep (path-> sink) ;}} return con ;}
First, understand Linux kernel linked list operations such as init_list_head, list_add, and list_for_each_entry. For list_for_each_entry (path, & widget-> sinks, list_source), the head of the linked list is widget-> sinks, and member is list_source, which is a member of the struct snd_soc_dapm_path.
NOTE 3: list_add (& Path-> list_source, & wsource-> sinks ), here, we will add the Node path-> list_source to the linked list wsource-> sinks. How can we understand wsource-> sinks? I think it is to record every sink connected to the wsource (similarly, wsink-> sources is used to record every source connected to the wsink ). Therefore, to obtain a widget, you can find all the paths, regardless of the angle traversing forward or backward traversing.
Summary: This part is too much written, because the details are indeed complicated (I have changed it, but I still cannot guarantee whether there are any errors in the details ), to understand the concept of endpoint, how the path connection status is updated, and what is the complete audio path complete paths, the difficulty is the path linked list analysis. However, the principle is simple, and the above diagram is clearer. The two functions check whether a widget runs through both the input endpoint and output endpoint. If yes, the widget must be powered up.
5. dapm_generic_check_power
/* Generic check to see if a widget should be powered. */static int dapm_generic_check_power(struct snd_soc_dapm_widget *w){int in, out;in = is_connected_input_ep(w);dapm_clear_walk(w->codec);out = is_connected_output_ep(w);dapm_clear_walk(w->codec);return out != 0 && in != 0;}
Check whether a widget needs to be opened. Call is_connected_input_ep to check whether the widget is connected to the input endpoint, and is_connected_output_ep to check whether the widget is connected to the output endpoint. A widget must be connected to both the input endpoint and output endpoint.
This is actually a series of functions, which are dapm_generic_check_power, dapm_adc_check_power, dapm_dac_check_power, and dapm_supply_check_power, depending on the widget type. For example, for mixer/MUX widgets, W-> power_check = dapm_generic_check_power; for ADC/aif_out widgets, W-> power_check = dapm_adc_check_power. For details, see snd_soc_dapm_new_widgets.
6. dapm_generic_apply_power
The core function is to switch the specified widget, And the dapm mechanism is bypassed to control the opening and closing of the widget. However, the principle is very simple and complicated. For example, check whether widgets run through the input/out endpoint, power up/down list sequence, and other preliminary processing details.
/* Standard power change method, used to apply power changes to most * widgets. */static int dapm_generic_apply_power (struct snd_soc_dapm_widget * w) {int ret;/* call any power change event handlers */If (W-> event) pr_debug ("Power % s event for % s flags % x \ n", W-> power? "On": "off", W-> name, W-> event_flags); // before the widget is switched, first run the event // callback function branch identified as snd_soc_dapm_pre_pmu/snd_soc_dapm_pre_pmd. For the event flag definition, see dapm. h. /* Power up pre event */If (W-> Power & W-> event & (W-> event_flags & snd_soc_dapm_pre_pmu )) {ret = W-> event (W, null, snd_soc_dapm_pre_pmu); If (Ret <0) return ret;}/* power down pre event */If (! W-> Power & W-> event & (W-> event_flags & snd_soc_dapm_pre_pmd) {ret = W-> event (W, null, snd_soc_dapm_pre_pmd ); if (Ret <0) return ret;} // before closing the widget, if the widget is a PGA part, call dapm_set_pga to control the volume of the PGA part to decrease gradually, // reduce the pop sound generated when the component is powered off. /* Lower PGA volume to reduce POPs */If (W-> id = snd_soc_dapm_pga &&! W-> power) dapm_set_pga (W, W-> power); // The operation widget is enabled or disabled. Dapm_update_bits (w); // After the widget is enabled, if the widget is a PGA widget, call dapm_set_pga to control the volume of the PGA widget to increase gradually, and // reduce the pop sound generated when the widget is powered off. /* Raise PGA volume to reduce POPs */If (W-> id = snd_soc_dapm_pga & W-> power) dapm_set_pga (W, W-> power ); // After the widget is switched, execute the event // callback function branch marked as snd_soc_dapm_post_pmu/snd_soc_dapm_post_pmd. For the event flag definition, see dapm. h. /* Power up post event */If (W-> Power & W-> event & (W-> event_flags & snd_soc_dapm_post_pmu )) {ret = W-> event (W, null, snd_soc_dapm_post_pmu); If (Ret <0) return ret;}/* power down post event */If (! W-> Power & W-> event & (W-> event_flags & snd_soc_dapm_post_pmd) {ret = W-> event (W, null, snd_soc_dapm_post_pmd ); if (Ret <0) return ret;} return 0 ;}
VII. dapm_seq_insert
/* Insert a widget in order into a DAPM power sequence. */static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, struct list_head *list, int sort[]){struct snd_soc_dapm_widget *w;list_for_each_entry(w, list, power_list)if (dapm_seq_compare(new_widget, w, sort) < 0) {list_add_tail(&new_widget->power_list, &w->power_list);return;}list_add_tail(&new_widget->power_list, list);}
This function is used to insert widgets into the list of linked lists in a certain order. In power up/down, in order to reduce the Pop sounds that may be generated, you must operate these widgets in a certain order, which is specified by dapm_seq_insert.
The order of power down is as follows:
static int dapm_down_seq[] = {[snd_soc_dapm_pre] = 0,[snd_soc_dapm_adc] = 1,[snd_soc_dapm_hp] = 2,[snd_soc_dapm_spk] = 2,[snd_soc_dapm_pga] = 4,[snd_soc_dapm_mixer_named_ctl] = 5,[snd_soc_dapm_mixer] = 5,[snd_soc_dapm_dac] = 6,[snd_soc_dapm_mic] = 7,[snd_soc_dapm_micbias] = 8,[snd_soc_dapm_mux] = 9,[snd_soc_dapm_value_mux] = 9,[snd_soc_dapm_aif_in] = 10,[snd_soc_dapm_aif_out] = 10,[snd_soc_dapm_supply] = 11,[snd_soc_dapm_post] = 12,};
The order of power up is as follows:
static int dapm_up_seq[] = {[snd_soc_dapm_pre] = 0,[snd_soc_dapm_supply] = 1,[snd_soc_dapm_micbias] = 2,[snd_soc_dapm_aif_in] = 3,[snd_soc_dapm_aif_out] = 3,[snd_soc_dapm_mic] = 4,[snd_soc_dapm_mux] = 5,[snd_soc_dapm_value_mux] = 5,[snd_soc_dapm_dac] = 6,[snd_soc_dapm_mixer] = 7,[snd_soc_dapm_mixer_named_ctl] = 7,[snd_soc_dapm_pga] = 8,[snd_soc_dapm_adc] = 9,[snd_soc_dapm_hp] = 10,[snd_soc_dapm_spk] = 10,[snd_soc_dapm_post] = 11,};
It can be seen that this order is reasonable and complies with the conventional principles of audio depop.
Summary: This is the last article on the analysis of the dapm mechanism. It introduces some important sub-function details. There is not much to mention about the dapm mechanism. The next article will involve the establishment and triggering process of dapm. In fact, for me, the most difficult thing to understand is these details. To eliminate POP as much as possible, dapm_set_pga is responsible for depop of PGA components, while dapm_seq_insert is responsible for organizing the operation order of widgets power up/down. power_check functions are used to check whether the specified widget requires power up, this involves concepts such as endpoint and complete paths.