// SPDX-License-Identifier: GPL-2.0+ // // soc-core.c -- ALSA SoC Audio Layer // // Copyright 2005 Wolfson Microelectronics PLC. // Copyright 2005 Openedhand Ltd. // Copyright (C) 2010 Slimlogic Ltd. // Copyright (C) 2010 Texas Instruments Inc. // // Author: Liam Girdwood <lrg@slimlogic.co.uk> // with code, comments and ideas from :- // Richard Purdie <richard@openedhand.com> // // TODO: // o Add hw rules to enforce rates, etc. // o More testing with other codecs/machines. // o Add more codecs and platforms to ensure good API coverage. // o Support TDM on PCM and I2S
/* * This is used if driver don't need to have CPU/Codec/Platform * dai_link. see soc.h
*/ struct snd_soc_dai_link_component null_dailink_component[0];
EXPORT_SYMBOL_GPL(null_dailink_component);
/* * This is a timeout to do a DAPM powerdown after a stream is closed(). * It can be used to eliminate pops between different playback streams, e.g. * between two audio tracks.
*/ staticint pmdown_time = 5000;
module_param(pmdown_time, int, 0);
MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
/* * NOTE * * snd_soc_rtdcom_lookup() will find component from rtd by using * specified driver name. * But, if many components which have same driver name are connected * to 1 rtd, this function will return 1st found component.
*/
for_each_rtd_components(rtd, i, component) { constchar *component_name = component->driver->name;
/* * Power down the audio subsystem pmdown_time msecs after close is called. * This is to ensure there are no pops or clicks in between any music tracks * due to DAPM power cycling.
*/ void snd_soc_close_delayed_work(struct snd_soc_pcm_runtime *rtd)
{ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); int playback = SNDRV_PCM_STREAM_PLAYBACK;
/* are we waiting on this codec DAI stream */ if (rtd->pop_wait == 1) {
rtd->pop_wait = 0;
snd_soc_dapm_stream_event(rtd, playback,
SND_SOC_DAPM_STREAM_STOP);
}
staticvoid soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd)
{ if (!rtd) return;
list_del(&rtd->list);
if (delayed_work_pending(&rtd->delayed_work))
flush_delayed_work(&rtd->delayed_work);
snd_soc_pcm_component_free(rtd);
/* * we don't need to call kfree() for rtd->dev * see * soc_release_rtd_dev() * * We don't need rtd->dev NULL check, because * it is alloced *before* rtd. * see * soc_new_pcm_runtime() * * We don't need to mind freeing for rtd, * because it was created from dev (= rtd->dev) * see * soc_new_pcm_runtime() * * rtd = devm_kzalloc(dev, ...); * rtd->dev = dev
*/
device_unregister(rtd->dev);
}
staticvoid snd_soc_fill_dummy_dai(struct snd_soc_card *card)
{ struct snd_soc_dai_link *dai_link; int i;
/* * COMP_DUMMY() creates size 0 array on dai_link. * Fill it as dummy DAI in case of CPU/Codec here. * Do nothing for Platform.
*/
for_each_card_prelinks(card, i, dai_link) { if (dai_link->num_cpus == 0 && dai_link->cpus) {
dai_link->num_cpus = 1;
dai_link->cpus = &snd_soc_dummy_dlc;
} if (dai_link->num_codecs == 0 && dai_link->codecs) {
dai_link->num_codecs = 1;
dai_link->codecs = &snd_soc_dummy_dlc;
}
}
}
/* powers down audio subsystem for suspend */ int snd_soc_suspend(struct device *dev)
{ struct snd_soc_card *card = dev_get_drvdata(dev); struct snd_soc_component *component; struct snd_soc_pcm_runtime *rtd; int i;
/* If the card is not initialized yet there is nothing to do */ if (!snd_soc_card_is_instantiated(card)) return 0;
/* * Due to the resume being scheduled into a workqueue we could * suspend before that's finished - wait for it to complete.
*/
snd_power_wait(card->snd_card);
/* we're going to block userspace touching us until resume completes */
snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot);
/* mute any active DACs */
soc_playback_digital_mute(card, 1);
/* suspend all pcms */
for_each_card_rtds(card, rtd) { if (rtd->dai_link->ignore_suspend) continue;
snd_pcm_suspend_all(rtd->pcm);
}
snd_soc_card_suspend_pre(card);
/* close any waiting streams */
snd_soc_flush_all_delayed_work(card);
/* Recheck all endpoints too, their state is affected by suspend */
snd_soc_dapm_mark_endpoints_dirty(card);
snd_soc_dapm_sync(&card->dapm);
/* suspend all COMPONENTs */
for_each_card_rtds(card, rtd) {
if (rtd->dai_link->ignore_suspend) continue;
for_each_rtd_components(rtd, i, component) { struct snd_soc_dapm_context *dapm =
snd_soc_component_get_dapm(component);
/* * ignore if component was already suspended
*/ if (snd_soc_component_is_suspended(component)) continue;
/* * If there are paths active then the COMPONENT will be * held with bias _ON and should not be suspended.
*/ switch (snd_soc_dapm_get_bias_level(dapm)) { case SND_SOC_BIAS_STANDBY: /* * If the COMPONENT is capable of idle * bias off then being in STANDBY * means it's doing something, * otherwise fall through.
*/ if (dapm->idle_bias_off) {
dev_dbg(component->dev, "ASoC: idle_bias_off CODEC on over suspend\n"); break;
}
fallthrough;
case SND_SOC_BIAS_OFF:
snd_soc_component_suspend(component); if (component->regmap)
regcache_mark_dirty(component->regmap); /* deactivate pins to sleep state */
pinctrl_pm_select_sleep_state(component->dev); break; default:
dev_dbg(component->dev, "ASoC: COMPONENT is on over suspend\n"); break;
}
}
}
snd_soc_card_suspend_post(card);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_suspend);
/* * deferred resume work, so resume can complete before we finished * setting our codec back up, which can be very slow on I2C
*/ staticvoid soc_resume_deferred(struct work_struct *work)
{ struct snd_soc_card *card =
container_of(work, struct snd_soc_card,
deferred_resume_work); struct snd_soc_component *component;
/* * our power state is still SNDRV_CTL_POWER_D3hot from suspend time, * so userspace apps are blocked from touching us
*/
/* unmute any active DACs */
soc_playback_digital_mute(card, 0);
snd_soc_card_resume_post(card);
dev_dbg(card->dev, "ASoC: resume work completed\n");
/* Recheck all endpoints too, their state is affected by suspend */
snd_soc_dapm_mark_endpoints_dirty(card);
snd_soc_dapm_sync(&card->dapm);
/* userspace can access us now we are back as we were before */
snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0);
}
/* powers up audio subsystem after a suspend */ int snd_soc_resume(struct device *dev)
{ struct snd_soc_card *card = dev_get_drvdata(dev); struct snd_soc_component *component;
/* If the card is not initialized yet there is nothing to do */ if (!snd_soc_card_is_instantiated(card)) return 0;
/* activate pins from sleep state */
for_each_card_components(card, component) if (snd_soc_component_active(component))
pinctrl_pm_select_default_state(component->dev);
dev_dbg(dev, "ASoC: Scheduling resume work\n"); if (!schedule_work(&card->deferred_resume_work))
dev_err(dev, "ASoC: resume work item may be lost\n");
/* * NOTE * * It returns *1st* found component, but some driver * has few components by same of_node/name * ex) * CPU component and generic DMAEngine component
*/
for_each_component(component) if (snd_soc_is_matching_component(dlc, component)) return component;
return NULL;
}
/** * snd_soc_find_dai - Find a registered DAI * * @dlc: name of the DAI or the DAI driver and optional component info to match * * This function will search all registered components and their DAIs to * find the DAI of the same name. The component's of_node and name * should also match if being specified. * * Return: pointer of DAI, or NULL if not found.
*/ struct snd_soc_dai *snd_soc_find_dai( conststruct snd_soc_dai_link_component *dlc)
{ struct snd_soc_component *component; struct snd_soc_dai *dai;
lockdep_assert_held(&client_mutex);
/* Find CPU DAI from registered DAIs */
for_each_component(component) if (snd_soc_is_matching_component(dlc, component))
for_each_component_dais(component, dai) if (snd_soc_is_matching_dai(dlc, dai)) return dai;
/* Codec check */
for_each_link_codecs(link, i, dlc) { /* * Codec must be specified by 1 of name or OF node, * not both or neither.
*/ if (snd_soc_dlc_component_is_invalid(dlc)) goto component_invalid;
if (snd_soc_dlc_component_is_empty(dlc)) goto component_empty;
/* Codec DAI name must be specified */ if (snd_soc_dlc_dai_is_empty(dlc)) goto dai_empty;
/* * Defer card registration if codec component is not added to * component list.
*/ if (!soc_find_component(dlc)) goto component_not_found;
}
/* Platform check */
for_each_link_platforms(link, i, dlc) { /* * Platform may be specified by either name or OF node, but it * can be left unspecified, then no components will be inserted * in the rtdcom list
*/ if (snd_soc_dlc_component_is_invalid(dlc)) goto component_invalid;
if (snd_soc_dlc_component_is_empty(dlc)) goto component_empty;
/* * Defer card registration if platform component is not added to * component list.
*/ if (!soc_find_component(dlc)) goto component_not_found;
}
/* CPU check */
for_each_link_cpus(link, i, dlc) { /* * CPU device may be specified by either name or OF node, but * can be left unspecified, and will be matched based on DAI * name alone..
*/ if (snd_soc_dlc_component_is_invalid(dlc)) goto component_invalid;
if (snd_soc_dlc_component_is_empty(dlc)) { /* * At least one of CPU DAI name or CPU device name/node must be specified
*/ if (snd_soc_dlc_dai_is_empty(dlc)) goto component_dai_empty;
} else { /* * Defer card registration if Component is not added
*/ if (!soc_find_component(dlc)) goto component_not_found;
}
}
return 0;
component_invalid:
dev_err(card->dev, "ASoC: Both Component name/of_node are set for %s\n", link->name); return -EINVAL;
component_empty:
dev_err(card->dev, "ASoC: Neither Component name/of_node are set for %s\n", link->name); return -EINVAL;
component_not_found:
dev_dbg(card->dev, "ASoC: Component %s not found for link %s\n", dlc->name, link->name); return -EPROBE_DEFER;
dai_empty:
dev_err(card->dev, "ASoC: DAI name is not set for %s\n", link->name); return -EINVAL;
component_dai_empty:
dev_err(card->dev, "ASoC: Neither DAI/Component name/of_node are set for %s\n", link->name); return -EINVAL;
}
/* * dai_link->ch_maps indicates how CPU/Codec are connected. * It will be a map seen from a larger number of DAI. * see * soc.h :: [dai_link->ch_maps Image sample]
*/
/* it should have ch_maps if connection was N:M */ if (dai_link->num_cpus > 1 && dai_link->num_codecs > 1 &&
dai_link->num_cpus != dai_link->num_codecs && !dai_link->ch_maps) {
dev_err(card->dev, "need to have ch_maps when N:M connection (%s)",
dai_link->name); return -EINVAL;
}
/* do nothing if it has own maps */ if (dai_link->ch_maps) goto sanity_check;
/* Compensate missing map for ... */ if (dai_link->num_cpus == dai_link->num_codecs)
dai_link->ch_maps = default_ch_map_sync; /* for 1:1 or N:N */ elseif (dai_link->num_cpus < dai_link->num_codecs)
dai_link->ch_maps = default_ch_map_1cpu; /* for 1:N */ else
dai_link->ch_maps = default_ch_map_1codec; /* for N:1 */
sanity_check:
dev_dbg(card->dev, "dai_link %s\n", dai_link->stream_name);
for_each_link_ch_maps(dai_link, i, ch_maps) { if ((ch_maps->cpu >= dai_link->num_cpus) ||
(ch_maps->codec >= dai_link->num_codecs)) {
dev_err(card->dev, "unexpected dai_link->ch_maps[%d] index (cpu(%d/%d) codec(%d/%d))",
i,
ch_maps->cpu, dai_link->num_cpus,
ch_maps->codec, dai_link->num_codecs); return -EINVAL;
}
dev_dbg(card->dev, " [%d] cpu%d <-> codec%d\n",
i, ch_maps->cpu, ch_maps->codec);
}
return 0;
}
/** * snd_soc_remove_pcm_runtime - Remove a pcm_runtime from card * @card: The ASoC card to which the pcm_runtime has * @rtd: The pcm_runtime to remove * * This function removes a pcm_runtime from the ASoC card.
*/ void snd_soc_remove_pcm_runtime(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd)
{ if (!rtd) return;
lockdep_assert_held(&client_mutex);
/* * Notify the machine driver for extra destruction
*/
snd_soc_card_remove_dai_link(card, rtd->dai_link);
/** * snd_soc_add_pcm_runtime - Add a pcm_runtime dynamically via dai_link * @card: The ASoC card to which the pcm_runtime is added * @dai_link: The DAI link to find pcm_runtime * * This function adds a pcm_runtime ASoC card by using dai_link. * * Note: Topology can use this API to add pcm_runtime when probing the * topology component. And machine drivers can still define static * DAI links in dai_link array.
*/ staticint snd_soc_add_pcm_runtime(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
{ struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai_link_component *codec, *platform, *cpu; struct snd_soc_component *component; int i, id, ret;
lockdep_assert_held(&client_mutex);
/* * Notify the machine driver for extra initialization
*/
ret = snd_soc_card_add_dai_link(card, dai_link); if (ret < 0) return ret;
/* Find PLATFORM from registered PLATFORMs */
for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) { if (!snd_soc_is_matching_component(platform, component)) continue;
if (snd_soc_component_is_dummy(component) && component->num_dai) continue;
snd_soc_rtd_add_component(rtd, component);
}
}
/* * Most drivers will register their PCMs using DAI link ordering but * topology based drivers can use the DAI link id field to set PCM * device number and then use rtd + a base offset of the BEs. * * FIXME * * This should be implemented by using "dai_link" feature instead of * "component" feature.
*/
id = rtd->id;
for_each_rtd_components(rtd, i, component) { if (!component->driver->use_dai_pcm_id) continue;
if (rtd->dai_link->no_pcm)
id += component->driver->be_pcm_base; else
id = rtd->dai_link->id;
}
rtd->id = id;
int snd_soc_add_pcm_runtimes(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link, int num_dai_link)
{ for (int i = 0; i < num_dai_link; i++) { int ret;
ret = snd_soc_compensate_channel_connection_map(card, dai_link + i); if (ret < 0) return ret;
ret = snd_soc_add_pcm_runtime(card, dai_link + i); if (ret < 0) return ret;
}
pri = (j >= i) ? priority : priority - 1;
fmt = snd_soc_dai_get_fmt(dai, pri);
possible_fmt &= fmt;
} if (possible_fmt) goto found;
}
} /* Not Found */ return;
found: /* * convert POSSIBLE_DAIFMT to DAIFMT * * Some basic/default settings on each is defined as 0. * see * SND_SOC_DAIFMT_NB_NF * SND_SOC_DAIFMT_GATED * * SND_SOC_DAIFMT_xxx_MASK can't notice it if Sound Card specify * these value, and will be overwrite to auto selected value. * * To avoid such issue, loop from 63 to 0 here. * Small number of SND_SOC_POSSIBLE_xxx will be Hi priority. * Basic/Default settings of each part and above are defined * as Hi priority (= small number) of SND_SOC_POSSIBLE_xxx.
*/ for (i = 63; i >= 0; i--) {
pos = 1ULL << i; switch (possible_fmt & pos) { /* * for format
*/ case SND_SOC_POSSIBLE_DAIFMT_I2S: case SND_SOC_POSSIBLE_DAIFMT_RIGHT_J: case SND_SOC_POSSIBLE_DAIFMT_LEFT_J: case SND_SOC_POSSIBLE_DAIFMT_DSP_A: case SND_SOC_POSSIBLE_DAIFMT_DSP_B: case SND_SOC_POSSIBLE_DAIFMT_AC97: case SND_SOC_POSSIBLE_DAIFMT_PDM:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | i; break; /* * for clock
*/ case SND_SOC_POSSIBLE_DAIFMT_CONT:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_CONT; break; case SND_SOC_POSSIBLE_DAIFMT_GATED:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_GATED; break; /* * for clock invert
*/ case SND_SOC_POSSIBLE_DAIFMT_NB_NF:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_NF; break; case SND_SOC_POSSIBLE_DAIFMT_NB_IF:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_IF; break; case SND_SOC_POSSIBLE_DAIFMT_IB_NF:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_NF; break; case SND_SOC_POSSIBLE_DAIFMT_IB_IF:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_IF; break; /* * for clock provider / consumer
*/ case SND_SOC_POSSIBLE_DAIFMT_CBP_CFP:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFP; break; case SND_SOC_POSSIBLE_DAIFMT_CBC_CFP:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFP; break; case SND_SOC_POSSIBLE_DAIFMT_CBP_CFC:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFC; break; case SND_SOC_POSSIBLE_DAIFMT_CBC_CFC:
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFC; break;
}
}
/* * Some driver might have very complex limitation. * In such case, user want to auto-select non-limitation part, * and want to manually specify complex part. * * Or for example, if both CPU and Codec can be clock provider, * but because of its quality, user want to specify it manually. * * Use manually specified settings if sound card did.
*/ if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK))
mask |= SND_SOC_DAIFMT_FORMAT_MASK; if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_MASK))
mask |= SND_SOC_DAIFMT_CLOCK_MASK; if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_INV_MASK))
mask |= SND_SOC_DAIFMT_INV_MASK; if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK))
mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
dai_link->dai_fmt |= (dai_fmt & mask);
}
/** * snd_soc_runtime_set_dai_fmt() - Change DAI link format for a ASoC runtime * @rtd: The runtime for which the DAI link format should be changed * @dai_fmt: The new DAI link format * * This function updates the DAI link format for all DAIs connected to the DAI * link for the specified runtime. * * Note: For setups with a static format set the dai_fmt field in the * corresponding snd_dai_link struct instead of using this function. * * Returns 0 on success, otherwise a negative error code.
*/ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, unsignedint dai_fmt)
{ struct snd_soc_dai *cpu_dai; struct snd_soc_dai *codec_dai; unsignedint ext_fmt; unsignedint i; int ret;
if (!dai_fmt) return 0;
/* * dai_fmt has 4 types * 1. SND_SOC_DAIFMT_FORMAT_MASK * 2. SND_SOC_DAIFMT_CLOCK * 3. SND_SOC_DAIFMT_INV * 4. SND_SOC_DAIFMT_CLOCK_PROVIDER * * 4. CLOCK_PROVIDER is set from Codec perspective in dai_fmt. So it will be flipped * when this function calls set_fmt() for CPU (CBx_CFx -> Bx_Cx). see below. * This mean, we can't set CPU/Codec both are clock consumer for example. * New idea handles 4. in each dai->ext_fmt. It can keep compatibility. * * Legacy * dai_fmt includes 1, 2, 3, 4 * * New idea * dai_fmt includes 1, 2, 3 * ext_fmt includes 4
*/
for_each_rtd_codec_dais(rtd, i, codec_dai) {
ext_fmt = rtd->dai_link->codecs[i].ext_fmt;
ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt | ext_fmt); if (ret != 0 && ret != -ENOTSUPP) return ret;
}
/* Flip the polarity for the "CPU" end of link */ /* Will effect only for 4. SND_SOC_DAIFMT_CLOCK_PROVIDER */
dai_fmt = snd_soc_daifmt_clock_provider_flipped(dai_fmt);
for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
ext_fmt = rtd->dai_link->cpus[i].ext_fmt;
ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt | ext_fmt); if (ret != 0 && ret != -ENOTSUPP) return ret;
}
/* * If there is no configuration table or no match in the table, * check if a prefix is provided in the node
*/
ret = of_property_read_string(of_node, "sound-name-prefix", &str); if (ret < 0) return;
component->name_prefix = str;
}
staticvoid soc_remove_component(struct snd_soc_component *component, int probed)
{
if (snd_soc_component_is_dummy(component)) return 0;
if (component->card) { if (component->card != card) {
dev_err(component->dev, "Trying to bind component \"%s\" to card \"%s\" but is already bound to card \"%s\"\n",
component->name, card->name, component->card->name); return -ENODEV;
} return 0;
}
ret = snd_soc_component_module_get_when_probe(component); if (ret < 0) return ret;
ret = snd_soc_dapm_new_controls(dapm,
component->driver->dapm_widgets,
component->driver->num_dapm_widgets);
if (ret != 0) {
dev_err(component->dev, "Failed to create new controls %d\n", ret); goto err_probe;
}
for_each_component_dais(component, dai) {
ret = snd_soc_dapm_new_dai_widgets(dapm, dai); if (ret != 0) {
dev_err(component->dev, "Failed to create DAI widgets %d\n", ret); goto err_probe;
}
}
ret = snd_soc_component_probe(component); if (ret < 0) goto err_probe;
WARN(dapm->idle_bias_off &&
dapm->bias_level != SND_SOC_BIAS_OFF, "codec %s can not start from non-off bias with idle_bias_off==1\n",
component->name);
probed = 1;
/* * machine specific init * see * snd_soc_component_set_aux()
*/
ret = snd_soc_component_init(component); if (ret < 0) goto err_probe;
ret = snd_soc_add_component_controls(component,
component->driver->controls,
component->driver->num_controls); if (ret < 0) goto err_probe;
ret = snd_soc_dapm_add_routes(dapm,
component->driver->dapm_routes,
component->driver->num_dapm_routes); if (ret < 0) goto err_probe;
/* see for_each_card_components */
list_add(&component->card_list, &card->component_dev_list);
err_probe: if (ret < 0)
soc_remove_component(component, probed);
return ret;
}
staticvoid soc_remove_link_dais(struct snd_soc_card *card)
{ struct snd_soc_pcm_runtime *rtd; int order;
for_each_comp_order(order) {
for_each_card_rtds(card, rtd) { /* remove all rtd connected DAIs in good order */
snd_soc_pcm_dai_remove(rtd, order);
}
}
}
for_each_comp_order(order) {
for_each_card_rtds(card, rtd) { /* probe all rtd connected DAIs in good order */
ret = snd_soc_pcm_dai_probe(rtd, order); if (ret) return ret;
}
}
return 0;
}
staticvoid soc_remove_link_components(struct snd_soc_card *card)
{ struct snd_soc_component *component; struct snd_soc_pcm_runtime *rtd; int i, order;
for_each_comp_order(order) {
for_each_card_rtds(card, rtd) {
for_each_rtd_components(rtd, i, component) { if (component->driver->remove_order != order) continue;
soc_remove_component(component, 1);
}
}
}
}
staticint soc_probe_link_components(struct snd_soc_card *card)
{ struct snd_soc_component *component; struct snd_soc_pcm_runtime *rtd; int i, ret, order;
for_each_comp_order(order) {
for_each_card_rtds(card, rtd) {
for_each_rtd_components(rtd, i, component) { if (component->driver->probe_order != order) continue;
ret = soc_probe_component(card, component); if (ret < 0) return ret;
}
}
}
for_each_card_pre_auxs(card, i, aux) { /* codecs, usually analog devices */
component = soc_find_component(&aux->dlc); if (!component) return -EPROBE_DEFER;
/* for snd_soc_component_init() */
snd_soc_component_set_aux(component, aux); /* see for_each_card_auxs */
list_add(&component->card_aux_list, &card->aux_comp_list);
} return 0;
}
staticint soc_probe_aux_devices(struct snd_soc_card *card)
{ struct snd_soc_component *component; int order; int ret;
for_each_comp_order(order) {
for_each_card_auxs(card, component) { if (component->driver->probe_order != order) continue;
ret = soc_probe_component(card, component); if (ret < 0) return ret;
}
}
#ifdef CONFIG_DMI /* * If a DMI filed contain strings in this blacklist (e.g. * "Type2 - Board Manufacturer" or "Type1 - TBD by OEM"), it will be taken * as invalid and dropped when setting the card long name from DMI info.
*/ staticconstchar * const dmi_blacklist[] = { "To be filled by OEM", "TBD by OEM", "Default String", "Board Manufacturer", "Board Vendor Name", "Board Product Name",
NULL, /* terminator */
};
/* * Trim special characters, and replace '-' with '_' since '-' is used to * separate different DMI fields in the card long name. Only number and * alphabet characters and a few separator characters are kept.
*/ staticvoid cleanup_dmi_name(char *name)
{ int i, j = 0;
for (i = 0; name[i]; i++) { if (isalnum(name[i]) || (name[i] == '.')
|| (name[i] == '_'))
name[j++] = name[i]; elseif (name[i] == '-')
name[j++] = '_';
}
name[j] = '\0';
}
/* * Check if a DMI field is valid, i.e. not containing any string * in the black list.
*/ staticint is_dmi_valid(constchar *field)
{ int i = 0;
while (dmi_blacklist[i]) { if (strstr(field, dmi_blacklist[i])) return 0;
i++;
}
return 1;
}
/* * Append a string to card->dmi_longname with character cleanups.
*/ staticvoid append_dmi_string(struct snd_soc_card *card, constchar *str)
{ char *dst = card->dmi_longname;
size_t dst_len = sizeof(card->dmi_longname);
size_t len;
len++; /* skip the separator "-" */ if (len < dst_len)
cleanup_dmi_name(dst + len);
}
/** * snd_soc_set_dmi_name() - Register DMI names to card * @card: The card to register DMI names * * An Intel machine driver may be used by many different devices but are * difficult for userspace to differentiate, since machine drivers usually * use their own name as the card short name and leave the card long name * blank. To differentiate such devices and fix bugs due to lack of * device-specific configurations, this function allows DMI info to be used * as the sound card long name, in the format of * "vendor-product-version-board" * (Character '-' is used to separate different DMI fields here). * This will help the user space to load the device-specific Use Case Manager * (UCM) configurations for the card. * * Possible card long names may be: * DellInc.-XPS139343-01-0310JH * ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA * Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX * * This function also supports flavoring the card longname to provide * the extra differentiation, like "vendor-product-version-board-flavor". * * We only keep number and alphabet characters and a few separator characters * in the card long name since UCM in the user space uses the card long names * as card configuration directory names and AudoConf cannot support special * characters like SPACE. * * Returns 0 on success, otherwise a negative error code.
*/ staticint snd_soc_set_dmi_name(struct snd_soc_card *card)
{ constchar *vendor, *product, *board;
if (card->long_name) return 0; /* long name already set by driver or from DMI */
if (!dmi_available) return 0;
/* make up dmi long name as: vendor-product-version-board */
vendor = dmi_get_system_info(DMI_BOARD_VENDOR); if (!vendor || !is_dmi_valid(vendor)) {
dev_warn(card->dev, "ASoC: no DMI vendor name!\n"); return 0;
}
/* * some vendors like Lenovo may only put a self-explanatory * name in the product version field
*/ if (product_version && is_dmi_valid(product_version))
append_dmi_string(card, product_version);
}
board = dmi_get_system_info(DMI_BOARD_NAME); if (board && is_dmi_valid(board)) { if (!product || strcasecmp(board, product))
append_dmi_string(card, board);
} elseif (!product) { /* fall back to using legacy name */
dev_warn(card->dev, "ASoC: no DMI board/product name!\n"); return 0;
}
/* set the card long name */
card->long_name = card->dmi_longname;
/* does this component override BEs ? */ if (!component->driver->ignore_machine) continue;
/* for this machine ? */ if (!strcmp(component->driver->ignore_machine,
card->dev->driver->name)) goto match; if (strcmp(component->driver->ignore_machine,
dev_name(card->dev))) continue;
match: /* machine matches, so override the rtd data */
for_each_card_prelinks(card, i, dai_link) {
/* ignore this FE */ if (dai_link->dynamic) {
dai_link->ignore = true; continue;
}
dev_dbg(card->dev, "info: override BE DAI link %s\n",
card->dai_link[i].name);
if (component->dev->of_node)
dai_link->platforms->of_node = component->dev->of_node; else
dai_link->platforms->name = component->name;
/* convert non BE into BE */
dai_link->no_pcm = 1;
/* * override any BE fixups * see * snd_soc_link_be_hw_params_fixup()
*/
dai_link->be_hw_params_fixup =
component->driver->be_hw_params_fixup;
/* * most BE links don't set stream name, so set it to * dai link name if it's NULL to help bind widgets.
*/ if (!dai_link->stream_name)
dai_link->stream_name = dai_link->name;
}
/* Inform userspace we are using alternate topology */ if (component->driver->topology_name_prefix) {
/* * Name normalization (driver field) * * The driver name is somewhat special, as it's used as a key for * searches in the user-space. * * ex) * "abcd??efg" -> "abcd__efg"
*/ for (i = 0; i < len; i++) { switch (name[i]) { case'_': case'-': case'\0': break; default: if (!isalnum(name[i]))
name[i] = '_'; break;
}
}
/* * The driver field should contain a valid string from the user view. * The wrapping usually does not work so well here. Set a smaller string * in the specific ASoC driver.
*/ if (strlen(src) > len - 1)
dev_err(card->dev, "ASoC: driver name too long '%s' -> '%s'\n", src, name);
}
if (card->snd_card)
snd_card_disconnect_sync(card->snd_card);
snd_soc_dapm_shutdown(card);
/* release machine specific resources */
for_each_card_rtds(card, rtd) if (rtd->initialized)
snd_soc_link_exit(rtd); /* remove and free each DAI */
soc_remove_link_dais(card);
soc_remove_link_components(card);
/* check whether any platform is ignore machine FE and using topology */
soc_check_tplg_fes(card);
/* bind aux_devs too */
ret = soc_bind_aux_dev(card); if (ret < 0) goto probe_end;
/* add predefined DAI links to the list */
card->num_rtd = 0;
ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links); if (ret < 0) goto probe_end;
/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card); if (ret < 0) {
dev_err(card->dev, "ASoC: can't create sound card for card %s: %d\n",
card->name, ret); goto probe_end;
}
soc_init_card_debugfs(card);
soc_resume_init(card);
ret = snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
card->num_dapm_widgets); if (ret < 0) goto probe_end;
ret = snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
card->num_of_dapm_widgets); if (ret < 0) goto probe_end;
/* initialise the sound card only once */
ret = snd_soc_card_probe(card); if (ret < 0) goto probe_end;
/* probe all components used by DAI links on this card */
ret = soc_probe_link_components(card); if (ret < 0) { if (ret != -EPROBE_DEFER) {
dev_err(card->dev, "ASoC: failed to instantiate card %d\n", ret);
} goto probe_end;
}
/* probe auxiliary components */
ret = soc_probe_aux_devices(card); if (ret < 0) {
dev_err(card->dev, "ASoC: failed to probe aux component %d\n", ret); goto probe_end;
}
/* probe all DAI links on this card */
ret = soc_probe_link_dais(card); if (ret < 0) {
dev_err(card->dev, "ASoC: failed to instantiate card %d\n", ret); goto probe_end;
}
for_each_card_rtds(card, rtd) {
ret = soc_init_pcm_runtime(card, rtd); if (ret < 0) goto probe_end;
}
if (card->components) { /* the current implementation of snd_component_add() accepts */ /* multiple components in the string separated by space, */ /* but the string collision (identical string) check might */ /* not work correctly */
ret = snd_component_add(card->snd_card, card->components); if (ret < 0) {
dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n",
card->name, ret); goto probe_end;
}
}
ret = snd_soc_card_late_probe(card); if (ret < 0) goto probe_end;
/* deactivate pins to sleep state */
for_each_card_components(card, component) if (!snd_soc_component_active(component))
pinctrl_pm_select_sleep_state(component->dev);
probe_end: if (ret < 0)
soc_cleanup_card_resources(card);
snd_soc_card_mutex_unlock(card);
if (!snd_soc_card_is_instantiated(card)) return 0;
/* * Flush out pmdown_time work - we actually do want to run it * now, we're shutting down so no imminent restart.
*/
snd_soc_flush_all_delayed_work(card);
snd_soc_dapm_shutdown(card);
/* deactivate pins to sleep state */
for_each_card_components(card, component)
pinctrl_pm_select_sleep_state(component->dev);
/** * snd_soc_cnew - create new control * @_template: control template * @data: control private data * @long_name: control long name * @prefix: control name prefix * * Create a new mixer control from a template control. * * Returns 0 for success, else error.
*/ struct snd_kcontrol *snd_soc_cnew(conststruct snd_kcontrol_new *_template, void *data, constchar *long_name, constchar *prefix)
{ struct snd_kcontrol_new template; struct snd_kcontrol *kcontrol; char *name = NULL;
staticint snd_soc_add_controls(struct snd_card *card, struct device *dev, conststruct snd_kcontrol_new *controls, int num_controls, constchar *prefix, void *data)
{ int i;
for (i = 0; i < num_controls; i++) { conststruct snd_kcontrol_new *control = &controls[i]; int err = snd_ctl_add(card, snd_soc_cnew(control, data,
control->name, prefix)); if (err < 0) {
dev_err(dev, "ASoC: Failed to add %s: %d\n",
control->name, err); return err;
}
}
return 0;
}
/** * snd_soc_add_component_controls - Add an array of controls to a component. * * @component: Component to add controls to * @controls: Array of controls to add * @num_controls: Number of elements in the array * * Return: 0 for success, else error.
*/ int snd_soc_add_component_controls(struct snd_soc_component *component, conststruct snd_kcontrol_new *controls, unsignedint num_controls)
{ struct snd_card *card = component->card->snd_card;
/** * snd_soc_add_card_controls - add an array of controls to a SoC card. * Convenience function to add a list of controls. * * @soc_card: SoC card to add controls to * @controls: array of controls to add * @num_controls: number of elements in the array * * Return 0 for success, else error.
*/ int snd_soc_add_card_controls(struct snd_soc_card *soc_card, conststruct snd_kcontrol_new *controls, int num_controls)
{ struct snd_card *card = soc_card->snd_card;
/** * snd_soc_add_dai_controls - add an array of controls to a DAI. * Convenience function to add a list of controls. * * @dai: DAI to add controls to * @controls: array of controls to add * @num_controls: number of elements in the array * * Return 0 for success, else error.
*/ int snd_soc_add_dai_controls(struct snd_soc_dai *dai, conststruct snd_kcontrol_new *controls, int num_controls)
{ struct snd_card *card = dai->component->card->snd_card;
/** * snd_soc_register_card - Register a card with the ASoC core * * @card: Card to register *
*/ int snd_soc_register_card(struct snd_soc_card *card)
{ int ret;
if (card->devres_dev) {
ret = devm_snd_soc_bind_card(card->devres_dev, card); if (ret == -EPROBE_DEFER) {
list_add(&card->list, &unbind_card_list);
ret = 0;
}
} else {
ret = snd_soc_bind_card(card);
}
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.