// 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);
}
/** * snd_soc_unregister_card - Unregister a card with the ASoC core * * @card: Card to unregister *
*/ void snd_soc_unregister_card(struct snd_soc_card *card)
{
mutex_lock(&client_mutex);
snd_soc_unbind_card(card);
list_del(&card->list);
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "ASoC: Unregistered card '%s'\n", card->name);
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_card);
/* * Simplify DAI link configuration by removing ".-1" from device names * and sanitizing names.
*/ staticchar *fmt_single_name(struct device *dev, int *id)
{ constchar *devname = dev_name(dev); char *found, *name; unsignedint id1, id2; int __id;
if (devname == NULL) return NULL;
name = devm_kstrdup(dev, devname, GFP_KERNEL); if (!name) return NULL;
/* are we a "%s.%d" name (platform and SPI components) */
found = strstr(name, dev->driver->name); if (found) { /* get ID */ if (sscanf(&found[strlen(dev->driver->name)], ".%d", &__id) == 1) {
/* discard ID from name if ID == -1 */ if (__id == -1)
found[strlen(dev->driver->name)] = '\0';
}
/* I2C component devices are named "bus-addr" */
} elseif (sscanf(name, "%x-%x", &id1, &id2) == 2) {
/* create unique ID number from I2C addr and bus */
__id = ((id1 & 0xffff) << 16) + id2;
devm_kfree(dev, name);
/* sanitize component name for DAI link creation */
name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", dev->driver->name, devname);
} else {
__id = 0;
}
if (id)
*id = __id;
return name;
}
/* * Simplify DAI link naming for single devices with multiple DAIs by removing * any ".-1" and using the DAI name (instead of device name).
*/ staticinlinechar *fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv)
{ if (dai_drv->name == NULL) {
dev_err(dev, "ASoC: error - multiple DAI %s registered with no name\n",
dev_name(dev)); return NULL;
}
/** * snd_soc_register_dai - Register a DAI dynamically & create its widgets * * @component: The component the DAIs are registered for * @dai_drv: DAI driver to use for the DAI * @legacy_dai_naming: if %true, use legacy single-name format; * if %false, use multiple-name format; * * Topology can use this API to register DAIs when probing a component. * These DAIs's widgets will be freed in the card cleanup and the DAIs * will be freed in the component cleanup.
*/ struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, bool legacy_dai_naming)
{ struct device *dev = component->dev; struct snd_soc_dai *dai;
lockdep_assert_held(&client_mutex);
dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL); if (dai == NULL) return NULL;
/* * Back in the old days when we still had component-less DAIs, * instead of having a static name, component-less DAIs would * inherit the name of the parent device so it is possible to * register multiple instances of the DAI. We still need to keep * the same naming style even though those DAIs are not * component-less anymore.
*/ if (legacy_dai_naming &&
(dai_drv->id == 0 || dai_drv->name == NULL)) {
dai->name = fmt_single_name(dev, &dai->id);
} else {
dai->name = fmt_multiple_name(dev, dai_drv); if (dai_drv->id)
dai->id = dai_drv->id; else
dai->id = component->num_dai;
} if (!dai->name) return NULL;
/* see for_each_component_dais */
list_add_tail(&dai->list, &component->dai_list);
component->num_dai++;
dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); return dai;
}
EXPORT_SYMBOL_GPL(snd_soc_register_dai);
/** * snd_soc_unregister_dais - Unregister DAIs from the ASoC core * * @component: The component for which the DAIs should be unregistered
*/ staticvoid snd_soc_unregister_dais(struct snd_soc_component *component)
{ struct snd_soc_dai *dai, *_dai;
/** * snd_soc_register_dais - Register a DAI with the ASoC core * * @component: The component the DAIs are registered for * @dai_drv: DAI driver to use for the DAIs * @count: Number of DAIs
*/ staticint snd_soc_register_dais(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv,
size_t count)
{ struct snd_soc_dai *dai; unsignedint i; int ret;
for (i = 0; i < count; i++) {
dai = snd_soc_register_dai(component, dai_drv + i, count == 1 &&
component->driver->legacy_dai_naming); if (dai == NULL) {
ret = -ENOMEM; goto err;
}
}
/* * Fix up the DAI formats for endianness: codecs don't actually see * the endianness of the data but we're using the CPU format * definitions which do need to include endianness so we ensure that * codec DAIs always have both big and little endian variants set.
*/ staticvoid convert_endianness_formats(struct snd_soc_pcm_stream *stream)
{ int i;
for (i = 0; i < ARRAY_SIZE(endianness_format_map); i++) if (stream->formats & endianness_format_map[i])
stream->formats |= endianness_format_map[i];
}
int snd_soc_add_component(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, int num_dai)
{ struct snd_soc_card *card, *c; int ret; int i;
mutex_lock(&client_mutex);
if (component->driver->endianness) { for (i = 0; i < num_dai; i++) {
convert_endianness_formats(&dai_drv[i].playback);
convert_endianness_formats(&dai_drv[i].capture);
}
}
ret = snd_soc_register_dais(component, dai_drv, num_dai); if (ret < 0) {
dev_err(component->dev, "ASoC: Failed to register DAIs: %d\n",
ret); goto err_cleanup;
}
if (!component->driver->write && !component->driver->read) { if (!component->regmap)
component->regmap = dev_get_regmap(component->dev,
NULL); if (component->regmap)
snd_soc_component_setup_regmap(component);
}
/* see for_each_component */
list_add(&component->list, &component_list);
/** * snd_soc_unregister_component_by_driver - Unregister component using a given driver * from the ASoC core * * @dev: The device to unregister * @component_driver: The component driver to unregister
*/ void snd_soc_unregister_component_by_driver(struct device *dev, conststruct snd_soc_component_driver *component_driver)
{ constchar *driver_name = NULL;
if (component_driver)
driver_name = component_driver->name;
mutex_lock(&client_mutex); while (1) { struct snd_soc_component *component = snd_soc_lookup_component_nolocked(dev, driver_name);
/* Retrieve a card's name from device tree */ int snd_soc_of_parse_card_name(struct snd_soc_card *card, constchar *propname)
{ struct device_node *np; int ret;
if (!card->dev) {
pr_err("card->dev is not set before calling %s\n", __func__); return -EINVAL;
}
np = card->dev->of_node;
ret = of_property_read_string_index(np, propname, 0, &card->name); /* * EINVAL means the property does not exist. This is fine providing * card->name was previously set, which is checked later in * snd_soc_register_card.
*/ if (ret < 0 && ret != -EINVAL) {
dev_err(card->dev, "ASoC: Property '%s' could not be read: %d\n",
propname, ret); return ret;
}
int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card, constchar *propname)
{ struct device_node *np = card->dev->of_node; struct snd_soc_dapm_widget *widgets; constchar *template, *wname; int i, j, num_widgets;
num_widgets = of_property_count_strings(np, propname); if (num_widgets < 0) {
dev_err(card->dev, "ASoC: Property '%s' does not exist\n", propname); return -EINVAL;
} if (!num_widgets) {
dev_err(card->dev, "ASoC: Property '%s's length is zero\n",
propname); return -EINVAL;
} if (num_widgets & 1) {
dev_err(card->dev, "ASoC: Property '%s' length is not even\n", propname); return -EINVAL;
}
num_widgets /= 2;
widgets = devm_kcalloc(card->dev, num_widgets, sizeof(*widgets),
GFP_KERNEL); if (!widgets) {
dev_err(card->dev, "ASoC: Could not allocate memory for widgets\n"); return -ENOMEM;
}
for (i = 0; i < num_widgets; i++) { int ret = of_property_read_string_index(np, propname,
2 * i, &template); if (ret) {
dev_err(card->dev, "ASoC: Property '%s' index %d read error:%d\n",
propname, 2 * i, ret); return -EINVAL;
}
for (j = 0; j < ARRAY_SIZE(simple_widgets); j++) { if (!strncmp(template, simple_widgets[j].name,
strlen(simple_widgets[j].name))) {
widgets[i] = simple_widgets[j]; break;
}
}
if (j >= ARRAY_SIZE(simple_widgets)) {
dev_err(card->dev, "ASoC: DAPM widget '%s' is not supported\n", template); return -EINVAL;
}
int snd_soc_of_parse_tdm_slot(struct device_node *np, unsignedint *tx_mask, unsignedint *rx_mask, unsignedint *slots, unsignedint *slot_width)
{
u32 val; int ret;
if (tx_mask)
snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", tx_mask); if (rx_mask)
snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", rx_mask);
ret = of_property_read_u32(np, "dai-tdm-slot-num", &val); if (ret && ret != -EINVAL) return ret; if (!ret && slots)
*slots = val;
ret = of_property_read_u32(np, "dai-tdm-slot-width", &val); if (ret && ret != -EINVAL) return ret; if (!ret && slot_width)
*slot_width = val;
int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, constchar *propname)
{ struct device_node *np = card->dev->of_node; int num_routes; struct snd_soc_dapm_route *routes; int i;
num_routes = of_property_count_strings(np, propname); if (num_routes < 0 || num_routes & 1) {
dev_err(card->dev, "ASoC: Property '%s' does not exist or its length is not even\n",
propname); return -EINVAL;
}
num_routes /= 2;
routes = devm_kcalloc(card->dev, num_routes, sizeof(*routes),
GFP_KERNEL); if (!routes) {
dev_err(card->dev, "ASoC: Could not allocate DAPM route table\n"); return -ENOMEM;
}
for (i = 0; i < num_routes; i++) { int ret = of_property_read_string_index(np, propname,
2 * i, &routes[i].sink); if (ret) {
dev_err(card->dev, "ASoC: Property '%s' index %d could not be read: %d\n",
propname, 2 * i, ret); return -EINVAL;
}
ret = of_property_read_string_index(np, propname,
(2 * i) + 1, &routes[i].source); if (ret) {
dev_err(card->dev, "ASoC: Property '%s' index %d could not be read: %d\n",
propname, (2 * i) + 1, ret); return -EINVAL;
}
}
unsignedint snd_soc_daifmt_clock_provider_from_bitmap(unsignedint bit_frame)
{ /* * bit_frame is return value from * snd_soc_daifmt_parse_clock_provider_raw()
*/
/* Codec base */ switch (bit_frame) { case 0x11: return SND_SOC_DAIFMT_CBP_CFP; case 0x10: return SND_SOC_DAIFMT_CBP_CFC; case 0x01: return SND_SOC_DAIFMT_CBC_CFP; default: return SND_SOC_DAIFMT_CBC_CFC;
}
switch ((bit << 4) + frame) { case 0x11:
format |= SND_SOC_DAIFMT_IB_IF; break; case 0x10:
format |= SND_SOC_DAIFMT_IB_NF; break; case 0x01:
format |= SND_SOC_DAIFMT_NB_IF; break; default: /* SND_SOC_DAIFMT_NB_NF is default */ break;
}
int snd_soc_get_dai_id(struct device_node *ep)
{ struct snd_soc_component *component; struct snd_soc_dai_link_component dlc = {
.of_node = of_graph_get_port_parent(ep),
}; int ret;
/* * For example HDMI case, HDMI has video/sound port, * but ALSA SoC needs sound port number only. * Thus counting HDMI DT port/endpoint doesn't work. * Then, it should have .of_xlate_dai_id
*/
ret = -ENOTSUPP;
mutex_lock(&client_mutex);
component = soc_find_component(&dlc); if (component)
ret = snd_soc_component_of_xlate_dai_id(component, ep);
mutex_unlock(&client_mutex);
int snd_soc_get_dlc(conststruct of_phandle_args *args, struct snd_soc_dai_link_component *dlc)
{ struct snd_soc_component *pos; int ret = -EPROBE_DEFER;
if (component_of_node != args->np || !pos->num_dai) continue;
ret = snd_soc_component_of_xlate_dai_name(pos, args, &dlc->dai_name); if (ret == -ENOTSUPP) { struct snd_soc_dai *dai; int id = -1;
switch (args->args_count) { case 0:
id = 0; /* same as dai_drv[0] */ break; case 1:
id = args->args[0]; break; default: /* not supported */ break;
}
if (id < 0 || id >= pos->num_dai) {
ret = -EINVAL; continue;
}
ret = 0;
/* find target DAI */
for_each_component_dais(pos, dai) { if (id == 0) break;
id--;
}
dlc->dai_name = snd_soc_dai_name_get(dai);
} elseif (ret) { /* * if another error than ENOTSUPP is returned go on and * check if another component is provided with the same * node. This may happen if a device provides several * components
*/ continue;
}
int snd_soc_get_dai_name(conststruct of_phandle_args *args, constchar **dai_name)
{ struct snd_soc_dai_link_component dlc; int ret = snd_soc_get_dlc(args, &dlc);
int snd_soc_of_get_dai_name(struct device_node *of_node, constchar **dai_name, int index)
{ struct snd_soc_dai_link_component dlc; int ret = snd_soc_of_get_dlc(of_node, NULL, &dlc, index);
staticint __snd_soc_of_get_dai_link_component_alloc( struct device *dev, struct device_node *of_node, struct snd_soc_dai_link_component **ret_component, int *ret_num)
{ struct snd_soc_dai_link_component *component; int num;
/* Count the number of CPUs/CODECs */
num = of_count_phandle_with_args(of_node, "sound-dai", "#sound-dai-cells"); if (num <= 0) { if (num == -ENOENT)
dev_err(dev, "No 'sound-dai' property\n"); else
dev_err(dev, "Bad phandle in 'sound-dai'\n"); return num;
}
component = devm_kcalloc(dev, num, sizeof(*component), GFP_KERNEL); if (!component) return -ENOMEM;
*ret_component = component;
*ret_num = num;
return 0;
}
/* * snd_soc_of_put_dai_link_codecs - Dereference device nodes in the codecs array * @dai_link: DAI link * * Dereference device nodes acquired by snd_soc_of_get_dai_link_codecs().
*/ void snd_soc_of_put_dai_link_codecs(struct snd_soc_dai_link *dai_link)
{ struct snd_soc_dai_link_component *component; int index;
/* * snd_soc_of_get_dai_link_codecs - Parse a list of CODECs in the devicetree * @dev: Card device * @of_node: Device node * @dai_link: DAI link * * Builds an array of CODEC DAI components from the DAI link property * 'sound-dai'. * The array is set in the DAI link and the number of DAIs is set accordingly. * The device nodes in the array (of_node) must be dereferenced by calling * snd_soc_of_put_dai_link_codecs() on @dai_link. * * Returns 0 for success
*/ int snd_soc_of_get_dai_link_codecs(struct device *dev, struct device_node *of_node, struct snd_soc_dai_link *dai_link)
{ struct snd_soc_dai_link_component *component; int index, ret;
ret = __snd_soc_of_get_dai_link_component_alloc(dev, of_node,
&dai_link->codecs, &dai_link->num_codecs); if (ret < 0) return ret;
/* Parse the list */
for_each_link_codecs(dai_link, index, component) {
ret = snd_soc_of_get_dlc(of_node, NULL, component, index); if (ret) goto err;
} return 0;
err:
snd_soc_of_put_dai_link_codecs(dai_link);
dai_link->codecs = NULL;
dai_link->num_codecs = 0; return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_link_codecs);
/* * snd_soc_of_put_dai_link_cpus - Dereference device nodes in the codecs array * @dai_link: DAI link * * Dereference device nodes acquired by snd_soc_of_get_dai_link_cpus().
*/ void snd_soc_of_put_dai_link_cpus(struct snd_soc_dai_link *dai_link)
{ struct snd_soc_dai_link_component *component; int index;
/* * snd_soc_of_get_dai_link_cpus - Parse a list of CPU DAIs in the devicetree * @dev: Card device * @of_node: Device node * @dai_link: DAI link * * Is analogous to snd_soc_of_get_dai_link_codecs but parses a list of CPU DAIs * instead. * * Returns 0 for success
*/ int snd_soc_of_get_dai_link_cpus(struct device *dev, struct device_node *of_node, struct snd_soc_dai_link *dai_link)
{ struct snd_soc_dai_link_component *component; int index, ret;
/* Count the number of CPUs */
ret = __snd_soc_of_get_dai_link_component_alloc(dev, of_node,
&dai_link->cpus, &dai_link->num_cpus); if (ret < 0) return ret;
/* Parse the list */
for_each_link_cpus(dai_link, index, component) {
ret = snd_soc_of_get_dlc(of_node, NULL, component, index); if (ret) goto err;
} return 0;
err:
snd_soc_of_put_dai_link_cpus(dai_link);
dai_link->cpus = NULL;
dai_link->num_cpus = 0; return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_link_cpus);
staticint __init snd_soc_init(void)
{ int ret;
snd_soc_debugfs_init();
ret = snd_soc_util_init(); if (ret) goto err_util_init;
ret = platform_driver_register(&soc_driver); if (ret) goto err_register; return 0;
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.