From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@gmail.com>
Date: Tue, 10 Nov 2020 20:42:44 +0100
Subject: ASOC: sun9i-hdmi-audio: Initial implementation

This implements HDMI audio sound card which is used to enable HDMI audio
on all Allwinner SoCs with DW-HDMI core. First such SoC is A80, but it's
been used on plenty of others, like A64, A83t, H2+, H3, H5, H6, R40 and
V40.

Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
 sound/soc/sunxi/Kconfig            |   8 +
 sound/soc/sunxi/Makefile           |   1 +
 sound/soc/sunxi/sun9i-hdmi-audio.c | 180 ++++++++++
 3 files changed, 189 insertions(+)

diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
index 1f18f016acbb..6a17e49535bd 100644
--- a/sound/soc/sunxi/Kconfig
+++ b/sound/soc/sunxi/Kconfig
@@ -63,6 +63,14 @@ config SND_SUN50I_DMIC
 	  Say Y or M to add support for the DMIC audio block in the Allwinner
 	  H6 and affiliated SoCs.
 
+config SND_SUN9I_HDMI_AUDIO
+	tristate "Allwinner sun9i HDMI Audio Sound Card"
+	depends on OF
+	depends on SND_SUN4I_I2S
+	help
+	  Say Y or M to add support for the HDMI Audio sound card for Allwinner
+	  SoCs with DW-HDMI core.
+
 config SND_SUN8I_ADDA_PR_REGMAP
 	tristate
 	select REGMAP
diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
index 4483fe9c94ef..f9b7daf063fe 100644
--- a/sound/soc/sunxi/Makefile
+++ b/sound/soc/sunxi/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_SND_SUN50I_CODEC_ANALOG) += sun50i-codec-analog.o
 obj-$(CONFIG_SND_SUN8I_CODEC) += sun8i-codec.o
 obj-$(CONFIG_SND_SUN8I_ADDA_PR_REGMAP) += sun8i-adda-pr-regmap.o
 obj-$(CONFIG_SND_SUN50I_DMIC) += sun50i-dmic.o
+obj-$(CONFIG_SND_SUN9I_HDMI_AUDIO) += sun9i-hdmi-audio.o
diff --git a/sound/soc/sunxi/sun9i-hdmi-audio.c b/sound/soc/sunxi/sun9i-hdmi-audio.c
new file mode 100644
index 000000000000..fc8f03d398c0
--- /dev/null
+++ b/sound/soc/sunxi/sun9i-hdmi-audio.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// sun9i hdmi audio sound card
+//
+// Copyright (C) 2021 Jernej Skrabec <jernej.skrabec@gmail.com>
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+static int sun9i_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+	unsigned int mclk;
+
+	mclk = params_rate(params) * 128;
+
+	return snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), 0, mclk,
+				      SND_SOC_CLOCK_OUT);
+}
+
+static const struct snd_soc_ops sun9i_hdmi_audio_ops = {
+	.hw_params = sun9i_hdmi_audio_hw_params,
+};
+
+static int sun9i_hdmi_audio_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	int ret;
+
+	/* TODO: switch to custom api once it's implemented in sun4i-i2s */
+	ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0, 0, 2, 32);
+	if (ret) {
+		dev_err(asoc_rtd_to_cpu(rtd, 0)->dev,
+			"setting tdm link slots failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int sun9i_hdmi_audio_parse_dai(struct device_node *node,
+				      struct snd_soc_dai_link_component *dlc)
+{
+	struct of_phandle_args args;
+	int ret;
+
+	if (!node)
+		return 0;
+
+	ret = of_parse_phandle_with_args(node, "sound-dai",
+					 "#sound-dai-cells", 0, &args);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_get_dai_name(&args, &dlc->dai_name);
+	if (ret < 0) {
+		of_node_put(args.np);
+
+		return ret;
+	}
+
+	dlc->of_node = args.np;
+
+	return 0;
+}
+
+static int sun9i_hdmi_audio_probe(struct platform_device *pdev)
+{
+	struct snd_soc_dai_link_component *dlc;
+	struct device *dev = &pdev->dev;
+	struct snd_soc_dai_link *link;
+	struct snd_soc_card *card;
+	struct device_node *child;
+	int ret;
+
+	card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+	if (!card)
+		return -ENOMEM;
+
+	link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL);
+	if (!link)
+		return -ENOMEM;
+
+	dlc = devm_kzalloc(dev, sizeof(*dlc) * 3, GFP_KERNEL);
+	if (!dlc)
+		return -ENOMEM;
+
+	child = of_get_child_by_name(dev->of_node, "codec");
+	if (!child)
+		return -ENODEV;
+
+	ret = sun9i_hdmi_audio_parse_dai(child, &dlc[1]);
+	of_node_put(child);
+	if (ret)
+		return ret;
+
+	child = of_get_child_by_name(dev->of_node, "cpu");
+	if (!child) {
+		ret = -ENODEV;
+		goto out_err;
+	}
+
+	ret = sun9i_hdmi_audio_parse_dai(child, &dlc[0]);
+	of_node_put(child);
+	if (ret)
+		goto out_err;
+
+	dlc[2].of_node = dlc[0].of_node;
+
+	platform_set_drvdata(pdev, card);
+
+	link->cpus = &dlc[0];
+	link->codecs = &dlc[1];
+	link->platforms = &dlc[2];
+
+	link->num_cpus = 1;
+	link->num_codecs = 1;
+	link->num_platforms = 1;
+
+	link->playback_only = 1;
+
+	link->name = "SUN9I-HDMI";
+	link->stream_name = "SUN9I-HDMI PCM";
+
+	link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_CBS_CFS;
+
+	link->ops = &sun9i_hdmi_audio_ops;
+	link->init = sun9i_hdmi_audio_dai_init;
+
+	card->dai_link = link;
+	card->num_links = 1;
+	card->owner = THIS_MODULE;
+	card->dev = dev;
+	card->name = "sun9i-hdmi";
+
+	ret = devm_snd_soc_register_card(dev, card);
+	if (ret)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	of_node_put(dlc[0].of_node);
+	of_node_put(dlc[1].of_node);
+
+	return ret;
+}
+
+static int sun9i_hdmi_audio_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	of_node_put(card->dai_link->cpus->of_node);
+	of_node_put(card->dai_link->codecs->of_node);
+
+	return 0;
+}
+
+static const struct of_device_id sun9i_hdmi_audio_match[] = {
+	{ .compatible = "allwinner,sun9i-a80-hdmi-audio" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sun9i_hdmi_audio_match);
+
+static struct platform_driver sun9i_hdmi_audio_driver = {
+	.probe = sun9i_hdmi_audio_probe,
+	.remove = sun9i_hdmi_audio_remove,
+	.driver = {
+		.name = "sun9i-hdmi-audio",
+		.of_match_table = sun9i_hdmi_audio_match,
+	},
+};
+module_platform_driver(sun9i_hdmi_audio_driver);
+
+MODULE_DESCRIPTION("sun9i HDMI Audio Sound Card");
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@gmail.com>");
+MODULE_LICENSE("GPL v2");
-- 
Armbian

