// 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.
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;
}
}
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]; return1;
}
}
}
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);
}
}
return0;
}
/* 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);
}
return0;
}
/* 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;
}
return0;
}
/* 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) return0;
/* 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 *theALSAcard-whenwearesuspendingtheALSAstateforthecard *issettoD3.
*/ 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: return1;
}
}
/* *Handlerforregulatorsupplywidget.
*/ 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;
return0;
}
/* 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. * *Wewalkoverapre-sortedlistofwidgetstoapplypowerto.In *ordertominimisethenumberofwritestothedevicerequired *multiplewidgetswillbeupdatedinasinglewritewherepossible. *Currentlyanythingthatrequiresmorethanasinglewriteisnot *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'rechangingstate.
*/ 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 *willhavemarkedthepeerdirty,otherwisethewidgetsare
* 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");
}
/* Check which widgets we need to power and store them in *listsindicatingiftheyshouldbepoweredupordown.We *onlycheckwidgetsthathavebeenflaggedasdirtybutnote *thatnewwidgetsmaybeaddedtothedirtylistwhilewe *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 *contextuptoSTANDBYasunlesssomething *elseisactiveandpassingaudiothey *generallydon'trequirefullpower.Signal *generatorsarevirtualpinsandhaveno *powerimpactthemselves.
*/ 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'renotgroundreferenced.
*/
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);
/* 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;
/* card won't be set for the dummy component, as a spot fix *we'recheckingforthatcasespecificallyherebutinfuture *wewillensurethatthedummycomponentlookslikeothers.
*/ if (!component->card) return0;
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
*/
static int __snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
const char *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
*/
static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm,
const char *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);
static int 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,
const struct 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;
unsigned int 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_get_volsw - dapm mixer get callback
* @kcontrol: mixer control
* @ucontrol: control element information
*
* Callback to get the value of a dapm mixer control.
*
* Returns 0 for success.
*/
int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int reg = mc->reg;
unsigned int shift = mc->shift;
int max = mc->max;
unsigned int width = fls(max);
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
unsigned int reg_val, val, rval = 0;
snd_soc_dapm_mutex_lock(dapm);
if (dapm_kcontrol_is_powered(kcontrol) && reg != SND_SOC_NOPM) {
reg_val = soc_dapm_read(dapm, reg);
val = (reg_val >> shift) & mask;
if (reg != mc->rreg)
reg_val = soc_dapm_read(dapm, mc->rreg);
if (snd_soc_volsw_is_stereo(mc))
rval = (reg_val >> mc->rshift) & mask;
} else {
reg_val = dapm_kcontrol_get_value(kcontrol);
val = reg_val & mask;
/**
* 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;
unsigned int shift = mc->shift;
int max = mc->max;
unsigned int width = fls(max);
unsigned int mask = (1 << width) - 1;
unsigned int invert = mc->invert;
unsigned int 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(unsigned int) * 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;
unsigned int 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);
const char *pin = (const char *)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);
const char *pin = (const char *)kcontrol->private_value;
static int __snd_soc_dapm_put_pin_switch(struct snd_soc_dapm_context *dapm,
const char *pin,
struct snd_ctl_elem_value *ucontrol)
{
int ret;
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);
const char *pin = (const char *)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);
const char *pin = (const char *)kcontrol->private_value;
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,
const struct 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,
const struct snd_soc_dapm_widget *widget,
unsigned int 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]);
static void 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,
const char *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, const char *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,
const char *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,
const char *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,
const char *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,
const char *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,
const char *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
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.177Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-11)
¤
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.