ASoC: core: only flush inited work during free
[sfrench/cifs-2.6.git] / sound / soc / soc-core.c
index d39d908f3204299512a9a259dd01bde87a4b6272..1c84ff1a5bf96b09e303ec08d5d4c6e1e565aad9 100644 (file)
@@ -277,14 +277,27 @@ static inline void snd_soc_debugfs_exit(void)
 
 #endif
 
+/*
+ * This is glue code between snd_pcm_lib_ioctl() and
+ * snd_soc_component_driver :: ioctl
+ */
+int snd_soc_pcm_lib_ioctl(struct snd_soc_component *component,
+                         struct snd_pcm_substream *substream,
+                         unsigned int cmd, void *arg)
+{
+       return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+EXPORT_SYMBOL_GPL(snd_soc_pcm_lib_ioctl);
+
 static int snd_soc_rtdcom_add(struct snd_soc_pcm_runtime *rtd,
                              struct snd_soc_component *component)
 {
        struct snd_soc_rtdcom_list *rtdcom;
+       struct snd_soc_component *comp;
 
-       for_each_rtdcom(rtd, rtdcom) {
+       for_each_rtd_components(rtd, rtdcom, comp) {
                /* already connected */
-               if (rtdcom->component == component)
+               if (comp == component)
                        return 0;
        }
 
@@ -315,6 +328,7 @@ 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;
 
        if (!driver_name)
                return NULL;
@@ -327,8 +341,8 @@ struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd,
         * But, if many components which have same driver name are connected
         * to 1 rtd, this function will return 1st found component.
         */
-       for_each_rtdcom(rtd, rtdcom) {
-               const char *component_name = rtdcom->component->driver->name;
+       for_each_rtd_components(rtd, rtdcom, component) {
+               const char *component_name = component->driver->name;
 
                if (!component_name)
                        continue;
@@ -342,6 +356,39 @@ struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd,
 }
 EXPORT_SYMBOL_GPL(snd_soc_rtdcom_lookup);
 
+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_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card,
                const char *dai_link, int stream)
 {
@@ -372,6 +419,10 @@ static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd)
 
        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
@@ -385,6 +436,15 @@ static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd)
        device_unregister(rtd->dev);
 }
 
+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)
 {
@@ -420,6 +480,7 @@ static struct snd_soc_pcm_runtime *soc_new_pcm_runtime(
 
        rtd->dev = dev;
        dev_set_drvdata(dev, rtd);
+       INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
 
        /*
         * for rtd->codec_dais
@@ -456,14 +517,6 @@ free_rtd:
        return NULL;
 }
 
-static void soc_remove_pcm_runtimes(struct snd_soc_card *card)
-{
-       struct snd_soc_pcm_runtime *rtd, *_rtd;
-
-       for_each_card_rtds_safe(card, rtd, _rtd)
-               soc_free_pcm_runtime(rtd);
-}
-
 struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card,
                const char *dai_link)
 {
@@ -914,37 +967,168 @@ struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card,
 }
 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)
+static int soc_dai_link_sanity_check(struct snd_soc_card *card,
+                                    struct snd_soc_dai_link *link)
 {
-       struct snd_soc_pcm_runtime *rtd;
+       int i;
+       struct snd_soc_dai_link_component *codec, *platform;
 
-       for_each_card_rtds(card, rtd) {
-               if (rtd->dai_link == dai_link)
-                       return true;
+       for_each_link_codecs(link, i, codec) {
+               /*
+                * Codec must be specified by 1 of name or OF node,
+                * not both or neither.
+                */
+               if (!!codec->name == !!codec->of_node) {
+                       dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n",
+                               link->name);
+                       return -EINVAL;
+               }
+
+               /* Codec DAI name must be specified */
+               if (!codec->dai_name) {
+                       dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n",
+                               link->name);
+                       return -EINVAL;
+               }
+
+               /*
+                * Defer card registration if codec component is not added to
+                * component list.
+                */
+               if (!soc_find_component(codec))
+                       return -EPROBE_DEFER;
+       }
+
+       for_each_link_platforms(link, i, platform) {
+               /*
+                * 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 (!!platform->name == !!platform->of_node) {
+                       dev_err(card->dev,
+                               "ASoC: Neither/both platform name/of_node are set for %s\n",
+                               link->name);
+                       return -EINVAL;
+               }
+
+               /*
+                * Defer card registration if platform component is not added to
+                * component list.
+                */
+               if (!soc_find_component(platform))
+                       return -EPROBE_DEFER;
+       }
+
+       /* FIXME */
+       if (link->num_cpus > 1) {
+               dev_err(card->dev,
+                       "ASoC: multi cpu is not yet supported %s\n",
+                       link->name);
+               return -EINVAL;
+       }
+
+       /*
+        * 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 (link->cpus->name && link->cpus->of_node) {
+               dev_err(card->dev,
+                       "ASoC: Neither/both cpu name/of_node are set for %s\n",
+                       link->name);
+               return -EINVAL;
+       }
+
+       /*
+        * Defer card registration if cpu dai component is not added to
+        * component list.
+        */
+       if ((link->cpus->of_node || link->cpus->name) &&
+           !soc_find_component(link->cpus))
+               return -EPROBE_DEFER;
+
+       /*
+        * At least one of CPU DAI name or CPU device name/node must be
+        * specified
+        */
+       if (!link->cpus->dai_name &&
+           !(link->cpus->name || link->cpus->of_node)) {
+               dev_err(card->dev,
+                       "ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n",
+                       link->name);
+               return -EINVAL;
        }
 
-       return false;
+       return 0;
+}
+
+/**
+ * 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)
+{
+       struct snd_soc_pcm_runtime *rtd;
+
+       lockdep_assert_held(&client_mutex);
+
+       /*
+        * Notify the machine driver for extra destruction
+        */
+       if (card->remove_dai_link)
+               card->remove_dai_link(card, dai_link);
+
+       list_del(&dai_link->list);
+
+       rtd = snd_soc_get_pcm_runtime(card, dai_link->name);
+       if (rtd)
+               soc_free_pcm_runtime(rtd);
 }
+EXPORT_SYMBOL_GPL(snd_soc_remove_dai_link);
 
-static int soc_bind_dai_link(struct snd_soc_card *card,
-       struct snd_soc_dai_link *dai_link)
+/**
+ * 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
+ *
+ * This function adds a DAI link to the ASoC card's link list.
+ *
+ * Note: Topology can use this API to add DAI links 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)
 {
        struct snd_soc_pcm_runtime *rtd;
        struct snd_soc_dai_link_component *codec, *platform;
        struct snd_soc_component *component;
-       int i;
+       int i, ret;
+
+       lockdep_assert_held(&client_mutex);
+
+       /*
+        * Notify the machine driver for extra initialization
+        */
+       if (card->add_dai_link)
+               card->add_dai_link(card, dai_link);
 
        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;
-       }
+       ret = soc_dai_link_sanity_check(card, dai_link);
+       if (ret < 0)
+               return ret;
 
        rtd = soc_new_pcm_runtime(card, dai_link);
        if (!rtd)
@@ -985,12 +1169,16 @@ static int soc_bind_dai_link(struct snd_soc_card *card,
                }
        }
 
+       /* see for_each_card_links */
+       list_add_tail(&dai_link->list, &card->dai_link_list);
+
        return 0;
 
 _err_defer:
        soc_free_pcm_runtime(rtd);
        return -EPROBE_DEFER;
 }
+EXPORT_SYMBOL_GPL(snd_soc_add_dai_link);
 
 static void soc_set_of_name_prefix(struct snd_soc_component *component)
 {
@@ -1027,8 +1215,16 @@ static void soc_set_name_prefix(struct snd_soc_card *card,
        soc_set_of_name_prefix(component);
 }
 
-static void soc_cleanup_component(struct snd_soc_component *component)
+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);
 
@@ -1039,22 +1235,13 @@ static void soc_cleanup_component(struct snd_soc_component *component)
        snd_soc_component_module_put_when_remove(component);
 }
 
-static void soc_remove_component(struct snd_soc_component *component)
-{
-       if (!component->card)
-               return;
-
-       snd_soc_component_remove(component);
-
-       soc_cleanup_component(component);
-}
-
 static int soc_probe_component(struct snd_soc_card *card,
                               struct snd_soc_component *component)
 {
        struct snd_soc_dapm_context *dapm =
                snd_soc_component_get_dapm(component);
        struct snd_soc_dai *dai;
+       int probed = 0;
        int ret;
 
        if (!strcmp(component->name, "snd-soc-dummy"))
@@ -1110,6 +1297,7 @@ static int soc_probe_component(struct snd_soc_card *card,
             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) {
@@ -1138,7 +1326,7 @@ static int soc_probe_component(struct snd_soc_card *card,
 
 err_probe:
        if (ret < 0)
-               soc_cleanup_component(component);
+               soc_remove_component(component, probed);
 
        return ret;
 }
@@ -1236,13 +1424,11 @@ static void soc_remove_link_components(struct snd_soc_card *card)
 
        for_each_comp_order(order) {
                for_each_card_rtds(card, rtd) {
-                       for_each_rtdcom(rtd, rtdcom) {
-                               component = rtdcom->component;
-
+                       for_each_rtd_components(rtd, rtdcom, component) {
                                if (component->driver->remove_order != order)
                                        continue;
 
-                               soc_remove_component(component);
+                               soc_remove_component(component, 1);
                        }
                }
        }
@@ -1257,9 +1443,7 @@ static int soc_probe_link_components(struct snd_soc_card *card)
 
        for_each_comp_order(order) {
                for_each_card_rtds(card, rtd) {
-                       for_each_rtdcom(rtd, rtdcom) {
-                               component = rtdcom->component;
-
+                       for_each_rtd_components(rtd, rtdcom, component) {
                                if (component->driver->probe_order != order)
                                        continue;
 
@@ -1273,189 +1457,25 @@ static int soc_probe_link_components(struct snd_soc_card *card)
        return 0;
 }
 
-static int soc_init_dai_link(struct snd_soc_card *card,
-                            struct snd_soc_dai_link *link)
+void snd_soc_disconnect_sync(struct device *dev)
 {
-       int i;
-       struct snd_soc_dai_link_component *codec, *platform;
+       struct snd_soc_component *component =
+                       snd_soc_lookup_component(dev, NULL);
 
-       for_each_link_codecs(link, i, codec) {
-               /*
-                * Codec must be specified by 1 of name or OF node,
-                * not both or neither.
-                */
-               if (!!codec->name == !!codec->of_node) {
-                       dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n",
-                               link->name);
-                       return -EINVAL;
-               }
+       if (!component || !component->card)
+               return;
 
-               /* Codec DAI name must be specified */
-               if (!codec->dai_name) {
-                       dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n",
-                               link->name);
-                       return -EINVAL;
-               }
+       snd_card_disconnect_sync(component->card->snd_card);
+}
+EXPORT_SYMBOL_GPL(snd_soc_disconnect_sync);
 
-               /*
-                * Defer card registration if codec component is not added to
-                * component list.
-                */
-               if (!soc_find_component(codec))
-                       return -EPROBE_DEFER;
-       }
+static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais,
+                               struct snd_soc_pcm_runtime *rtd)
+{
+       int i, ret = 0;
 
-       for_each_link_platforms(link, i, platform) {
-               /*
-                * 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 (!!platform->name == !!platform->of_node) {
-                       dev_err(card->dev,
-                               "ASoC: Neither/both platform name/of_node are set for %s\n",
-                               link->name);
-                       return -EINVAL;
-               }
-
-               /*
-                * Defer card registration if platform component is not added to
-                * component list.
-                */
-               if (!soc_find_component(platform))
-                       return -EPROBE_DEFER;
-       }
-
-       /* FIXME */
-       if (link->num_cpus > 1) {
-               dev_err(card->dev,
-                       "ASoC: multi cpu is not yet supported %s\n",
-                       link->name);
-               return -EINVAL;
-       }
-
-       /*
-        * 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 (link->cpus->name && link->cpus->of_node) {
-               dev_err(card->dev,
-                       "ASoC: Neither/both cpu name/of_node are set for %s\n",
-                       link->name);
-               return -EINVAL;
-       }
-
-       /*
-        * Defer card registartion if cpu dai component is not added to
-        * component list.
-        */
-       if ((link->cpus->of_node || link->cpus->name) &&
-           !soc_find_component(link->cpus))
-               return -EPROBE_DEFER;
-
-       /*
-        * At least one of CPU DAI name or CPU device name/node must be
-        * specified
-        */
-       if (!link->cpus->dai_name &&
-           !(link->cpus->name || link->cpus->of_node)) {
-               dev_err(card->dev,
-                       "ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n",
-                       link->name);
-               return -EINVAL;
-       }
-
-       return 0;
-}
-
-void snd_soc_disconnect_sync(struct device *dev)
-{
-       struct snd_soc_component *component =
-                       snd_soc_lookup_component(dev, NULL);
-
-       if (!component || !component->card)
-               return;
-
-       snd_card_disconnect_sync(component->card->snd_card);
-}
-EXPORT_SYMBOL_GPL(snd_soc_disconnect_sync);
-
-/**
- * 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
- *
- * This function adds a DAI link to the ASoC card's link list.
- *
- * Note: Topology can use this API to add DAI links 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)
-{
-       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;
-       }
-
-       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)
-               card->add_dai_link(card, dai_link);
-
-       /* see for_each_card_links */
-       list_add_tail(&dai_link->list, &card->dai_link_list);
-
-       return 0;
-}
-EXPORT_SYMBOL_GPL(snd_soc_add_dai_link);
-
-/**
- * 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)
-{
-       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;
-       }
-
-       lockdep_assert_held(&client_mutex);
-       /*
-        * Notify the machine driver for extra destruction
-        * on the link created by topology.
-        */
-       if (dai_link->dobj.type && card->remove_dai_link)
-               card->remove_dai_link(card, dai_link);
-
-       list_del(&dai_link->list);
-}
-EXPORT_SYMBOL_GPL(snd_soc_remove_dai_link);
-
-static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais,
-                               struct snd_soc_pcm_runtime *rtd)
-{
-       int i, ret = 0;
-
-       for (i = 0; i < num_dais; ++i) {
-               struct snd_soc_dai_driver *drv = dais[i]->driver;
+       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]);
@@ -1508,9 +1528,7 @@ static int soc_link_init(struct snd_soc_card *card,
         * 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;
-
+       for_each_rtd_components(rtd, rtdcom, component) {
                if (!component->driver->use_dai_pcm_id)
                        continue;
 
@@ -1575,21 +1593,18 @@ static int soc_bind_aux_dev(struct snd_soc_card *card)
 
 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) {
-               for_each_card_auxs(card, comp) {
-                       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;
                }
        }
 
@@ -1604,7 +1619,7 @@ static void soc_remove_aux_devices(struct snd_soc_card *card)
        for_each_comp_order(order) {
                for_each_card_auxs_safe(card, comp, _comp) {
                        if (comp->driver->remove_order == order)
-                               soc_remove_component(comp);
+                               soc_remove_component(comp, 1);
                }
        }
 }
@@ -1714,6 +1729,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
@@ -1748,61 +1780,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");
@@ -1810,16 +1818,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;
@@ -1939,15 +1939,15 @@ static void __soc_setup_card_name(char *name, int len,
        }
 }
 
-static void soc_cleanup_card_resources(struct snd_soc_card *card)
+static void soc_cleanup_card_resources(struct snd_soc_card *card,
+                                      int card_probed)
 {
        struct snd_soc_dai_link *link, *_link;
 
-       /* 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;
-       }
+       if (card->snd_card)
+               snd_card_disconnect_sync(card->snd_card);
+
+       snd_soc_dapm_shutdown(card);
 
        /* remove and free each DAI */
        soc_remove_link_dais(card);
@@ -1956,8 +1956,6 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card)
        for_each_card_links_safe(card, link, _link)
                snd_soc_remove_dai_link(card, link);
 
-       soc_remove_pcm_runtimes(card);
-
        /* remove auxiliary devices */
        soc_remove_aux_devices(card);
        soc_unbind_aux_dev(card);
@@ -1966,26 +1964,39 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card)
        soc_cleanup_card_debugfs(card);
 
        /* remove the card */
-       if (card->remove)
+       if (card_probed && card->remove)
                card->remove(card);
+
+       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_dai_link *dai_link;
-       int ret, i;
+       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);
 
        snd_soc_dapm_init(&card->dapm, card, NULL);
@@ -1993,13 +2004,6 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
        /* 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 */
        ret = soc_bind_aux_dev(card);
        if (ret < 0)
@@ -2042,6 +2046,7 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
                ret = card->probe(card);
                if (ret < 0)
                        goto probe_end;
+               card_probed = 1;
        }
 
        /* probe all components used by DAI links on this card */
@@ -2054,23 +2059,10 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
 
        /* 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 */
@@ -2112,6 +2104,19 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
        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;
+               }
+       }
+
        if (card->late_probe) {
                ret = card->late_probe(card);
                if (ret < 0) {
@@ -2120,6 +2125,7 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card)
                        goto probe_end;
                }
        }
+       card_probed = 1;
 
        snd_soc_dapm_new_widgets(card);
 
@@ -2134,9 +2140,22 @@ 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_rtds(card, rtd) {
+               struct snd_soc_dai *dai;
+
+               for_each_rtd_codec_dai(rtd, i, dai) {
+                       if (!dai->active)
+                               pinctrl_pm_select_sleep_state(dai->dev);
+               }
+
+               if (!rtd->cpu_dai->active)
+                       pinctrl_pm_select_sleep_state(rtd->cpu_dai->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);
@@ -2366,33 +2385,6 @@ int snd_soc_add_dai_controls(struct snd_soc_dai *dai,
 }
 EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls);
 
-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
  *
@@ -2427,25 +2419,6 @@ int snd_soc_register_card(struct snd_soc_card *card)
 }
 EXPORT_SYMBOL_GPL(snd_soc_register_card);
 
-static void snd_soc_unbind_card(struct snd_soc_card *card, bool unregister)
-{
-       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 */
-               soc_remove_link_components(card);
-
-               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
  *
@@ -2524,32 +2497,34 @@ static inline char *fmt_multiple_name(struct device *dev,
        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);
-       }
+       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
+ *
+ * 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));
 
+       lockdep_assert_held(&client_mutex);
+
        dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL);
        if (dai == NULL)
                return NULL;
@@ -2589,6 +2564,19 @@ static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component,
        return dai;
 }
 
+/**
+ * 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
  *
@@ -2600,16 +2588,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;
@@ -2625,49 +2609,6 @@ 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 int snd_soc_component_initialize(struct snd_soc_component *component,
        const struct snd_soc_component_driver *driver, struct device *dev)
 {
@@ -2738,39 +2679,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);
-
-       mutex_unlock(&client_mutex);
-}
-
-static void snd_soc_component_cleanup(struct snd_soc_component *component)
-{
-       snd_soc_unregister_dais(component);
-}
-
-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[] = {
@@ -2815,6 +2723,18 @@ static void snd_soc_try_rebind_card(void)
                        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,
                        struct snd_soc_component *component,
                        const struct snd_soc_component_driver *component_driver,
@@ -2824,6 +2744,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;
@@ -2841,14 +2763,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);
@@ -2875,62 +2809,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,