aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/xilinx/xlnx_pl_snd_card.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/xilinx/xlnx_pl_snd_card.c')
-rw-r--r--sound/soc/xilinx/xlnx_pl_snd_card.c432
1 files changed, 432 insertions, 0 deletions
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");