aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/xilinx/Kconfig35
-rw-r--r--sound/soc/xilinx/Makefile6
-rw-r--r--sound/soc/xilinx/xilinx-dp-card.c113
-rw-r--r--sound/soc/xilinx/xilinx-dp-codec.c178
-rw-r--r--sound/soc/xilinx/xilinx-dp-pcm.c76
-rw-r--r--sound/soc/xilinx/xlnx_formatter_pcm.c226
-rw-r--r--sound/soc/xilinx/xlnx_i2s.c115
-rw-r--r--sound/soc/xilinx/xlnx_pl_snd_card.c432
-rw-r--r--sound/soc/xilinx/xlnx_sdi_audio.c610
-rw-r--r--sound/soc/xilinx/xlnx_snd_common.h23
10 files changed, 1751 insertions, 63 deletions
diff --git a/sound/soc/xilinx/Kconfig b/sound/soc/xilinx/Kconfig
index 69973179ef15..3660a42f7673 100644
--- a/sound/soc/xilinx/Kconfig
+++ b/sound/soc/xilinx/Kconfig
@@ -1,4 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-only
+config SND_SOC_XILINX_DP
+ tristate "Audio support for the the Xilinx DisplayPort"
+ select SND_DMAENGINE_PCM
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Audio support the for Xilinx DisplayPort.
+
+config SND_SOC_XILINX_SDI
+ tristate "Audio support for the the Xilinx SDI"
+ depends on DRM_XLNX_SDI
+ depends on VIDEO_XILINX_SDIRXSS
+ help
+ Select this option to enable Xilinx SDI Audio.This enables
+ SDI audio playback and capture using xilinx soft IP
+
config SND_SOC_XILINX_I2S
tristate "Audio support for the Xilinx I2S"
help
@@ -7,6 +22,26 @@ config SND_SOC_XILINX_I2S
mode, IP receives audio in AES format, extracts PCM and sends
PCM data. In receiver mode, IP receives PCM audio and
encapsulates PCM in AES format and sends AES data.
+ I2S playback and capture using xilinx soft IP
+
+config SND_SOC_XILINX_SPDIF
+ tristate "Audio support for the the Xilinx SPDIF"
+ help
+ Select this option to enable Xilinx SPDIF Audio.
+ Enabling this provides one of the component required in ASoC
+ audio pipeline.
+ This supports playback and capture usecases.
+
+config SND_SOC_XILINX_PL_SND_CARD
+ tristate "Audio support for the the Xilinx PL sound card"
+ depends on SND_SOC_XILINX_AUDIO_FORMATTER
+ depends on SND_SOC_XILINX_I2S
+ depends on SND_SOC_XILINX_SDI
+ select SND_SOC_HDMI_CODEC
+ help
+ Select this option to enable Xilinx PL sound card
+ support. This enables sound card using xilinx soft IPs
+ in audio pipeline.
config SND_SOC_XILINX_AUDIO_FORMATTER
tristate "Audio support for the the Xilinx audio formatter"
diff --git a/sound/soc/xilinx/Makefile b/sound/soc/xilinx/Makefile
index be7652ce7c13..aea78bae7e86 100644
--- a/sound/soc/xilinx/Makefile
+++ b/sound/soc/xilinx/Makefile
@@ -1,6 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SND_SOC_XILINX_DP) += xilinx-dp-pcm.o
+obj-$(CONFIG_SND_SOC_XILINX_DP) += xilinx-dp-codec.o
+obj-$(CONFIG_SND_SOC_XILINX_DP) += xilinx-dp-card.o
+obj-$(CONFIG_SND_SOC_XILINX_SDI) += xlnx_sdi_audio.o
snd-soc-xlnx-i2s-objs := xlnx_i2s.o
obj-$(CONFIG_SND_SOC_XILINX_I2S) += snd-soc-xlnx-i2s.o
+obj-$(CONFIG_SND_SOC_XILINX_SPDIF) += xlnx_spdif.o
+obj-$(CONFIG_SND_SOC_XILINX_PL_SND_CARD) += xlnx_pl_snd_card.o
snd-soc-xlnx-formatter-pcm-objs := xlnx_formatter_pcm.o
obj-$(CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER) += snd-soc-xlnx-formatter-pcm.o
snd-soc-xlnx-spdif-objs := xlnx_spdif.o
diff --git a/sound/soc/xilinx/xilinx-dp-card.c b/sound/soc/xilinx/xilinx-dp-card.c
new file mode 100644
index 000000000000..a149da095df8
--- /dev/null
+++ b/sound/soc/xilinx/xilinx-dp-card.c
@@ -0,0 +1,113 @@
+/*
+ * Xilinx DisplayPort SoC Sound Card support
+ *
+ * Copyright (C) 2015 Xilinx, Inc.
+ *
+ * Author: Hyun Woo Kwon <hyunk@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+static int xilinx_dp_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 256);
+ return 0;
+}
+
+static const struct snd_soc_ops xilinx_dp_ops = {
+ .startup = xilinx_dp_startup,
+};
+
+static struct snd_soc_dai_link xilinx_dp_dai_links[] = {
+ {
+ .name = "xilinx-dp0",
+ .stream_name = "xilinx-dp0",
+ .codec_dai_name = "xilinx-dp-snd-codec-dai",
+ .ops = &xilinx_dp_ops,
+ },
+ {
+ .name = "xilinx-dp1",
+ .stream_name = "xilinx-dp1",
+ .codec_dai_name = "xilinx-dp-snd-codec-dai",
+ .ops = &xilinx_dp_ops,
+ },
+
+};
+
+static struct snd_soc_card xilinx_dp_card = {
+ .name = "DisplayPort monitor",
+ .owner = THIS_MODULE,
+ .dai_link = xilinx_dp_dai_links,
+ .num_links = 2,
+};
+
+static int xilinx_dp_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &xilinx_dp_card;
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *codec, *pcm;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ codec = of_parse_phandle(node, "xlnx,dp-snd-codec", 0);
+ if (!codec)
+ return -ENODEV;
+
+ pcm = of_parse_phandle(node, "xlnx,dp-snd-pcm", 0);
+ if (!pcm)
+ return -ENODEV;
+ xilinx_dp_dai_links[0].platform_of_node = pcm;
+ xilinx_dp_dai_links[0].cpu_of_node = codec;
+ xilinx_dp_dai_links[0].codec_of_node = codec;
+
+ pcm = of_parse_phandle(node, "xlnx,dp-snd-pcm", 1);
+ if (!pcm)
+ return -ENODEV;
+ xilinx_dp_dai_links[1].platform_of_node = pcm;
+ xilinx_dp_dai_links[1].cpu_of_node = codec;
+ xilinx_dp_dai_links[1].codec_of_node = codec;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ return ret;
+
+ dev_info(&pdev->dev, "Xilinx DisplayPort Sound Card probed\n");
+
+ return 0;
+}
+
+static const struct of_device_id xilinx_dp_of_match[] = {
+ { .compatible = "xlnx,dp-snd-card", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xilinx_dp_of_match);
+
+static struct platform_driver xilinx_dp_aud_driver = {
+ .driver = {
+ .name = "xilinx-dp-snd-card",
+ .of_match_table = xilinx_dp_of_match,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = xilinx_dp_probe,
+};
+module_platform_driver(xilinx_dp_aud_driver);
+
+MODULE_DESCRIPTION("Xilinx DisplayPort Sound Card module");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/xilinx/xilinx-dp-codec.c b/sound/soc/xilinx/xilinx-dp-codec.c
new file mode 100644
index 000000000000..af6e6b08c415
--- /dev/null
+++ b/sound/soc/xilinx/xilinx-dp-codec.c
@@ -0,0 +1,178 @@
+/*
+ * Xilinx DisplayPort Sound Codec support
+ *
+ * Copyright (C) 2015 Xilinx, Inc.
+ *
+ * Author: Hyun Woo Kwon <hyunk@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+/**
+ * struct xilinx_dp_codec - DisplayPort codec
+ * @aud_clk: audio clock
+ */
+struct xilinx_dp_codec {
+ struct clk *aud_clk;
+};
+
+struct xilinx_dp_codec_fmt {
+ unsigned long rate;
+ unsigned int snd_rate;
+};
+
+static struct snd_soc_dai_driver xilinx_dp_codec_dai = {
+ .name = "xilinx-dp-snd-codec-dai",
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_44100,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static const struct xilinx_dp_codec_fmt rates[] = {
+ {
+ .rate = 48000 * 512,
+ .snd_rate = SNDRV_PCM_RATE_48000
+ },
+ {
+ .rate = 44100 * 512,
+ .snd_rate = SNDRV_PCM_RATE_44100
+ }
+};
+
+static const struct snd_soc_component_driver xilinx_dp_component_driver = {
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+static int xilinx_dp_codec_probe(struct platform_device *pdev)
+{
+ struct xilinx_dp_codec *codec;
+ unsigned int i;
+ unsigned long rate;
+ int ret;
+
+ codec = devm_kzalloc(&pdev->dev, sizeof(*codec), GFP_KERNEL);
+ if (!codec)
+ return -ENOMEM;
+
+ codec->aud_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(codec->aud_clk))
+ return PTR_ERR(codec->aud_clk);
+
+ ret = clk_prepare_enable(codec->aud_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable the aud_clk\n");
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(rates); i++) {
+ clk_disable_unprepare(codec->aud_clk);
+ ret = clk_set_rate(codec->aud_clk, rates[i].rate);
+ clk_prepare_enable(codec->aud_clk);
+ if (ret)
+ continue;
+
+ rate = clk_get_rate(codec->aud_clk);
+ /* Ignore some offset +- 10 */
+ if (abs(rates[i].rate - rate) < 10) {
+ xilinx_dp_codec_dai.playback.rates = rates[i].snd_rate;
+ break;
+ }
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get required clock freq\n");
+ goto error_clk;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &xilinx_dp_component_driver,
+ &xilinx_dp_codec_dai, 1);
+ if (ret)
+ goto error_clk;
+
+ platform_set_drvdata(pdev, codec);
+
+ dev_info(&pdev->dev, "Xilinx DisplayPort Sound Codec probed\n");
+
+ return 0;
+
+error_clk:
+ clk_disable_unprepare(codec->aud_clk);
+ return ret;
+}
+
+static int xilinx_dp_codec_dev_remove(struct platform_device *pdev)
+{
+ struct xilinx_dp_codec *codec = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(codec->aud_clk);
+
+ return 0;
+}
+
+static int __maybe_unused xilinx_dp_codec_pm_suspend(struct device *dev)
+{
+ struct xilinx_dp_codec *codec = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(codec->aud_clk);
+
+ return 0;
+}
+
+static int __maybe_unused xilinx_dp_codec_pm_resume(struct device *dev)
+{
+ struct xilinx_dp_codec *codec = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(codec->aud_clk);
+ if (ret)
+ dev_err(dev, "failed to enable the aud_clk\n");
+
+ return ret;
+}
+
+static const struct dev_pm_ops xilinx_dp_codec_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(xilinx_dp_codec_pm_suspend,
+ xilinx_dp_codec_pm_resume)
+};
+
+static const struct of_device_id xilinx_dp_codec_of_match[] = {
+ { .compatible = "xlnx,dp-snd-codec", },
+ { /* end of table */ },
+};
+MODULE_DEVICE_TABLE(of, xilinx_dp_codec_of_match);
+
+static struct platform_driver xilinx_dp_codec_driver = {
+ .driver = {
+ .name = "xilinx-dp-snd-codec",
+ .of_match_table = xilinx_dp_codec_of_match,
+ .pm = &xilinx_dp_codec_pm_ops,
+ },
+ .probe = xilinx_dp_codec_probe,
+ .remove = xilinx_dp_codec_dev_remove,
+};
+module_platform_driver(xilinx_dp_codec_driver);
+
+MODULE_DESCRIPTION("Xilinx DisplayPort Sound Codec module");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/xilinx/xilinx-dp-pcm.c b/sound/soc/xilinx/xilinx-dp-pcm.c
new file mode 100644
index 000000000000..fa8abe788cf7
--- /dev/null
+++ b/sound/soc/xilinx/xilinx-dp-pcm.c
@@ -0,0 +1,76 @@
+/*
+ * Xilinx DisplayPort Sound PCM support
+ *
+ * Copyright (C) 2015 Xilinx, Inc.
+ *
+ * Author: Hyun Woo Kwon <hyunk@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+static const struct snd_pcm_hardware xilinx_pcm_hw = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .buffer_bytes_max = 128 * 1024,
+ .period_bytes_min = 256,
+ .period_bytes_max = 1024 * 1024,
+ .periods_min = 2,
+ .periods_max = 256,
+};
+
+static const struct snd_dmaengine_pcm_config xilinx_dmaengine_pcm_config = {
+ .pcm_hardware = &xilinx_pcm_hw,
+ .prealloc_buffer_size = 64 * 1024,
+};
+
+static int xilinx_dp_pcm_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ dev_set_name(&pdev->dev, pdev->dev.of_node->name);
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
+ &xilinx_dmaengine_pcm_config, 0);
+ if (ret)
+ return ret;
+
+ dev_info(&pdev->dev, "Xilinx DisplayPort Sound PCM probed\n");
+
+ return 0;
+}
+
+static const struct of_device_id xilinx_dp_pcm_of_match[] = {
+ { .compatible = "xlnx,dp-snd-pcm", },
+ { /* end of table */ },
+};
+MODULE_DEVICE_TABLE(of, xilinx_dp_pcm_of_match);
+
+static struct platform_driver xilinx_dp_pcm_driver = {
+ .driver = {
+ .name = "xilinx-dp-snd-pcm",
+ .of_match_table = xilinx_dp_pcm_of_match,
+ },
+ .probe = xilinx_dp_pcm_probe,
+};
+module_platform_driver(xilinx_dp_pcm_driver);
+
+MODULE_DESCRIPTION("Xilinx DisplayPort Sound PCM module");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c
index dc8721f4f56b..8215b9a4438c 100644
--- a/sound/soc/xilinx/xlnx_formatter_pcm.c
+++ b/sound/soc/xilinx/xlnx_formatter_pcm.c
@@ -17,6 +17,8 @@
#include <sound/soc.h>
#include <sound/pcm_params.h>
+#include "xlnx_snd_common.h"
+
#define DRV_NAME "xlnx_formatter_pcm"
#define XLNX_S2MM_OFFSET 0
@@ -56,7 +58,9 @@
#define CFG_S2MM_XFER_SHIFT 29
#define CFG_S2MM_PKG_MASK BIT(28)
+#define AUD_CTRL_DATA_WIDTH_MASK GENMASK(18, 16)
#define AUD_CTRL_DATA_WIDTH_SHIFT 16
+#define AUD_CTRL_ACTIVE_CH_MASK GENMASK(22, 19)
#define AUD_CTRL_ACTIVE_CH_SHIFT 19
#define PERIOD_CFG_PERIODS_SHIFT 16
@@ -82,7 +86,12 @@ struct xlnx_pcm_drv_data {
int mm2s_irq;
struct snd_pcm_substream *play_stream;
struct snd_pcm_substream *capture_stream;
+ struct platform_device *pdev;
+ struct device_node *nodes[XLNX_MAX_PATHS];
struct clk *axi_clk;
+ struct clk *mm2s_axis_clk;
+ struct clk *s2mm_axis_clk;
+ struct clk *aud_mclk;
};
/*
@@ -430,11 +439,13 @@ static int xlnx_formatter_pcm_hw_params(struct snd_pcm_substream *substream,
u32 aes_reg1_val, aes_reg2_val;
int status;
u64 size;
+ struct pl_card_data *prv;
struct snd_soc_pcm_runtime *prtd = substream->private_data;
struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd,
DRV_NAME);
struct snd_pcm_runtime *runtime = substream->runtime;
struct xlnx_pcm_stream_param *stream_data = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
active_ch = params_channels(params);
if (active_ch > stream_data->ch_limit)
@@ -467,6 +478,7 @@ static int xlnx_formatter_pcm_hw_params(struct snd_pcm_substream *substream,
writel(high, stream_data->mmio + XLNX_AUD_BUFF_ADDR_MSB);
val = readl(stream_data->mmio + XLNX_AUD_CTRL);
+ val &= ~AUD_CTRL_DATA_WIDTH_MASK;
bits_per_sample = params_width(params);
switch (bits_per_sample) {
case 8:
@@ -488,6 +500,7 @@ static int xlnx_formatter_pcm_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
+ val &= ~AUD_CTRL_ACTIVE_CH_MASK;
val |= active_ch << AUD_CTRL_ACTIVE_CH_SHIFT;
writel(val, stream_data->mmio + XLNX_AUD_CTRL);
@@ -497,6 +510,12 @@ static int xlnx_formatter_pcm_hw_params(struct snd_pcm_substream *substream,
bytes_per_ch = DIV_ROUND_UP(params_period_bytes(params), active_ch);
writel(bytes_per_ch, stream_data->mmio + XLNX_BYTES_PER_CH);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ prv = snd_soc_card_get_drvdata(rtd->card);
+ writel(prv->mclk_ratio,
+ stream_data->mmio + XLNX_AUD_FS_MULTIPLIER);
+ }
+
return 0;
}
@@ -559,10 +578,146 @@ static const struct snd_soc_component_driver xlnx_asoc_component = {
.pcm_new = xlnx_formatter_pcm_new,
};
+static int configure_mm2s(struct xlnx_pcm_drv_data *aud_drv_data,
+ struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+
+ aud_drv_data->mm2s_axis_clk = devm_clk_get(dev, "m_axis_mm2s_aclk");
+ if (IS_ERR(aud_drv_data->mm2s_axis_clk)) {
+ ret = PTR_ERR(aud_drv_data->mm2s_axis_clk);
+ dev_err(dev, "failed to get m_axis_mm2s_aclk(%d)\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(aud_drv_data->mm2s_axis_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable m_axis_mm2s_aclk(%d)\n", ret);
+ return ret;
+ }
+
+ aud_drv_data->aud_mclk = devm_clk_get(dev, "aud_mclk");
+ if (IS_ERR(aud_drv_data->aud_mclk)) {
+ ret = PTR_ERR(aud_drv_data->aud_mclk);
+ dev_err(dev, "failed to get aud_mclk(%d)\n", ret);
+ goto axis_clk_err;
+ }
+ ret = clk_prepare_enable(aud_drv_data->aud_mclk);
+ if (ret) {
+ dev_err(dev, "failed to enable aud_mclk(%d)\n", ret);
+ goto axis_clk_err;
+ }
+
+ aud_drv_data->mm2s_irq = platform_get_irq_byname(pdev,
+ "irq_mm2s");
+ if (aud_drv_data->mm2s_irq < 0) {
+ dev_err(dev, "xlnx audio mm2s irq resource failed\n");
+ ret = aud_drv_data->mm2s_irq;
+ goto mm2s_err;
+ }
+ ret = devm_request_irq(dev, aud_drv_data->mm2s_irq,
+ xlnx_mm2s_irq_handler, 0,
+ "xlnx_formatter_pcm_mm2s_irq",
+ dev);
+ if (ret) {
+ dev_err(dev, "xlnx audio mm2s irq request failed\n");
+ goto mm2s_err;
+ }
+ ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio +
+ XLNX_MM2S_OFFSET);
+ if (ret) {
+ dev_err(dev, "audio formatter reset failed\n");
+ goto mm2s_err;
+ }
+ xlnx_formatter_disable_irqs(aud_drv_data->mmio +
+ XLNX_MM2S_OFFSET,
+ SNDRV_PCM_STREAM_PLAYBACK);
+
+ aud_drv_data->nodes[XLNX_PLAYBACK] =
+ of_parse_phandle(dev->of_node, "xlnx,tx", 0);
+ if (!aud_drv_data->nodes[XLNX_PLAYBACK])
+ dev_err(dev, "tx node not found\n");
+ else
+ dev_info(dev,
+ "sound card device will use DAI link: %s\n",
+ (aud_drv_data->nodes[XLNX_PLAYBACK])->name);
+ of_node_put(aud_drv_data->nodes[XLNX_PLAYBACK]);
+
+ aud_drv_data->mm2s_presence = true;
+ return 0;
+
+mm2s_err:
+ clk_disable_unprepare(aud_drv_data->aud_mclk);
+axis_clk_err:
+ clk_disable_unprepare(aud_drv_data->mm2s_axis_clk);
+
+ return ret;
+}
+
+static int configure_s2mm(struct xlnx_pcm_drv_data *aud_drv_data,
+ struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+
+ aud_drv_data->s2mm_axis_clk = devm_clk_get(dev, "s_axis_s2mm_aclk");
+ if (IS_ERR(aud_drv_data->s2mm_axis_clk)) {
+ ret = PTR_ERR(aud_drv_data->s2mm_axis_clk);
+ dev_err(dev, "failed to get s_axis_s2mm_aclk(%d)\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(aud_drv_data->s2mm_axis_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable s_axis_s2mm_aclk(%d)\n", ret);
+ return ret;
+ }
+
+ aud_drv_data->s2mm_irq = platform_get_irq_byname(pdev, "irq_s2mm");
+ if (aud_drv_data->s2mm_irq < 0) {
+ dev_err(dev, "xlnx audio s2mm irq resource failed\n");
+ ret = aud_drv_data->s2mm_irq;
+ goto s2mm_err;
+ }
+ ret = devm_request_irq(dev, aud_drv_data->s2mm_irq,
+ xlnx_s2mm_irq_handler, 0,
+ "xlnx_formatter_pcm_s2mm_irq",
+ dev);
+ if (ret) {
+ dev_err(dev, "xlnx audio s2mm irq request failed\n");
+ goto s2mm_err;
+ }
+ ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio +
+ XLNX_S2MM_OFFSET);
+ if (ret) {
+ dev_err(dev, "audio formatter reset failed\n");
+ goto s2mm_err;
+ }
+ xlnx_formatter_disable_irqs(aud_drv_data->mmio +
+ XLNX_S2MM_OFFSET,
+ SNDRV_PCM_STREAM_CAPTURE);
+
+ aud_drv_data->nodes[XLNX_CAPTURE] =
+ of_parse_phandle(dev->of_node, "xlnx,rx", 0);
+ if (!aud_drv_data->nodes[XLNX_CAPTURE])
+ dev_err(dev, "rx node not found\n");
+ else
+ dev_info(dev, "sound card device will use DAI link: %s\n",
+ (aud_drv_data->nodes[XLNX_CAPTURE])->name);
+ of_node_put(aud_drv_data->nodes[XLNX_CAPTURE]);
+
+ aud_drv_data->s2mm_presence = true;
+ return 0;
+
+s2mm_err:
+ clk_disable_unprepare(aud_drv_data->s2mm_axis_clk);
+ return ret;
+}
+
static int xlnx_formatter_pcm_probe(struct platform_device *pdev)
{
int ret;
u32 val;
+ size_t pdata_size;
struct xlnx_pcm_drv_data *aud_drv_data;
struct resource *res;
struct device *dev = &pdev->dev;
@@ -599,59 +754,15 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev)
val = readl(aud_drv_data->mmio + XLNX_AUD_CORE_CONFIG);
if (val & AUD_CFG_MM2S_MASK) {
- aud_drv_data->mm2s_presence = true;
- ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio +
- XLNX_MM2S_OFFSET);
- if (ret) {
- dev_err(dev, "audio formatter reset failed\n");
- goto clk_err;
- }
- xlnx_formatter_disable_irqs(aud_drv_data->mmio +
- XLNX_MM2S_OFFSET,
- SNDRV_PCM_STREAM_PLAYBACK);
-
- aud_drv_data->mm2s_irq = platform_get_irq_byname(pdev,
- "irq_mm2s");
- if (aud_drv_data->mm2s_irq < 0) {
- dev_err(dev, "xlnx audio mm2s irq resource failed\n");
- ret = aud_drv_data->mm2s_irq;
+ ret = configure_mm2s(aud_drv_data, pdev);
+ if (ret)
goto clk_err;
- }
- ret = devm_request_irq(dev, aud_drv_data->mm2s_irq,
- xlnx_mm2s_irq_handler, 0,
- "xlnx_formatter_pcm_mm2s_irq", dev);
- if (ret) {
- dev_err(dev, "xlnx audio mm2s irq request failed\n");
- goto clk_err;
- }
}
+
if (val & AUD_CFG_S2MM_MASK) {
- aud_drv_data->s2mm_presence = true;
- ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio +
- XLNX_S2MM_OFFSET);
- if (ret) {
- dev_err(dev, "audio formatter reset failed\n");
+ ret = configure_s2mm(aud_drv_data, pdev);
+ if (ret)
goto clk_err;
- }
- xlnx_formatter_disable_irqs(aud_drv_data->mmio +
- XLNX_S2MM_OFFSET,
- SNDRV_PCM_STREAM_CAPTURE);
-
- aud_drv_data->s2mm_irq = platform_get_irq_byname(pdev,
- "irq_s2mm");
- if (aud_drv_data->s2mm_irq < 0) {
- dev_err(dev, "xlnx audio s2mm irq resource failed\n");
- ret = aud_drv_data->s2mm_irq;
- goto clk_err;
- }
- ret = devm_request_irq(dev, aud_drv_data->s2mm_irq,
- xlnx_s2mm_irq_handler, 0,
- "xlnx_formatter_pcm_s2mm_irq",
- dev);
- if (ret) {
- dev_err(dev, "xlnx audio s2mm irq request failed\n");
- goto clk_err;
- }
}
dev_set_drvdata(dev, aud_drv_data);
@@ -663,6 +774,21 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev)
goto clk_err;
}
+ pdata_size = sizeof(aud_drv_data->nodes);
+ if (aud_drv_data->nodes[XLNX_PLAYBACK] ||
+ aud_drv_data->nodes[XLNX_CAPTURE])
+ aud_drv_data->pdev =
+ platform_device_register_resndata(dev,
+ "xlnx_snd_card",
+ PLATFORM_DEVID_AUTO,
+ NULL, 0,
+ &aud_drv_data->nodes,
+ pdata_size);
+ if (!aud_drv_data->pdev) {
+ dev_err(dev, "sound card device creation failed\n");
+ goto clk_err;
+ }
+ dev_info(dev, "pcm platform device registered\n");
return 0;
clk_err:
@@ -675,6 +801,8 @@ static int xlnx_formatter_pcm_remove(struct platform_device *pdev)
int ret = 0;
struct xlnx_pcm_drv_data *adata = dev_get_drvdata(&pdev->dev);
+ platform_device_unregister(adata->pdev);
+
if (adata->s2mm_presence)
ret = xlnx_formatter_pcm_reset(adata->mmio + XLNX_S2MM_OFFSET);
diff --git a/sound/soc/xilinx/xlnx_i2s.c b/sound/soc/xilinx/xlnx_i2s.c
index 8b353166ad44..b1a51b28a836 100644
--- a/sound/soc/xilinx/xlnx_i2s.c
+++ b/sound/soc/xilinx/xlnx_i2s.c
@@ -7,6 +7,7 @@
// Author: Praveen Vuppala <praveenv@xilinx.com>
// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>
+#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -22,15 +23,22 @@
#define I2S_CH0_OFFSET 0x30
#define I2S_I2STIM_VALID_MASK GENMASK(7, 0)
+struct xlnx_i2s_dev_data {
+ void __iomem *base;
+ struct clk *axi_clk;
+ struct clk *axis_clk;
+ struct clk *aud_mclk;
+};
+
static int xlnx_i2s_set_sclkout_div(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
- void __iomem *base = snd_soc_dai_get_drvdata(cpu_dai);
+ struct xlnx_i2s_dev_data *dev_data = snd_soc_dai_get_drvdata(cpu_dai);
if (!div || (div & ~I2S_I2STIM_VALID_MASK))
return -EINVAL;
- writel(div, base + I2S_I2STIM_OFFSET);
+ writel(div, dev_data->base + I2S_I2STIM_OFFSET);
return 0;
}
@@ -40,13 +48,13 @@ static int xlnx_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *i2s_dai)
{
u32 reg_off, chan_id;
- void __iomem *base = snd_soc_dai_get_drvdata(i2s_dai);
+ struct xlnx_i2s_dev_data *dev_data = snd_soc_dai_get_drvdata(i2s_dai);
chan_id = params_channels(params) / 2;
while (chan_id > 0) {
reg_off = I2S_CH0_OFFSET + ((chan_id - 1) * 4);
- writel(chan_id, base + reg_off);
+ writel(chan_id, dev_data->base + reg_off);
chan_id--;
}
@@ -56,18 +64,18 @@ static int xlnx_i2s_hw_params(struct snd_pcm_substream *substream,
static int xlnx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *i2s_dai)
{
- void __iomem *base = snd_soc_dai_get_drvdata(i2s_dai);
+ struct xlnx_i2s_dev_data *dev_data = snd_soc_dai_get_drvdata(i2s_dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- writel(1, base + I2S_CORE_CTRL_OFFSET);
+ writel(1, dev_data->base + I2S_CORE_CTRL_OFFSET);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- writel(0, base + I2S_CORE_CTRL_OFFSET);
+ writel(0, dev_data->base + I2S_CORE_CTRL_OFFSET);
break;
default:
return -EINVAL;
@@ -96,8 +104,8 @@ MODULE_DEVICE_TABLE(of, xlnx_i2s_of_match);
static int xlnx_i2s_probe(struct platform_device *pdev)
{
struct resource *res;
- void __iomem *base;
struct snd_soc_dai_driver *dai_drv;
+ struct xlnx_i2s_dev_data *dev_data;
int ret;
u32 ch, format, data_width;
struct device *dev = &pdev->dev;
@@ -107,10 +115,16 @@ static int xlnx_i2s_probe(struct platform_device *pdev)
if (!dai_drv)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(base))
- return PTR_ERR(base);
+ dev_data = devm_kzalloc(&pdev->dev, sizeof(*dev_data), GFP_KERNEL);
+ if (!dev_data)
+ return -ENOMEM;
+
+ dev_data->axi_clk = devm_clk_get(&pdev->dev, "s_axi_ctrl_aclk");
+ if (IS_ERR(dev_data->axi_clk)) {
+ ret = PTR_ERR(dev_data->axi_clk);
+ dev_err(&pdev->dev, "failed to get s_axi_ctrl_aclk(%d)\n", ret);
+ return ret;
+ }
ret = of_property_read_u32(node, "xlnx,num-channels", &ch);
if (ret < 0) {
@@ -143,6 +157,15 @@ static int xlnx_i2s_probe(struct platform_device *pdev)
dai_drv->playback.channels_max = ch;
dai_drv->playback.rates = SNDRV_PCM_RATE_8000_192000;
dai_drv->ops = &xlnx_i2s_dai_ops;
+
+ dev_data->axis_clk = devm_clk_get(&pdev->dev,
+ "s_axis_aud_aclk");
+ if (IS_ERR(dev_data->axis_clk)) {
+ ret = PTR_ERR(dev_data->axis_clk);
+ dev_err(&pdev->dev,
+ "failed to get s_axis_aud_aclk(%d)\n", ret);
+ return ret;
+ }
} else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) {
dai_drv->name = "xlnx_i2s_capture";
dai_drv->capture.stream_name = "Capture";
@@ -151,30 +174,94 @@ static int xlnx_i2s_probe(struct platform_device *pdev)
dai_drv->capture.channels_max = ch;
dai_drv->capture.rates = SNDRV_PCM_RATE_8000_192000;
dai_drv->ops = &xlnx_i2s_dai_ops;
+
+ dev_data->axis_clk = devm_clk_get(&pdev->dev,
+ "m_axis_aud_aclk");
+ if (IS_ERR(dev_data->axis_clk)) {
+ ret = PTR_ERR(dev_data->axis_clk);
+ dev_err(&pdev->dev,
+ "failed to get m_axis_aud_aclk(%d)\n", ret);
+ return ret;
+ }
} else {
return -ENODEV;
}
- dev_set_drvdata(&pdev->dev, base);
+ dev_data->aud_mclk = devm_clk_get(&pdev->dev, "aud_mclk");
+ if (IS_ERR(dev_data->aud_mclk)) {
+ ret = PTR_ERR(dev_data->aud_mclk);
+ dev_err(&pdev->dev, "failed to get aud_mclk(%d)\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dev_data->axi_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to enable s_axi_ctrl_aclk(%d)\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dev_data->axis_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to enable axis_aud_aclk(%d)\n", ret);
+ goto err_axis_clk;
+ }
+
+ ret = clk_prepare_enable(dev_data->aud_mclk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to enable aud_mclk(%d)\n", ret);
+ goto err_aud_mclk;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dev_data->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dev_data->base)) {
+ ret = PTR_ERR(dev_data->base);
+ goto clk_err;
+ }
+
+ dev_set_drvdata(&pdev->dev, dev_data);
ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component,
dai_drv, 1);
if (ret) {
dev_err(&pdev->dev, "i2s component registration failed\n");
- return ret;
+ goto clk_err;
}
dev_info(&pdev->dev, "%s DAI registered\n", dai_drv->name);
+ return 0;
+clk_err:
+ clk_disable_unprepare(dev_data->aud_mclk);
+err_aud_mclk:
+ clk_disable_unprepare(dev_data->axis_clk);
+err_axis_clk:
+ clk_disable_unprepare(dev_data->axi_clk);
+
return ret;
}
+static int xlnx_i2s_remove(struct platform_device *pdev)
+{
+ struct xlnx_i2s_dev_data *dev_data = dev_get_drvdata(&pdev->dev);
+
+ clk_disable_unprepare(dev_data->aud_mclk);
+ clk_disable_unprepare(dev_data->axis_clk);
+ clk_disable_unprepare(dev_data->axi_clk);
+
+ return 0;
+}
+
static struct platform_driver xlnx_i2s_aud_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = xlnx_i2s_of_match,
},
.probe = xlnx_i2s_probe,
+ .remove = xlnx_i2s_remove,
};
module_platform_driver(xlnx_i2s_aud_driver);
diff --git a/sound/soc/xilinx/xlnx_pl_snd_card.c b/sound/soc/xilinx/xlnx_pl_snd_card.c
new file mode 100644
index 000000000000..2fa7c9ceb08d
--- /dev/null
+++ b/sound/soc/xilinx/xlnx_pl_snd_card.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx ASoC sound card support
+ *
+ * Copyright (C) 2018 Xilinx, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "xlnx_snd_common.h"
+
+#define I2S_CLOCK_RATIO 384
+#define XLNX_MAX_PL_SND_DEV 5
+
+static DEFINE_IDA(xlnx_snd_card_dev);
+
+enum {
+ I2S_AUDIO = 0,
+ HDMI_AUDIO,
+ SDI_AUDIO,
+ SPDIF_AUDIO,
+ XLNX_MAX_IFACE,
+};
+
+static const char *xlnx_snd_card_name[XLNX_MAX_IFACE] = {
+ [I2S_AUDIO] = "xlnx-i2s-snd-card",
+ [HDMI_AUDIO] = "xlnx-hdmi-snd-card",
+ [SDI_AUDIO] = "xlnx-sdi-snd-card",
+ [SPDIF_AUDIO] = "xlnx-spdif-snd-card",
+};
+
+static const char *dev_compat[][XLNX_MAX_IFACE] = {
+ [XLNX_PLAYBACK] = {
+ "xlnx,i2s-transmitter-1.0",
+ "xlnx,v-hdmi-tx-ss-3.1",
+ "xlnx,v-uhdsdi-audio-2.0",
+ "xlnx,spdif-2.0",
+ },
+
+ [XLNX_CAPTURE] = {
+ "xlnx,i2s-receiver-1.0",
+ "xlnx,v-hdmi-rx-ss-3.1",
+ "xlnx,v-uhdsdi-audio-2.0",
+ "xlnx,spdif-2.0",
+ },
+};
+
+static int xlnx_spdif_card_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct pl_card_data *prv = snd_soc_card_get_drvdata(rtd->card);
+ u32 sample_rate = params_rate(params);
+
+ /* mclk must be >=1024 * sampleing rate */
+ prv->mclk_val = 1024 * sample_rate;
+ prv->mclk_ratio = 1024;
+ return clk_set_rate(prv->mclk, prv->mclk_val);
+}
+
+static int xlnx_sdi_card_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct pl_card_data *prv = snd_soc_card_get_drvdata(rtd->card);
+ u32 sample_rate = params_rate(params);
+
+ prv->mclk_val = prv->mclk_ratio * sample_rate;
+ return clk_set_rate(prv->mclk, prv->mclk_val);
+}
+
+static int xlnx_hdmi_card_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct pl_card_data *prv = snd_soc_card_get_drvdata(rtd->card);
+ u32 sample_rate = params_rate(params);
+
+ switch (sample_rate) {
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ case 176400:
+ case 192000:
+ prv->mclk_ratio = 512;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ prv->mclk_val = prv->mclk_ratio * sample_rate;
+ return clk_set_rate(prv->mclk, prv->mclk_val);
+}
+
+static int xlnx_i2s_card_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ int ret, clk_div;
+ u32 ch, data_width, sample_rate;
+ struct pl_card_data *prv;
+
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ ch = params_channels(params);
+ data_width = params_width(params);
+ sample_rate = params_rate(params);
+
+ /* only 2 channels supported */
+ if (ch != 2)
+ return -EINVAL;
+
+ prv = snd_soc_card_get_drvdata(rtd->card);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ switch (sample_rate) {
+ case 5512:
+ case 8000:
+ case 11025:
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 64000:
+ case 88200:
+ case 96000:
+ prv->mclk_ratio = 384;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ switch (sample_rate) {
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ prv->mclk_ratio = 384;
+ break;
+ case 64000:
+ case 176400:
+ case 192000:
+ prv->mclk_ratio = 192;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ prv->mclk_val = prv->mclk_ratio * sample_rate;
+ clk_div = DIV_ROUND_UP(prv->mclk_ratio, 2 * ch * data_width);
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, clk_div);
+ if (ret)
+ return ret;
+
+ return clk_set_rate(prv->mclk, prv->mclk_val);
+}
+
+static const struct snd_soc_ops xlnx_sdi_card_ops = {
+ .hw_params = xlnx_sdi_card_hw_params,
+};
+
+static const struct snd_soc_ops xlnx_i2s_card_ops = {
+ .hw_params = xlnx_i2s_card_hw_params,
+};
+
+static const struct snd_soc_ops xlnx_hdmi_card_ops = {
+ .hw_params = xlnx_hdmi_card_hw_params,
+};
+
+static const struct snd_soc_ops xlnx_spdif_card_ops = {
+ .hw_params = xlnx_spdif_card_hw_params,
+};
+
+static struct snd_soc_dai_link xlnx_snd_dai[][XLNX_MAX_PATHS] = {
+ [I2S_AUDIO] = {
+ {
+ .name = "xilinx-i2s_playback",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .ops = &xlnx_i2s_card_ops,
+ },
+ {
+ .name = "xilinx-i2s_capture",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .ops = &xlnx_i2s_card_ops,
+ },
+ },
+ [HDMI_AUDIO] = {
+ {
+ .name = "xilinx-hdmi-playback",
+ .codec_dai_name = "i2s-hifi",
+ .codec_name = "hdmi-audio-codec.0",
+ .cpu_dai_name = "snd-soc-dummy-dai",
+ .ops = &xlnx_hdmi_card_ops,
+ },
+ {
+ .name = "xilinx-hdmi-capture",
+ .codec_dai_name = "xlnx_hdmi_rx",
+ .cpu_dai_name = "snd-soc-dummy-dai",
+ },
+ },
+ [SDI_AUDIO] = {
+ {
+ .name = "xlnx-sdi-playback",
+ .codec_dai_name = "xlnx_sdi_tx",
+ .cpu_dai_name = "snd-soc-dummy-dai",
+ .ops = &xlnx_sdi_card_ops,
+ },
+ {
+ .name = "xlnx-sdi-capture",
+ .codec_dai_name = "xlnx_sdi_rx",
+ .cpu_dai_name = "snd-soc-dummy-dai",
+ },
+
+ },
+ [SPDIF_AUDIO] = {
+ {
+ .name = "xilinx-spdif_playback",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .ops = &xlnx_spdif_card_ops,
+ },
+ {
+ .name = "xilinx-spdif_capture",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .codec_name = "snd-soc-dummy",
+ .ops = &xlnx_spdif_card_ops,
+ },
+ },
+
+};
+
+static int find_link(struct device_node *node, int direction)
+{
+ int ret;
+ u32 i, size;
+ const char **link_names = dev_compat[direction];
+
+ size = ARRAY_SIZE(dev_compat[direction]);
+
+ for (i = 0; i < size; i++) {
+ ret = of_device_is_compatible(node, link_names[i]);
+ if (ret)
+ return i;
+ }
+ return -ENODEV;
+}
+
+static int xlnx_snd_probe(struct platform_device *pdev)
+{
+ u32 i;
+ size_t sz;
+ char *buf;
+ int ret, audio_interface;
+ struct snd_soc_dai_link *dai;
+ struct pl_card_data *prv;
+ struct platform_device *iface_pdev;
+
+ struct snd_soc_card *card;
+ struct device_node **node = pdev->dev.platform_data;
+
+ if (!node)
+ return -ENODEV;
+
+ card = devm_kzalloc(&pdev->dev, sizeof(struct snd_soc_card),
+ GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ card->dev = &pdev->dev;
+
+ card->dai_link = devm_kzalloc(card->dev,
+ sizeof(*dai) * XLNX_MAX_PATHS,
+ GFP_KERNEL);
+ if (!card->dai_link)
+ return -ENOMEM;
+
+ prv = devm_kzalloc(card->dev,
+ sizeof(struct pl_card_data),
+ GFP_KERNEL);
+ if (!prv)
+ return -ENOMEM;
+
+ card->num_links = 0;
+ for (i = XLNX_PLAYBACK; i < XLNX_MAX_PATHS; i++) {
+ struct device_node *pnode = of_parse_phandle(node[i],
+ "xlnx,snd-pcm", 0);
+ if (!pnode) {
+ dev_err(card->dev, "platform node not found\n");
+ of_node_put(pnode);
+ return -ENODEV;
+ }
+
+ /*
+ * Check for either playback or capture is enough, as
+ * same clock is used for both.
+ */
+ if (i == XLNX_PLAYBACK) {
+ iface_pdev = of_find_device_by_node(pnode);
+ if (!iface_pdev) {
+ of_node_put(pnode);
+ return -ENODEV;
+ }
+
+ prv->mclk = devm_clk_get(&iface_pdev->dev, "aud_mclk");
+ if (IS_ERR(prv->mclk))
+ return PTR_ERR(prv->mclk);
+
+ }
+ of_node_put(pnode);
+
+ dai = &card->dai_link[i];
+ audio_interface = find_link(node[i], i);
+ switch (audio_interface) {
+ case I2S_AUDIO:
+ *dai = xlnx_snd_dai[I2S_AUDIO][i];
+ dai->platform_of_node = pnode;
+ dai->cpu_of_node = node[i];
+ card->num_links++;
+ snd_soc_card_set_drvdata(card, prv);
+ dev_dbg(card->dev, "%s registered\n",
+ card->dai_link[i].name);
+ break;
+ case HDMI_AUDIO:
+ *dai = xlnx_snd_dai[HDMI_AUDIO][i];
+ dai->platform_of_node = pnode;
+ if (i == XLNX_CAPTURE)
+ dai->codec_of_node = node[i];
+ card->num_links++;
+ /* TODO: support multiple sampling rates */
+ prv->mclk_ratio = 384;
+ snd_soc_card_set_drvdata(card, prv);
+ dev_dbg(card->dev, "%s registered\n",
+ card->dai_link[i].name);
+ break;
+ case SDI_AUDIO:
+ *dai = xlnx_snd_dai[SDI_AUDIO][i];
+ dai->platform_of_node = pnode;
+ dai->codec_of_node = node[i];
+ card->num_links++;
+ /* TODO: support multiple sampling rates */
+ prv->mclk_ratio = 384;
+ snd_soc_card_set_drvdata(card, prv);
+ dev_dbg(card->dev, "%s registered\n",
+ card->dai_link[i].name);
+ break;
+ case SPDIF_AUDIO:
+ *dai = xlnx_snd_dai[SPDIF_AUDIO][i];
+ dai->platform_of_node = pnode;
+ dai->cpu_of_node = node[i];
+ card->num_links++;
+ prv->mclk_ratio = 384;
+ snd_soc_card_set_drvdata(card, prv);
+ dev_dbg(card->dev, "%s registered\n",
+ card->dai_link[i].name);
+ break;
+ default:
+ dev_err(card->dev, "Invalid audio interface\n");
+ return -ENODEV;
+ }
+ }
+
+ if (card->num_links) {
+ /*
+ * Example : i2s card name = xlnx-i2s-snd-card-0
+ * length = number of chars in "xlnx-i2s-snd-card"
+ * + 1 ('-'), + 1 (card instance num)
+ * + 1 ('\0')
+ */
+ sz = strlen(xlnx_snd_card_name[audio_interface]) + 3;
+ buf = devm_kzalloc(card->dev, sz, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ prv->xlnx_snd_dev_id = ida_simple_get(&xlnx_snd_card_dev, 0,
+ XLNX_MAX_PL_SND_DEV,
+ GFP_KERNEL);
+ if (prv->xlnx_snd_dev_id < 0)
+ return prv->xlnx_snd_dev_id;
+
+ snprintf(buf, sz, "%s-%d", xlnx_snd_card_name[audio_interface],
+ prv->xlnx_snd_dev_id);
+ card->name = buf;
+
+ ret = devm_snd_soc_register_card(card->dev, card);
+ if (ret) {
+ dev_err(card->dev, "%s registration failed\n",
+ card->name);
+ ida_simple_remove(&xlnx_snd_card_dev,
+ prv->xlnx_snd_dev_id);
+ return ret;
+ }
+
+ dev_set_drvdata(card->dev, prv);
+ dev_info(card->dev, "%s registered\n", card->name);
+ }
+
+ return 0;
+}
+
+static int xlnx_snd_remove(struct platform_device *pdev)
+{
+ struct pl_card_data *pdata = dev_get_drvdata(&pdev->dev);
+
+ ida_simple_remove(&xlnx_snd_card_dev, pdata->xlnx_snd_dev_id);
+ return 0;
+}
+
+static struct platform_driver xlnx_snd_driver = {
+ .driver = {
+ .name = "xlnx_snd_card",
+ },
+ .probe = xlnx_snd_probe,
+ .remove = xlnx_snd_remove,
+};
+
+module_platform_driver(xlnx_snd_driver);
+
+MODULE_DESCRIPTION("Xilinx FPGA sound card driver");
+MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/xilinx/xlnx_sdi_audio.c b/sound/soc/xilinx/xlnx_sdi_audio.c
new file mode 100644
index 000000000000..75b0b3150b6f
--- /dev/null
+++ b/sound/soc/xilinx/xlnx_sdi_audio.c
@@ -0,0 +1,610 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx SDI embed and extract audio support
+ *
+ * Copyright (c) 2018 Xilinx Pvt., Ltd
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+#include <drm/drm_modes.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define DRIVER_NAME "xlnx-sdi-audio"
+
+#define XSDIAUD_CNTRL_REG_OFFSET 0x00
+#define XSDIAUD_SOFT_RST_REG_OFFSET 0x04
+#define XSDIAUD_VER_REG_OFFSET 0x08
+#define XSDIAUD_INT_EN_REG_OFFSET 0x0C
+#define XSDIAUD_INT_STS_REG_OFFSET 0x10
+#define XSDIAUD_EMB_VID_CNTRL_REG_OFFSET 0X14
+#define XSDIAUD_AUD_CNTRL_REG_OFFSET 0x18
+#define XSDIAUD_CH_VALID_REG_OFFSET 0x20
+#define XSDIAUD_CH_MUTE_REG_OFFSET 0x30
+#define XSDIAUD_ACTIVE_GRP_REG_OFFSET 0X40
+#define XSDIAUD_EXT_CH_STAT0_REG_OFFSET 0X48
+#define XSDIAUD_EXT_SRATE_STS_REG_OFFSET 0X70
+#define XSDIAUD_GUI_PARAM_REG_OFFSET 0XFC
+
+#define XSDIAUD_CNTRL_EN_MASK BIT(0)
+#define XSDIAUD_SOFT_RST_CONFIG_MASK BIT(0)
+#define XSDIAUD_SOFT_RST_CORE_MASK BIT(1)
+#define XSDIAUD_VER_MAJOR_MASK GENMASK(31, 24)
+#define XSDIAUD_VER_MINOR_MASK GENMASK(23, 16)
+
+#define XSDIAUD_EXT_GROUP_1_STS_MASK BIT(0)
+#define XSDIAUD_EXT_AUDSTS_UPDATE_MASK BIT(8)
+#define XSDIAUD_EMB_VID_CNT_ELE_SHIFT (16)
+#define XSDIAUD_EMB_VID_CNT_ELE_MASK BIT(16)
+#define XSDIAUD_EMB_VID_CNT_TSCAN_MASK BIT(8)
+#define XSDIAUD_EMB_VID_CNT_TSCAN_SHIFT (8)
+#define XSDIAUD_EMB_VID_CNT_TRATE_SHIFT (4)
+#define XSDIAUD_EMB_AUD_CNT_SS_MASK BIT(3)
+#define XSDIAUD_EMB_AUD_CNT_ASYNC_AUDIO BIT(4)
+
+#define CH_STATUS_UPDATE_TIMEOUT 40
+
+enum IP_MODE {
+ EMBED,
+ EXTRACT,
+};
+
+enum channel_id {
+ CHAN_ID_0 = 1,
+ CHAN_ID_1,
+};
+
+enum sdi_transport_family {
+ SDI_TRANSPORT_FAMILY_1920,
+ SDI_TRANSPORT_FAMILY_1280,
+ SDI_TRANSPORT_FAMILY_2048,
+ SDI_TRANSPORT_FAMILY_NTSC = 8,
+ SDI_TRANSPORT_FAMILY_PAL = 9,
+};
+
+/**
+ * enum sdi_audio_samplerate - audio sampling rate
+ * @XSDIAUD_SAMPRATE0: 48 KHz
+ * @XSDIAUD_SAMPRATE1: 44.1 KHz
+ * @XSDIAUD_SAMPRATE2: 32 KHz
+ */
+enum sdi_audio_samplerate {
+ XSDIAUD_SAMPRATE0,
+ XSDIAUD_SAMPRATE1,
+ XSDIAUD_SAMPRATE2
+};
+
+/**
+ * enum sdi_audio_samplesize - bits per sample
+ * @XSDIAUD_SAMPSIZE0: 20 Bit Audio Sample
+ * @XSDIAUD_SAMPSIZE1: 24 Bit Audio Sample
+ */
+enum sdi_audio_samplesize {
+ XSDIAUD_SAMPSIZE0,
+ XSDIAUD_SAMPSIZE1
+};
+
+struct dev_ctx {
+ enum IP_MODE mode;
+ void __iomem *base;
+ struct device *dev;
+ struct drm_display_mode *video_mode;
+ struct snd_pcm_substream *stream;
+ struct clk *axi_clk;
+ struct clk *axis_clk;
+ struct clk *aud_clk;
+ bool rx_srate_updated;
+ wait_queue_head_t srate_q;
+};
+
+static irqreturn_t xtract_irq_handler(int irq, void *dev_id)
+{
+ u32 irq_sts, irq_en, active_grps;
+ struct dev_ctx *ctx = dev_id;
+
+ irq_sts = readl(ctx->base + XSDIAUD_INT_STS_REG_OFFSET);
+ active_grps = readl(ctx->base + XSDIAUD_ACTIVE_GRP_REG_OFFSET);
+ if ((irq_sts & XSDIAUD_EXT_AUDSTS_UPDATE_MASK) &&
+ (active_grps & XSDIAUD_EXT_GROUP_1_STS_MASK)) {
+ writel(XSDIAUD_EXT_AUDSTS_UPDATE_MASK,
+ ctx->base + XSDIAUD_INT_STS_REG_OFFSET);
+ irq_en = readl(ctx->base + XSDIAUD_INT_EN_REG_OFFSET);
+ /* Disable further interrupts. sample rate status got updated*/
+ writel(irq_en & ~XSDIAUD_EXT_AUDSTS_UPDATE_MASK,
+ ctx->base + XSDIAUD_INT_EN_REG_OFFSET);
+
+ ctx->rx_srate_updated = true;
+ wake_up_interruptible(&ctx->srate_q);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static void audio_enable(void __iomem *aud_base)
+{
+ u32 val;
+
+ val = readl(aud_base + XSDIAUD_CNTRL_REG_OFFSET);
+ val |= XSDIAUD_CNTRL_EN_MASK;
+ writel(val, aud_base + XSDIAUD_CNTRL_REG_OFFSET);
+}
+
+static void audio_disable(void __iomem *aud_base)
+{
+ u32 val;
+
+ val = readl(aud_base + XSDIAUD_CNTRL_REG_OFFSET);
+ val &= ~XSDIAUD_CNTRL_EN_MASK;
+ writel(val, aud_base + XSDIAUD_CNTRL_REG_OFFSET);
+}
+
+static void audio_reset_core(void __iomem *aud_base, bool reset)
+{
+ u32 val;
+
+ if (reset) {
+ /* reset the core */
+ val = readl(aud_base + XSDIAUD_SOFT_RST_REG_OFFSET);
+ val |= XSDIAUD_SOFT_RST_CORE_MASK;
+ writel(val, aud_base + XSDIAUD_SOFT_RST_REG_OFFSET);
+ } else {
+ /* bring the core out of reset */
+ val = readl(aud_base + XSDIAUD_SOFT_RST_REG_OFFSET);
+ val &= ~XSDIAUD_SOFT_RST_CORE_MASK;
+ writel(val, aud_base + XSDIAUD_SOFT_RST_REG_OFFSET);
+ }
+}
+
+static int xlnx_sdi_rx_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int err;
+ u32 val, sample_rate;
+
+ struct dev_ctx *ctx = dev_get_drvdata(dai->dev);
+ void __iomem *base = ctx->base;
+ unsigned long jiffies = msecs_to_jiffies(CH_STATUS_UPDATE_TIMEOUT);
+
+ audio_enable(base);
+ writel(XSDIAUD_EXT_AUDSTS_UPDATE_MASK,
+ ctx->base + XSDIAUD_INT_EN_REG_OFFSET);
+ err = wait_event_interruptible_timeout(ctx->srate_q,
+ ctx->rx_srate_updated,
+ jiffies);
+
+ if (!err) {
+ dev_err(ctx->dev, "Didn't get valid audio property update\n");
+ return -EINVAL;
+ }
+ ctx->rx_srate_updated = false;
+
+ val = readl(base + XSDIAUD_EXT_SRATE_STS_REG_OFFSET);
+ /* As both channels contain same sample rate, read either of them */
+ switch (val & CHAN_ID_0) {
+ case 0:
+ sample_rate = 48000;
+ break;
+ case 1:
+ sample_rate = 44100;
+ break;
+ case 2:
+ sample_rate = 32000;
+ break;
+ }
+
+ dev_dbg(ctx->dev,
+ "sdi rx audio enabled : sample rate = %d\n", sample_rate);
+ return 0;
+}
+
+static void xlnx_sdi_rx_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct dev_ctx *ctx = dev_get_drvdata(dai->dev);
+
+ audio_disable(ctx->base);
+
+ dev_info(dai->dev, " sdi rx audio disabled\n");
+}
+
+static int xlnx_sdi_tx_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct dev_ctx *ctx = dev_get_drvdata(dai->dev);
+
+ audio_enable(ctx->base);
+ ctx->stream = substream;
+
+ dev_info(ctx->dev, " sdi tx audio enabled\n");
+ return 0;
+}
+
+static int xlnx_sdi_tx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ u32 val = 0;
+ u32 num_channels, sample_rate, sig_bits;
+
+ struct dev_ctx *ctx = dev_get_drvdata(dai->dev);
+ void __iomem *base = ctx->base;
+
+ /* video mode properties needed by audio driver are shared to audio
+ * driver through a pointer in platform data. This is used here in
+ * audio driver. The solution may be needed to modify/extend to avoid
+ * probable error scenarios
+ */
+ if (!ctx->video_mode || !ctx->video_mode->vdisplay ||
+ !ctx->video_mode->vrefresh) {
+ dev_err(ctx->dev, "couldn't find video display properties\n");
+ return -EINVAL;
+ }
+
+ /*
+ * map video properties.
+ * Note: 1920x1080 and 2048x1080 are the resolutions of sub images for
+ * 3840x2160 and 4096x2160 resolutions respectively.
+ */
+ switch (ctx->video_mode->hdisplay) {
+ case 1920:
+ case 3840:
+ val = SDI_TRANSPORT_FAMILY_1920;
+ break;
+ case 1280:
+ val |= SDI_TRANSPORT_FAMILY_1280;
+ break;
+ case 2048:
+ case 4096:
+ val |= SDI_TRANSPORT_FAMILY_2048;
+ break;
+ case 720:
+ if (ctx->video_mode->vdisplay == 486)
+ val |= SDI_TRANSPORT_FAMILY_NTSC;
+ else if (ctx->video_mode->vdisplay == 576)
+ val |= SDI_TRANSPORT_FAMILY_PAL;
+ else
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (ctx->video_mode->vrefresh) {
+ case 24:
+ val |= (3 << XSDIAUD_EMB_VID_CNT_TRATE_SHIFT);
+ break;
+ case 25:
+ val |= (5 << XSDIAUD_EMB_VID_CNT_TRATE_SHIFT);
+ break;
+ case 30:
+ val |= (7 << XSDIAUD_EMB_VID_CNT_TRATE_SHIFT);
+ break;
+ case 48:
+ val |= (8 << XSDIAUD_EMB_VID_CNT_TRATE_SHIFT);
+ break;
+ case 50:
+ val |= (9 << XSDIAUD_EMB_VID_CNT_TRATE_SHIFT);
+ break;
+ case 60:
+ val |= (11 << XSDIAUD_EMB_VID_CNT_TRATE_SHIFT);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!(ctx->video_mode->flags & DRM_MODE_FLAG_INTERLACE))
+ val |= XSDIAUD_EMB_VID_CNT_TSCAN_MASK;
+
+ val |= XSDIAUD_EMB_VID_CNT_ELE_MASK;
+
+ writel(val, base + XSDIAUD_EMB_VID_CNTRL_REG_OFFSET);
+
+ /* map audio properties */
+ num_channels = params_channels(params);
+ sample_rate = params_rate(params);
+ sig_bits = snd_pcm_format_width(params_format(params));
+
+ dev_info(ctx->dev,
+ "stream params: channels = %d sample_rate = %d bits = %d\n",
+ num_channels, sample_rate, sig_bits);
+
+ val = 0;
+ val |= XSDIAUD_EMB_AUD_CNT_ASYNC_AUDIO;
+
+ switch (sample_rate) {
+ case 48000:
+ val |= XSDIAUD_SAMPRATE0;
+ break;
+ case 44100:
+ val |= XSDIAUD_SAMPRATE1;
+ break;
+ case 32000:
+ val |= XSDIAUD_SAMPRATE2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (sig_bits == 24)
+ val |= XSDIAUD_EMB_AUD_CNT_SS_MASK;
+
+ writel(val, base + XSDIAUD_AUD_CNTRL_REG_OFFSET);
+
+ /* TODO: support more channels, currently only 2. */
+ writel(CHAN_ID_1 | CHAN_ID_0, base + XSDIAUD_CH_VALID_REG_OFFSET);
+
+ return 0;
+}
+
+static void xlnx_sdi_tx_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct dev_ctx *ctx = dev_get_drvdata(dai->dev);
+ void __iomem *base = ctx->base;
+
+ audio_disable(base);
+ ctx->stream = NULL;
+
+ dev_info(ctx->dev, " sdi tx audio disabled\n");
+}
+
+static const struct snd_soc_component_driver xlnx_sdi_component = {
+ .name = "xlnx-sdi-dai-component",
+};
+
+static const struct snd_soc_dai_ops xlnx_sdi_rx_dai_ops = {
+ .startup = xlnx_sdi_rx_pcm_startup,
+ .shutdown = xlnx_sdi_rx_pcm_shutdown,
+};
+
+static struct snd_soc_dai_driver xlnx_sdi_rx_dai = {
+ .name = "xlnx_sdi_rx",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .ops = &xlnx_sdi_rx_dai_ops,
+};
+
+static const struct snd_soc_dai_ops xlnx_sdi_tx_dai_ops = {
+ .startup = xlnx_sdi_tx_pcm_startup,
+ .hw_params = xlnx_sdi_tx_hw_params,
+ .shutdown = xlnx_sdi_tx_pcm_shutdown,
+};
+
+static struct snd_soc_dai_driver xlnx_sdi_tx_dai = {
+ .name = "xlnx_sdi_tx",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .ops = &xlnx_sdi_tx_dai_ops,
+};
+
+static int xlnx_sdi_audio_probe(struct platform_device *pdev)
+{
+ u32 val;
+ int ret;
+ struct dev_ctx *ctx;
+ struct resource *res;
+ struct device *video_dev;
+ struct device_node *video_node;
+ struct platform_device *video_pdev;
+ struct snd_soc_dai_driver *snd_dai;
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+
+ /* TODO - remove before upstreaming */
+ if (of_device_is_compatible(node, "xlnx,v-uhdsdi-audio-1.0")) {
+ dev_err(&pdev->dev, "driver doesn't support sdi audio v1.0\n");
+ return -ENODEV;
+ }
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(struct dev_ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENODEV;
+
+ ctx->axi_clk = devm_clk_get(&pdev->dev, "s_axi_aclk");
+ if (IS_ERR(ctx->axi_clk)) {
+ ret = PTR_ERR(ctx->axi_clk);
+ dev_err(&pdev->dev, "failed to get s_axi_aclk(%d)\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(ctx->axi_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to enable s_axi_aclk(%d)\n", ret);
+ return ret;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No IO MEM resource found\n");
+ ret = -ENODEV;
+ goto err_axis;
+ }
+
+ ctx->base = devm_ioremap_resource(&pdev->dev, res);
+ if (!ctx->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -EADDRNOTAVAIL;
+ goto err_axis;
+ }
+
+ ctx->dev = &pdev->dev;
+
+ val = readl(ctx->base + XSDIAUD_GUI_PARAM_REG_OFFSET);
+ if (val & BIT(6)) {
+ ctx->mode = EXTRACT;
+
+ ctx->axis_clk = devm_clk_get(&pdev->dev, "m_axis_clk");
+ if (IS_ERR(ctx->axis_clk)) {
+ ret = PTR_ERR(ctx->axis_clk);
+ dev_err(&pdev->dev, "failed to get m_axis_clk(%d)\n",
+ ret);
+ goto err_axis;
+ }
+
+ ctx->aud_clk = devm_clk_get(&pdev->dev, "sdi_extract_clk");
+ if (IS_ERR(ctx->aud_clk)) {
+ ret = PTR_ERR(ctx->aud_clk);
+ dev_err(&pdev->dev, "failed to get sdi_extract_clk(%d)\n",
+ ret);
+ goto err_axis;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No IRQ resource found\n");
+ ret = -ENODEV;
+ goto err_axis;
+ }
+ ret = devm_request_irq(&pdev->dev, res->start,
+ xtract_irq_handler,
+ 0, "XLNX_SDI_AUDIO_XTRACT", ctx);
+ if (ret) {
+ dev_err(&pdev->dev, "extract irq request failed\n");
+ ret = -ENODEV;
+ goto err_axis;
+ }
+
+ init_waitqueue_head(&ctx->srate_q);
+
+ snd_dai = &xlnx_sdi_rx_dai;
+ } else {
+ ctx->mode = EMBED;
+ ctx->axis_clk = devm_clk_get(&pdev->dev, "s_axis_clk");
+ if (IS_ERR(ctx->axis_clk)) {
+ ret = PTR_ERR(ctx->axis_clk);
+ dev_err(&pdev->dev, "failed to get s_axis_clk(%d)\n",
+ ret);
+ goto err_axis;
+ }
+
+ ctx->aud_clk = devm_clk_get(&pdev->dev, "sdi_embed_clk");
+ if (IS_ERR(ctx->aud_clk)) {
+ ret = PTR_ERR(ctx->aud_clk);
+ dev_err(&pdev->dev, "failed to get aud_clk(%d)\n",
+ ret);
+ goto err_axis;
+ }
+
+ video_node = of_graph_get_remote_node(pdev->dev.of_node, 0, 0);
+ if (!video_node) {
+ dev_err(ctx->dev, "video_node not found\n");
+ of_node_put(video_node);
+ ret = -ENODEV;
+ goto err_axis;
+ }
+
+ video_pdev = of_find_device_by_node(video_node);
+ if (!video_pdev) {
+ of_node_put(video_node);
+ ret = -ENODEV;
+ goto err_axis;
+ }
+
+ video_dev = &video_pdev->dev;
+ ctx->video_mode =
+ (struct drm_display_mode *)video_dev->platform_data;
+ /* invalid 'platform_data' implies video driver is not loaded */
+ if (!ctx->video_mode) {
+ of_node_put(video_node);
+ ret = -EPROBE_DEFER;
+ goto err_axis;
+ }
+
+ snd_dai = &xlnx_sdi_tx_dai;
+ of_node_put(video_node);
+ }
+
+ ret = clk_prepare_enable(ctx->axis_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to enable s_axis_clk(%d)\n", ret);
+ goto err_axis;
+ }
+
+ ret = clk_prepare_enable(ctx->aud_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to enable sdi_extract_clk(%d)\n", ret);
+ goto err_aud_clk;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_sdi_component,
+ snd_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "couldn't register codec DAI\n");
+ goto err_clk;
+ }
+
+ dev_set_drvdata(&pdev->dev, ctx);
+
+ audio_reset_core(ctx->base, true);
+ audio_reset_core(ctx->base, false);
+
+ dev_info(&pdev->dev, "xlnx sdi codec dai component registered\n");
+ return 0;
+
+err_clk:
+ clk_disable_unprepare(ctx->aud_clk);
+err_aud_clk:
+ clk_disable_unprepare(ctx->axis_clk);
+err_axis:
+ clk_disable_unprepare(ctx->axi_clk);
+ return ret;
+}
+
+static int xlnx_sdi_audio_remove(struct platform_device *pdev)
+{
+ struct dev_ctx *ctx = dev_get_drvdata(&pdev->dev);
+
+ audio_disable(ctx->base);
+ audio_reset_core(ctx->base, true);
+
+ clk_disable_unprepare(ctx->aud_clk);
+ clk_disable_unprepare(ctx->axis_clk);
+ clk_disable_unprepare(ctx->axi_clk);
+ return 0;
+}
+
+static const struct of_device_id xlnx_sdi_audio_of_match[] = {
+ { .compatible = "xlnx,v-uhdsdi-audio-1.0"},
+ { .compatible = "xlnx,v-uhdsdi-audio-2.0"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, xlnx_sdi_audio_of_match);
+
+static struct platform_driver xlnx_sdi_audio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = xlnx_sdi_audio_of_match,
+ },
+ .probe = xlnx_sdi_audio_probe,
+ .remove = xlnx_sdi_audio_remove,
+};
+
+module_platform_driver(xlnx_sdi_audio_driver);
+
+MODULE_DESCRIPTION("xilinx sdi audio codec driver");
+MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/xilinx/xlnx_snd_common.h b/sound/soc/xilinx/xlnx_snd_common.h
new file mode 100644
index 000000000000..39461fac0d96
--- /dev/null
+++ b/sound/soc/xilinx/xlnx_snd_common.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx ASoC sound card support
+ *
+ * Copyright (C) 2018 Xilinx, Inc.
+ */
+
+#ifndef _XLNX_SND_COMMON_H
+#define _XLNX_SND_COMMON_H
+
+enum {
+ XLNX_PLAYBACK,
+ XLNX_CAPTURE,
+ XLNX_MAX_PATHS
+};
+
+struct pl_card_data {
+ u32 mclk_val;
+ u32 mclk_ratio;
+ int xlnx_snd_dev_id;
+ struct clk *mclk;
+};
+#endif /* _XLNX_SND_COMMON_H */