Merge remote-tracking branches 'asoc/topic/atmel', 'asoc/topic/chmap', 'asoc/topic...
[sfrench/cifs-2.6.git] / sound / soc / soc-core.c
index f1901bb1466ec67b12189713c66ad040f1ae75c7..4d9fb44db4f5be9341bf55908c214870a0c91a5f 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/ctype.h>
 #include <linux/slab.h>
 #include <linux/of.h>
+#include <linux/dmi.h>
 #include <sound/core.h>
 #include <sound/jack.h>
 #include <sound/pcm.h>
@@ -1593,6 +1594,27 @@ 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)
+{
+       int i, ret = 0;
+
+       for (i = 0; i < num_dais; ++i) {
+               struct snd_soc_dai_driver *drv = dais[i]->driver;
+
+               if (!rtd->dai_link->no_pcm && 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;
+               }
+       }
+
+       return 0;
+}
+
 static int soc_link_dai_widgets(struct snd_soc_card *card,
                                struct snd_soc_dai_link *dai_link,
                                struct snd_soc_pcm_runtime *rtd)
@@ -1704,6 +1726,13 @@ static int soc_probe_link_dais(struct snd_soc_card *card,
                                       dai_link->stream_name, ret);
                                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);
@@ -1748,6 +1777,7 @@ static int soc_bind_aux_dev(struct snd_soc_card *card, int num)
 
        component->init = aux_dev->init;
        component->auxiliary = 1;
+       list_add(&component->card_aux_list, &card->aux_comp_list);
 
        return 0;
 
@@ -1758,16 +1788,14 @@ err_defer:
 
 static int soc_probe_aux_devices(struct snd_soc_card *card)
 {
-       struct snd_soc_component *comp;
+       struct snd_soc_component *comp, *tmp;
        int order;
        int ret;
 
        for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
                order++) {
-               list_for_each_entry(comp, &card->component_dev_list, card_list) {
-                       if (!comp->auxiliary)
-                               continue;
-
+               list_for_each_entry_safe(comp, tmp, &card->aux_comp_list,
+                                        card_aux_list) {
                        if (comp->driver->probe_order == order) {
                                ret = soc_probe_component(card, comp);
                                if (ret < 0) {
@@ -1776,6 +1804,7 @@ static int soc_probe_aux_devices(struct snd_soc_card *card)
                                                comp->name, ret);
                                        return ret;
                                }
+                               list_del(&comp->card_aux_list);
                        }
                }
        }
@@ -1888,6 +1917,139 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
 }
 EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt);
 
+
+/* 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.
+ */
+static void cleanup_dmi_name(char *name)
+{
+       int i, j = 0;
+
+       for (i = 0; name[i]; i++) {
+               if (isalnum(name[i]) || (name[i] == '.')
+                   || (name[i] == '_'))
+                       name[j++] = name[i];
+               else if (name[i] == '-')
+                       name[j++] = '_';
+       }
+
+       name[j] = '\0';
+}
+
+/**
+ * snd_soc_set_dmi_name() - Register DMI names to card
+ * @card: The card to register DMI names
+ * @flavour: The flavour "differentiator" for the card amongst its peers.
+ *
+ * An Intel machine driver may be used by many different devices but are
+ * difficult for userspace to differentiate, since machine drivers ususally
+ * use their own name as the card short name and leave the card long name
+ * blank. To differentiate such devices and fix bugs due to lack of
+ * device-specific configurations, this function allows DMI info to be used
+ * as the sound card long name, in the format of
+ * "vendor-product-version-board"
+ * (Character '-' is used to separate different DMI fields here).
+ * This will help the user space to load the device-specific Use Case Manager
+ * (UCM) configurations for the card.
+ *
+ * Possible card long names may be:
+ * DellInc.-XPS139343-01-0310JH
+ * ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA
+ * Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX
+ *
+ * This function also supports flavoring the card longname to provide
+ * the extra differentiation, like "vendor-product-version-board-flavor".
+ *
+ * We only keep number and alphabet characters and a few separator characters
+ * in the card long name since UCM in the user space uses the card long names
+ * as card configuration directory names and AudoConf cannot support special
+ * charactors like SPACE.
+ *
+ * Returns 0 on success, otherwise a negative error code.
+ */
+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 */
+       vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
+       if (!vendor) {
+               dev_warn(card->dev, "ASoC: no DMI vendor name!\n");
+               return 0;
+       }
+
+       snprintf(card->dmi_longname, sizeof(card->snd_card->longname),
+                        "%s", vendor);
+       cleanup_dmi_name(card->dmi_longname);
+
+       product = dmi_get_system_info(DMI_PRODUCT_NAME);
+       if (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);
+
+               /* 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) {
+                       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);
+               }
+       }
+
+       board = dmi_get_system_info(DMI_BOARD_NAME);
+       if (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);
+       } else if (!product) {
+               /* fall back to using legacy name */
+               dev_warn(card->dev, "ASoC: no DMI board/product name!\n");
+               return 0;
+       }
+
+       /* 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);
+       }
+
+       /* set the card long name */
+       card->long_name = card->dmi_longname;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_set_dmi_name);
+
 static int snd_soc_instantiate_card(struct snd_soc_card *card)
 {
        struct snd_soc_codec *codec;
@@ -2976,6 +3138,8 @@ static int snd_soc_component_initialize(struct snd_soc_component *component,
        component->remove = component->driver->remove;
        component->suspend = component->driver->suspend;
        component->resume = component->driver->resume;
+       component->pcm_new = component->driver->pcm_new;
+       component->pcm_free= component->driver->pcm_free;
 
        dapm = &component->dapm;
        dapm->dev = dev;
@@ -3158,6 +3322,21 @@ static void snd_soc_platform_drv_remove(struct snd_soc_component *component)
        platform->driver->remove(platform);
 }
 
+static int snd_soc_platform_drv_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+       struct snd_soc_platform *platform = rtd->platform;
+
+       return platform->driver->pcm_new(rtd);
+}
+
+static void snd_soc_platform_drv_pcm_free(struct snd_pcm *pcm)
+{
+       struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+       struct snd_soc_platform *platform = rtd->platform;
+
+       platform->driver->pcm_free(pcm);
+}
+
 /**
  * snd_soc_add_platform - Add a platform to the ASoC core
  * @dev: The parent device for the platform
@@ -3181,6 +3360,10 @@ int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
                platform->component.probe = snd_soc_platform_drv_probe;
        if (platform_drv->remove)
                platform->component.remove = snd_soc_platform_drv_remove;
+       if (platform_drv->pcm_new)
+               platform->component.pcm_new = snd_soc_platform_drv_pcm_new;
+       if (platform_drv->pcm_free)
+               platform->component.pcm_free = snd_soc_platform_drv_pcm_free;
 
 #ifdef CONFIG_DEBUG_FS
        platform->component.debugfs_prefix = "platform";