diff options
Diffstat (limited to 'sound/soc/soc-core.c')
-rw-r--r-- | sound/soc/soc-core.c | 2378 |
1 files changed, 868 insertions, 1510 deletions
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 44f899b970c2..068d809c349a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -45,11 +45,6 @@ #define NAME_SIZE 32 -#ifdef CONFIG_DEBUG_FS -struct dentry *snd_soc_debugfs_root; -EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); -#endif - static DEFINE_MUTEX(client_mutex); static LIST_HEAD(component_list); static LIST_HEAD(unbind_card_list); @@ -73,21 +68,6 @@ static int pmdown_time = 5000; module_param(pmdown_time, int, 0); MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)"); -/* - * 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. - */ -static const char * const dmi_blacklist[] = { - "To be filled by OEM", - "TBD by OEM", - "Default String", - "Board Manufacturer", - "Board Vendor Name", - "Board Product Name", - NULL, /* terminator */ -}; - static ssize_t pmdown_time_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -123,6 +103,9 @@ static umode_t soc_dev_attr_is_visible(struct kobject *kobj, struct device *dev = kobj_to_dev(kobj); struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); + if (!rtd) + return 0; + if (attr == &dev_attr_pmdown_time.attr) return attr->mode; /* always visible */ return rtd->num_codecs ? attr->mode : 0; /* enabled only with codec */ @@ -145,6 +128,9 @@ static const struct attribute_group *soc_dev_attr_groups[] = { }; #ifdef CONFIG_DEBUG_FS +struct dentry *snd_soc_debugfs_root; +EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); + static void soc_init_component_debugfs(struct snd_soc_component *component) { if (!component->card->debugfs_card_root) @@ -165,20 +151,16 @@ static void soc_init_component_debugfs(struct snd_soc_component *component) component->card->debugfs_card_root); } - if (IS_ERR(component->debugfs_root)) { - dev_warn(component->dev, - "ASoC: Failed to create component debugfs directory: %ld\n", - PTR_ERR(component->debugfs_root)); - return; - } - snd_soc_dapm_debugfs_init(snd_soc_component_get_dapm(component), component->debugfs_root); } static void soc_cleanup_component_debugfs(struct snd_soc_component *component) { + if (!component->debugfs_root) + return; debugfs_remove_recursive(component->debugfs_root); + component->debugfs_root = NULL; } static int dai_list_show(struct seq_file *m, void *v) @@ -215,32 +197,17 @@ DEFINE_SHOW_ATTRIBUTE(component_list); static void soc_init_card_debugfs(struct snd_soc_card *card) { - if (!snd_soc_debugfs_root) - return; - card->debugfs_card_root = debugfs_create_dir(card->name, snd_soc_debugfs_root); - if (IS_ERR(card->debugfs_card_root)) { - dev_warn(card->dev, - "ASoC: Failed to create card debugfs directory: %ld\n", - PTR_ERR(card->debugfs_card_root)); - card->debugfs_card_root = NULL; - return; - } - card->debugfs_pop_time = debugfs_create_u32("dapm_pop_time", 0644, - card->debugfs_card_root, - &card->pop_time); - if (IS_ERR(card->debugfs_pop_time)) - dev_warn(card->dev, - "ASoC: Failed to create pop time debugfs file: %ld\n", - PTR_ERR(card->debugfs_pop_time)); + debugfs_create_u32("dapm_pop_time", 0644, card->debugfs_card_root, + &card->pop_time); + + snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); } static void soc_cleanup_card_debugfs(struct snd_soc_card *card) { - if (!card->debugfs_card_root) - return; debugfs_remove_recursive(card->debugfs_card_root); card->debugfs_card_root = NULL; } @@ -248,19 +215,12 @@ static void soc_cleanup_card_debugfs(struct snd_soc_card *card) static void snd_soc_debugfs_init(void) { snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); - if (IS_ERR_OR_NULL(snd_soc_debugfs_root)) { - pr_warn("ASoC: Failed to create debugfs directory\n"); - snd_soc_debugfs_root = NULL; - return; - } - if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL, - &dai_list_fops)) - pr_warn("ASoC: Failed to create DAI list debugfs file\n"); + debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL, + &dai_list_fops); - if (!debugfs_create_file("components", 0444, snd_soc_debugfs_root, NULL, - &component_list_fops)) - pr_warn("ASoC: Failed to create component list debugfs file\n"); + debugfs_create_file("components", 0444, snd_soc_debugfs_root, NULL, + &component_list_fops); } static void snd_soc_debugfs_exit(void) @@ -298,146 +258,246 @@ static inline void snd_soc_debugfs_exit(void) #endif -static int snd_soc_rtdcom_add(struct snd_soc_pcm_runtime *rtd, - struct snd_soc_component *component) +static int snd_soc_rtd_add_component(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_component *component) { - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_rtdcom_list *new_rtdcom; + struct snd_soc_component *comp; + int i; - for_each_rtdcom(rtd, rtdcom) { + for_each_rtd_components(rtd, i, comp) { /* already connected */ - if (rtdcom->component == component) + if (comp == component) return 0; } - new_rtdcom = kmalloc(sizeof(*new_rtdcom), GFP_KERNEL); - if (!new_rtdcom) - return -ENOMEM; - - new_rtdcom->component = component; - INIT_LIST_HEAD(&new_rtdcom->list); - - list_add_tail(&new_rtdcom->list, &rtd->component_list); + /* see for_each_rtd_components */ + rtd->components[rtd->num_components] = component; + rtd->num_components++; return 0; } -static void snd_soc_rtdcom_del_all(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_rtdcom_list *rtdcom1, *rtdcom2; - - for_each_rtdcom_safe(rtd, rtdcom1, rtdcom2) - kfree(rtdcom1); - - INIT_LIST_HEAD(&rtd->component_list); -} - struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd, const char *driver_name) { - struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + int i; if (!driver_name) return NULL; - for_each_rtdcom(rtd, rtdcom) { - const char *component_name = rtdcom->component->driver->name; + /* + * 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) { + const char *component_name = component->driver->name; if (!component_name) continue; if ((component_name == driver_name) || strcmp(component_name, driver_name) == 0) - return rtdcom->component; + return component; } return NULL; } EXPORT_SYMBOL_GPL(snd_soc_rtdcom_lookup); -struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card, - const char *dai_link, int stream) +static struct snd_soc_component +*snd_soc_lookup_component_nolocked(struct device *dev, const char *driver_name) +{ + struct snd_soc_component *component; + struct snd_soc_component *found_component; + + found_component = NULL; + for_each_component(component) { + if ((dev == component->dev) && + (!driver_name || + (driver_name == component->driver->name) || + (strcmp(component->driver->name, driver_name) == 0))) { + found_component = component; + break; + } + } + + return found_component; +} + +struct snd_soc_component *snd_soc_lookup_component(struct device *dev, + const char *driver_name) +{ + struct snd_soc_component *component; + + mutex_lock(&client_mutex); + component = snd_soc_lookup_component_nolocked(dev, driver_name); + mutex_unlock(&client_mutex); + + return component; +} +EXPORT_SYMBOL_GPL(snd_soc_lookup_component); + +struct snd_soc_pcm_runtime +*snd_soc_get_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) { struct snd_soc_pcm_runtime *rtd; for_each_card_rtds(card, rtd) { - if (rtd->dai_link->no_pcm && - !strcmp(rtd->dai_link->name, dai_link)) - return rtd->pcm->streams[stream].substream; + if (rtd->dai_link == dai_link) + return rtd; } - dev_dbg(card->dev, "ASoC: failed to find dai link %s\n", dai_link); + dev_dbg(card->dev, "ASoC: failed to find rtd %s\n", dai_link->name); return NULL; } -EXPORT_SYMBOL_GPL(snd_soc_get_dai_substream); - -static const struct snd_soc_ops null_snd_soc_ops; +EXPORT_SYMBOL_GPL(snd_soc_get_pcm_runtime); -static struct snd_soc_pcm_runtime *soc_new_pcm_runtime( - struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +/* + * 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_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai = rtd->codec_dai; - rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime), GFP_KERNEL); - if (!rtd) - return NULL; + mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); - INIT_LIST_HEAD(&rtd->component_list); - rtd->card = card; - rtd->dai_link = dai_link; - if (!rtd->dai_link->ops) - rtd->dai_link->ops = &null_snd_soc_ops; + dev_dbg(rtd->dev, + "ASoC: pop wq checking: %s status: %s waiting: %s\n", + codec_dai->driver->playback.stream_name, + codec_dai->playback_active ? "active" : "inactive", + rtd->pop_wait ? "yes" : "no"); - rtd->codec_dais = kcalloc(dai_link->num_codecs, - sizeof(struct snd_soc_dai *), - GFP_KERNEL); - if (!rtd->codec_dais) { - kfree(rtd); - return NULL; + /* are we waiting on this codec DAI stream */ + if (rtd->pop_wait == 1) { + rtd->pop_wait = 0; + snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_STOP); } - return rtd; + mutex_unlock(&rtd->card->pcm_mutex); } +EXPORT_SYMBOL_GPL(snd_soc_close_delayed_work); -static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd) +static void soc_release_rtd_dev(struct device *dev) { - kfree(rtd->codec_dais); - snd_soc_rtdcom_del_all(rtd); - kfree(rtd); + /* "dev" means "rtd->dev" */ + kfree(dev); } -static void soc_add_pcm_runtime(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd) +static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd) { - list_add_tail(&rtd->list, &card->rtd_list); - rtd->num = card->num_rtd; - card->num_rtd++; -} + if (!rtd) + return; -static void soc_remove_pcm_runtimes(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd, *_rtd; + list_del(&rtd->list); - for_each_card_rtds_safe(card, rtd, _rtd) { - list_del(&rtd->list); - soc_free_pcm_runtime(rtd); - } + if (delayed_work_pending(&rtd->delayed_work)) + flush_delayed_work(&rtd->delayed_work); + snd_soc_pcm_component_free(rtd); - card->num_rtd = 0; + /* + * 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() + */ + device_unregister(rtd->dev); } -struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, - const char *dai_link) +static void close_delayed_work(struct work_struct *work) { + struct snd_soc_pcm_runtime *rtd = + container_of(work, struct snd_soc_pcm_runtime, + delayed_work.work); + + if (rtd->close_delayed_work_func) + rtd->close_delayed_work_func(rtd); +} + +static struct snd_soc_pcm_runtime *soc_new_pcm_runtime( + struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) { struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; + struct device *dev; + int ret; - for_each_card_rtds(card, rtd) { - if (!strcmp(rtd->dai_link->name, dai_link)) - return rtd; + /* + * for rtd->dev + */ + dev = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!dev) + return NULL; + + dev->parent = card->dev; + dev->release = soc_release_rtd_dev; + dev->groups = soc_dev_attr_groups; + + dev_set_name(dev, "%s", dai_link->name); + + ret = device_register(dev); + if (ret < 0) { + put_device(dev); /* soc_release_rtd_dev */ + return NULL; } - dev_dbg(card->dev, "ASoC: failed to find rtd %s\n", dai_link); + + /* + * for rtd + */ + rtd = devm_kzalloc(dev, + sizeof(*rtd) + + sizeof(*component) * (dai_link->num_cpus + + dai_link->num_codecs + + dai_link->num_platforms), + GFP_KERNEL); + if (!rtd) + goto free_rtd; + + rtd->dev = dev; + INIT_LIST_HEAD(&rtd->list); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].fe_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].fe_clients); + dev_set_drvdata(dev, rtd); + INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); + + /* + * for rtd->codec_dais + */ + rtd->codec_dais = devm_kcalloc(dev, dai_link->num_codecs, + sizeof(struct snd_soc_dai *), + GFP_KERNEL); + if (!rtd->codec_dais) + goto free_rtd; + + /* + * rtd remaining settings + */ + rtd->card = card; + rtd->dai_link = dai_link; + + /* see for_each_card_rtds */ + list_add_tail(&rtd->list, &card->rtd_list); + rtd->num = card->num_rtd; + card->num_rtd++; + + return rtd; + +free_rtd: + soc_free_pcm_runtime(rtd); return NULL; } -EXPORT_SYMBOL_GPL(snd_soc_get_pcm_runtime); static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) { @@ -447,16 +507,6 @@ static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) flush_delayed_work(&rtd->delayed_work); } -static void codec2codec_close_delayed_work(struct work_struct *work) -{ - /* - * Currently nothing to do for c2c links - * Since c2c links are internal nodes in the DAPM graph and - * don't interface with the outside world or application layer - * we don't have to do any special handling on close. - */ -} - #ifdef CONFIG_PM_SLEEP /* powers down audio subsystem for suspend */ int snd_soc_suspend(struct device *dev) @@ -487,10 +537,9 @@ int snd_soc_suspend(struct device *dev) continue; for_each_rtd_codec_dai(rtd, i, dai) { - struct snd_soc_dai_driver *drv = dai->driver; - - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 1); + if (dai->playback_active) + snd_soc_dai_digital_mute(dai, 1, + SNDRV_PCM_STREAM_PLAYBACK); } } @@ -505,16 +554,6 @@ int snd_soc_suspend(struct device *dev) if (card->suspend_pre) card->suspend_pre(card); - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - - if (rtd->dai_link->ignore_suspend) - continue; - - if (cpu_dai->driver->suspend && !cpu_dai->driver->bus_control) - cpu_dai->driver->suspend(cpu_dai); - } - /* close any waiting streams */ snd_soc_flush_all_delayed_work(card); @@ -537,15 +576,25 @@ int snd_soc_suspend(struct device *dev) snd_soc_dapm_sync(&card->dapm); /* suspend all COMPONENTs */ - for_each_card_components(card, component) { - struct snd_soc_dapm_context *dapm = + 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); - /* - * If there are paths active then the COMPONENT will be held - * with bias _ON and should not be suspended. - */ - if (!component->suspended) { + /* + * 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: /* @@ -562,9 +611,7 @@ int snd_soc_suspend(struct device *dev) /* fall through */ case SND_SOC_BIAS_OFF: - if (component->driver->suspend) - component->driver->suspend(component); - component->suspended = 1; + snd_soc_component_suspend(component); if (component->regmap) regcache_mark_dirty(component->regmap); /* deactivate pins to sleep state */ @@ -578,19 +625,6 @@ int snd_soc_suspend(struct device *dev) } } - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - - if (rtd->dai_link->ignore_suspend) - continue; - - if (cpu_dai->driver->suspend && cpu_dai->driver->bus_control) - cpu_dai->driver->suspend(cpu_dai); - - /* deactivate pins to sleep state */ - pinctrl_pm_select_sleep_state(cpu_dai->dev); - } - if (card->suspend_post) card->suspend_post(card); @@ -624,23 +658,9 @@ static void soc_resume_deferred(struct work_struct *work) if (card->resume_pre) card->resume_pre(card); - /* resume control bus DAIs */ - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - - if (rtd->dai_link->ignore_suspend) - continue; - - if (cpu_dai->driver->resume && cpu_dai->driver->bus_control) - cpu_dai->driver->resume(cpu_dai); - } - for_each_card_components(card, component) { - if (component->suspended) { - if (component->driver->resume) - component->driver->resume(component); - component->suspended = 0; - } + if (snd_soc_component_is_suspended(component)) + snd_soc_component_resume(component); } for_each_card_rtds(card, rtd) { @@ -665,23 +685,12 @@ static void soc_resume_deferred(struct work_struct *work) continue; for_each_rtd_codec_dai(rtd, i, dai) { - struct snd_soc_dai_driver *drv = dai->driver; - - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 0); + if (dai->playback_active) + snd_soc_dai_digital_mute(dai, 0, + SNDRV_PCM_STREAM_PLAYBACK); } } - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - - if (rtd->dai_link->ignore_suspend) - continue; - - if (cpu_dai->driver->resume && !cpu_dai->driver->bus_control) - cpu_dai->driver->resume(cpu_dai); - } - if (card->resume_post) card->resume_post(card); @@ -699,54 +708,36 @@ static void soc_resume_deferred(struct work_struct *work) int snd_soc_resume(struct device *dev) { struct snd_soc_card *card = dev_get_drvdata(dev); - bool bus_control = false; - struct snd_soc_pcm_runtime *rtd; - struct snd_soc_dai *codec_dai; - int i; + struct snd_soc_component *component; /* If the card is not initialized yet there is nothing to do */ if (!card->instantiated) return 0; /* activate pins from sleep state */ - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - - if (cpu_dai->active) - pinctrl_pm_select_default_state(cpu_dai->dev); - - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->active) - pinctrl_pm_select_default_state(codec_dai->dev); - } - } - - /* - * DAIs that also act as the control bus master might have other drivers - * hanging off them so need to resume immediately. Other drivers don't - * have that problem and may take a substantial amount of time to resume - * due to I/O costs and anti-pop so handle them out of line. - */ - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + for_each_card_components(card, component) + if (component->active) + pinctrl_pm_select_default_state(component->dev); - bus_control |= cpu_dai->driver->bus_control; - } - if (bus_control) { - dev_dbg(dev, "ASoC: Resuming control bus master immediately\n"); - soc_resume_deferred(&card->deferred_resume_work); - } else { - 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"); - } + 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"); return 0; } EXPORT_SYMBOL_GPL(snd_soc_resume); + +static void soc_resume_init(struct snd_soc_card *card) +{ + /* deferred resume work */ + INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); +} #else #define snd_soc_suspend NULL #define snd_soc_resume NULL +static inline void soc_resume_init(struct snd_soc_card *card) +{ +} #endif static const struct snd_soc_dai_ops null_dai_ops = { @@ -842,226 +833,8 @@ struct snd_soc_dai *snd_soc_find_dai( } EXPORT_SYMBOL_GPL(snd_soc_find_dai); -/** - * snd_soc_find_dai_link - Find a DAI link - * - * @card: soc card - * @id: DAI link ID to match - * @name: DAI link name to match, optional - * @stream_name: DAI link stream name to match, optional - * - * This function will search all existing DAI links of the soc card to - * find the link of the same ID. Since DAI links may not have their - * unique ID, so name and stream name should also match if being - * specified. - * - * Return: pointer of DAI link, or NULL if not found. - */ -struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card, - int id, const char *name, - const char *stream_name) -{ - struct snd_soc_dai_link *link, *_link; - - lockdep_assert_held(&client_mutex); - - for_each_card_links_safe(card, link, _link) { - if (link->id != id) - continue; - - if (name && (!link->name || strcmp(name, link->name))) - continue; - - if (stream_name && (!link->stream_name - || strcmp(stream_name, link->stream_name))) - continue; - - return link; - } - - return NULL; -} -EXPORT_SYMBOL_GPL(snd_soc_find_dai_link); - -static bool soc_is_dai_link_bound(struct snd_soc_card *card, - struct snd_soc_dai_link *dai_link) -{ - struct snd_soc_pcm_runtime *rtd; - - for_each_card_rtds(card, rtd) { - if (rtd->dai_link == dai_link) - return true; - } - - return false; -} - -static int soc_bind_dai_link(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; - struct snd_soc_component *component; - int i; - - if (dai_link->ignore) - return 0; - - dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name); - - if (soc_is_dai_link_bound(card, dai_link)) { - dev_dbg(card->dev, "ASoC: dai link %s already bound\n", - dai_link->name); - return 0; - } - - rtd = soc_new_pcm_runtime(card, dai_link); - if (!rtd) - return -ENOMEM; - - /* FIXME: we need multi CPU support in the future */ - rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus); - if (!rtd->cpu_dai) { - dev_info(card->dev, "ASoC: CPU DAI %s not registered\n", - dai_link->cpus->dai_name); - goto _err_defer; - } - snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component); - - /* Find CODEC from registered CODECs */ - rtd->num_codecs = dai_link->num_codecs; - for_each_link_codecs(dai_link, i, codec) { - rtd->codec_dais[i] = snd_soc_find_dai(codec); - if (!rtd->codec_dais[i]) { - dev_info(card->dev, "ASoC: CODEC DAI %s not registered\n", - codec->dai_name); - goto _err_defer; - } - - snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component); - } - - /* Single codec links expect codec and codec_dai in runtime data */ - rtd->codec_dai = rtd->codec_dais[0]; - - /* 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; - - snd_soc_rtdcom_add(rtd, component); - } - } - - soc_add_pcm_runtime(card, rtd); - return 0; - -_err_defer: - soc_free_pcm_runtime(rtd); - return -EPROBE_DEFER; -} - -static void soc_cleanup_component(struct snd_soc_component *component) -{ - snd_soc_component_set_jack(component, NULL, NULL); - list_del(&component->card_list); - snd_soc_dapm_free(snd_soc_component_get_dapm(component)); - soc_cleanup_component_debugfs(component); - component->card = NULL; - if (!component->driver->module_get_upon_open) - module_put(component->dev->driver->owner); -} - -static void soc_remove_component(struct snd_soc_component *component) -{ - if (!component->card) - return; - - if (component->driver->remove) - component->driver->remove(component); - - soc_cleanup_component(component); -} - -static void soc_remove_dai(struct snd_soc_dai *dai, int order) -{ - int err; - - if (!dai || !dai->probed || !dai->driver || - dai->driver->remove_order != order) - return; - - if (dai->driver->remove) { - err = dai->driver->remove(dai); - if (err < 0) - dev_err(dai->dev, - "ASoC: failed to remove %s: %d\n", - dai->name, err); - } - dai->probed = 0; -} - -static void soc_remove_link_dais(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd, int order) -{ - int i; - struct snd_soc_dai *codec_dai; - - /* unregister the rtd device */ - if (rtd->dev_registered) { - device_unregister(rtd->dev); - rtd->dev_registered = 0; - } - - /* remove the CODEC DAI */ - for_each_rtd_codec_dai(rtd, i, codec_dai) - soc_remove_dai(codec_dai, order); - - soc_remove_dai(rtd->cpu_dai, order); -} - -static void soc_remove_link_components(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd, int order) -{ - struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (component->driver->remove_order == order) - soc_remove_component(component); - } -} - -static void soc_remove_dai_links(struct snd_soc_card *card) -{ - int order; - struct snd_soc_pcm_runtime *rtd; - struct snd_soc_dai_link *link, *_link; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) - soc_remove_link_dais(card, rtd, order); - } - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) - soc_remove_link_components(card, rtd, order); - } - - for_each_card_links_safe(card, link, _link) { - if (link->dobj.type == SND_SOC_DOBJ_DAI_LINK) - dev_warn(card->dev, "Topology forgot to remove link %s?\n", - link->name); - - list_del(&link->list); - } -} - -static int soc_init_dai_link(struct snd_soc_card *card, - struct snd_soc_dai_link *link) +static int soc_dai_link_sanity_check(struct snd_soc_card *card, + struct snd_soc_dai_link *link) { int i; struct snd_soc_dai_link_component *codec, *platform; @@ -1134,7 +907,7 @@ static int soc_init_dai_link(struct snd_soc_card *card, } /* - * Defer card registartion if cpu dai component is not added to + * Defer card registration if cpu dai component is not added to * component list. */ if ((link->cpus->of_node || link->cpus->name) && @@ -1156,134 +929,257 @@ static int soc_init_dai_link(struct snd_soc_card *card, return 0; } -void snd_soc_disconnect_sync(struct device *dev) +/** + * 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) { - struct snd_soc_component *component = - snd_soc_lookup_component(dev, NULL); + lockdep_assert_held(&client_mutex); - if (!component || !component->card) - return; + /* + * Notify the machine driver for extra destruction + */ + if (card->remove_dai_link) + card->remove_dai_link(card, rtd->dai_link); - snd_card_disconnect_sync(component->card->snd_card); + soc_free_pcm_runtime(rtd); } -EXPORT_SYMBOL_GPL(snd_soc_disconnect_sync); +EXPORT_SYMBOL_GPL(snd_soc_remove_pcm_runtime); /** - * snd_soc_add_dai_link - Add a DAI link dynamically - * @card: The ASoC card to which the DAI link is added - * @dai_link: The new DAI link to add + * 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 DAI link to the ASoC card's link list. + * This function adds a pcm_runtime ASoC card by using dai_link. * - * Note: Topology can use this API to add DAI links when probing the + * 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. */ -int snd_soc_add_dai_link(struct snd_soc_card *card, - struct snd_soc_dai_link *dai_link) +int snd_soc_add_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) { - if (dai_link->dobj.type - && dai_link->dobj.type != SND_SOC_DOBJ_DAI_LINK) { - dev_err(card->dev, "Invalid dai link type %d\n", - dai_link->dobj.type); - return -EINVAL; - } + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai_link_component *codec, *platform; + struct snd_soc_component *component; + int i, ret; lockdep_assert_held(&client_mutex); + /* * Notify the machine driver for extra initialization - * on the link created by topology. */ - if (dai_link->dobj.type && card->add_dai_link) + if (card->add_dai_link) card->add_dai_link(card, dai_link); - list_add_tail(&dai_link->list, &card->dai_link_list); + if (dai_link->ignore) + return 0; + + dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name); + + ret = soc_dai_link_sanity_check(card, dai_link); + if (ret < 0) + return ret; + + rtd = soc_new_pcm_runtime(card, dai_link); + if (!rtd) + return -ENOMEM; + + /* FIXME: we need multi CPU support in the future */ + rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus); + if (!rtd->cpu_dai) { + dev_info(card->dev, "ASoC: CPU DAI %s not registered\n", + dai_link->cpus->dai_name); + goto _err_defer; + } + snd_soc_rtd_add_component(rtd, rtd->cpu_dai->component); + + /* Find CODEC from registered CODECs */ + rtd->num_codecs = dai_link->num_codecs; + for_each_link_codecs(dai_link, i, codec) { + rtd->codec_dais[i] = snd_soc_find_dai(codec); + if (!rtd->codec_dais[i]) { + dev_info(card->dev, "ASoC: CODEC DAI %s not registered\n", + codec->dai_name); + goto _err_defer; + } + + snd_soc_rtd_add_component(rtd, rtd->codec_dais[i]->component); + } + + /* Single codec links expect codec and codec_dai in runtime data */ + rtd->codec_dai = rtd->codec_dais[0]; + + /* 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; + + snd_soc_rtd_add_component(rtd, component); + } + } return 0; + +_err_defer: + snd_soc_remove_pcm_runtime(card, rtd); + return -EPROBE_DEFER; } -EXPORT_SYMBOL_GPL(snd_soc_add_dai_link); +EXPORT_SYMBOL_GPL(snd_soc_add_pcm_runtime); -/** - * snd_soc_remove_dai_link - Remove a DAI link from the list - * @card: The ASoC card that owns the link - * @dai_link: The DAI link to remove - * - * This function removes a DAI link from the ASoC card's link list. - * - * For DAI links previously added by topology, topology should - * remove them by using the dobj embedded in the link. - */ -void snd_soc_remove_dai_link(struct snd_soc_card *card, - struct snd_soc_dai_link *dai_link) +static int soc_dai_pcm_new(struct snd_soc_dai **dais, int num_dais, + struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai_link *link, *_link; + int i, ret = 0; - if (dai_link->dobj.type - && dai_link->dobj.type != SND_SOC_DOBJ_DAI_LINK) { - dev_err(card->dev, "Invalid dai link type %d\n", - dai_link->dobj.type); - return; + for (i = 0; i < num_dais; ++i) { + struct snd_soc_dai_driver *drv = dais[i]->driver; + + if (drv->pcm_new) + ret = drv->pcm_new(rtd, dais[i]); + if (ret < 0) { + dev_err(dais[i]->dev, + "ASoC: Failed to bind %s with pcm device\n", + dais[i]->name); + return ret; + } } - lockdep_assert_held(&client_mutex); + return 0; +} + +static int soc_init_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_component *component; + int ret, num, i; + + /* set default power off timeout */ + rtd->pmdown_time = pmdown_time; + + /* do machine specific initialization */ + if (dai_link->init) { + ret = dai_link->init(rtd); + if (ret < 0) { + dev_err(card->dev, "ASoC: failed to init %s: %d\n", + dai_link->name, ret); + return ret; + } + } + + if (dai_link->dai_fmt) { + ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt); + if (ret) + return ret; + } + + /* add DPCM sysfs entries */ + soc_dpcm_debugfs_add(rtd); + + num = rtd->num; + /* - * Notify the machine driver for extra destruction - * on the link created by topology. + * 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. */ - if (dai_link->dobj.type && card->remove_dai_link) - card->remove_dai_link(card, dai_link); + for_each_rtd_components(rtd, i, component) { + if (!component->driver->use_dai_pcm_id) + continue; - for_each_card_links_safe(card, link, _link) { - if (link == dai_link) { - list_del(&link->list); - return; - } + if (rtd->dai_link->no_pcm) + num += component->driver->be_pcm_base; + else + num = rtd->dai_link->id; } -} -EXPORT_SYMBOL_GPL(snd_soc_remove_dai_link); -static void soc_set_of_name_prefix(struct snd_soc_component *component) -{ - struct device_node *component_of_node = soc_component_to_node(component); - const char *str; - int ret; + /* create compress_device if possible */ + ret = snd_soc_dai_compress_new(cpu_dai, rtd, num); + if (ret != -ENOTSUPP) { + if (ret < 0) + dev_err(card->dev, "ASoC: can't create compress %s\n", + dai_link->stream_name); + return ret; + } - ret = of_property_read_string(component_of_node, "sound-name-prefix", - &str); - if (!ret) - component->name_prefix = str; + /* create the pcm */ + ret = soc_new_pcm(rtd, num); + if (ret < 0) { + dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", + dai_link->stream_name, ret); + return ret; + } + ret = soc_dai_pcm_new(&cpu_dai, 1, rtd); + if (ret < 0) + return ret; + ret = soc_dai_pcm_new(rtd->codec_dais, + rtd->num_codecs, rtd); + return ret; } static void soc_set_name_prefix(struct snd_soc_card *card, struct snd_soc_component *component) { - int i; + struct device_node *of_node = soc_component_to_node(component); + const char *str; + int ret, i; - for (i = 0; i < card->num_configs && card->codec_conf; i++) { + for (i = 0; i < card->num_configs; i++) { struct snd_soc_codec_conf *map = &card->codec_conf[i]; - struct device_node *component_of_node = soc_component_to_node(component); - if (map->of_node && component_of_node != map->of_node) - continue; - if (map->dev_name && strcmp(component->name, map->dev_name)) - continue; - component->name_prefix = map->name_prefix; - return; + if (snd_soc_is_matching_component(&map->dlc, component)) { + component->name_prefix = map->name_prefix; + return; + } } /* * If there is no configuration table or no match in the table, * check if a prefix is provided in the node */ - soc_set_of_name_prefix(component); + ret = of_property_read_string(of_node, "sound-name-prefix", &str); + if (ret < 0) + return; + + component->name_prefix = str; +} + +static void soc_remove_component(struct snd_soc_component *component, + int probed) +{ + + if (!component->card) + return; + + if (probed) + snd_soc_component_remove(component); + + /* For framework level robustness */ + snd_soc_component_set_jack(component, NULL, NULL); + + list_del_init(&component->card_list); + snd_soc_dapm_free(snd_soc_component_get_dapm(component)); + soc_cleanup_component_debugfs(component); + component->card = NULL; + snd_soc_component_module_put_when_remove(component); } static int soc_probe_component(struct snd_soc_card *card, - struct snd_soc_component *component) + struct snd_soc_component *component) { struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); + snd_soc_component_get_dapm(component); struct snd_soc_dai *dai; + int probed = 0; int ret; if (!strcmp(component->name, "snd-soc-dummy")) @@ -1299,28 +1195,25 @@ static int soc_probe_component(struct snd_soc_card *card, return 0; } - if (!component->driver->module_get_upon_open && - !try_module_get(component->dev->driver->owner)) - return -ENODEV; + ret = snd_soc_component_module_get_when_probe(component); + if (ret < 0) + return ret; component->card = card; - dapm->card = card; - INIT_LIST_HEAD(&component->card_list); - INIT_LIST_HEAD(&dapm->list); soc_set_name_prefix(card, component); soc_init_component_debugfs(component); - if (component->driver->dapm_widgets) { - ret = snd_soc_dapm_new_controls(dapm, + snd_soc_dapm_init(dapm, card, component); + + 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; - } + if (ret != 0) { + dev_err(component->dev, + "Failed to create new controls %d\n", ret); + goto err_probe; } for_each_component_dais(component, dai) { @@ -1332,18 +1225,17 @@ static int soc_probe_component(struct snd_soc_card *card, } } - if (component->driver->probe) { - ret = component->driver->probe(component); - if (ret < 0) { - dev_err(component->dev, - "ASoC: failed to probe component %d\n", ret); - goto err_probe; - } + ret = snd_soc_component_probe(component); + if (ret < 0) { + dev_err(component->dev, + "ASoC: failed to probe component %d\n", ret); + 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 */ if (component->init) { @@ -1355,97 +1247,58 @@ static int soc_probe_component(struct snd_soc_card *card, } } - if (component->driver->controls) - snd_soc_add_component_controls(component, - component->driver->controls, - component->driver->num_controls); - if (component->driver->dapm_routes) - snd_soc_dapm_add_routes(dapm, - component->driver->dapm_routes, - component->driver->num_dapm_routes); + 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; - list_add(&dapm->list, &card->dapm_list); /* see for_each_card_components */ list_add(&component->card_list, &card->component_dev_list); err_probe: if (ret < 0) - soc_cleanup_component(component); + soc_remove_component(component, probed); return ret; } -static void rtd_release(struct device *dev) -{ - kfree(dev); -} - -static int soc_post_component_init(struct snd_soc_pcm_runtime *rtd, - const char *name) -{ - int ret = 0; - - /* register the rtd device */ - rtd->dev = kzalloc(sizeof(struct device), GFP_KERNEL); - if (!rtd->dev) - return -ENOMEM; - device_initialize(rtd->dev); - rtd->dev->parent = rtd->card->dev; - rtd->dev->release = rtd_release; - rtd->dev->groups = soc_dev_attr_groups; - dev_set_name(rtd->dev, "%s", name); - dev_set_drvdata(rtd->dev, rtd); - mutex_init(&rtd->pcm_mutex); - INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients); - INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients); - INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].fe_clients); - INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].fe_clients); - ret = device_add(rtd->dev); - if (ret < 0) { - /* calling put_device() here to free the rtd->dev */ - put_device(rtd->dev); - dev_err(rtd->card->dev, - "ASoC: failed to register runtime device: %d\n", ret); - return ret; - } - rtd->dev_registered = 1; - return 0; -} - -static int soc_probe_link_components(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd, int order) +static void soc_remove_dai(struct snd_soc_dai *dai, int order) { - struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - int ret; + int err; - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + if (!dai || !dai->probed || !dai->driver || + dai->driver->remove_order != order) + return; - if (component->driver->probe_order == order) { - ret = soc_probe_component(card, component); - if (ret < 0) - return ret; - } - } + err = snd_soc_dai_remove(dai); + if (err < 0) + dev_err(dai->dev, + "ASoC: failed to remove %s: %d\n", + dai->name, err); - return 0; + dai->probed = 0; } static int soc_probe_dai(struct snd_soc_dai *dai, int order) { + int ret; + if (dai->probed || dai->driver->probe_order != order) return 0; - if (dai->driver->probe) { - int ret = dai->driver->probe(dai); - - if (ret < 0) { - dev_err(dai->dev, "ASoC: failed to probe DAI %s: %d\n", - dai->name, ret); - return ret; - } + ret = snd_soc_dai_probe(dai); + if (ret < 0) { + dev_err(dai->dev, "ASoC: failed to probe DAI %s: %d\n", + dai->name, ret); + return ret; } dai->probed = 1; @@ -1453,189 +1306,136 @@ static int soc_probe_dai(struct snd_soc_dai *dai, int order) return 0; } -static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais, - struct snd_soc_pcm_runtime *rtd) +static void soc_remove_link_dais(struct snd_soc_card *card) { - int i, ret = 0; + int i; + struct snd_soc_dai *codec_dai; + struct snd_soc_pcm_runtime *rtd; + int order; - for (i = 0; i < num_dais; ++i) { - struct snd_soc_dai_driver *drv = dais[i]->driver; + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + /* remove the CODEC DAI */ + for_each_rtd_codec_dai(rtd, i, codec_dai) + soc_remove_dai(codec_dai, order); - if (drv->pcm_new) - ret = drv->pcm_new(rtd, dais[i]); - if (ret < 0) { - dev_err(dais[i]->dev, - "ASoC: Failed to bind %s with pcm device\n", - dais[i]->name); - return ret; + soc_remove_dai(rtd->cpu_dai, order); } } - - return 0; } -static int soc_probe_link_dais(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd, int order) +static int soc_probe_link_dais(struct snd_soc_card *card) { - struct snd_soc_dai_link *dai_link = rtd->dai_link; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; struct snd_soc_dai *codec_dai; - int i, ret, num; - - dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n", - card->name, rtd->num, order); - - /* set default power off timeout */ - rtd->pmdown_time = pmdown_time; + struct snd_soc_pcm_runtime *rtd; + int i, order, ret; - ret = soc_probe_dai(cpu_dai, order); - if (ret) - return ret; + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { - /* probe the CODEC DAI */ - for_each_rtd_codec_dai(rtd, i, codec_dai) { - ret = soc_probe_dai(codec_dai, order); - if (ret) - return ret; - } + dev_dbg(card->dev, + "ASoC: probe %s dai link %d late %d\n", + card->name, rtd->num, order); - /* complete DAI probe during last probe */ - if (order != SND_SOC_COMP_ORDER_LAST) - return 0; + ret = soc_probe_dai(rtd->cpu_dai, order); + if (ret) + return ret; - /* do machine specific initialization */ - if (dai_link->init) { - ret = dai_link->init(rtd); - if (ret < 0) { - dev_err(card->dev, "ASoC: failed to init %s: %d\n", - dai_link->name, ret); - return ret; + /* probe the CODEC DAI */ + for_each_rtd_codec_dai(rtd, i, codec_dai) { + ret = soc_probe_dai(codec_dai, order); + if (ret) + return ret; + } } } - if (dai_link->dai_fmt) { - ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt); - if (ret) - return ret; - } - - ret = soc_post_component_init(rtd, dai_link->name); - if (ret) - return ret; + return 0; +} -#ifdef CONFIG_DEBUG_FS - /* add DPCM sysfs entries */ - if (dai_link->dynamic) - soc_dpcm_debugfs_add(rtd); -#endif +static void soc_remove_link_components(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + int i, order; - num = rtd->num; + 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; - /* - * 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. - */ - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + soc_remove_component(component, 1); + } + } + } +} - if (!component->driver->use_dai_pcm_id) - continue; +static int soc_probe_link_components(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + int i, ret, order; - if (rtd->dai_link->no_pcm) - num += component->driver->be_pcm_base; - else - num = rtd->dai_link->id; - } + 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; - if (cpu_dai->driver->compress_new) { - /* create compress_device" */ - ret = cpu_dai->driver->compress_new(rtd, num); - if (ret < 0) { - dev_err(card->dev, "ASoC: can't create compress %s\n", - dai_link->stream_name); - return ret; - } - } else if (!dai_link->params) { - /* create the pcm */ - ret = soc_new_pcm(rtd, num); - if (ret < 0) { - dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", - dai_link->stream_name, ret); - return ret; + ret = soc_probe_component(card, component); + if (ret < 0) + return ret; + } } - ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd); - if (ret < 0) - return ret; - ret = soc_link_dai_pcm_new(rtd->codec_dais, - rtd->num_codecs, rtd); - if (ret < 0) - return ret; - } else { - INIT_DELAYED_WORK(&rtd->delayed_work, - codec2codec_close_delayed_work); } return 0; } -static int soc_bind_aux_dev(struct snd_soc_card *card, int num) +static void soc_unbind_aux_dev(struct snd_soc_card *card) +{ + struct snd_soc_component *component, *_component; + + for_each_card_auxs_safe(card, component, _component) { + component->init = NULL; + list_del(&component->card_aux_list); + } +} + +static int soc_bind_aux_dev(struct snd_soc_card *card) { - struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; struct snd_soc_component *component; - struct snd_soc_dai_link_component dlc; + struct snd_soc_aux_dev *aux; + int i; - if (aux_dev->codec_of_node || aux_dev->codec_name) { + for_each_card_pre_auxs(card, i, aux) { /* codecs, usually analog devices */ - dlc.name = aux_dev->codec_name; - dlc.of_node = aux_dev->codec_of_node; - component = soc_find_component(&dlc); - if (!component) { - if (dlc.of_node) - dlc.name = of_node_full_name(dlc.of_node); - goto err_defer; - } - } else if (aux_dev->name) { - /* generic components */ - dlc.name = aux_dev->name; - dlc.of_node = NULL; - component = soc_find_component(&dlc); + component = soc_find_component(&aux->dlc); if (!component) - goto err_defer; - } else { - dev_err(card->dev, "ASoC: Invalid auxiliary device\n"); - return -EINVAL; - } - - component->init = aux_dev->init; - list_add(&component->card_aux_list, &card->aux_comp_list); + return -EPROBE_DEFER; + component->init = aux->init; + /* see for_each_card_auxs */ + list_add(&component->card_aux_list, &card->aux_comp_list); + } return 0; - -err_defer: - dev_err(card->dev, "ASoC: %s not registered\n", dlc.name); - return -EPROBE_DEFER; } static int soc_probe_aux_devices(struct snd_soc_card *card) { - struct snd_soc_component *comp; + struct snd_soc_component *component; int order; int ret; for_each_comp_order(order) { - list_for_each_entry(comp, &card->aux_comp_list, card_aux_list) { - if (comp->driver->probe_order == order) { - ret = soc_probe_component(card, comp); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to probe aux component %s %d\n", - comp->name, ret); - return ret; - } - } + for_each_card_auxs(card, component) { + if (component->driver->probe_order != order) + continue; + + ret = soc_probe_component(card, component); + if (ret < 0) + return ret; } } @@ -1648,14 +1448,9 @@ static void soc_remove_aux_devices(struct snd_soc_card *card) int order; for_each_comp_order(order) { - list_for_each_entry_safe(comp, _comp, - &card->aux_comp_list, card_aux_list) { - - if (comp->driver->remove_order == order) { - soc_remove_component(comp); - /* remove it from the card's aux_comp_list */ - list_del(&comp->card_aux_list); - } + for_each_card_auxs_safe(card, comp, _comp) { + if (comp->driver->remove_order == order) + soc_remove_component(comp, 1); } } } @@ -1729,6 +1524,21 @@ EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); #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. + */ +static const char * 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. @@ -1765,6 +1575,23 @@ static int is_dmi_valid(const char *field) return 1; } +/* + * Append a string to card->dmi_longname with character cleanups. + */ +static void append_dmi_string(struct snd_soc_card *card, const char *str) +{ + char *dst = card->dmi_longname; + size_t dst_len = sizeof(card->dmi_longname); + size_t len; + + len = strlen(dst); + snprintf(dst + len, dst_len - len, "-%s", str); + + 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 @@ -1799,61 +1626,37 @@ static int is_dmi_valid(const char *field) int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour) { const char *vendor, *product, *product_version, *board; - size_t longname_buf_size = sizeof(card->snd_card->longname); - size_t len; if (card->long_name) return 0; /* long name already set by driver or from DMI */ - /* make up dmi long name as: vendor.product.version.board */ + /* 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; } - snprintf(card->dmi_longname, sizeof(card->snd_card->longname), - "%s", vendor); + snprintf(card->dmi_longname, sizeof(card->dmi_longname), "%s", vendor); cleanup_dmi_name(card->dmi_longname); product = dmi_get_system_info(DMI_PRODUCT_NAME); if (product && is_dmi_valid(product)) { - len = strlen(card->dmi_longname); - snprintf(card->dmi_longname + len, - longname_buf_size - len, - "-%s", product); - - len++; /* skip the separator "-" */ - if (len < longname_buf_size) - cleanup_dmi_name(card->dmi_longname + len); + append_dmi_string(card, product); /* * some vendors like Lenovo may only put a self-explanatory * name in the product version field */ product_version = dmi_get_system_info(DMI_PRODUCT_VERSION); - if (product_version && is_dmi_valid(product_version)) { - len = strlen(card->dmi_longname); - snprintf(card->dmi_longname + len, - longname_buf_size - len, - "-%s", product_version); - - len++; - if (len < longname_buf_size) - cleanup_dmi_name(card->dmi_longname + len); - } + 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)) { - len = strlen(card->dmi_longname); - snprintf(card->dmi_longname + len, - longname_buf_size - len, - "-%s", board); - - len++; - if (len < longname_buf_size) - cleanup_dmi_name(card->dmi_longname + len); + if (!product || strcasecmp(board, product)) + append_dmi_string(card, board); } else if (!product) { /* fall back to using legacy name */ dev_warn(card->dev, "ASoC: no DMI board/product name!\n"); @@ -1861,16 +1664,8 @@ int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour) } /* Add flavour to dmi long name */ - if (flavour) { - len = strlen(card->dmi_longname); - snprintf(card->dmi_longname + len, - longname_buf_size - len, - "-%s", flavour); - - len++; - if (len < longname_buf_size) - cleanup_dmi_name(card->dmi_longname + len); - } + if (flavour) + append_dmi_string(card, flavour); /* set the card long name */ card->long_name = card->dmi_longname; @@ -1889,7 +1684,7 @@ static void soc_check_tplg_fes(struct snd_soc_card *card) for_each_component(component) { - /* does this component override FEs ? */ + /* does this component override BEs ? */ if (!component->driver->ignore_machine) continue; @@ -1910,7 +1705,7 @@ match: continue; } - dev_info(card->dev, "info: override FE DAI link %s\n", + dev_info(card->dev, "info: override BE DAI link %s\n", card->dai_link[i].name); /* override platform component */ @@ -1922,6 +1717,8 @@ match: /* convert non BE into BE */ dai_link->no_pcm = 1; + dai_link->dpcm_playback = 1; + dai_link->dpcm_capture = 1; /* override any BE fixups */ dai_link->be_hw_params_fixup = @@ -1954,74 +1751,120 @@ match: } } -static int soc_cleanup_card_resources(struct snd_soc_card *card) +#define soc_setup_card_name(name, name1, name2, norm) \ + __soc_setup_card_name(name, sizeof(name), name1, name2, norm) +static void __soc_setup_card_name(char *name, int len, + const char *name1, const char *name2, + int normalization) { - /* free the ALSA card at first; this syncs with pending operations */ - if (card->snd_card) { - snd_card_free(card->snd_card); - card->snd_card = NULL; + int i; + + snprintf(name, len, "%s", name1 ? name1 : name2); + + if (!normalization) + return; + + /* + * Name normalization + * + * 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; + } } +} + +static void soc_cleanup_card_resources(struct snd_soc_card *card, + int card_probed) +{ + struct snd_soc_pcm_runtime *rtd, *n; + + if (card->snd_card) + snd_card_disconnect_sync(card->snd_card); + + snd_soc_dapm_shutdown(card); /* remove and free each DAI */ - soc_remove_dai_links(card); - soc_remove_pcm_runtimes(card); + soc_remove_link_dais(card); + soc_remove_link_components(card); + + for_each_card_rtds_safe(card, rtd, n) + snd_soc_remove_pcm_runtime(card, rtd); /* remove auxiliary devices */ soc_remove_aux_devices(card); + soc_unbind_aux_dev(card); snd_soc_dapm_free(&card->dapm); soc_cleanup_card_debugfs(card); /* remove the card */ - if (card->remove) + if (card_probed && card->remove) card->remove(card); - return 0; + if (card->snd_card) { + snd_card_free(card->snd_card); + card->snd_card = NULL; + } } -static int snd_soc_instantiate_card(struct snd_soc_card *card) +static void snd_soc_unbind_card(struct snd_soc_card *card, bool unregister) +{ + if (card->instantiated) { + int card_probed = 1; + + card->instantiated = false; + snd_soc_flush_all_delayed_work(card); + + soc_cleanup_card_resources(card, card_probed); + if (!unregister) + list_add(&card->list, &unbind_card_list); + } else { + if (unregister) + list_del(&card->list); + } +} + +static int snd_soc_bind_card(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; struct snd_soc_dai_link *dai_link; - int ret, i, order; + int ret, i, card_probed = 0; mutex_lock(&client_mutex); - for_each_card_prelinks(card, i, dai_link) { - ret = soc_init_dai_link(card, dai_link); - if (ret) { - dev_err(card->dev, "ASoC: failed to init link %s: %d\n", - dai_link->name, ret); - mutex_unlock(&client_mutex); - return ret; - } - } mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT); - card->dapm.bias_level = SND_SOC_BIAS_OFF; - card->dapm.dev = card->dev; - card->dapm.card = card; - list_add(&card->dapm.list, &card->dapm_list); + snd_soc_dapm_init(&card->dapm, card, NULL); /* check whether any platform is ignore machine FE and using topology */ soc_check_tplg_fes(card); - /* bind DAIs */ - for_each_card_prelinks(card, i, dai_link) { - ret = soc_bind_dai_link(card, dai_link); - if (ret != 0) - goto probe_end; - } - /* bind aux_devs too */ - for (i = 0; i < card->num_aux_devs; i++) { - ret = soc_bind_aux_dev(card, i); - if (ret != 0) - goto probe_end; - } + ret = soc_bind_aux_dev(card); + if (ret < 0) + goto probe_end; /* add predefined DAI links to the list */ - for_each_card_prelinks(card, i, dai_link) - snd_soc_add_dai_link(card, dai_link); + card->num_rtd = 0; + for_each_card_prelinks(card, i, dai_link) { + ret = snd_soc_add_pcm_runtime(card, dai_link); + 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, @@ -2035,111 +1878,94 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) soc_init_card_debugfs(card); -#ifdef CONFIG_DEBUG_FS - snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); -#endif - -#ifdef CONFIG_PM_SLEEP - /* deferred resume work */ - INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); -#endif + soc_resume_init(card); - if (card->dapm_widgets) - snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, - card->num_dapm_widgets); + ret = snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, + card->num_dapm_widgets); + if (ret < 0) + goto probe_end; - if (card->of_dapm_widgets) - snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets, - card->num_of_dapm_widgets); + 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 */ if (card->probe) { ret = card->probe(card); if (ret < 0) goto probe_end; + card_probed = 1; } /* probe all components used by DAI links on this card */ - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - ret = soc_probe_link_components(card, rtd, order); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to instantiate card %d\n", - ret); - goto probe_end; - } - } + ret = soc_probe_link_components(card); + if (ret < 0) { + 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) + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to probe aux component %d\n", ret); goto probe_end; - - /* - * Find new DAI links added during probing components and bind them. - * Components with topology may bring new DAIs and DAI links. - */ - for_each_card_links(card, dai_link) { - if (soc_is_dai_link_bound(card, dai_link)) - continue; - - ret = soc_init_dai_link(card, dai_link); - if (ret) - goto probe_end; - ret = soc_bind_dai_link(card, dai_link); - if (ret) - goto probe_end; } /* probe all DAI links on this card */ - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - ret = soc_probe_link_dais(card, rtd, order); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to instantiate card %d\n", - ret); - goto probe_end; - } - } + 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; } snd_soc_dapm_link_dai_widgets(card); snd_soc_dapm_connect_dai_link_widgets(card); - if (card->controls) - snd_soc_add_card_controls(card, card->controls, - card->num_controls); + ret = snd_soc_add_card_controls(card, card->controls, + card->num_controls); + if (ret < 0) + goto probe_end; - if (card->dapm_routes) - snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, - card->num_dapm_routes); + ret = snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, + card->num_dapm_routes); + if (ret < 0) + goto probe_end; - if (card->of_dapm_routes) - snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, - card->num_of_dapm_routes); + ret = snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, + card->num_of_dapm_routes); + if (ret < 0) + goto probe_end; /* try to set some sane longname if DMI is available */ snd_soc_set_dmi_name(card, NULL); - snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), - "%s", card->name); - snprintf(card->snd_card->longname, sizeof(card->snd_card->longname), - "%s", card->long_name ? card->long_name : card->name); - snprintf(card->snd_card->driver, sizeof(card->snd_card->driver), - "%s", card->driver_name ? card->driver_name : card->name); - for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) { - switch (card->snd_card->driver[i]) { - case '_': - case '-': - case '\0': - break; - default: - if (!isalnum(card->snd_card->driver[i])) - card->snd_card->driver[i] = '_'; - break; + soc_setup_card_name(card->snd_card->shortname, + card->name, NULL, 0); + soc_setup_card_name(card->snd_card->longname, + card->long_name, card->name, 0); + soc_setup_card_name(card->snd_card->driver, + card->driver_name, card->name, 1); + + 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; } } @@ -2151,6 +1977,7 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) goto probe_end; } } + card_probed = 1; snd_soc_dapm_new_widgets(card); @@ -2165,9 +1992,14 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) dapm_mark_endpoints_dirty(card); snd_soc_dapm_sync(&card->dapm); + /* deactivate pins to sleep state */ + for_each_card_components(card, component) + if (!component->active) + pinctrl_pm_select_sleep_state(component->dev); + probe_end: if (ret < 0) - soc_cleanup_card_resources(card); + soc_cleanup_card_resources(card, card_probed); mutex_unlock(&card->mutex); mutex_unlock(&client_mutex); @@ -2209,7 +2041,7 @@ static int soc_remove(struct platform_device *pdev) int snd_soc_poweroff(struct device *dev) { struct snd_soc_card *card = dev_get_drvdata(dev); - struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; if (!card->instantiated) return 0; @@ -2223,16 +2055,8 @@ int snd_soc_poweroff(struct device *dev) snd_soc_dapm_shutdown(card); /* deactivate pins to sleep state */ - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - int i; - - pinctrl_pm_select_sleep_state(cpu_dai->dev); - for_each_rtd_codec_dai(rtd, i, codec_dai) { - pinctrl_pm_select_sleep_state(codec_dai->dev); - } - } + for_each_card_components(card, component) + pinctrl_pm_select_sleep_state(component->dev); return 0; } @@ -2398,320 +2222,6 @@ int snd_soc_add_dai_controls(struct snd_soc_dai *dai, EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls); /** - * snd_soc_dai_set_sysclk - configure DAI system or master clock. - * @dai: DAI - * @clk_id: DAI specific clock ID - * @freq: new clock frequency in Hz - * @dir: new clock direction - input/output. - * - * Configures the DAI master (MCLK) or system (SYSCLK) clocking. - */ -int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, - unsigned int freq, int dir) -{ - if (dai->driver->ops->set_sysclk) - return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir); - - return snd_soc_component_set_sysclk(dai->component, clk_id, 0, - freq, dir); -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); - -/** - * snd_soc_component_set_sysclk - configure COMPONENT system or master clock. - * @component: COMPONENT - * @clk_id: DAI specific clock ID - * @source: Source for the clock - * @freq: new clock frequency in Hz - * @dir: new clock direction - input/output. - * - * Configures the CODEC master (MCLK) or system (SYSCLK) clocking. - */ -int snd_soc_component_set_sysclk(struct snd_soc_component *component, - int clk_id, int source, unsigned int freq, - int dir) -{ - if (component->driver->set_sysclk) - return component->driver->set_sysclk(component, clk_id, source, - freq, dir); - - return -ENOTSUPP; -} -EXPORT_SYMBOL_GPL(snd_soc_component_set_sysclk); - -/** - * snd_soc_dai_set_clkdiv - configure DAI clock dividers. - * @dai: DAI - * @div_id: DAI specific clock divider ID - * @div: new clock divisor. - * - * Configures the clock dividers. This is used to derive the best DAI bit and - * frame clocks from the system or master clock. It's best to set the DAI bit - * and frame clocks as low as possible to save system power. - */ -int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, - int div_id, int div) -{ - if (dai->driver->ops->set_clkdiv) - return dai->driver->ops->set_clkdiv(dai, div_id, div); - else - return -EINVAL; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); - -/** - * snd_soc_dai_set_pll - configure DAI PLL. - * @dai: DAI - * @pll_id: DAI specific PLL ID - * @source: DAI specific source for the PLL - * @freq_in: PLL input clock frequency in Hz - * @freq_out: requested PLL output clock frequency in Hz - * - * Configures and enables PLL to generate output clock based on input clock. - */ -int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, - unsigned int freq_in, unsigned int freq_out) -{ - if (dai->driver->ops->set_pll) - return dai->driver->ops->set_pll(dai, pll_id, source, - freq_in, freq_out); - - return snd_soc_component_set_pll(dai->component, pll_id, source, - freq_in, freq_out); -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); - -/* - * snd_soc_component_set_pll - configure component PLL. - * @component: COMPONENT - * @pll_id: DAI specific PLL ID - * @source: DAI specific source for the PLL - * @freq_in: PLL input clock frequency in Hz - * @freq_out: requested PLL output clock frequency in Hz - * - * Configures and enables PLL to generate output clock based on input clock. - */ -int snd_soc_component_set_pll(struct snd_soc_component *component, int pll_id, - int source, unsigned int freq_in, - unsigned int freq_out) -{ - if (component->driver->set_pll) - return component->driver->set_pll(component, pll_id, source, - freq_in, freq_out); - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(snd_soc_component_set_pll); - -/** - * snd_soc_dai_set_bclk_ratio - configure BCLK to sample rate ratio. - * @dai: DAI - * @ratio: Ratio of BCLK to Sample rate. - * - * Configures the DAI for a preset BCLK to sample rate ratio. - */ -int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) -{ - if (dai->driver->ops->set_bclk_ratio) - return dai->driver->ops->set_bclk_ratio(dai, ratio); - else - return -EINVAL; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio); - -/** - * snd_soc_dai_set_fmt - configure DAI hardware audio format. - * @dai: DAI - * @fmt: SND_SOC_DAIFMT_* format value. - * - * Configures the DAI hardware format and clocking. - */ -int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) -{ - if (dai->driver->ops->set_fmt == NULL) - return -ENOTSUPP; - return dai->driver->ops->set_fmt(dai, fmt); -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); - -/** - * snd_soc_xlate_tdm_slot - generate tx/rx slot mask. - * @slots: Number of slots in use. - * @tx_mask: bitmask representing active TX slots. - * @rx_mask: bitmask representing active RX slots. - * - * Generates the TDM tx and rx slot default masks for DAI. - */ -static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, - unsigned int *tx_mask, - unsigned int *rx_mask) -{ - if (*tx_mask || *rx_mask) - return 0; - - if (!slots) - return -EINVAL; - - *tx_mask = (1 << slots) - 1; - *rx_mask = (1 << slots) - 1; - - return 0; -} - -/** - * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation - * @dai: The DAI to configure - * @tx_mask: bitmask representing active TX slots. - * @rx_mask: bitmask representing active RX slots. - * @slots: Number of slots in use. - * @slot_width: Width in bits for each slot. - * - * This function configures the specified DAI for TDM operation. @slot contains - * the total number of slots of the TDM stream and @slot_with the width of each - * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the - * active slots of the TDM stream for the specified DAI, i.e. which slots the - * DAI should write to or read from. If a bit is set the corresponding slot is - * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to - * the first slot, bit 1 to the second slot and so on. The first active slot - * maps to the first channel of the DAI, the second active slot to the second - * channel and so on. - * - * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask, - * @rx_mask and @slot_width will be ignored. - * - * Returns 0 on success, a negative error code otherwise. - */ -int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, - unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) -{ - if (dai->driver->ops->xlate_tdm_slot_mask) - dai->driver->ops->xlate_tdm_slot_mask(slots, - &tx_mask, &rx_mask); - else - snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); - - dai->tx_mask = tx_mask; - dai->rx_mask = rx_mask; - - if (dai->driver->ops->set_tdm_slot) - return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, - slots, slot_width); - else - return -ENOTSUPP; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); - -/** - * snd_soc_dai_set_channel_map - configure DAI audio channel map - * @dai: DAI - * @tx_num: how many TX channels - * @tx_slot: pointer to an array which imply the TX slot number channel - * 0~num-1 uses - * @rx_num: how many RX channels - * @rx_slot: pointer to an array which imply the RX slot number channel - * 0~num-1 uses - * - * configure the relationship between channel number and TDM slot number. - */ -int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, - unsigned int tx_num, unsigned int *tx_slot, - unsigned int rx_num, unsigned int *rx_slot) -{ - if (dai->driver->ops->set_channel_map) - return dai->driver->ops->set_channel_map(dai, tx_num, tx_slot, - rx_num, rx_slot); - else - return -ENOTSUPP; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); - -/** - * snd_soc_dai_get_channel_map - Get DAI audio channel map - * @dai: DAI - * @tx_num: how many TX channels - * @tx_slot: pointer to an array which imply the TX slot number channel - * 0~num-1 uses - * @rx_num: how many RX channels - * @rx_slot: pointer to an array which imply the RX slot number channel - * 0~num-1 uses - */ -int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, - unsigned int *tx_num, unsigned int *tx_slot, - unsigned int *rx_num, unsigned int *rx_slot) -{ - if (dai->driver->ops->get_channel_map) - return dai->driver->ops->get_channel_map(dai, tx_num, tx_slot, - rx_num, rx_slot); - else - return -ENOTSUPP; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map); - -/** - * snd_soc_dai_set_tristate - configure DAI system or master clock. - * @dai: DAI - * @tristate: tristate enable - * - * Tristates the DAI so that others can use it. - */ -int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) -{ - if (dai->driver->ops->set_tristate) - return dai->driver->ops->set_tristate(dai, tristate); - else - return -EINVAL; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); - -/** - * snd_soc_dai_digital_mute - configure DAI system or master clock. - * @dai: DAI - * @mute: mute enable - * @direction: stream to mute - * - * Mutes the DAI DAC. - */ -int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, - int direction) -{ - if (dai->driver->ops->mute_stream) - return dai->driver->ops->mute_stream(dai, mute, direction); - else if (direction == SNDRV_PCM_STREAM_PLAYBACK && - dai->driver->ops->digital_mute) - return dai->driver->ops->digital_mute(dai, mute); - else - return -ENOTSUPP; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); - -static int snd_soc_bind_card(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - int ret; - - ret = snd_soc_instantiate_card(card); - if (ret != 0) - return ret; - - /* deactivate pins to sleep state */ - for_each_card_rtds(card, rtd) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - int j; - - for_each_rtd_codec_dai(rtd, j, codec_dai) { - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); - } - - if (!cpu_dai->active) - pinctrl_pm_select_sleep_state(cpu_dai->dev); - } - - return ret; -} - -/** * snd_soc_register_card - Register a card with the ASoC core * * @card: Card to register @@ -2724,50 +2234,26 @@ int snd_soc_register_card(struct snd_soc_card *card) dev_set_drvdata(card->dev, card); - snd_soc_initialize_card_lists(card); - - INIT_LIST_HEAD(&card->dai_link_list); - + INIT_LIST_HEAD(&card->widgets); + INIT_LIST_HEAD(&card->paths); + INIT_LIST_HEAD(&card->dapm_list); + INIT_LIST_HEAD(&card->aux_comp_list); + INIT_LIST_HEAD(&card->component_dev_list); + INIT_LIST_HEAD(&card->list); INIT_LIST_HEAD(&card->rtd_list); - card->num_rtd = 0; - INIT_LIST_HEAD(&card->dapm_dirty); INIT_LIST_HEAD(&card->dobj_list); + card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); + mutex_init(&card->pcm_mutex); spin_lock_init(&card->dpcm_lock); return snd_soc_bind_card(card); } EXPORT_SYMBOL_GPL(snd_soc_register_card); -static void snd_soc_unbind_card(struct snd_soc_card *card, bool unregister) -{ - struct snd_soc_pcm_runtime *rtd; - int order; - - if (card->instantiated) { - card->instantiated = false; - snd_soc_dapm_shutdown(card); - snd_soc_flush_all_delayed_work(card); - - /* remove all components used by DAI links on this card */ - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - soc_remove_link_components(card, rtd, order); - } - } - - soc_cleanup_card_resources(card); - if (!unregister) - list_add(&card->list, &unbind_card_list); - } else { - if (unregister) - list_del(&card->list); - } -} - /** * snd_soc_unregister_card - Unregister a card with the ASoC core * @@ -2826,7 +2312,7 @@ static char *fmt_single_name(struct device *dev, int *id) *id = 0; } - return kstrdup(name, GFP_KERNEL); + return devm_kstrdup(dev, name, GFP_KERNEL); } /* @@ -2843,38 +2329,40 @@ static inline char *fmt_multiple_name(struct device *dev, return NULL; } - return kstrdup(dai_drv->name, GFP_KERNEL); + return devm_kstrdup(dev, dai_drv->name, GFP_KERNEL); } -/** - * snd_soc_unregister_dai - Unregister DAIs from the ASoC core - * - * @component: The component for which the DAIs should be unregistered - */ -static void snd_soc_unregister_dais(struct snd_soc_component *component) +void snd_soc_unregister_dai(struct snd_soc_dai *dai) { - struct snd_soc_dai *dai, *_dai; - - for_each_component_dais_safe(component, dai, _dai) { - dev_dbg(component->dev, "ASoC: Unregistered DAI '%s'\n", - dai->name); - list_del(&dai->list); - kfree(dai->name); - kfree(dai); - } + dev_dbg(dai->dev, "ASoC: Unregistered DAI '%s'\n", dai->name); + list_del(&dai->list); } +EXPORT_SYMBOL_GPL(snd_soc_unregister_dai); -/* Create a DAI and add it to the component's DAI list */ -static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - bool legacy_dai_naming) +/** + * 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; dev_dbg(dev, "ASoC: dynamically register DAI %s\n", dev_name(dev)); - dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); + lockdep_assert_held(&client_mutex); + + dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL); if (dai == NULL) return NULL; @@ -2896,10 +2384,8 @@ static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component, else dai->id = component->num_dai; } - if (dai->name == NULL) { - kfree(dai); + if (!dai->name) return NULL; - } dai->component = component; dai->dev = dev; @@ -2916,6 +2402,19 @@ static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component, } /** + * snd_soc_unregister_dai - Unregister DAIs from the ASoC core + * + * @component: The component for which the DAIs should be unregistered + */ +static void snd_soc_unregister_dais(struct snd_soc_component *component) +{ + struct snd_soc_dai *dai, *_dai; + + for_each_component_dais_safe(component, dai, _dai) + snd_soc_unregister_dai(dai); +} + +/** * snd_soc_register_dais - Register a DAI with the ASoC core * * @component: The component the DAIs are registered for @@ -2926,16 +2425,12 @@ static int snd_soc_register_dais(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, size_t count) { - struct device *dev = component->dev; struct snd_soc_dai *dai; unsigned int i; int ret; - dev_dbg(dev, "ASoC: dai register %s #%zu\n", dev_name(dev), count); - for (i = 0; i < count; i++) { - - dai = soc_add_dai(component, dai_drv + i, count == 1 && + dai = snd_soc_register_dai(component, dai_drv + i, count == 1 && !component->driver->non_legacy_dai_naming); if (dai == NULL) { ret = -ENOMEM; @@ -2951,77 +2446,13 @@ err: return ret; } -/** - * 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 - * - * 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. - */ -int snd_soc_register_dai(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - struct snd_soc_dai *dai; - int ret; - - if (dai_drv->dobj.type != SND_SOC_DOBJ_PCM) { - dev_err(component->dev, "Invalid dai type %d\n", - dai_drv->dobj.type); - return -EINVAL; - } - - lockdep_assert_held(&client_mutex); - dai = soc_add_dai(component, dai_drv, false); - if (!dai) - return -ENOMEM; - - /* - * Create the DAI widgets here. After adding DAIs, topology may - * also add routes that need these widgets as source or sink. - */ - ret = snd_soc_dapm_new_dai_widgets(dapm, dai); - if (ret != 0) { - dev_err(component->dev, - "Failed to create DAI widgets %d\n", ret); - } - - return ret; -} -EXPORT_SYMBOL_GPL(snd_soc_register_dai); - -static void snd_soc_component_seq_notifier(struct snd_soc_dapm_context *dapm, - enum snd_soc_dapm_type type, int subseq) -{ - struct snd_soc_component *component = dapm->component; - - component->driver->seq_notifier(component, type, subseq); -} - -static int snd_soc_component_stream_event(struct snd_soc_dapm_context *dapm, - int event) -{ - struct snd_soc_component *component = dapm->component; - - return component->driver->stream_event(component, event); -} - -static int snd_soc_component_set_bias_level(struct snd_soc_dapm_context *dapm, - enum snd_soc_bias_level level) -{ - struct snd_soc_component *component = dapm->component; - - return component->driver->set_bias_level(component, level); -} - static int snd_soc_component_initialize(struct snd_soc_component *component, const struct snd_soc_component_driver *driver, struct device *dev) { - struct snd_soc_dapm_context *dapm; + INIT_LIST_HEAD(&component->dai_list); + INIT_LIST_HEAD(&component->dobj_list); + INIT_LIST_HEAD(&component->card_list); + mutex_init(&component->io_mutex); component->name = fmt_single_name(dev, &component->id); if (!component->name) { @@ -3032,22 +2463,6 @@ static int snd_soc_component_initialize(struct snd_soc_component *component, component->dev = dev; component->driver = driver; - dapm = snd_soc_component_get_dapm(component); - dapm->dev = dev; - dapm->component = component; - dapm->bias_level = SND_SOC_BIAS_OFF; - dapm->idle_bias_off = !driver->idle_bias_on; - dapm->suspend_bias_off = driver->suspend_bias_off; - if (driver->seq_notifier) - dapm->seq_notifier = snd_soc_component_seq_notifier; - if (driver->stream_event) - dapm->stream_event = snd_soc_component_stream_event; - if (driver->set_bias_level) - dapm->set_bias_level = snd_soc_component_set_bias_level; - - INIT_LIST_HEAD(&component->dai_list); - mutex_init(&component->io_mutex); - return 0; } @@ -3101,41 +2516,6 @@ EXPORT_SYMBOL_GPL(snd_soc_component_exit_regmap); #endif -static void snd_soc_component_add(struct snd_soc_component *component) -{ - mutex_lock(&client_mutex); - - 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); - INIT_LIST_HEAD(&component->dobj_list); - - mutex_unlock(&client_mutex); -} - -static void snd_soc_component_cleanup(struct snd_soc_component *component) -{ - snd_soc_unregister_dais(component); - kfree(component->name); -} - -static void snd_soc_component_del_unlocked(struct snd_soc_component *component) -{ - struct snd_soc_card *card = component->card; - - if (card) - snd_soc_unbind_card(card, false); - - list_del(&component->list); -} - #define ENDIANNESS_MAP(name) \ (SNDRV_PCM_FMTBIT_##name##LE | SNDRV_PCM_FMTBIT_##name##BE) static u64 endianness_format_map[] = { @@ -3175,12 +2555,21 @@ static void snd_soc_try_rebind_card(void) { struct snd_soc_card *card, *c; - if (!list_empty(&unbind_card_list)) { - list_for_each_entry_safe(card, c, &unbind_card_list, list) { - if (!snd_soc_bind_card(card)) - list_del(&card->list); - } - } + list_for_each_entry_safe(card, c, &unbind_card_list, list) + if (!snd_soc_bind_card(card)) + list_del(&card->list); +} + +static void snd_soc_del_component_unlocked(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + + snd_soc_unregister_dais(component); + + if (card) + snd_soc_unbind_card(card, false); + + list_del(&component->list); } int snd_soc_add_component(struct device *dev, @@ -3192,6 +2581,8 @@ int snd_soc_add_component(struct device *dev, int ret; int i; + mutex_lock(&client_mutex); + ret = snd_soc_component_initialize(component, component_driver, dev); if (ret) goto err_free; @@ -3209,14 +2600,26 @@ int snd_soc_add_component(struct device *dev, goto err_cleanup; } - snd_soc_component_add(component); - snd_soc_try_rebind_card(); + 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); + } - return 0; + /* see for_each_component */ + list_add(&component->list, &component_list); err_cleanup: - snd_soc_component_cleanup(component); + if (ret < 0) + snd_soc_del_component_unlocked(component); err_free: + mutex_unlock(&client_mutex); + + if (ret == 0) + snd_soc_try_rebind_card(); + return ret; } EXPORT_SYMBOL_GPL(snd_soc_add_component); @@ -3243,62 +2646,21 @@ EXPORT_SYMBOL_GPL(snd_soc_register_component); * * @dev: The device to unregister */ -static int __snd_soc_unregister_component(struct device *dev) -{ - struct snd_soc_component *component; - int found = 0; - - mutex_lock(&client_mutex); - for_each_component(component) { - if (dev != component->dev) - continue; - - snd_soc_tplg_component_remove(component, - SND_SOC_TPLG_INDEX_ALL); - snd_soc_component_del_unlocked(component); - found = 1; - break; - } - mutex_unlock(&client_mutex); - - if (found) - snd_soc_component_cleanup(component); - - return found; -} - void snd_soc_unregister_component(struct device *dev) { - while (__snd_soc_unregister_component(dev)) - ; -} -EXPORT_SYMBOL_GPL(snd_soc_unregister_component); - -struct snd_soc_component *snd_soc_lookup_component(struct device *dev, - const char *driver_name) -{ struct snd_soc_component *component; - struct snd_soc_component *ret; - ret = NULL; mutex_lock(&client_mutex); - for_each_component(component) { - if (dev != component->dev) - continue; - - if (driver_name && - (driver_name != component->driver->name) && - (strcmp(component->driver->name, driver_name) != 0)) - continue; + while (1) { + component = snd_soc_lookup_component_nolocked(dev, NULL); + if (!component) + break; - ret = component; - break; + snd_soc_del_component_unlocked(component); } mutex_unlock(&client_mutex); - - return ret; } -EXPORT_SYMBOL_GPL(snd_soc_lookup_component); +EXPORT_SYMBOL_GPL(snd_soc_unregister_component); /* Retrieve a card's name from device tree */ int snd_soc_of_parse_card_name(struct snd_soc_card *card, @@ -3487,7 +2849,7 @@ void snd_soc_of_parse_node_prefix(struct device_node *np, return; } - codec_conf->of_node = of_node; + codec_conf->dlc.of_node = of_node; codec_conf->name_prefix = str; } EXPORT_SYMBOL_GPL(snd_soc_of_parse_node_prefix); @@ -3682,9 +3044,8 @@ int snd_soc_get_dai_id(struct device_node *ep) ret = -ENOTSUPP; mutex_lock(&client_mutex); component = soc_find_component(&dlc); - if (component && - component->driver->of_xlate_dai_id) - ret = component->driver->of_xlate_dai_id(component, ep); + if (component) + ret = snd_soc_component_of_xlate_dai_id(component, ep); mutex_unlock(&client_mutex); of_node_put(dlc.of_node); @@ -3707,11 +3068,8 @@ int snd_soc_get_dai_name(struct of_phandle_args *args, if (component_of_node != args->np) continue; - if (pos->driver->of_xlate_dai_name) { - ret = pos->driver->of_xlate_dai_name(pos, - args, - dai_name); - } else { + ret = snd_soc_component_of_xlate_dai_name(pos, args, dai_name); + if (ret == -ENOTSUPP) { struct snd_soc_dai *dai; int id = -1; |