// 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;
/* test and update the power status of a mux widget */ staticint soc_dapm_mux_update_power(struct snd_soc_card *card, struct snd_kcontrol *kcontrol, struct snd_soc_dapm_update *update, int mux, struct soc_enum *e)
{ struct snd_soc_dapm_path *path; int found = 0; bool connect;
snd_soc_dapm_mutex_assert_held(card);
/* find dapm widget path assoc with kcontrol */
dapm_kcontrol_for_each_path(path, kcontrol) {
found = 1; /* we now need to match the string in the enum to the path */ if (e && !(strcmp(path->name, e->texts[mux])))
connect = true; else
connect = false;
if (found)
dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP, update);
return found;
}
int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_context *dapm, struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e, struct snd_soc_dapm_update *update)
{ struct snd_soc_card *card = dapm->card; int ret;
snd_soc_dapm_mutex_lock(card);
ret = soc_dapm_mux_update_power(card, kcontrol, update, mux, e);
snd_soc_dapm_mutex_unlock(card); if (ret > 0)
snd_soc_dpcm_runtime_update(card); return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_mux_update_power);
/* test and update the power status of a mixer or switch widget */ staticint soc_dapm_mixer_update_power(struct snd_soc_card *card, struct snd_kcontrol *kcontrol, struct snd_soc_dapm_update *update, int connect, int rconnect)
{ struct snd_soc_dapm_path *path; int found = 0;
snd_soc_dapm_mutex_assert_held(card);
/* find dapm widget path assoc with kcontrol */
dapm_kcontrol_for_each_path(path, kcontrol) { /* * Ideally this function should 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 (when 'found == 0') is the left channel, * and all remaining paths (when 'found == 1') are the * right channel. * * A stereo control is signified by a valid 'rconnect' * value, either 0 for unconnected, or >= 0 for connected. * This is chosen instead of using snd_soc_volsw_is_stereo, * so that the behavior of snd_soc_dapm_mixer_update_power * doesn't change even when the kcontrol passed in is * stereo. * * It passes 'connect' as the path connect status for * the left channel, and 'rconnect' for the right * channel.
*/ if (found && rconnect >= 0)
soc_dapm_connect_path(path, rconnect, "mixer update"); else
soc_dapm_connect_path(path, connect, "mixer update");
found = 1;
}
if (found)
dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP, update);
return found;
}
int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_context *dapm, struct snd_kcontrol *kcontrol, int connect, struct snd_soc_dapm_update *update)
{ struct snd_soc_card *card = dapm->card; int ret;
snd_soc_dapm_mutex_lock(card);
ret = soc_dapm_mixer_update_power(card, kcontrol, update, connect, -1);
snd_soc_dapm_mutex_unlock(card); if (ret > 0)
snd_soc_dpcm_runtime_update(card); return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power);
/* card won't be set for the dummy component, as a spot fix * we're checking for that case specifically here but in future * we will ensure that the dummy component looks like others.
*/ if (!component->card) return 0;
for_each_card_widgets(component->card, w) { if (w->dapm != dapm) continue;
/* only display widgets that burn power */ switch (w->id) { case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_spk: case snd_soc_dapm_line: case snd_soc_dapm_micbias: case snd_soc_dapm_dac: case snd_soc_dapm_adc: case snd_soc_dapm_pga: case snd_soc_dapm_effect: case snd_soc_dapm_out_drv: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: case snd_soc_dapm_supply: case snd_soc_dapm_regulator_supply: case snd_soc_dapm_pinctrl: case snd_soc_dapm_clock_supply: if (w->name)
count += sysfs_emit_at(buf, count, "%s: %s\n",
w->name, w->power ? "On":"Off"); break; default: break;
}
}
switch (snd_soc_dapm_get_bias_level(dapm)) { case SND_SOC_BIAS_ON:
state = "On"; break; case SND_SOC_BIAS_PREPARE:
state = "Prepare"; break; case SND_SOC_BIAS_STANDBY:
state = "Standby"; break; case SND_SOC_BIAS_OFF:
state = "Off"; break;
}
count += sysfs_emit_at(buf, count, "PM State: %s\n", state);
return count;
}
/* show dapm widget status in sys fs */ static ssize_t dapm_widget_show(struct device *dev, struct device_attribute *attr, char *buf)
{ struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); struct snd_soc_dai *codec_dai; int i, count = 0;
snd_soc_dapm_mutex_lock_root(rtd->card);
for_each_rtd_codec_dais(rtd, i, codec_dai) { struct snd_soc_component *component = codec_dai->component;
/** * snd_soc_dapm_free_widget - Free specified widget * @w: widget to free * * Removes widget from all paths and frees memory occupied by it.
*/ void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w)
{ struct snd_soc_dapm_path *p, *next_p; enum snd_soc_dapm_direction dir;
if (!w) return;
list_del(&w->list);
list_del(&w->dirty); /* * remove source and sink paths associated to this widget. * While removing the path, remove reference to it from both * source and sink widgets so that path is removed only once.
*/
snd_soc_dapm_for_each_direction(dir) {
snd_soc_dapm_widget_for_each_path_safe(w, dir, p, next_p)
dapm_free_path(p);
}
for_each_card_widgets(dapm->card, w) { if (!strcmp(w->name, pin_name)) { if (w->dapm == dapm) return w; else
fallback = w;
}
}
if (search_other_contexts) return fallback;
return NULL;
}
/* * set the DAPM pin status: * returns 1 when the value has been updated, 0 when unchanged, or a negative * error code; called from kcontrol put callback
*/ staticint __snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, constchar *pin, int status)
{ struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true); int ret = 0;
if (w->connected != status) {
dapm_mark_dirty(w, "pin configuration");
dapm_widget_invalidate_input_paths(w);
dapm_widget_invalidate_output_paths(w);
ret = 1;
}
w->connected = status; if (status == 0)
w->force = 0;
return ret;
}
/* * similar as __snd_soc_dapm_set_pin(), but returns 0 when successful; * called from several API functions below
*/ staticint snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, constchar *pin, int status)
{ int ret = __snd_soc_dapm_set_pin(dapm, pin, status);
return ret < 0 ? ret : 0;
}
/** * snd_soc_dapm_sync_unlocked - scan and power dapm paths * @dapm: DAPM context * * Walks all dapm audio paths and powers widgets according to their * stream or path usage. * * Requires external locking. * * Returns 0 for success.
*/ int snd_soc_dapm_sync_unlocked(struct snd_soc_dapm_context *dapm)
{ /* * Suppress early reports (eg, jacks syncing their state) to avoid * silly DAPM runs during card startup.
*/ if (!snd_soc_card_is_instantiated(dapm->card)) return 0;
/** * snd_soc_dapm_sync - scan and power dapm paths * @dapm: DAPM context * * Walks all dapm audio paths and powers widgets according to their * stream or path usage. * * Returns 0 for success.
*/ int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm)
{ int ret;
snd_soc_dapm_mutex_lock(dapm);
ret = snd_soc_dapm_sync_unlocked(dapm);
snd_soc_dapm_mutex_unlock(dapm); return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
staticint dapm_update_dai_chan(struct snd_soc_dapm_path *p, struct snd_soc_dapm_widget *w, int channels)
{ switch (w->id) { case snd_soc_dapm_aif_out: case snd_soc_dapm_aif_in: break; default: return 0;
}
/* * find src and dest widgets over all widgets but favor a widget from * current DAPM context
*/
for_each_card_widgets(dapm->card, w) { if (!wsink && !(strcmp(w->name, sink))) {
wtsink = w; if (w->dapm == dapm) {
wsink = w; if (wsource) break;
}
sink_ref++; if (sink_ref > 1)
dev_warn(dapm->dev, "ASoC: sink widget %s overwritten\n",
w->name); continue;
} if (!wsource && !(strcmp(w->name, source))) {
wtsource = w; if (w->dapm == dapm) {
wsource = w; if (wsink) break;
}
source_ref++; if (source_ref > 1)
dev_warn(dapm->dev, "ASoC: source widget %s overwritten\n",
w->name);
}
} /* use widget from another DAPM context if not found from this */ if (!wsink)
wsink = wtsink; if (!wsource)
wsource = wtsource;
ret = -ENODEV; if (!wsource) goto err; if (!wsink) goto err;
dapm_mark_dirty(wsource, "Route removed");
dapm_mark_dirty(wsink, "Route removed"); if (path->connect)
dapm_path_invalidate(path);
dapm_free_path(path);
/* Update any path related flags */
dapm_update_widget_flags(wsource);
dapm_update_widget_flags(wsink);
} else {
dev_warn(dapm->dev, "ASoC: Route %s->%s does not exist\n",
source, sink);
}
return 0;
}
/** * snd_soc_dapm_add_routes - Add routes between DAPM widgets * @dapm: DAPM context * @route: audio routes * @num: number of routes * * Connects 2 dapm widgets together via a named audio path. The sink is * the widget receiving the audio signal, whilst the source is the sender * of the audio signal. * * Returns 0 for success else error. On error all resources can be freed * with a call to snd_soc_card_free().
*/ int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm, conststruct snd_soc_dapm_route *route, int num)
{ int i, ret = 0;
snd_soc_dapm_mutex_lock(dapm); for (i = 0; i < num; i++) { int r = snd_soc_dapm_add_route(dapm, route); if (r < 0)
ret = r;
route++;
}
snd_soc_dapm_mutex_unlock(dapm);
/** * snd_soc_dapm_new_widgets - add new dapm widgets * @card: card to be checked for new dapm widgets * * Checks the codec for any new dapm widgets and creates them if found. * * Returns 0 for success.
*/ int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{ struct snd_soc_dapm_widget *w; unsignedint val;
snd_soc_dapm_mutex_lock_root(card);
for_each_card_widgets(card, w)
{ if (w->new) continue;
if (w->num_kcontrols) {
w->kcontrols = kcalloc(w->num_kcontrols, sizeof(struct snd_kcontrol *),
GFP_KERNEL); if (!w->kcontrols) {
snd_soc_dapm_mutex_unlock(card); return -ENOMEM;
}
}
switch(w->id) { case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl:
dapm_new_mixer(w); break; case snd_soc_dapm_mux: case snd_soc_dapm_demux:
dapm_new_mux(w); break; case snd_soc_dapm_pga: case snd_soc_dapm_effect: case snd_soc_dapm_out_drv:
dapm_new_pga(w); break; case snd_soc_dapm_dai_link:
dapm_new_dai_link(w); break; default: break;
}
/* Read the initial power state from the device */ if (w->reg >= 0) {
val = soc_dapm_read(w->dapm, w->reg);
val = val >> w->shift;
val &= w->mask; if (val == w->on_val)
w->power = 1;
}
w->new = 1;
dapm_mark_dirty(w, "new widget");
dapm_debugfs_add_widget(w);
}
/** * snd_soc_dapm_put_volsw - dapm mixer set callback * @kcontrol: mixer control * @ucontrol: control element information * * Callback to set the value of a dapm mixer control. * * Returns 0 for success.
*/ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct snd_soc_card *card = dapm->card; struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value; int reg = mc->reg; unsignedint shift = mc->shift; int max = mc->max; unsignedint width = fls(max); unsignedint mask = (1 << width) - 1; unsignedint invert = mc->invert; unsignedint val, rval = 0; int connect, rconnect = -1, change, reg_change = 0; struct snd_soc_dapm_update update = {}; struct snd_soc_dapm_update *pupdate = NULL; int ret = 0;
val = (ucontrol->value.integer.value[0] & mask);
connect = !!val;
if (invert)
val = max - val;
if (snd_soc_volsw_is_stereo(mc)) {
rval = (ucontrol->value.integer.value[1] & mask);
rconnect = !!rval; if (invert)
rval = max - rval;
}
snd_soc_dapm_mutex_lock(card);
/* This assumes field width < (bits in unsigned int / 2) */ if (width > sizeof(unsignedint) * 8 / 2)
dev_warn(dapm->dev, "ASoC: control %s field width limit exceeded\n",
kcontrol->id.name);
change = dapm_kcontrol_set_value(kcontrol, val | (rval << width));
if (reg != SND_SOC_NOPM) {
val = val << shift;
rval = rval << mc->rshift;
/** * snd_soc_dapm_get_enum_double - dapm enumerated double mixer get callback * @kcontrol: mixer control * @ucontrol: control element information * * Callback to get the value of a dapm enumerated double mixer control. * * Returns 0 for success.
*/ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsignedint reg_val, val;
/** * snd_soc_dapm_info_pin_switch - Info for a pin switch * * @kcontrol: mixer control * @uinfo: control element information * * Callback to provide information about a pin switch control.
*/ int snd_soc_dapm_info_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
/** * snd_soc_dapm_get_pin_switch - Get information for a pin switch * * @kcontrol: mixer control * @ucontrol: Value * * Callback to provide information for a pin switch added at the card * level.
*/ int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); constchar *pin = (constchar *)kcontrol->private_value;
/** * snd_soc_dapm_get_component_pin_switch - Get information for a pin switch * * @kcontrol: mixer control * @ucontrol: Value * * Callback to provide information for a pin switch added at the component * level.
*/ int snd_soc_dapm_get_component_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); constchar *pin = (constchar *)kcontrol->private_value;
snd_soc_dapm_mutex_lock(dapm);
ret = __snd_soc_dapm_set_pin(dapm, pin, !!ucontrol->value.integer.value[0]);
snd_soc_dapm_mutex_unlock(dapm);
snd_soc_dapm_sync(dapm);
return ret;
}
/** * snd_soc_dapm_put_pin_switch - Set information for a pin switch * * @kcontrol: mixer control * @ucontrol: Value * * Callback to provide information for a pin switch added at the card * level.
*/ int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); constchar *pin = (constchar *)kcontrol->private_value;
/** * snd_soc_dapm_put_component_pin_switch - Set information for a pin switch * * @kcontrol: mixer control * @ucontrol: Value * * Callback to provide information for a pin switch added at the component * level.
*/ int snd_soc_dapm_put_component_pin_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); constchar *pin = (constchar *)kcontrol->private_value;
struct snd_soc_dapm_widget *
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, conststruct snd_soc_dapm_widget *widget)
{ enum snd_soc_dapm_direction dir; struct snd_soc_dapm_widget *w; int ret = -ENOMEM;
w = dapm_cnew_widget(widget, soc_dapm_prefix(dapm)); if (!w) goto cnew_failed;
switch (w->id) { case snd_soc_dapm_regulator_supply:
w->regulator = devm_regulator_get(dapm->dev, widget->name); if (IS_ERR(w->regulator)) {
ret = PTR_ERR(w->regulator); goto request_failed;
}
if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
ret = regulator_allow_bypass(w->regulator, true); if (ret != 0)
dev_warn(dapm->dev, "ASoC: Failed to bypass %s: %d\n",
w->name, ret);
} break; case snd_soc_dapm_pinctrl:
w->pinctrl = devm_pinctrl_get(dapm->dev); if (IS_ERR(w->pinctrl)) {
ret = PTR_ERR(w->pinctrl); goto request_failed;
}
/* set to sleep_state when initializing */
snd_soc_dapm_pinctrl_event(w, NULL, SND_SOC_DAPM_POST_PMD); break; case snd_soc_dapm_clock_supply:
w->clk = devm_clk_get(dapm->dev, widget->name); if (IS_ERR(w->clk)) {
ret = PTR_ERR(w->clk); goto request_failed;
} break; default: break;
}
switch (w->id) { case snd_soc_dapm_mic:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_input: if (!dapm->card->fully_routed)
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_spk: case snd_soc_dapm_hp:
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_output: if (!dapm->card->fully_routed)
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_generic_check_power; break; case snd_soc_dapm_vmid: case snd_soc_dapm_siggen:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_always_on_check_power; break; case snd_soc_dapm_sink:
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_always_on_check_power; break;
case snd_soc_dapm_mux: case snd_soc_dapm_demux: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: case snd_soc_dapm_pga: case snd_soc_dapm_buffer: case snd_soc_dapm_scheduler: case snd_soc_dapm_effect: case snd_soc_dapm_src: case snd_soc_dapm_asrc: case snd_soc_dapm_encoder: case snd_soc_dapm_decoder: case snd_soc_dapm_out_drv: case snd_soc_dapm_micbias: case snd_soc_dapm_line: case snd_soc_dapm_dai_link: case snd_soc_dapm_dai_out: case snd_soc_dapm_dai_in:
w->power_check = dapm_generic_check_power; 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_kcontrol:
w->is_supply = 1;
w->power_check = dapm_supply_check_power; break; default:
w->power_check = dapm_always_on_check_power; break;
}
/** * snd_soc_dapm_new_control - create new dapm control * @dapm: DAPM context * @widget: widget template * * Creates new DAPM control based upon a template. * * Returns a widget pointer on success or an error pointer on failure
*/ struct snd_soc_dapm_widget *
snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, conststruct snd_soc_dapm_widget *widget)
{ struct snd_soc_dapm_widget *w;
snd_soc_dapm_mutex_lock(dapm);
w = snd_soc_dapm_new_control_unlocked(dapm, widget);
snd_soc_dapm_mutex_unlock(dapm);
/** * snd_soc_dapm_new_controls - create new dapm controls * @dapm: DAPM context * @widget: widget array * @num: number of widgets * * Creates new DAPM controls based upon the templates. * * Returns 0 for success else error.
*/ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, conststruct snd_soc_dapm_widget *widget, unsignedint num)
{ int i; int ret = 0;
snd_soc_dapm_mutex_lock_root(dapm); for (i = 0; i < num; i++) { struct snd_soc_dapm_widget *w = snd_soc_dapm_new_control_unlocked(dapm, widget); if (IS_ERR(w)) {
ret = PTR_ERR(w); break;
}
widget++;
}
snd_soc_dapm_mutex_unlock(dapm); return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
/* * NOTE * * snd_pcm_hw_params is quite large (608 bytes on arm64) and is * starting to get a bit excessive for allocation on the stack, * especially when you're building with some of the KASAN type * stuff that increases stack usage. * So, we use kzalloc()/kfree() for params in this function.
*/ struct snd_pcm_hw_params *params __free(kfree) = kzalloc(sizeof(*params),
GFP_KERNEL); if (!params) return -ENOMEM;
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); if (!runtime) return -ENOMEM;
ret = snd_soc_dai_startup(sink, substream); if (ret < 0) return ret;
snd_soc_dai_activate(sink, substream->stream);
}
substream->hw_opened = 1;
/* * Note: getting the config after .startup() gives a chance to * either party on the link to alter the configuration if * necessary
*/
config = rtd->dai_link->c2c_params + rtd->c2c_params_select; if (!config) {
dev_err(w->dapm->dev, "ASoC: link config missing\n"); return -EINVAL;
}
/* Be a little careful as we don't want to overflow the mask array */ if (!config->formats) {
dev_warn(w->dapm->dev, "ASoC: Invalid format was specified\n");
/* For each DAI widget... */
for_each_card_widgets(card, dai_w) { switch (dai_w->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: break; default: continue;
}
/* let users know there is no DAI to link */ if (!dai_w->priv) {
dev_dbg(card->dev, "dai widget %s has no DAI\n",
dai_w->name); continue;
}
dai = dai_w->priv;
/* ...find all widgets with the same stream and link them */
for_each_card_widgets(card, w) { if (w->dapm != dai_w->dapm) continue;
switch (w->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: continue; default: break;
}
if (!w->sname || !strstr(w->sname, dai_w->sname)) continue;
/* connect BE DAI playback if widgets are valid */
cpu = snd_soc_dai_get_widget(cpu_dai, stream_cpu);
codec = snd_soc_dai_get_widget(codec_dai, stream_codec);
if (!cpu || !codec) continue;
/* special handling for [Codec2Codec] */ if (dai_link->c2c_params && !rtd->c2c_widget[stream]) { struct snd_pcm_substream *substream = rtd->pcm->streams[stream].substream; struct snd_soc_dapm_widget *dai = snd_soc_dapm_new_dai(card, substream,
widget_name[stream]);
staticvoid soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, int event)
{ struct snd_soc_dai *dai; int i;
for_each_rtd_dais(rtd, i, dai)
soc_dapm_dai_stream_event(dai, stream, event);
dapm_power_widgets(rtd->card, event, NULL);
}
/** * snd_soc_dapm_stream_event - send a stream event to the dapm core * @rtd: PCM runtime data * @stream: stream name * @event: stream event * * Sends a stream event to the dapm core. The core then makes any * necessary widget power changes. * * Returns 0 for success else error.
*/ void snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, int event)
{ struct snd_soc_card *card = rtd->card;
void snd_soc_dapm_stream_stop(struct snd_soc_pcm_runtime *rtd, int stream)
{ if (stream == SNDRV_PCM_STREAM_PLAYBACK) { if (snd_soc_runtime_ignore_pmdown_time(rtd)) { /* powered down playback stream now */
snd_soc_dapm_stream_event(rtd,
SNDRV_PCM_STREAM_PLAYBACK,
SND_SOC_DAPM_STREAM_STOP);
} else { /* start delayed pop wq here for playback streams */
rtd->pop_wait = 1;
queue_delayed_work(system_power_efficient_wq,
&rtd->delayed_work,
msecs_to_jiffies(rtd->pmdown_time));
}
} else { /* capture streams can be powered down now */
snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE,
SND_SOC_DAPM_STREAM_STOP);
}
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_stop);
/** * snd_soc_dapm_enable_pin_unlocked - enable pin. * @dapm: DAPM context * @pin: pin name * * Enables input/output pin and its parents or children widgets iff there is * a valid audio route and active audio stream. * * Requires external locking. * * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching.
*/ int snd_soc_dapm_enable_pin_unlocked(struct snd_soc_dapm_context *dapm, constchar *pin)
{ return snd_soc_dapm_set_pin(dapm, pin, 1);
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin_unlocked);
/** * snd_soc_dapm_enable_pin - enable pin. * @dapm: DAPM context * @pin: pin name * * Enables input/output pin and its parents or children widgets iff there is * a valid audio route and active audio stream. * * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching.
*/ int snd_soc_dapm_enable_pin(struct snd_soc_dapm_context *dapm, constchar *pin)
{ int ret;
/** * snd_soc_dapm_force_enable_pin_unlocked - force a pin to be enabled * @dapm: DAPM context * @pin: pin name * * Enables input/output pin regardless of any other state. This is * intended for use with microphone bias supplies used in microphone * jack detection. * * Requires external locking. * * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching.
*/ int snd_soc_dapm_force_enable_pin_unlocked(struct snd_soc_dapm_context *dapm, constchar *pin)
{ struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true);
dev_dbg(w->dapm->dev, "ASoC: force enable pin %s\n", pin); if (!w->connected) { /* * w->force does not affect the number of input or output paths, * so we only have to recheck if w->connected is changed
*/
dapm_widget_invalidate_input_paths(w);
dapm_widget_invalidate_output_paths(w);
w->connected = 1;
}
w->force = 1;
dapm_mark_dirty(w, "force enable");
/** * snd_soc_dapm_force_enable_pin - force a pin to be enabled * @dapm: DAPM context * @pin: pin name * * Enables input/output pin regardless of any other state. This is * intended for use with microphone bias supplies used in microphone * jack detection. * * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching.
*/ int snd_soc_dapm_force_enable_pin(struct snd_soc_dapm_context *dapm, constchar *pin)
{ int ret;
snd_soc_dapm_mutex_lock(dapm);
ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, pin);
/** * snd_soc_dapm_disable_pin_unlocked - disable pin. * @dapm: DAPM context * @pin: pin name * * Disables input/output pin and its parents or children widgets. * * Requires external locking. * * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching.
*/ int snd_soc_dapm_disable_pin_unlocked(struct snd_soc_dapm_context *dapm, constchar *pin)
{ return snd_soc_dapm_set_pin(dapm, pin, 0);
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin_unlocked);
/** * snd_soc_dapm_disable_pin - disable pin. * @dapm: DAPM context * @pin: pin name * * Disables input/output pin and its parents or children widgets. * * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching.
*/ int snd_soc_dapm_disable_pin(struct snd_soc_dapm_context *dapm, constchar *pin)
{ int ret;
/** * snd_soc_dapm_ignore_suspend - ignore suspend status for DAPM endpoint * @dapm: DAPM context * @pin: audio signal pin endpoint (or start point) * * Mark the given endpoint or pin as ignoring suspend. When the * system is disabled a path between two endpoints flagged as ignoring * suspend will not be disabled. The path must already be enabled via * normal means at suspend time, it will not be turned on if it was not * already enabled.
*/ int snd_soc_dapm_ignore_suspend(struct snd_soc_dapm_context *dapm, constchar *pin)
{ struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, false);
/* If there were no widgets to power down we're already in * standby.
*/ if (powerdown) { if (dapm->bias_level == SND_SOC_BIAS_ON)
snd_soc_dapm_set_bias_level(dapm,
SND_SOC_BIAS_PREPARE);
dapm_seq_run(card, &down_list, 0, false); if (dapm->bias_level == SND_SOC_BIAS_PREPARE)
snd_soc_dapm_set_bias_level(dapm,
SND_SOC_BIAS_STANDBY);
}
snd_soc_dapm_mutex_unlock(card);
}
/* * snd_soc_dapm_shutdown - callback for system shutdown
*/ void snd_soc_dapm_shutdown(struct snd_soc_card *card)
{ struct snd_soc_dapm_context *dapm;
for_each_card_dapms(card, dapm) { if (dapm != &card->dapm) {
soc_dapm_shutdown_dapm(dapm); if (dapm->bias_level == SND_SOC_BIAS_STANDBY)
snd_soc_dapm_set_bias_level(dapm,
SND_SOC_BIAS_OFF);
}
}
soc_dapm_shutdown_dapm(&card->dapm); if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY)
snd_soc_dapm_set_bias_level(&card->dapm,
SND_SOC_BIAS_OFF);
}
/* Module information */
MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC");
MODULE_LICENSE("GPL");
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.56 Sekunden
(vorverarbeitet am 2026-04-26)
¤
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.