Merge branch 'stable/for-linus-fixes-3.3' of git://git.kernel.org/pub/scm/linux/kerne...
[firefly-linux-kernel-4.4.55.git] / sound / soc / soc-core.c
index a25fa63ce9a27501a4f2d4a6334911076200532e..3986520b4677244b9bd592b85f2350889bda5cdf 100644 (file)
@@ -32,6 +32,7 @@
 #include <linux/platform_device.h>
 #include <linux/ctype.h>
 #include <linux/slab.h>
+#include <linux/of.h>
 #include <sound/ac97_codec.h>
 #include <sound/core.h>
 #include <sound/jack.h>
@@ -58,8 +59,6 @@ static LIST_HEAD(dai_list);
 static LIST_HEAD(platform_list);
 static LIST_HEAD(codec_list);
 
-int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
-
 /*
  * This is a timeout to do a DAPM powerdown after a stream is closed().
  * It can be used to eliminate pops between different playback streams, e.g.
@@ -170,8 +169,7 @@ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf,
 static ssize_t codec_reg_show(struct device *dev,
        struct device_attribute *attr, char *buf)
 {
-       struct snd_soc_pcm_runtime *rtd =
-                       container_of(dev, struct snd_soc_pcm_runtime, dev);
+       struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
 
        return soc_codec_reg_show(rtd->codec, buf, PAGE_SIZE, 0);
 }
@@ -181,8 +179,7 @@ static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
 static ssize_t pmdown_time_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
 {
-       struct snd_soc_pcm_runtime *rtd =
-                       container_of(dev, struct snd_soc_pcm_runtime, dev);
+       struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
 
        return sprintf(buf, "%ld\n", rtd->pmdown_time);
 }
@@ -191,8 +188,7 @@ static ssize_t pmdown_time_set(struct device *dev,
                               struct device_attribute *attr,
                               const char *buf, size_t count)
 {
-       struct snd_soc_pcm_runtime *rtd =
-                       container_of(dev, struct snd_soc_pcm_runtime, dev);
+       struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
        int ret;
 
        ret = strict_strtol(buf, 10, &rtd->pmdown_time);
@@ -412,7 +408,7 @@ static void soc_init_card_debugfs(struct snd_soc_card *card)
                                                     snd_soc_debugfs_root);
        if (!card->debugfs_card_root) {
                dev_warn(card->dev,
-                        "ASoC: Failed to create codec debugfs directory\n");
+                        "ASoC: Failed to create card debugfs directory\n");
                return;
        }
 
@@ -572,7 +568,7 @@ int snd_soc_suspend(struct device *dev)
                        switch (codec->dapm.bias_level) {
                        case SND_SOC_BIAS_STANDBY:
                        case SND_SOC_BIAS_OFF:
-                               codec->driver->suspend(codec, PMSG_SUSPEND);
+                               codec->driver->suspend(codec);
                                codec->suspended = 1;
                                codec->cache_sync = 1;
                                break;
@@ -741,7 +737,7 @@ EXPORT_SYMBOL_GPL(snd_soc_resume);
 #define snd_soc_resume NULL
 #endif
 
-static struct snd_soc_dai_ops null_dai_ops = {
+static const struct snd_soc_dai_ops null_dai_ops = {
 };
 
 static int soc_bind_dai_link(struct snd_soc_card *card, int num)
@@ -763,10 +759,16 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num)
        }
        /* no, then find CPU DAI from registered DAIs*/
        list_for_each_entry(cpu_dai, &dai_list, list) {
-               if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {
-                       rtd->cpu_dai = cpu_dai;
-                       goto find_codec;
+               if (dai_link->cpu_dai_of_node) {
+                       if (cpu_dai->dev->of_node != dai_link->cpu_dai_of_node)
+                               continue;
+               } else {
+                       if (strcmp(cpu_dai->name, dai_link->cpu_dai_name))
+                               continue;
                }
+
+               rtd->cpu_dai = cpu_dai;
+               goto find_codec;
        }
        dev_dbg(card->dev, "CPU DAI %s not registered\n",
                        dai_link->cpu_dai_name);
@@ -779,22 +781,33 @@ find_codec:
 
        /* no, then find CODEC from registered CODECs*/
        list_for_each_entry(codec, &codec_list, list) {
-               if (!strcmp(codec->name, dai_link->codec_name)) {
-                       rtd->codec = codec;
-
-                       /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
-                       list_for_each_entry(codec_dai, &dai_list, list) {
-                               if (codec->dev == codec_dai->dev &&
-                                               !strcmp(codec_dai->name, dai_link->codec_dai_name)) {
-                                       rtd->codec_dai = codec_dai;
-                                       goto find_platform;
-                               }
-                       }
-                       dev_dbg(card->dev, "CODEC DAI %s not registered\n",
-                                       dai_link->codec_dai_name);
+               if (dai_link->codec_of_node) {
+                       if (codec->dev->of_node != dai_link->codec_of_node)
+                               continue;
+               } else {
+                       if (strcmp(codec->name, dai_link->codec_name))
+                               continue;
+               }
+
+               rtd->codec = codec;
 
-                       goto find_platform;
+               /*
+                * CODEC found, so find CODEC DAI from registered DAIs from
+                * this CODEC
+                */
+               list_for_each_entry(codec_dai, &dai_list, list) {
+                       if (codec->dev == codec_dai->dev &&
+                               !strcmp(codec_dai->name,
+                                       dai_link->codec_dai_name)) {
+
+                               rtd->codec_dai = codec_dai;
+                               goto find_platform;
+                       }
                }
+               dev_dbg(card->dev, "CODEC DAI %s not registered\n",
+                               dai_link->codec_dai_name);
+
+               goto find_platform;
        }
        dev_dbg(card->dev, "CODEC %s not registered\n",
                        dai_link->codec_name);
@@ -806,15 +819,22 @@ find_platform:
 
        /* if there's no platform we match on the empty platform */
        platform_name = dai_link->platform_name;
-       if (!platform_name)
+       if (!platform_name && !dai_link->platform_of_node)
                platform_name = "snd-soc-dummy";
 
        /* no, then find one from the set of registered platforms */
        list_for_each_entry(platform, &platform_list, list) {
-               if (!strcmp(platform->name, platform_name)) {
-                       rtd->platform = platform;
-                       goto out;
+               if (dai_link->platform_of_node) {
+                       if (platform->dev->of_node !=
+                           dai_link->platform_of_node)
+                               continue;
+               } else {
+                       if (strcmp(platform->name, platform_name))
+                               continue;
                }
+
+               rtd->platform = platform;
+               goto out;
        }
 
        dev_dbg(card->dev, "platform %s not registered\n",
@@ -861,9 +881,9 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num, int order)
 
        /* unregister the rtd device */
        if (rtd->dev_registered) {
-               device_remove_file(&rtd->dev, &dev_attr_pmdown_time);
-               device_remove_file(&rtd->dev, &dev_attr_codec_reg);
-               device_unregister(&rtd->dev);
+               device_remove_file(rtd->dev, &dev_attr_pmdown_time);
+               device_remove_file(rtd->dev, &dev_attr_codec_reg);
+               device_unregister(rtd->dev);
                rtd->dev_registered = 0;
        }
 
@@ -1038,7 +1058,10 @@ err_probe:
        return ret;
 }
 
-static void rtd_release(struct device *dev) {}
+static void rtd_release(struct device *dev)
+{
+       kfree(dev);
+}
 
 static int soc_post_component_init(struct snd_soc_card *card,
                                   struct snd_soc_codec *codec,
@@ -1081,11 +1104,17 @@ static int soc_post_component_init(struct snd_soc_card *card,
 
        /* register the rtd device */
        rtd->codec = codec;
-       rtd->dev.parent = card->dev;
-       rtd->dev.release = rtd_release;
-       rtd->dev.init_name = name;
+
+       rtd->dev = kzalloc(sizeof(struct device), GFP_KERNEL);
+       if (!rtd->dev)
+               return -ENOMEM;
+       device_initialize(rtd->dev);
+       rtd->dev->parent = card->dev;
+       rtd->dev->release = rtd_release;
+       rtd->dev->init_name = name;
+       dev_set_drvdata(rtd->dev, rtd);
        mutex_init(&rtd->pcm_mutex);
-       ret = device_register(&rtd->dev);
+       ret = device_add(rtd->dev);
        if (ret < 0) {
                dev_err(card->dev,
                        "asoc: failed to register runtime device: %d\n", ret);
@@ -1094,14 +1123,14 @@ static int soc_post_component_init(struct snd_soc_card *card,
        rtd->dev_registered = 1;
 
        /* add DAPM sysfs entries for this codec */
-       ret = snd_soc_dapm_sys_add(&rtd->dev);
+       ret = snd_soc_dapm_sys_add(rtd->dev);
        if (ret < 0)
                dev_err(codec->dev,
                        "asoc: failed to add codec dapm sysfs entries: %d\n",
                        ret);
 
        /* add codec sysfs entries */
-       ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
+       ret = device_create_file(rtd->dev, &dev_attr_codec_reg);
        if (ret < 0)
                dev_err(codec->dev,
                        "asoc: failed to add codec sysfs files: %d\n", ret);
@@ -1190,7 +1219,7 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)
        if (ret)
                return ret;
 
-       ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);
+       ret = device_create_file(rtd->dev, &dev_attr_pmdown_time);
        if (ret < 0)
                printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");
 
@@ -1288,8 +1317,8 @@ static void soc_remove_aux_dev(struct snd_soc_card *card, int num)
 
        /* unregister the rtd device */
        if (rtd->dev_registered) {
-               device_remove_file(&rtd->dev, &dev_attr_codec_reg);
-               device_unregister(&rtd->dev);
+               device_remove_file(rtd->dev, &dev_attr_codec_reg);
+               device_del(rtd->dev);
                rtd->dev_registered = 0;
        }
 
@@ -1488,6 +1517,10 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
 
        snd_soc_dapm_new_widgets(&card->dapm);
 
+       if (card->fully_routed)
+               list_for_each_entry(codec, &card->codec_dev_list, card_list)
+                       snd_soc_dapm_auto_nc_codec_pins(codec);
+
        ret = snd_card_register(card->snd_card);
        if (ret < 0) {
                printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
@@ -2818,6 +2851,40 @@ int snd_soc_register_card(struct snd_soc_card *card)
        if (!card->name || !card->dev)
                return -EINVAL;
 
+       for (i = 0; i < card->num_links; i++) {
+               struct snd_soc_dai_link *link = &card->dai_link[i];
+
+               /*
+                * Codec must be specified by 1 of name or OF node,
+                * not both or neither.
+                */
+               if (!!link->codec_name == !!link->codec_of_node) {
+                       dev_err(card->dev,
+                               "Neither/both codec name/of_node are set\n");
+                       return -EINVAL;
+               }
+
+               /*
+                * Platform may be specified by either name or OF node, but
+                * can be left unspecified, and a dummy platform will be used.
+                */
+               if (link->platform_name && link->platform_of_node) {
+                       dev_err(card->dev,
+                               "Both platform name/of_node are set\n");
+                       return -EINVAL;
+               }
+
+               /*
+                * CPU DAI must be specified by 1 of name or OF node,
+                * not both or neither.
+                */
+               if (!!link->cpu_dai_name == !!link->cpu_dai_of_node) {
+                       dev_err(card->dev,
+                               "Neither/both cpu_dai name/of_node are set\n");
+                       return -EINVAL;
+               }
+       }
+
        dev_set_drvdata(card->dev, card);
 
        snd_soc_initialize_card_lists(card);
@@ -3305,6 +3372,87 @@ found:
 }
 EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);
 
+/* Retrieve a card's name from device tree */
+int snd_soc_of_parse_card_name(struct snd_soc_card *card,
+                              const char *propname)
+{
+       struct device_node *np = card->dev->of_node;
+       int ret;
+
+       ret = of_property_read_string_index(np, propname, 0, &card->name);
+       /*
+        * EINVAL means the property does not exist. This is fine providing
+        * card->name was previously set, which is checked later in
+        * snd_soc_register_card.
+        */
+       if (ret < 0 && ret != -EINVAL) {
+               dev_err(card->dev,
+                       "Property '%s' could not be read: %d\n",
+                       propname, ret);
+               return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_of_parse_card_name);
+
+int snd_soc_of_parse_audio_routing(struct snd_soc_card *card,
+                                  const char *propname)
+{
+       struct device_node *np = card->dev->of_node;
+       int num_routes;
+       struct snd_soc_dapm_route *routes;
+       int i, ret;
+
+       num_routes = of_property_count_strings(np, propname);
+       if (num_routes & 1) {
+               dev_err(card->dev,
+                       "Property '%s's length is not even\n",
+                       propname);
+               return -EINVAL;
+       }
+       num_routes /= 2;
+       if (!num_routes) {
+               dev_err(card->dev,
+                       "Property '%s's length is zero\n",
+                       propname);
+               return -EINVAL;
+       }
+
+       routes = devm_kzalloc(card->dev, num_routes * sizeof(*routes),
+                             GFP_KERNEL);
+       if (!routes) {
+               dev_err(card->dev,
+                       "Could not allocate DAPM route table\n");
+               return -EINVAL;
+       }
+
+       for (i = 0; i < num_routes; i++) {
+               ret = of_property_read_string_index(np, propname,
+                       2 * i, &routes[i].sink);
+               if (ret) {
+                       dev_err(card->dev,
+                               "Property '%s' index %d could not be read: %d\n",
+                               propname, 2 * i, ret);
+                       return -EINVAL;
+               }
+               ret = of_property_read_string_index(np, propname,
+                       (2 * i) + 1, &routes[i].source);
+               if (ret) {
+                       dev_err(card->dev,
+                               "Property '%s' index %d could not be read: %d\n",
+                               propname, (2 * i) + 1, ret);
+                       return -EINVAL;
+               }
+       }
+
+       card->num_dapm_routes = num_routes;
+       card->dapm_routes = routes;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_routing);
+
 static int __init snd_soc_init(void)
 {
 #ifdef CONFIG_DEBUG_FS