// SPDX-License-Identifier: GPL-2.0+ // // soc-dapm.c -- ALSA SoC Dynamic Audio Power Management // // Copyright 2005 Wolfson Microelectronics PLC. // Author: Liam Girdwood <lrg@slimlogic.co.uk> // // Features: // o Changes power status of internal codec blocks depending on the // dynamic configuration of codec internal audio paths and active // DACs/ADCs. // o Platform power domain - can support external components i.e. amps and // mic/headphone insertion events. // o Automatic Mic Bias support // o Jack insertion power event initiation - e.g. hp insertion will enable // sinks, dacs, etc // o Delayed power down of audio subsystem to reduce pops between a quick // device reopen.
if (!dapm_dirty_widget(w)) {
dev_vdbg(w->dapm->dev, "Marking %s dirty due to %s\n",
w->name, reason);
list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
}
}
/* * Common implementation for dapm_widget_invalidate_input_paths() and * dapm_widget_invalidate_output_paths(). The function is inlined since the * combined size of the two specialized functions is only marginally larger then * the size of the generic function and at the same time the fast path of the * specialized functions is significantly smaller than the generic function.
*/ static __always_inline void dapm_widget_invalidate_paths( struct snd_soc_dapm_widget *w, enum snd_soc_dapm_direction dir)
{ enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); struct snd_soc_dapm_widget *node; struct snd_soc_dapm_path *p;
LIST_HEAD(list);
/* * dapm_widget_invalidate_input_paths() - Invalidate the cached number of * input paths * @w: The widget for which to invalidate the cached number of input paths * * Resets the cached number of inputs for the specified widget and all widgets * that can be reached via outcoming paths from the widget. * * This function must be called if the number of output paths for a widget might * have changed. E.g. if the source state of a widget changes or a path is added * or activated with the widget as the sink.
*/ staticvoid dapm_widget_invalidate_input_paths(struct snd_soc_dapm_widget *w)
{
dapm_widget_invalidate_paths(w, SND_SOC_DAPM_DIR_IN);
}
/* * dapm_widget_invalidate_output_paths() - Invalidate the cached number of * output paths * @w: The widget for which to invalidate the cached number of output paths * * Resets the cached number of outputs for the specified widget and all widgets * that can be reached via incoming paths from the widget. * * This function must be called if the number of output paths for a widget might * have changed. E.g. if the sink state of a widget changes or a path is added * or activated with the widget as the source.
*/ staticvoid dapm_widget_invalidate_output_paths(struct snd_soc_dapm_widget *w)
{
dapm_widget_invalidate_paths(w, SND_SOC_DAPM_DIR_OUT);
}
/* * dapm_path_invalidate() - Invalidates the cached number of inputs and outputs * for the widgets connected to a path * @p: The path to invalidate * * Resets the cached number of inputs for the sink of the path and the cached * number of outputs for the source of the path. * * This function must be called when a path is added, removed or the connected * state changes.
*/ staticvoid dapm_path_invalidate(struct snd_soc_dapm_path *p)
{ /* * Weak paths or supply paths do not influence the number of input or * output paths of their neighbors.
*/ if (p->is_supply) return;
/* * The number of connected endpoints is the sum of the number of * connected endpoints of all neighbors. If a node with 0 connected * endpoints is either connected or disconnected that sum won't change, * so there is no need to re-check the path.
*/ if (p->source->endpoints[SND_SOC_DAPM_DIR_IN] != 0)
dapm_widget_invalidate_input_paths(p->sink); if (p->sink->endpoints[SND_SOC_DAPM_DIR_OUT] != 0)
dapm_widget_invalidate_output_paths(p->source);
}
staticunsignedint soc_dapm_read(struct snd_soc_dapm_context *dapm, int reg)
{ if (!dapm->component) return -EIO; return snd_soc_component_read(dapm->component, reg);
}
/* set up initial codec paths */ staticvoid dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i, int nth_path)
{ struct soc_mixer_control *mc = (struct soc_mixer_control *)
p->sink->kcontrol_news[i].private_value; unsignedint reg = mc->reg; unsignedint invert = mc->invert;
if (reg != SND_SOC_NOPM) { unsignedint shift = mc->shift; unsignedint max = mc->max; unsignedint mask = (1 << fls(max)) - 1; unsignedint val = soc_dapm_read(p->sink->dapm, reg);
/* * The nth_path argument allows this function to know * which path of a kcontrol it is setting the initial * status for. Ideally this would support any number * of paths and channels. But since kcontrols only come * in mono and stereo variants, we are limited to 2 * channels. * * The following code assumes for stereo controls the * first path is the left channel, and all remaining * paths are the right channel.
*/ if (snd_soc_volsw_is_stereo(mc) && nth_path > 0) { if (reg != mc->rreg)
val = soc_dapm_read(p->sink->dapm, mc->rreg);
val = (val >> mc->rshift) & mask;
} else {
val = (val >> shift) & mask;
} if (invert)
val = max - val;
p->connect = !!val;
} else { /* since a virtual mixer has no backing registers to * decide which path to connect, it will try to match * with initial state. This is to ensure * that the default mixer choice will be * correctly powered up during initialization.
*/
p->connect = invert;
}
}
val = soc_dapm_read(dapm, e->reg);
val = (val >> e->shift_l) & e->mask;
item = snd_soc_enum_val_to_item(e, val);
} else { /* since a virtual mux has no backing registers to * decide which path to connect, it will try to match * with the first enumeration. This is to ensure * that the default mux choice (the first) will be * correctly powered up during initialization.
*/
item = 0;
}
i = match_string(e->texts, e->items, control_name); if (i < 0) return -ENODEV;
path->name = e->texts[i];
path->connect = (i == item); return 0;
}
/* connect mixer widget to its interconnecting audio paths */ staticint dapm_connect_mixer(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_path *path, constchar *control_name)
{ int i, nth_path = 0;
/* search for mixer kcontrol */ for (i = 0; i < path->sink->num_kcontrols; i++) { if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) {
path->name = path->sink->kcontrol_news[i].name;
dapm_set_mixer_path_status(path, i, nth_path++); return 0;
}
} return -ENODEV;
}
/* * dapm_update_widget_flags() - Re-compute widget sink and source flags * @w: The widget for which to update the flags * * Some widgets have a dynamic category which depends on which neighbors they * are connected to. This function update the category for these widgets. * * This function must be called whenever a path is added or removed to a widget.
*/ staticvoid dapm_update_widget_flags(struct snd_soc_dapm_widget *w)
{ enum snd_soc_dapm_direction dir; struct snd_soc_dapm_path *p; unsignedint ep;
switch (w->id) { case snd_soc_dapm_input: /* On a fully routed card an input is never a source */ if (w->dapm->card->fully_routed) return;
ep = SND_SOC_DAPM_EP_SOURCE;
snd_soc_dapm_widget_for_each_source_path(w, p) { if (p->source->id == snd_soc_dapm_micbias ||
p->source->id == snd_soc_dapm_mic ||
p->source->id == snd_soc_dapm_line ||
p->source->id == snd_soc_dapm_output) {
ep = 0; break;
}
} break; case snd_soc_dapm_output: /* On a fully routed card a output is never a sink */ if (w->dapm->card->fully_routed) return;
ep = SND_SOC_DAPM_EP_SINK;
snd_soc_dapm_widget_for_each_sink_path(w, p) { if (p->sink->id == snd_soc_dapm_spk ||
p->sink->id == snd_soc_dapm_hp ||
p->sink->id == snd_soc_dapm_line ||
p->sink->id == snd_soc_dapm_input) {
ep = 0; break;
}
} break; case snd_soc_dapm_line:
ep = 0;
snd_soc_dapm_for_each_direction(dir) { if (!list_empty(&w->edges[dir]))
ep |= SND_SOC_DAPM_DIR_TO_EP(dir);
} break; default: return;
}
switch (sink->id) { case snd_soc_dapm_mux: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl:
dynamic_sink = true; break; default: break;
}
if (dynamic_source && dynamic_sink) {
dev_err(dapm->dev, "Direct connection between demux and mixer/mux not supported for path %s -> [%s] -> %s\n",
source->name, control, sink->name); return -EINVAL;
} elseif (!dynamic_source && !dynamic_sink) {
dev_err(dapm->dev, "Control not supported for path %s -> [%s] -> %s\n",
source->name, control, sink->name); return -EINVAL;
}
if (wsource->is_supply || wsink->is_supply)
path->is_supply = 1;
/* connect static paths */ if (control == NULL) {
path->connect = 1;
} else { switch (wsource->id) { case snd_soc_dapm_demux:
ret = dapm_connect_mux(dapm, path, control, wsource); if (ret) goto err; break; default: break;
}
switch (wsink->id) { case snd_soc_dapm_mux:
ret = dapm_connect_mux(dapm, path, control, wsink); if (ret != 0) goto err; break; case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl:
ret = dapm_connect_mixer(dapm, path, control); if (ret != 0) goto err; break; default: break;
}
}
data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM;
INIT_LIST_HEAD(&data->paths);
switch (widget->id) { case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl:
mc = (struct soc_mixer_control *)kcontrol->private_value;
if (mc->autodisable) { struct snd_soc_dapm_widget template;
if (snd_soc_volsw_is_stereo(mc))
dev_warn(widget->dapm->dev, "ASoC: Unsupported stereo autodisable control '%s'\n",
ctrl_name);
name = kasprintf(GFP_KERNEL, "%s %s", ctrl_name, "Autodisable"); if (!name) {
ret = -ENOMEM; goto err_data;
}
data->widget =
snd_soc_dapm_new_control_unlocked(widget->dapm,
&template);
kfree(name); if (IS_ERR(data->widget)) {
ret = PTR_ERR(data->widget); goto err_data;
}
} break; case snd_soc_dapm_demux: case snd_soc_dapm_mux:
e = (struct soc_enum *)kcontrol->private_value;
if (e->autodisable) { struct snd_soc_dapm_widget template;
name = kasprintf(GFP_KERNEL, "%s %s", ctrl_name, "Autodisable"); if (!name) {
ret = -ENOMEM; goto err_data;
}
if (data->widget) { switch (dapm_kcontrol_get_wlist(kcontrol)->widgets[0]->id) { case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl:
data->widget->on_val = value & data->widget->mask; break; case snd_soc_dapm_demux: case snd_soc_dapm_mux:
data->widget->on_val = value >> data->widget->shift; break; default:
data->widget->on_val = value; break;
}
}
data->value = value;
returntrue;
}
/** * snd_soc_dapm_kcontrol_widget() - Returns the widget associated to a * kcontrol * @kcontrol: The kcontrol
*/ struct snd_soc_dapm_widget *snd_soc_dapm_kcontrol_widget( struct snd_kcontrol *kcontrol)
{ return dapm_kcontrol_get_wlist(kcontrol)->widgets[0];
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_widget);
/** * snd_soc_dapm_kcontrol_dapm() - Returns the dapm context associated to a * kcontrol * @kcontrol: The kcontrol * * Note: This function must only be used on kcontrols that are known to have * been registered for a CODEC. Otherwise the behaviour is undefined.
*/ struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm( struct snd_kcontrol *kcontrol)
{ return dapm_kcontrol_get_wlist(kcontrol)->widgets[0]->dapm;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_dapm);
staticvoid soc_dapm_async_complete(struct snd_soc_dapm_context *dapm)
{ if (dapm->component)
snd_soc_component_async_complete(dapm->component);
}
staticstruct snd_soc_dapm_widget *
dapm_wcache_lookup(struct snd_soc_dapm_widget *w, constchar *name)
{ if (w) { struct list_head *wlist = &w->dapm->card->widgets; constint depth = 2; int i = 0;
list_for_each_entry_from(w, wlist, list) { if (!strcmp(name, w->name)) return w;
if (++i == depth) break;
}
}
return NULL;
}
/** * snd_soc_dapm_force_bias_level() - Sets the DAPM bias level * @dapm: The DAPM context for which to set the level * @level: The level to set * * Forces the DAPM bias level to a specific state. It will call the bias level * callback of DAPM context with the specified level. This will even happen if * the context is already at the same level. Furthermore it will not go through * the normal bias level sequencing, meaning any intermediate states between the * current and the target state will not be entered. * * Note that the change in bias level is only temporary and the next time * snd_soc_dapm_sync() is called the state will be set to the level as * determined by the DAPM core. The function is mainly intended to be used to * used during probe or resume from suspend to power up the device so * initialization can be done, before the DAPM core takes over.
*/ int snd_soc_dapm_force_bias_level(struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
{ int ret = 0;
if (dapm->component)
ret = snd_soc_component_set_bias_level(dapm->component, level);
/** * snd_soc_dapm_set_bias_level - set the bias level for the system * @dapm: DAPM context * @level: level to configure * * Configure the bias (power) levels for the SoC audio device. * * Returns 0 for success else error.
*/ staticint snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
{ struct snd_soc_card *card = dapm->card; int ret = 0;
trace_snd_soc_bias_level_start(dapm, level);
ret = snd_soc_card_set_bias_level(card, dapm, level); if (ret != 0) goto out;
if (dapm != &card->dapm)
ret = snd_soc_dapm_force_bias_level(dapm, level);
if (ret != 0) goto out;
ret = snd_soc_card_set_bias_level_post(card, dapm, level);
out:
trace_snd_soc_bias_level_done(dapm, level);
/* success */ if (ret == 0)
snd_soc_dapm_init_bias_level(dapm, level);
for_each_card_widgets(dapm->card, w) { if (w == kcontrolw || w->dapm != kcontrolw->dapm) continue; for (i = 0; i < w->num_kcontrols; i++) { if (&w->kcontrol_news[i] == kcontrol_new) { if (w->kcontrols)
*kcontrol = w->kcontrols[i]; return 1;
}
}
}
return 0;
}
/* * Determine if a kcontrol is shared. If it is, look it up. If it isn't, * create it. Either way, add the widget into the control's widget list
*/ staticint dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, int kci)
{ struct snd_soc_dapm_context *dapm = w->dapm; struct snd_card *card = dapm->card->snd_card; constchar *prefix;
size_t prefix_len; int shared; struct snd_kcontrol *kcontrol; bool wname_in_long_name, kcname_in_long_name; char *long_name = NULL; constchar *name; int ret = 0;
if (!kcontrol) { if (shared) {
wname_in_long_name = false;
kcname_in_long_name = true;
} else { switch (w->id) { case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_pga: case snd_soc_dapm_effect: case snd_soc_dapm_out_drv:
wname_in_long_name = true;
kcname_in_long_name = true; break; case snd_soc_dapm_mixer_named_ctl:
wname_in_long_name = false;
kcname_in_long_name = true; break; case snd_soc_dapm_demux: case snd_soc_dapm_mux:
wname_in_long_name = true;
kcname_in_long_name = false; break; default: return -EINVAL;
}
} if (w->no_wname_in_kcontrol_name)
wname_in_long_name = false;
if (wname_in_long_name && kcname_in_long_name) { /* * The control will get a prefix from the control * creation process but we're also using the same * prefix for widgets so cut the prefix off the * front of the widget name.
*/
long_name = kasprintf(GFP_KERNEL, "%s %s",
w->name + prefix_len,
w->kcontrol_news[kci].name); if (long_name == NULL) return -ENOMEM;
name = long_name;
} elseif (wname_in_long_name) {
long_name = NULL;
name = w->name + prefix_len;
} else {
long_name = NULL;
name = w->kcontrol_news[kci].name;
}
kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,
prefix); if (!kcontrol) {
ret = -ENOMEM; goto exit_free;
}
kcontrol->private_free = dapm_kcontrol_free;
ret = dapm_kcontrol_data_alloc(w, kcontrol, name); if (ret) {
snd_ctl_free_one(kcontrol); goto exit_free;
}
ret = snd_ctl_add(card, kcontrol); if (ret < 0) {
dev_err(dapm->dev, "ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
w->name, name, ret); goto exit_free;
}
}
ret = dapm_kcontrol_add_widget(kcontrol, w); if (ret == 0)
w->kcontrols[kci] = kcontrol;
exit_free:
kfree(long_name);
return ret;
}
/* create new dapm mixer control */ staticint dapm_new_mixer(struct snd_soc_dapm_widget *w)
{ int i, ret; struct snd_soc_dapm_path *path; struct dapm_kcontrol_data *data;
/* add kcontrol */ for (i = 0; i < w->num_kcontrols; i++) { /* match name */
snd_soc_dapm_widget_for_each_source_path(w, path) { /* mixer/mux paths name must match control name */ if (path->name != (char *)w->kcontrol_news[i].name) continue;
if (!w->kcontrols[i]) {
ret = dapm_create_or_share_kcontrol(w, i); if (ret < 0) return ret;
}
dapm_kcontrol_add_path(w->kcontrols[i], path);
data = snd_kcontrol_chip(w->kcontrols[i]); if (data->widget)
snd_soc_dapm_add_path(data->widget->dapm,
data->widget,
path->source,
NULL, NULL);
}
}
return 0;
}
/* create new dapm mux control */ staticint dapm_new_mux(struct snd_soc_dapm_widget *w)
{ struct snd_soc_dapm_context *dapm = w->dapm; enum snd_soc_dapm_direction dir; struct snd_soc_dapm_path *path; constchar *type; int ret;
switch (w->id) { case snd_soc_dapm_mux:
dir = SND_SOC_DAPM_DIR_OUT;
type = "mux"; break; case snd_soc_dapm_demux:
dir = SND_SOC_DAPM_DIR_IN;
type = "demux"; break; default: return -EINVAL;
}
if (w->num_kcontrols != 1) {
dev_err(dapm->dev, "ASoC: %s %s has incorrect number of controls\n", type,
w->name); return -EINVAL;
}
if (list_empty(&w->edges[dir])) {
dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name); return -EINVAL;
}
ret = dapm_create_or_share_kcontrol(w, 0); if (ret < 0) return ret;
snd_soc_dapm_widget_for_each_path(w, dir, path) { if (path->name)
dapm_kcontrol_add_path(w->kcontrols[0], path);
}
return 0;
}
/* create new dapm volume control */ staticint dapm_new_pga(struct snd_soc_dapm_widget *w)
{ int i;
for (i = 0; i < w->num_kcontrols; i++) { int ret = dapm_create_or_share_kcontrol(w, i); if (ret < 0) return ret;
}
return 0;
}
/* create new dapm dai link control */ staticint dapm_new_dai_link(struct snd_soc_dapm_widget *w)
{ int i; struct snd_soc_pcm_runtime *rtd = w->priv;
/* create control for links with > 1 config */ if (rtd->dai_link->num_c2c_params <= 1) return 0;
/* add kcontrol */ for (i = 0; i < w->num_kcontrols; i++) { struct snd_soc_dapm_context *dapm = w->dapm; struct snd_card *card = dapm->card->snd_card; struct snd_kcontrol *kcontrol = snd_soc_cnew(&w->kcontrol_news[i],
w, w->name, NULL); int ret = snd_ctl_add(card, kcontrol);
/* We implement power down on suspend by checking the power state of * the ALSA card - when we are suspending the ALSA state for the card * is set to D3.
*/ staticint snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget)
{ int level = snd_power_get_state(widget->dapm->card->snd_card);
switch (level) { case SNDRV_CTL_POWER_D3hot: case SNDRV_CTL_POWER_D3cold: if (widget->ignore_suspend)
dev_dbg(widget->dapm->dev, "ASoC: %s ignoring suspend\n",
widget->name); return widget->ignore_suspend; default: return 1;
}
}
/* * Recursively reset the cached number of inputs or outputs for the specified * widget and all widgets that can be reached via incoming or outcoming paths * from the widget.
*/ staticvoid invalidate_paths_ep(struct snd_soc_dapm_widget *widget, enum snd_soc_dapm_direction dir)
{ enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); struct snd_soc_dapm_path *path;
widget->endpoints[dir] = -1;
snd_soc_dapm_widget_for_each_path(widget, rdir, path) { if (path->is_supply) continue;
/* * Common implementation for is_connected_output_ep() and * is_connected_input_ep(). The function is inlined since the combined size of * the two specialized functions is only marginally larger then the size of the * generic function and at the same time the fast path of the specialized * functions is significantly smaller than the generic function.
*/ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, struct list_head *list, enum snd_soc_dapm_direction dir, int (*fn)(struct snd_soc_dapm_widget *, struct list_head *, bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, enum snd_soc_dapm_direction)), bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, enum snd_soc_dapm_direction))
{ enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); struct snd_soc_dapm_path *path; int con = 0;
if (widget->endpoints[dir] >= 0) return widget->endpoints[dir];
DAPM_UPDATE_STAT(widget, path_checks);
/* do we need to add this widget to the list ? */ if (list)
list_add_tail(&widget->work_list, list);
if (custom_stop_condition && custom_stop_condition(widget, dir)) {
list = NULL;
custom_stop_condition = NULL;
}
if (path->connect) {
path->walking = 1;
con += fn(path->node[dir], list, custom_stop_condition);
path->walking = 0;
}
}
widget->endpoints[dir] = con;
return con;
}
/* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. * * Optionally, can be supplied with a function acting as a stopping condition. * This function takes the dapm widget currently being examined and the walk * direction as an arguments, it should return true if widgets from that point * in the graph onwards should not be added to the widget list.
*/ staticint is_connected_output_ep(struct snd_soc_dapm_widget *widget, struct list_head *list, bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, enum snd_soc_dapm_direction))
{ return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT,
is_connected_output_ep, custom_stop_condition);
}
/* * Recursively check for a completed path to an active or physically connected * input widget. Returns number of complete paths. * * Optionally, can be supplied with a function acting as a stopping condition. * This function takes the dapm widget currently being examined and the walk * direction as an arguments, it should return true if the walk should be * stopped and false otherwise.
*/ staticint is_connected_input_ep(struct snd_soc_dapm_widget *widget, struct list_head *list, bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, enum snd_soc_dapm_direction))
{ return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN,
is_connected_input_ep, custom_stop_condition);
}
/** * snd_soc_dapm_dai_get_connected_widgets - query audio path and it's widgets. * @dai: the soc DAI. * @stream: stream direction. * @list: list of active widgets for this stream. * @custom_stop_condition: (optional) a function meant to stop the widget graph * walk based on custom logic. * * Queries DAPM graph as to whether a valid audio stream path exists for * the initial stream specified by name. This takes into account * current mixer and mux kcontrol settings. Creates list of valid widgets. * * Optionally, can be supplied with a function acting as a stopping condition. * This function takes the dapm widget currently being examined and the walk * direction as an arguments, it should return true if the walk should be * stopped and false otherwise. * * Returns the number of valid paths or negative error.
*/ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget_list **list, bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, enum snd_soc_dapm_direction))
{ struct snd_soc_card *card = dai->component->card; struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, stream);
LIST_HEAD(widgets); int paths; int ret;
/* * Handler for regulator supply widget.
*/ int snd_soc_dapm_regulator_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event)
{ int ret;
soc_dapm_async_complete(w->dapm);
if (SND_SOC_DAPM_EVENT_ON(event)) { if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
ret = regulator_allow_bypass(w->regulator, false); if (ret != 0)
dev_warn(w->dapm->dev, "ASoC: Failed to unbypass %s: %d\n",
w->name, ret);
}
return regulator_enable(w->regulator);
} else { if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
ret = regulator_allow_bypass(w->regulator, true); if (ret != 0)
dev_warn(w->dapm->dev, "ASoC: Failed to bypass %s: %d\n",
w->name, ret);
}
if (power_up)
sort = dapm_up_seq; else
sort = dapm_down_seq;
WARN_ONCE(sort[a->id] == 0, "offset a->id %d not initialized\n", a->id);
WARN_ONCE(sort[b->id] == 0, "offset b->id %d not initialized\n", b->id);
if (sort[a->id] != sort[b->id]) return sort[a->id] - sort[b->id]; if (a->subseq != b->subseq) { if (power_up) return a->subseq - b->subseq; else return b->subseq - a->subseq;
} if (a->reg != b->reg) return a->reg - b->reg; if (a->dapm != b->dapm) return (unsignedlong)a->dapm - (unsignedlong)b->dapm;
return 0;
}
/* Insert a widget in order into a DAPM power sequence. */ staticvoid dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, struct list_head *list, bool power_up)
{ struct snd_soc_dapm_widget *w;
/* Apply a DAPM power sequence. * * We walk over a pre-sorted list of widgets to apply power to. In * order to minimise the number of writes to the device required * multiple widgets will be updated in a single write where possible. * Currently anything that requires more than a single write is not * handled.
*/ staticvoid dapm_seq_run(struct snd_soc_card *card, struct list_head *list, int event, bool power_up)
{ struct snd_soc_dapm_widget *w, *n; struct snd_soc_dapm_context *d;
LIST_HEAD(pending); int cur_sort = -1; int cur_subseq = -1; int cur_reg = SND_SOC_NOPM; struct snd_soc_dapm_context *cur_dapm = NULL; int i; int *sort;
if (power_up)
sort = dapm_up_seq; else
sort = dapm_down_seq;
list_for_each_entry_safe(w, n, list, power_list) { int ret = 0;
/* Do we need to apply any queued changes? */ if (sort[w->id] != cur_sort || w->reg != cur_reg ||
w->dapm != cur_dapm || w->subseq != cur_subseq) { if (!list_empty(&pending))
dapm_seq_run_coalesced(card, &pending);
if (cur_dapm && cur_dapm->component) { for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) if (sort[i] == cur_sort)
snd_soc_component_seq_notifier(
cur_dapm->component,
i, cur_subseq);
}
if (cur_dapm && w->dapm != cur_dapm)
soc_dapm_async_complete(cur_dapm);
switch (w->id) { case snd_soc_dapm_pre: if (!w->event) continue;
if (event == SND_SOC_DAPM_STREAM_START)
ret = w->event(w,
NULL, SND_SOC_DAPM_PRE_PMU); elseif (event == SND_SOC_DAPM_STREAM_STOP)
ret = w->event(w,
NULL, SND_SOC_DAPM_PRE_PMD); break;
case snd_soc_dapm_post: if (!w->event) continue;
if (event == SND_SOC_DAPM_STREAM_START)
ret = w->event(w,
NULL, SND_SOC_DAPM_POST_PMU); elseif (event == SND_SOC_DAPM_STREAM_STOP)
ret = w->event(w,
NULL, SND_SOC_DAPM_POST_PMD); break;
default: /* Queue it up for application */
cur_sort = sort[w->id];
cur_subseq = w->subseq;
cur_reg = w->reg;
cur_dapm = w->dapm;
list_move(&w->power_list, &pending); break;
}
if (ret < 0)
dev_err(w->dapm->dev, "ASoC: Failed to apply widget power: %d\n", ret);
}
if (!list_empty(&pending))
dapm_seq_run_coalesced(card, &pending);
if (cur_dapm && cur_dapm->component) { for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) if (sort[i] == cur_sort)
snd_soc_component_seq_notifier(
cur_dapm->component,
i, cur_subseq);
}
/* Async callback run prior to DAPM sequences - brings to _PREPARE if * they're changing state.
*/ staticvoid dapm_pre_sequence_async(void *data, async_cookie_t cookie)
{ struct snd_soc_dapm_context *dapm = data; int ret;
/* If we're off and we're not supposed to go into STANDBY */ if (dapm->bias_level == SND_SOC_BIAS_OFF &&
dapm->target_bias_level != SND_SOC_BIAS_OFF) { if (dapm->dev && cookie)
pm_runtime_get_sync(dapm->dev);
ret = snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_STANDBY); if (ret != 0)
dev_err(dapm->dev, "ASoC: Failed to turn on bias: %d\n", ret);
}
/* Prepare for a transition to ON or away from ON */ if ((dapm->target_bias_level == SND_SOC_BIAS_ON &&
dapm->bias_level != SND_SOC_BIAS_ON) ||
(dapm->target_bias_level != SND_SOC_BIAS_ON &&
dapm->bias_level == SND_SOC_BIAS_ON)) {
ret = snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_PREPARE); if (ret != 0)
dev_err(dapm->dev, "ASoC: Failed to prepare bias: %d\n", ret);
}
}
/* Async callback run prior to DAPM sequences - brings to their final * state.
*/ staticvoid dapm_post_sequence_async(void *data, async_cookie_t cookie)
{ struct snd_soc_dapm_context *dapm = data; int ret;
/* If we just powered the last thing off drop to standby bias */ if (dapm->bias_level == SND_SOC_BIAS_PREPARE &&
(dapm->target_bias_level == SND_SOC_BIAS_STANDBY ||
dapm->target_bias_level == SND_SOC_BIAS_OFF)) {
ret = snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_STANDBY); if (ret != 0)
dev_err(dapm->dev, "ASoC: Failed to apply standby bias: %d\n",
ret);
}
/* If we're in standby and can support bias off then do that */ if (dapm->bias_level == SND_SOC_BIAS_STANDBY &&
dapm->target_bias_level == SND_SOC_BIAS_OFF) {
ret = snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_OFF); if (ret != 0)
dev_err(dapm->dev, "ASoC: Failed to turn off bias: %d\n",
ret);
if (dapm->dev && cookie)
pm_runtime_put(dapm->dev);
}
/* If we just powered up then move to active bias */ if (dapm->bias_level == SND_SOC_BIAS_PREPARE &&
dapm->target_bias_level == SND_SOC_BIAS_ON) {
ret = snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_ON); if (ret != 0)
dev_err(dapm->dev, "ASoC: Failed to apply active bias: %d\n",
ret);
}
}
staticvoid dapm_widget_set_peer_power(struct snd_soc_dapm_widget *peer, bool power, bool connect)
{ /* If a connection is being made or broken then that update * will have marked the peer dirty, otherwise the widgets are
* not connected and this update has no impact. */ if (!connect) return;
/* If the peer is already in the state we're moving to then we
* won't have an impact on it. */ if (power != peer->power)
dapm_mark_dirty(peer, "peer state change");
}
switch (w->id) { case snd_soc_dapm_pre:
power = 0; goto end; case snd_soc_dapm_post:
power = 1; goto end; default: break;
}
power = dapm_widget_power_check(w);
if (w->power == power) return;
trace_snd_soc_dapm_widget_power(w, power);
/* * If we changed our power state perhaps our neigbours * changed also.
*/
snd_soc_dapm_widget_for_each_source_path(w, path)
dapm_widget_set_peer_power(path->source, power, path->connect);
/* * Supplies can't affect their outputs, only their inputs
*/ if (!w->is_supply)
snd_soc_dapm_widget_for_each_sink_path(w, path)
dapm_widget_set_peer_power(path->sink, power, path->connect);
staticbool dapm_idle_bias_off(struct snd_soc_dapm_context *dapm)
{ if (dapm->idle_bias_off) returntrue;
switch (snd_power_get_state(dapm->card->snd_card)) { case SNDRV_CTL_POWER_D3hot: case SNDRV_CTL_POWER_D3cold: return dapm->suspend_bias_off; default: break;
}
returnfalse;
}
/* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- * * o DAC to output pin. * o Input pin to ADC. * o Input pin to Output pin (bypass, sidetone) * o DAC to ADC (loopback).
*/ staticint dapm_power_widgets(struct snd_soc_card *card, int event, struct snd_soc_dapm_update *update)
{ struct snd_soc_dapm_widget *w; struct snd_soc_dapm_context *d;
LIST_HEAD(up_list);
LIST_HEAD(down_list);
ASYNC_DOMAIN_EXCLUSIVE(async_domain); enum snd_soc_bias_level bias; int ret;
/* Check which widgets we need to power and store them in * lists indicating if they should be powered up or down. We * only check widgets that have been flagged as dirty but note * that new widgets may be added to the dirty list while we * iterate.
*/
list_for_each_entry(w, &card->dapm_dirty, dirty) {
dapm_power_one_widget(w, &up_list, &down_list);
}
for_each_card_widgets(card, w) { switch (w->id) { case snd_soc_dapm_pre: case snd_soc_dapm_post: /* These widgets always need to be powered */ break; default:
list_del_init(&w->dirty); break;
}
if (w->new_power) {
d = w->dapm;
/* Supplies and micbiases only bring the * context up to STANDBY as unless something * else is active and passing audio they * generally don't require full power. Signal * generators are virtual pins and have no * power impact themselves.
*/ switch (w->id) { case snd_soc_dapm_siggen: case snd_soc_dapm_vmid: break; case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_pinctrl: case snd_soc_dapm_clock_supply: case snd_soc_dapm_micbias: if (d->target_bias_level < SND_SOC_BIAS_STANDBY)
d->target_bias_level = SND_SOC_BIAS_STANDBY; break; default:
d->target_bias_level = SND_SOC_BIAS_ON; break;
}
}
}
/* Force all contexts in the card to the same bias state if * they're not ground referenced.
*/
bias = SND_SOC_BIAS_OFF;
for_each_card_dapms(card, d) if (d->target_bias_level > bias)
bias = d->target_bias_level;
for_each_card_dapms(card, d) if (!dapm_idle_bias_off(d))
d->target_bias_level = bias;
trace_snd_soc_dapm_walk_done(card);
/* Run card bias changes at first */
dapm_pre_sequence_async(&card->dapm, 0); /* Run other bias changes in parallel */
for_each_card_dapms(card, d) { if (d != &card->dapm && d->bias_level != d->target_bias_level)
async_schedule_domain(dapm_pre_sequence_async, d,
&async_domain);
}
async_synchronize_full_domain(&async_domain);
/* Power down widgets first; try to avoid amplifying pops. */
dapm_seq_run(card, &down_list, event, false);
dapm_widget_update(card, update);
/* Now power up. */
dapm_seq_run(card, &up_list, event, true);
/* Run all the bias changes in parallel */
for_each_card_dapms(card, d) { if (d != &card->dapm && d->bias_level != d->target_bias_level)
async_schedule_domain(dapm_post_sequence_async, d,
&async_domain);
}
async_synchronize_full_domain(&async_domain); /* Run card bias changes at last */
dapm_post_sequence_async(&card->dapm, 0);
/* do we need to notify any clients that DAPM event is complete */
for_each_card_dapms(card, d) { if (!d->component) continue;
ret = snd_soc_component_stream_event(d->component, event); if (ret < 0) return ret;
}
buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM;
snd_soc_dapm_mutex_lock_root(w->dapm);
/* Supply widgets are not handled by is_connected_{input,output}_ep() */ if (w->is_supply) {
in = 0;
out = 0;
} else {
in = is_connected_input_ep(w, NULL, NULL);
out = is_connected_output_ep(w, NULL, NULL);
}
ret = scnprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d",
w->name, w->power ? "On" : "Off",
w->force ? " (forced)" : "", in, out);
/* * soc_dapm_connect_path() - Connects or disconnects a path * @path: The path to update * @connect: The new connect state of the path. True if the path is connected, * false if it is disconnected. * @reason: The reason why the path changed (for debugging only)
*/ staticvoid soc_dapm_connect_path(struct snd_soc_dapm_path *path, bool connect, constchar *reason)
{ if (path->connect == connect) return;
path->connect = connect;
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.