aboutsummaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2017-05-24 18:45:17 +0100
committerMark Brown <broonie@kernel.org>2017-05-24 18:45:17 +0100
commite82f1f7907db860febf6e55bf9702bba34f504c7 (patch)
tree65464de07c3080f410f0aff2bdfcada6f4fa5a02 /sound
parent0c343a35bfecdf26c7041781815f3b639a45d93a (diff)
parent73b17f1a65c881fcf97109d77056006da2d40152 (diff)
Merge branch 'topic/of-graph' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into asoc-hdmi
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/generic/Kconfig8
-rw-r--r--sound/soc/generic/Makefile2
-rw-r--r--sound/soc/generic/audio-graph-card.c310
-rw-r--r--sound/soc/generic/simple-card-utils.c82
-rw-r--r--sound/soc/soc-core.c47
5 files changed, 441 insertions, 8 deletions
diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig
index d023959b8cd6..121a48e8bb7d 100644
--- a/sound/soc/generic/Kconfig
+++ b/sound/soc/generic/Kconfig
@@ -14,3 +14,11 @@ config SND_SIMPLE_SCU_CARD
help
This option enables generic simple SCU sound card support.
It supports DPCM of multi CPU single Codec system.
+
+config SND_AUDIO_GRAPH_CARD
+ tristate "ASoC Audio Graph sound card support"
+ depends on OF
+ select SND_SIMPLE_CARD_UTILS
+ help
+ This option enables generic simple simple sound card support
+ with OF-graph DT bindings.
diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile
index ee750f3023ba..670068f257b9 100644
--- a/sound/soc/generic/Makefile
+++ b/sound/soc/generic/Makefile
@@ -1,7 +1,9 @@
snd-soc-simple-card-utils-objs := simple-card-utils.o
snd-soc-simple-card-objs := simple-card.o
snd-soc-simple-scu-card-objs := simple-scu-card.o
+snd-soc-audio-graph-card-objs := audio-graph-card.o
obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o
obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o
obj-$(CONFIG_SND_SIMPLE_SCU_CARD) += snd-soc-simple-scu-card.o
+obj-$(CONFIG_SND_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o
diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c
new file mode 100644
index 000000000000..93c167a91d2d
--- /dev/null
+++ b/sound/soc/generic/audio-graph-card.c
@@ -0,0 +1,310 @@
+/*
+ * ASoC audio graph sound card support
+ *
+ * Copyright (C) 2016 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * based on ${LINUX}/sound/soc/generic/simple-card.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <sound/jack.h>
+#include <sound/simple_card_utils.h>
+
+struct graph_card_data {
+ struct snd_soc_card snd_card;
+ struct graph_dai_props {
+ struct asoc_simple_dai cpu_dai;
+ struct asoc_simple_dai codec_dai;
+ } *dai_props;
+ struct snd_soc_dai_link *dai_link;
+};
+
+#define graph_priv_to_card(priv) (&(priv)->snd_card)
+#define graph_priv_to_props(priv, i) ((priv)->dai_props + (i))
+#define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev)
+#define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i))
+
+static int asoc_graph_card_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num);
+ int ret;
+
+ ret = clk_prepare_enable(dai_props->cpu_dai.clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dai_props->codec_dai.clk);
+ if (ret)
+ clk_disable_unprepare(dai_props->cpu_dai.clk);
+
+ return ret;
+}
+
+static void asoc_graph_card_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num);
+
+ clk_disable_unprepare(dai_props->cpu_dai.clk);
+
+ clk_disable_unprepare(dai_props->codec_dai.clk);
+}
+
+static struct snd_soc_ops asoc_graph_card_ops = {
+ .startup = asoc_graph_card_startup,
+ .shutdown = asoc_graph_card_shutdown,
+};
+
+static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *codec = rtd->codec_dai;
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
+ struct graph_dai_props *dai_props =
+ graph_priv_to_props(priv, rtd->num);
+ int ret;
+
+ ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai);
+ if (ret < 0)
+ return ret;
+
+ ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int asoc_graph_card_dai_link_of(struct device_node *cpu_port,
+ struct graph_card_data *priv,
+ int idx)
+{
+ struct device *dev = graph_priv_to_dev(priv);
+ struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx);
+ struct graph_dai_props *dai_props = graph_priv_to_props(priv, idx);
+ struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai;
+ struct asoc_simple_dai *codec_dai = &dai_props->codec_dai;
+ struct snd_soc_card *card = graph_priv_to_card(priv);
+ struct device_node *cpu_ep = of_get_next_child(cpu_port, NULL);
+ struct device_node *codec_ep = of_graph_get_remote_endpoint(cpu_ep);
+ struct device_node *rcpu_ep = of_graph_get_remote_endpoint(codec_ep);
+ int ret;
+
+ if (rcpu_ep != cpu_ep) {
+ dev_err(dev, "remote-endpoint mismatch (%s/%s/%s)\n",
+ cpu_ep->name, codec_ep->name, rcpu_ep->name);
+ ret = -EINVAL;
+ goto dai_link_of_err;
+ }
+
+ ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep,
+ NULL, &dai_link->dai_fmt);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ /*
+ * we need to consider "mclk-fs" around here
+ * see simple-card
+ */
+
+ ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = snd_soc_of_parse_tdm_slot(cpu_ep,
+ &cpu_dai->tx_slot_mask,
+ &cpu_dai->rx_slot_mask,
+ &cpu_dai->slots,
+ &cpu_dai->slot_width);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = snd_soc_of_parse_tdm_slot(codec_ep,
+ &codec_dai->tx_slot_mask,
+ &codec_dai->rx_slot_mask,
+ &codec_dai->slots,
+ &codec_dai->slot_width);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = asoc_simple_card_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = asoc_simple_card_parse_clk_codec(dev, codec_ep, dai_link, codec_dai);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = asoc_simple_card_canonicalize_dailink(dai_link);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ ret = asoc_simple_card_set_dailink_name(dev, dai_link,
+ "%s-%s",
+ dai_link->cpu_dai_name,
+ dai_link->codec_dai_name);
+ if (ret < 0)
+ goto dai_link_of_err;
+
+ dai_link->ops = &asoc_graph_card_ops;
+ dai_link->init = asoc_graph_card_dai_init;
+
+ dev_dbg(dev, "\tname : %s\n", dai_link->stream_name);
+ dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt);
+ dev_dbg(dev, "\tcpu : %s / %d\n",
+ dai_link->cpu_dai_name,
+ cpu_dai->sysclk);
+ dev_dbg(dev, "\tcodec : %s / %d\n",
+ dai_link->codec_dai_name,
+ codec_dai->sysclk);
+
+ asoc_simple_card_canonicalize_cpu(dai_link,
+ card->num_links == 1);
+
+dai_link_of_err:
+ of_node_put(cpu_ep);
+ of_node_put(rcpu_ep);
+ of_node_put(codec_ep);
+
+ return ret;
+}
+
+static int asoc_graph_card_parse_of(struct graph_card_data *priv)
+{
+ struct of_phandle_iterator it;
+ struct device *dev = graph_priv_to_dev(priv);
+ struct snd_soc_card *card = graph_priv_to_card(priv);
+ struct device_node *node = dev->of_node;
+ int rc, idx = 0;
+ int ret;
+
+ /*
+ * we need to consider "widgets", "routing", "mclk-fs" around here
+ * see simple-card
+ */
+
+ of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
+ ret = asoc_graph_card_dai_link_of(it.node, priv, idx++);
+ of_node_put(it.node);
+ if (ret < 0)
+ return ret;
+ }
+
+ return asoc_simple_card_parse_card_name(card, NULL);
+}
+
+static int asoc_graph_get_dais_count(struct device *dev)
+{
+ struct of_phandle_iterator it;
+ struct device_node *node = dev->of_node;
+ int count = 0;
+ int rc;
+
+ of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
+ count++;
+ of_node_put(it.node);
+ }
+
+ return count;
+}
+
+static int asoc_graph_card_probe(struct platform_device *pdev)
+{
+ struct graph_card_data *priv;
+ struct snd_soc_dai_link *dai_link;
+ struct graph_dai_props *dai_props;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_card *card;
+ int num, ret;
+
+ /* Allocate the private data and the DAI link array */
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ num = asoc_graph_get_dais_count(dev);
+ if (num == 0)
+ return -EINVAL;
+
+ dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL);
+ dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL);
+ if (!dai_props || !dai_link)
+ return -ENOMEM;
+
+ priv->dai_props = dai_props;
+ priv->dai_link = dai_link;
+
+ /* Init snd_soc_card */
+ card = graph_priv_to_card(priv);
+ card->owner = THIS_MODULE;
+ card->dev = dev;
+ card->dai_link = dai_link;
+ card->num_links = num;
+
+ ret = asoc_graph_card_parse_of(priv);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "parse error %d\n", ret);
+ goto err;
+ }
+
+ snd_soc_card_set_drvdata(card, priv);
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret < 0)
+ goto err;
+
+ return 0;
+err:
+ asoc_simple_card_clean_reference(card);
+
+ return ret;
+}
+
+static int asoc_graph_card_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ return asoc_simple_card_clean_reference(card);
+}
+
+static const struct of_device_id asoc_graph_of_match[] = {
+ { .compatible = "audio-graph-card", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, asoc_graph_of_match);
+
+static struct platform_driver asoc_graph_card = {
+ .driver = {
+ .name = "asoc-audio-graph-card",
+ .of_match_table = asoc_graph_of_match,
+ },
+ .probe = asoc_graph_card_probe,
+ .remove = asoc_graph_card_remove,
+};
+module_platform_driver(asoc_graph_card);
+
+MODULE_ALIAS("platform:asoc-audio-graph-card");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASoC Audio Graph Sound Card");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c
index 343b291fc372..fe726e83d0bd 100644
--- a/sound/soc/generic/simple-card-utils.c
+++ b/sound/soc/generic/simple-card-utils.c
@@ -10,6 +10,7 @@
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_graph.h>
#include <sound/simple_card_utils.h>
int asoc_simple_card_parse_daifmt(struct device *dev,
@@ -81,15 +82,21 @@ EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name);
int asoc_simple_card_parse_card_name(struct snd_soc_card *card,
char *prefix)
{
- char prop[128];
int ret;
- snprintf(prop, sizeof(prop), "%sname", prefix);
+ if (!prefix)
+ prefix = "";
/* Parse the card name from DT */
- ret = snd_soc_of_parse_card_name(card, prop);
- if (ret < 0)
- return ret;
+ ret = snd_soc_of_parse_card_name(card, "label");
+ if (ret < 0) {
+ char prop[128];
+
+ snprintf(prop, sizeof(prop), "%sname", prefix);
+ ret = snd_soc_of_parse_card_name(card, prop);
+ if (ret < 0)
+ return ret;
+ }
if (!card->name && card->dai_link)
card->name = card->dai_link->name;
@@ -165,6 +172,71 @@ int asoc_simple_card_parse_dai(struct device_node *node,
}
EXPORT_SYMBOL_GPL(asoc_simple_card_parse_dai);
+static int asoc_simple_card_get_dai_id(struct device_node *ep)
+{
+ struct device_node *node;
+ struct device_node *endpoint;
+ int i, id;
+ int ret;
+
+ ret = snd_soc_get_dai_id(ep);
+ if (ret != -ENOTSUPP)
+ return ret;
+
+ node = of_graph_get_port_parent(ep);
+
+ /*
+ * Non HDMI sound case, counting port/endpoint on its DT
+ * is enough. Let's count it.
+ */
+ i = 0;
+ id = -1;
+ for_each_endpoint_of_node(node, endpoint) {
+ if (endpoint == ep)
+ id = i;
+ i++;
+ }
+ if (id < 0)
+ return -ENODEV;
+
+ return id;
+}
+
+int asoc_simple_card_parse_graph_dai(struct device_node *ep,
+ struct device_node **dai_of_node,
+ const char **dai_name)
+{
+ struct device_node *node;
+ struct of_phandle_args args;
+ int ret;
+
+ if (!ep)
+ return 0;
+ if (!dai_name)
+ return 0;
+
+ /*
+ * of_graph_get_port_parent() will call
+ * of_node_put(). So, call of_node_get() here
+ */
+ of_node_get(ep);
+ node = of_graph_get_port_parent(ep);
+
+ /* Get dai->name */
+ args.np = node;
+ args.args[0] = asoc_simple_card_get_dai_id(ep);
+ args.args_count = (of_graph_get_endpoint_count(node) > 1);
+
+ ret = snd_soc_get_dai_name(&args, dai_name);
+ if (ret < 0)
+ return ret;
+
+ *dai_of_node = node;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asoc_simple_card_parse_graph_dai);
+
int asoc_simple_card_init_dai(struct snd_soc_dai *dai,
struct asoc_simple_dai *simple_dai)
{
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index aae099c0e502..9ba183781017 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -34,6 +34,7 @@
#include <linux/ctype.h>
#include <linux/slab.h>
#include <linux/of.h>
+#include <linux/of_graph.h>
#include <linux/dmi.h>
#include <sound/core.h>
#include <sound/jack.h>
@@ -3960,11 +3961,15 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np,
prefix = "";
/*
- * check "[prefix]format = xxx"
+ * check "dai-format = xxx"
+ * or "[prefix]format = xxx"
* SND_SOC_DAIFMT_FORMAT_MASK area
*/
- snprintf(prop, sizeof(prop), "%sformat", prefix);
- ret = of_property_read_string(np, prop, &str);
+ ret = of_property_read_string(np, "dai-format", &str);
+ if (ret < 0) {
+ snprintf(prop, sizeof(prop), "%sformat", prefix);
+ ret = of_property_read_string(np, prop, &str);
+ }
if (ret == 0) {
for (i = 0; i < ARRAY_SIZE(of_fmt_table); i++) {
if (strcmp(str, of_fmt_table[i].name) == 0) {
@@ -4044,6 +4049,42 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np,
}
EXPORT_SYMBOL_GPL(snd_soc_of_parse_daifmt);
+int snd_soc_get_dai_id(struct device_node *ep)
+{
+ struct snd_soc_component *pos;
+ struct device_node *node;
+ int ret;
+
+ node = of_graph_get_port_parent(ep);
+
+ /*
+ * For example HDMI case, HDMI has video/sound port,
+ * but ALSA SoC needs sound port number only.
+ * Thus counting HDMI DT port/endpoint doesn't work.
+ * Then, it should have .of_xlate_dai_id
+ */
+ ret = -ENOTSUPP;
+ mutex_lock(&client_mutex);
+ list_for_each_entry(pos, &component_list, list) {
+ struct device_node *component_of_node = pos->dev->of_node;
+
+ if (!component_of_node && pos->dev->parent)
+ component_of_node = pos->dev->parent->of_node;
+
+ if (component_of_node != node)
+ continue;
+
+ if (pos->driver->of_xlate_dai_id)
+ ret = pos->driver->of_xlate_dai_id(pos, ep);
+
+ break;
+ }
+ mutex_unlock(&client_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_dai_id);
+
int snd_soc_get_dai_name(struct of_phandle_args *args,
const char **dai_name)
{