[PATCH v10 9/9] ASoC: add generic dt-card support

From: Jean-Francois Moine
Date: Tue Jan 20 2015 - 14:44:47 EST


To create simple cards, the syntax of the simple-card does not follow
the common binding of the device graphs
(Documentation/devicetree/bindings/graph.txt).

dt-card devices are created by audio controllers with themselves as the
root of the graph. The sound card is created according to the parameters
found in the tree.

Signed-off-by: Jean-Francois Moine <moinejf@xxxxxxx>
---
sound/soc/generic/Kconfig | 2 +
sound/soc/generic/Makefile | 2 +
sound/soc/generic/dt-card.c | 275 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 279 insertions(+)
create mode 100644 sound/soc/generic/dt-card.c

diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig
index 610f612..9c5e1e2 100644
--- a/sound/soc/generic/Kconfig
+++ b/sound/soc/generic/Kconfig
@@ -2,3 +2,5 @@ config SND_SIMPLE_CARD
tristate "ASoC Simple sound card support"
help
This option enables generic simple sound card support
+config SND_DT_CARD
+ tristate
diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile
index 9c3b246..56834a9 100644
--- a/sound/soc/generic/Makefile
+++ b/sound/soc/generic/Makefile
@@ -1,3 +1,5 @@
snd-soc-simple-card-objs := simple-card.o
+snd-soc-dt-card-objs := dt-card.o

obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o
+obj-$(CONFIG_SND_DT_CARD) += snd-soc-dt-card.o
diff --git a/sound/soc/generic/dt-card.c b/sound/soc/generic/dt-card.c
new file mode 100644
index 0000000..6a5de2f
--- /dev/null
+++ b/sound/soc/generic/dt-card.c
@@ -0,0 +1,275 @@
+/*
+ * ALSA SoC DT based sound card support
+ *
+ * Copyright (C) 2015 Jean-Francois Moine
+ *
+ * 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/module.h>
+#include <sound/soc.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+/* check if a node is an audio port */
+static int asoc_dt_card_is_audio_port(struct device_node *of_port)
+{
+ const char *name;
+ int ret;
+
+ if (!of_port->name ||
+ of_node_cmp(of_port->name, "port") != 0)
+ return 0;
+ ret = of_property_read_string(of_port,
+ "port-type",
+ &name);
+ if (!ret &&
+ (strcmp(name, "i2s") == 0 ||
+ strcmp(name, "spdif") == 0))
+ return 1;
+ return 0;
+}
+
+/*
+ * Get the DAI number from the DT by counting the audio ports
+ * of the remote device node (codec).
+ */
+static int asoc_dt_card_get_dai_number(struct device_node *of_codec,
+ struct device_node *of_remote_endpoint)
+{
+ struct device_node *of_port, *of_endpoint;
+ int ndai;
+
+ ndai = 0;
+ for_each_child_of_node(of_codec, of_port) {
+ if (!asoc_dt_card_is_audio_port(of_port))
+ continue;
+ for_each_child_of_node(of_port, of_endpoint) {
+ if (!of_endpoint->name ||
+ of_node_cmp(of_endpoint->name, "endpoint") != 0)
+ continue;
+ if (of_endpoint == of_remote_endpoint) {
+ of_node_put(of_port);
+ of_node_put(of_endpoint);
+ return ndai;
+ }
+ }
+ ndai++;
+ }
+ return 0; /* should never be reached */
+}
+
+/*
+ * Parse a graph of audio ports
+ * @dev: Card device
+ * @of_cpu: Device node of the audio controller
+ * @card: Card definition
+ *
+ * Builds the DAI links of the card from the DT graph of audio ports
+ * starting from the audio controller.
+ * It does not handle the port groups.
+ * The CODEC device nodes in the DAI links must be dereferenced by the caller.
+ *
+ * Returns the number of DAI links or (< 0) on error
+ */
+static int asoc_dt_card_of_parse_graph(struct device *dev,
+ struct device_node *of_cpu,
+ struct snd_soc_card *card)
+{
+ struct device_node *of_codec, *of_port, *of_endpoint,
+ *of_remote_endpoint;
+ struct snd_soc_dai_link *link;
+ struct snd_soc_dai_link_component *component;
+ struct of_phandle_args args, args2;
+ int ret, ilink, icodec, nlinks, ncodecs;
+
+ /* count the number of DAI links */
+ nlinks = 0;
+ for_each_child_of_node(of_cpu, of_port) {
+ if (asoc_dt_card_is_audio_port(of_port))
+ nlinks++;
+ }
+
+ /* allocate the DAI link array */
+ link = devm_kzalloc(dev, sizeof(*link) * nlinks, GFP_KERNEL);
+ if (!link)
+ return -ENOMEM;
+ card->dai_link = link;
+
+ /* build the DAI links */
+ ilink = 0;
+ args.np = of_cpu;
+ args.args_count = 1;
+ for_each_child_of_node(of_cpu, of_port) {
+ if (!asoc_dt_card_is_audio_port(of_port))
+ continue;
+
+ link->platform_of_node =
+ link->cpu_of_node = of_cpu;
+ args.args[0] = ilink;
+ ret = snd_soc_get_dai_name(&args, &link->cpu_dai_name);
+ if (ret) {
+ dev_err(dev, "no CPU DAI name for link %d!\n",
+ ilink);
+ continue;
+ }
+
+ /* count the number of codecs of this DAI link */
+ ncodecs = 0;
+ for_each_child_of_node(of_port, of_endpoint) {
+ if (of_parse_phandle(of_endpoint,
+ "remote-endpoint", 0))
+ ncodecs++;
+ }
+ if (ncodecs == 0)
+ continue;
+ component = devm_kzalloc(dev,
+ sizeof(*component) * ncodecs,
+ GFP_KERNEL);
+ if (!component)
+ return -ENOMEM;
+ link->codecs = component;
+
+ icodec = 0;
+ args2.args_count = 1;
+ for_each_child_of_node(of_port, of_endpoint) {
+ of_remote_endpoint = of_parse_phandle(of_endpoint,
+ "remote-endpoint", 0);
+ if (!of_remote_endpoint)
+ continue;
+ component->of_node = of_codec =
+ of_remote_endpoint->parent->parent;
+ args2.np = of_codec;
+ args2.args[0] = asoc_dt_card_get_dai_number(of_codec,
+ of_remote_endpoint);
+ ret = snd_soc_get_dai_name(&args2,
+ &component->dai_name);
+ if (ret) {
+ if (ret == -EPROBE_DEFER) {
+ card->num_links = ilink + 1;
+ link->num_codecs = icodec;
+ return ret;
+ }
+ dev_err(dev,
+ "no CODEC DAI name for link %d\n",
+ ilink);
+ continue;
+ }
+ of_node_get(of_codec);
+
+ icodec++;
+ if (icodec >= ncodecs)
+ break;
+ component++;
+ }
+ if (icodec == 0)
+ continue;
+ link->num_codecs = icodec;
+
+ ilink++;
+ if (ilink >= nlinks)
+ break;
+ link++;
+ }
+ card->num_links = ilink;
+
+ return ilink;
+}
+
+static void asoc_dt_card_unref(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct snd_soc_dai_link *link;
+ int nlinks, ncodecs;
+
+ if (card) {
+ for (nlinks = 0, link = card->dai_link;
+ nlinks < card->num_links;
+ nlinks++, link++) {
+ for (ncodecs = 0;
+ ncodecs < link->num_codecs;
+ ncodecs++)
+ of_node_put(card->dai_link->codecs[ncodecs].of_node);
+ }
+ }
+}
+
+/*
+ * The platform data contains the pointer to the device node
+ * which starts the description of the graph of the audio ports,
+ * This device node is usually the audio controller.
+ */
+static int asoc_dt_card_probe(struct platform_device *pdev)
+{
+ struct device_node **p_np = pdev->dev.platform_data;
+ struct device_node *of_cpu = *p_np;
+ struct snd_soc_card *card;
+ struct snd_soc_dai_link *link;
+ char *name;
+ int ret, i;
+
+ card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+ ret = asoc_dt_card_of_parse_graph(&pdev->dev, of_cpu, card);
+ if (ret < 0)
+ goto err;
+
+ /* fill the remaining values of the card */
+ card->owner = THIS_MODULE;
+ card->dev = &pdev->dev;
+ card->name = "DT-card";
+ for (i = 0, link = card->dai_link;
+ i < card->num_links;
+ i++, link++) {
+ name = devm_kzalloc(&pdev->dev,
+ strlen(link->cpu_dai_name) +
+ strlen(link->codecs[0].dai_name) +
+ 2,
+ GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ sprintf(name, "%s-%s", link->cpu_dai_name,
+ link->codecs[0].dai_name);
+ link->name = link->stream_name = name;
+ }
+
+ card->dai_link->dai_fmt =
+ snd_soc_of_parse_daifmt(of_cpu, "dt-audio-card,",
+ NULL, NULL) &
+ ~SND_SOC_DAIFMT_MASTER_MASK;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret >= 0)
+ return ret;
+
+err:
+ asoc_dt_card_unref(pdev);
+ return ret;
+}
+
+static int asoc_dt_card_remove(struct platform_device *pdev)
+{
+ asoc_dt_card_unref(pdev);
+ snd_soc_unregister_card(platform_get_drvdata(pdev));
+ return 0;
+}
+
+static struct platform_driver asoc_dt_card = {
+ .driver = {
+ .name = "asoc-dt-card",
+ },
+ .probe = asoc_dt_card_probe,
+ .remove = asoc_dt_card_remove,
+};
+
+module_platform_driver(asoc_dt_card);
+
+MODULE_ALIAS("platform:asoc-dt-card");
+MODULE_DESCRIPTION("ASoC DT Sound Card");
+MODULE_AUTHOR("Jean-Francois Moine <moinejf@xxxxxxx>");
+MODULE_LICENSE("GPL");
--
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/