236 lines
6.0 KiB
Diff
236 lines
6.0 KiB
Diff
|
From 97b74cd075a60f0868d2ae237fa1babb64becc14 Mon Sep 17 00:00:00 2001
|
||
|
From: Jernej Skrabec <jernej.skrabec@gmail.com>
|
||
|
Date: Tue, 10 Nov 2020 20:42:44 +0100
|
||
|
Subject: [PATCH 242/464] 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(+)
|
||
|
create mode 100644 sound/soc/sunxi/sun9i-hdmi-audio.c
|
||
|
|
||
|
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");
|
||
|
--
|
||
|
2.34.1
|
||
|
|