This patch adds a generic way to create audio cards from a graph of ports
defined in a DT.

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

Signed-off-by: Jean-Francois Moine <moin...@free.fr>
---
 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 <moin...@free.fr>");
+MODULE_LICENSE("GPL");
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to