aboutsummaryrefslogtreecommitdiff
path: root/sound/soc/soc-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/soc-core.c')
-rw-r--r--sound/soc/soc-core.c242
1 files changed, 197 insertions, 45 deletions
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index a25fa63ce9a..b5ecf6d2321 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -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;
+
+ /*
+ * 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)) {
- goto find_platform;
+ 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;
}
@@ -887,6 +907,10 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num, int order)
if (err < 0)
printk(KERN_ERR "asoc: failed to remove %s\n", platform->name);
}
+
+ /* Make sure all DAPM widgets are freed */
+ snd_soc_dapm_free(&platform->dapm);
+
platform->probed = 0;
list_del(&platform->card_list);
module_put(platform->dev->driver->owner);
@@ -1038,7 +1062,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 +1108,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 +1127,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 +1223,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 +1321,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 +1521,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 +2855,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 +3376,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