summaryrefslogtreecommitdiffstats
path: root/sound/soc/xilinx
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2019-02-08 14:20:32 +0100
committerTakashi Iwai <tiwai@suse.de>2019-02-08 14:20:32 +0100
commitd02cac152c97dffcb0cdd91e09b54fd6e2cca63d (patch)
tree68e4c6bd842703009f3edbf8f0e0e9326e4b2fad /sound/soc/xilinx
parent36e4617c01153757cde9e5fcd375a75a8f8425c3 (diff)
parenta50e32694fbcdbf55875095258b9398e2eabd71f (diff)
downloadlinux-d02cac152c97dffcb0cdd91e09b54fd6e2cca63d.tar.bz2
Merge tag 'asoc-v5.1' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-next
ASoC: Updates for v5.1 Lots and lots of new drivers so far, a highlight being the MediaTek BTCVSD which is a driver for a Bluetooth radio chip - the first such driver we've had upstream. Hopefully we will soon also see a baseband with an upstream driver! - Support for only powering up channels that are actively being used. - Quite a few improvements to simplify the generic card drivers, especially the merge of the SCU cards into the main generic drivers. - Lots of fixes for probing on Intel systems, trying to rationalize things to look more standard from a framework point of view. - New drivers for Asahi Kasei Microdevices AK4497, Cirrus Logic CS4341, Google ChromeOS embedded controllers, Ingenic JZ4725B, MediaTek BTCVSD, MT8183 and MT6358, NXP MICFIL, Rockchip RK3328, Spreadtrum DMA controllers, Qualcomm WCD9335, Xilinx S/PDIF and PCM formatters.
Diffstat (limited to 'sound/soc/xilinx')
-rw-r--r--sound/soc/xilinx/Kconfig14
-rw-r--r--sound/soc/xilinx/Makefile4
-rw-r--r--sound/soc/xilinx/xlnx_formatter_pcm.c708
-rw-r--r--sound/soc/xilinx/xlnx_spdif.c339
4 files changed, 1065 insertions, 0 deletions
diff --git a/sound/soc/xilinx/Kconfig b/sound/soc/xilinx/Kconfig
index 723a583a8d57..47f606b924e4 100644
--- a/sound/soc/xilinx/Kconfig
+++ b/sound/soc/xilinx/Kconfig
@@ -6,3 +6,17 @@ 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.
+
+config SND_SOC_XILINX_AUDIO_FORMATTER
+ tristate "Audio support for the the Xilinx audio formatter"
+ help
+ Select this option to enable Xilinx audio formatter
+ support. This provides DMA platform device support for
+ audio functionality.
+
+config SND_SOC_XILINX_SPDIF
+ tristate "Audio support for the the Xilinx SPDIF"
+ help
+ Select this option to enable Xilinx SPDIF Audio.
+ This provides playback and capture of SPDIF audio in
+ AES format.
diff --git a/sound/soc/xilinx/Makefile b/sound/soc/xilinx/Makefile
index 6c1209b9ee75..d79fd38b094b 100644
--- a/sound/soc/xilinx/Makefile
+++ b/sound/soc/xilinx/Makefile
@@ -1,2 +1,6 @@
snd-soc-xlnx-i2s-objs := xlnx_i2s.o
obj-$(CONFIG_SND_SOC_XILINX_I2S) += snd-soc-xlnx-i2s.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
+obj-$(CONFIG_SND_SOC_XILINX_SPDIF) += snd-soc-xlnx-spdif.o
diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c
new file mode 100644
index 000000000000..97177d35652e
--- /dev/null
+++ b/sound/soc/xilinx/xlnx_formatter_pcm.c
@@ -0,0 +1,708 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Xilinx ASoC audio formatter support
+//
+// Copyright (C) 2018 Xilinx, Inc.
+//
+// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/sizes.h>
+
+#include <sound/asoundef.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#define DRV_NAME "xlnx_formatter_pcm"
+
+#define XLNX_S2MM_OFFSET 0
+#define XLNX_MM2S_OFFSET 0x100
+
+#define XLNX_AUD_CORE_CONFIG 0x4
+#define XLNX_AUD_CTRL 0x10
+#define XLNX_AUD_STS 0x14
+
+#define AUD_CTRL_RESET_MASK BIT(1)
+#define AUD_CFG_MM2S_MASK BIT(15)
+#define AUD_CFG_S2MM_MASK BIT(31)
+
+#define XLNX_AUD_FS_MULTIPLIER 0x18
+#define XLNX_AUD_PERIOD_CONFIG 0x1C
+#define XLNX_AUD_BUFF_ADDR_LSB 0x20
+#define XLNX_AUD_BUFF_ADDR_MSB 0x24
+#define XLNX_AUD_XFER_COUNT 0x28
+#define XLNX_AUD_CH_STS_START 0x2C
+#define XLNX_BYTES_PER_CH 0x44
+
+#define AUD_STS_IOC_IRQ_MASK BIT(31)
+#define AUD_STS_CH_STS_MASK BIT(29)
+#define AUD_CTRL_IOC_IRQ_MASK BIT(13)
+#define AUD_CTRL_TOUT_IRQ_MASK BIT(14)
+#define AUD_CTRL_DMA_EN_MASK BIT(0)
+
+#define CFG_MM2S_CH_MASK GENMASK(11, 8)
+#define CFG_MM2S_CH_SHIFT 8
+#define CFG_MM2S_XFER_MASK GENMASK(14, 13)
+#define CFG_MM2S_XFER_SHIFT 13
+#define CFG_MM2S_PKG_MASK BIT(12)
+
+#define CFG_S2MM_CH_MASK GENMASK(27, 24)
+#define CFG_S2MM_CH_SHIFT 24
+#define CFG_S2MM_XFER_MASK GENMASK(30, 29)
+#define CFG_S2MM_XFER_SHIFT 29
+#define CFG_S2MM_PKG_MASK BIT(28)
+
+#define AUD_CTRL_DATA_WIDTH_SHIFT 16
+#define AUD_CTRL_ACTIVE_CH_SHIFT 19
+#define PERIOD_CFG_PERIODS_SHIFT 16
+
+#define PERIODS_MIN 2
+#define PERIODS_MAX 6
+#define PERIOD_BYTES_MIN 192
+#define PERIOD_BYTES_MAX (50 * 1024)
+#define XLNX_PARAM_UNKNOWN 0
+
+enum bit_depth {
+ BIT_DEPTH_8,
+ BIT_DEPTH_16,
+ BIT_DEPTH_20,
+ BIT_DEPTH_24,
+ BIT_DEPTH_32,
+};
+
+struct xlnx_pcm_drv_data {
+ void __iomem *mmio;
+ bool s2mm_presence;
+ bool mm2s_presence;
+ int s2mm_irq;
+ int mm2s_irq;
+ struct snd_pcm_substream *play_stream;
+ struct snd_pcm_substream *capture_stream;
+ struct clk *axi_clk;
+};
+
+/*
+ * struct xlnx_pcm_stream_param - stream configuration
+ * @mmio: base address offset
+ * @interleaved: audio channels arrangement in buffer
+ * @xfer_mode: data formatting mode during transfer
+ * @ch_limit: Maximum channels supported
+ * @buffer_size: stream ring buffer size
+ */
+struct xlnx_pcm_stream_param {
+ void __iomem *mmio;
+ bool interleaved;
+ u32 xfer_mode;
+ u32 ch_limit;
+ u64 buffer_size;
+};
+
+static const struct snd_pcm_hardware xlnx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .buffer_bytes_max = PERIODS_MAX * PERIOD_BYTES_MAX,
+ .period_bytes_min = PERIOD_BYTES_MIN,
+ .period_bytes_max = PERIOD_BYTES_MAX,
+ .periods_min = PERIODS_MIN,
+ .periods_max = PERIODS_MAX,
+};
+
+enum {
+ AES_TO_AES,
+ AES_TO_PCM,
+ PCM_TO_PCM,
+ PCM_TO_AES
+};
+
+static void xlnx_parse_aes_params(u32 chsts_reg1_val, u32 chsts_reg2_val,
+ struct device *dev)
+{
+ u32 padded, srate, bit_depth, status[2];
+
+ if (chsts_reg1_val & IEC958_AES0_PROFESSIONAL) {
+ status[0] = chsts_reg1_val & 0xff;
+ status[1] = (chsts_reg1_val >> 16) & 0xff;
+
+ switch (status[0] & IEC958_AES0_PRO_FS) {
+ case IEC958_AES0_PRO_FS_44100:
+ srate = 44100;
+ break;
+ case IEC958_AES0_PRO_FS_48000:
+ srate = 48000;
+ break;
+ case IEC958_AES0_PRO_FS_32000:
+ srate = 32000;
+ break;
+ case IEC958_AES0_PRO_FS_NOTID:
+ default:
+ srate = XLNX_PARAM_UNKNOWN;
+ break;
+ }
+
+ switch (status[1] & IEC958_AES2_PRO_SBITS) {
+ case IEC958_AES2_PRO_WORDLEN_NOTID:
+ case IEC958_AES2_PRO_SBITS_20:
+ padded = 0;
+ break;
+ case IEC958_AES2_PRO_SBITS_24:
+ padded = 4;
+ break;
+ default:
+ bit_depth = XLNX_PARAM_UNKNOWN;
+ goto log_params;
+ }
+
+ switch (status[1] & IEC958_AES2_PRO_WORDLEN) {
+ case IEC958_AES2_PRO_WORDLEN_20_16:
+ bit_depth = 16 + padded;
+ break;
+ case IEC958_AES2_PRO_WORDLEN_22_18:
+ bit_depth = 18 + padded;
+ break;
+ case IEC958_AES2_PRO_WORDLEN_23_19:
+ bit_depth = 19 + padded;
+ break;
+ case IEC958_AES2_PRO_WORDLEN_24_20:
+ bit_depth = 20 + padded;
+ break;
+ case IEC958_AES2_PRO_WORDLEN_NOTID:
+ default:
+ bit_depth = XLNX_PARAM_UNKNOWN;
+ break;
+ }
+
+ } else {
+ status[0] = (chsts_reg1_val >> 24) & 0xff;
+ status[1] = chsts_reg2_val & 0xff;
+
+ switch (status[0] & IEC958_AES3_CON_FS) {
+ case IEC958_AES3_CON_FS_44100:
+ srate = 44100;
+ break;
+ case IEC958_AES3_CON_FS_48000:
+ srate = 48000;
+ break;
+ case IEC958_AES3_CON_FS_32000:
+ srate = 32000;
+ break;
+ default:
+ srate = XLNX_PARAM_UNKNOWN;
+ break;
+ }
+
+ if (status[1] & IEC958_AES4_CON_MAX_WORDLEN_24)
+ padded = 4;
+ else
+ padded = 0;
+
+ switch (status[1] & IEC958_AES4_CON_WORDLEN) {
+ case IEC958_AES4_CON_WORDLEN_20_16:
+ bit_depth = 16 + padded;
+ break;
+ case IEC958_AES4_CON_WORDLEN_22_18:
+ bit_depth = 18 + padded;
+ break;
+ case IEC958_AES4_CON_WORDLEN_23_19:
+ bit_depth = 19 + padded;
+ break;
+ case IEC958_AES4_CON_WORDLEN_24_20:
+ bit_depth = 20 + padded;
+ break;
+ case IEC958_AES4_CON_WORDLEN_21_17:
+ bit_depth = 17 + padded;
+ break;
+ case IEC958_AES4_CON_WORDLEN_NOTID:
+ default:
+ bit_depth = XLNX_PARAM_UNKNOWN;
+ break;
+ }
+ }
+
+log_params:
+ if (srate != XLNX_PARAM_UNKNOWN)
+ dev_info(dev, "sample rate = %d\n", srate);
+ else
+ dev_info(dev, "sample rate = unknown\n");
+
+ if (bit_depth != XLNX_PARAM_UNKNOWN)
+ dev_info(dev, "bit_depth = %d\n", bit_depth);
+ else
+ dev_info(dev, "bit_depth = unknown\n");
+}
+
+static int xlnx_formatter_pcm_reset(void __iomem *mmio_base)
+{
+ u32 val, retries = 0;
+
+ val = readl(mmio_base + XLNX_AUD_CTRL);
+ val |= AUD_CTRL_RESET_MASK;
+ writel(val, mmio_base + XLNX_AUD_CTRL);
+
+ val = readl(mmio_base + XLNX_AUD_CTRL);
+ /* Poll for maximum timeout of approximately 100ms (1 * 100)*/
+ while ((val & AUD_CTRL_RESET_MASK) && (retries < 100)) {
+ mdelay(1);
+ retries++;
+ val = readl(mmio_base + XLNX_AUD_CTRL);
+ }
+ if (val & AUD_CTRL_RESET_MASK)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void xlnx_formatter_disable_irqs(void __iomem *mmio_base, int stream)
+{
+ u32 val;
+
+ val = readl(mmio_base + XLNX_AUD_CTRL);
+ val &= ~AUD_CTRL_IOC_IRQ_MASK;
+ if (stream == SNDRV_PCM_STREAM_CAPTURE)
+ val &= ~AUD_CTRL_TOUT_IRQ_MASK;
+
+ writel(val, mmio_base + XLNX_AUD_CTRL);
+}
+
+static irqreturn_t xlnx_mm2s_irq_handler(int irq, void *arg)
+{
+ u32 val;
+ void __iomem *reg;
+ struct device *dev = arg;
+ struct xlnx_pcm_drv_data *adata = dev_get_drvdata(dev);
+
+ reg = adata->mmio + XLNX_MM2S_OFFSET + XLNX_AUD_STS;
+ val = readl(reg);
+ if (val & AUD_STS_IOC_IRQ_MASK) {
+ writel(val & AUD_STS_IOC_IRQ_MASK, reg);
+ if (adata->play_stream)
+ snd_pcm_period_elapsed(adata->play_stream);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t xlnx_s2mm_irq_handler(int irq, void *arg)
+{
+ u32 val;
+ void __iomem *reg;
+ struct device *dev = arg;
+ struct xlnx_pcm_drv_data *adata = dev_get_drvdata(dev);
+
+ reg = adata->mmio + XLNX_S2MM_OFFSET + XLNX_AUD_STS;
+ val = readl(reg);
+ if (val & AUD_STS_IOC_IRQ_MASK) {
+ writel(val & AUD_STS_IOC_IRQ_MASK, reg);
+ if (adata->capture_stream)
+ snd_pcm_period_elapsed(adata->capture_stream);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int xlnx_formatter_pcm_open(struct snd_pcm_substream *substream)
+{
+ int err;
+ u32 val, data_format_mode;
+ u32 ch_count_mask, ch_count_shift, data_xfer_mode, data_xfer_shift;
+ struct xlnx_pcm_stream_param *stream_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *prtd = substream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd,
+ DRV_NAME);
+ struct xlnx_pcm_drv_data *adata = dev_get_drvdata(component->dev);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ !adata->mm2s_presence)
+ return -ENODEV;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE &&
+ !adata->s2mm_presence)
+ return -ENODEV;
+
+ stream_data = kzalloc(sizeof(*stream_data), GFP_KERNEL);
+ if (!stream_data)
+ return -ENOMEM;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ch_count_mask = CFG_MM2S_CH_MASK;
+ ch_count_shift = CFG_MM2S_CH_SHIFT;
+ data_xfer_mode = CFG_MM2S_XFER_MASK;
+ data_xfer_shift = CFG_MM2S_XFER_SHIFT;
+ data_format_mode = CFG_MM2S_PKG_MASK;
+ stream_data->mmio = adata->mmio + XLNX_MM2S_OFFSET;
+ adata->play_stream = substream;
+
+ } else {
+ ch_count_mask = CFG_S2MM_CH_MASK;
+ ch_count_shift = CFG_S2MM_CH_SHIFT;
+ data_xfer_mode = CFG_S2MM_XFER_MASK;
+ data_xfer_shift = CFG_S2MM_XFER_SHIFT;
+ data_format_mode = CFG_S2MM_PKG_MASK;
+ stream_data->mmio = adata->mmio + XLNX_S2MM_OFFSET;
+ adata->capture_stream = substream;
+ }
+
+ val = readl(adata->mmio + XLNX_AUD_CORE_CONFIG);
+
+ if (!(val & data_format_mode))
+ stream_data->interleaved = true;
+
+ stream_data->xfer_mode = (val & data_xfer_mode) >> data_xfer_shift;
+ stream_data->ch_limit = (val & ch_count_mask) >> ch_count_shift;
+ dev_info(component->dev,
+ "stream %d : format = %d mode = %d ch_limit = %d\n",
+ substream->stream, stream_data->interleaved,
+ stream_data->xfer_mode, stream_data->ch_limit);
+
+ snd_soc_set_runtime_hwparams(substream, &xlnx_pcm_hardware);
+ runtime->private_data = stream_data;
+
+ /* Resize the period size divisible by 64 */
+ err = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64);
+ if (err) {
+ dev_err(component->dev,
+ "unable to set constraint on period bytes\n");
+ return err;
+ }
+
+ /* enable DMA IOC irq */
+ val = readl(stream_data->mmio + XLNX_AUD_CTRL);
+ val |= AUD_CTRL_IOC_IRQ_MASK;
+ writel(val, stream_data->mmio + XLNX_AUD_CTRL);
+
+ return 0;
+}
+
+static int xlnx_formatter_pcm_close(struct snd_pcm_substream *substream)
+{
+ int ret;
+ struct xlnx_pcm_stream_param *stream_data =
+ substream->runtime->private_data;
+ struct snd_soc_pcm_runtime *prtd = substream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd,
+ DRV_NAME);
+
+ ret = xlnx_formatter_pcm_reset(stream_data->mmio);
+ if (ret) {
+ dev_err(component->dev, "audio formatter reset failed\n");
+ goto err_reset;
+ }
+ xlnx_formatter_disable_irqs(stream_data->mmio, substream->stream);
+
+err_reset:
+ kfree(stream_data);
+ return 0;
+}
+
+static snd_pcm_uframes_t
+xlnx_formatter_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ u32 pos;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct xlnx_pcm_stream_param *stream_data = runtime->private_data;
+
+ pos = readl(stream_data->mmio + XLNX_AUD_XFER_COUNT);
+
+ if (pos >= stream_data->buffer_size)
+ pos = 0;
+
+ return bytes_to_frames(runtime, pos);
+}
+
+static int xlnx_formatter_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ u32 low, high, active_ch, val, bytes_per_ch, bits_per_sample;
+ u32 aes_reg1_val, aes_reg2_val;
+ int status;
+ u64 size;
+ 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;
+
+ active_ch = params_channels(params);
+ if (active_ch > stream_data->ch_limit)
+ return -EINVAL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE &&
+ stream_data->xfer_mode == AES_TO_PCM) {
+ val = readl(stream_data->mmio + XLNX_AUD_STS);
+ if (val & AUD_STS_CH_STS_MASK) {
+ aes_reg1_val = readl(stream_data->mmio +
+ XLNX_AUD_CH_STS_START);
+ aes_reg2_val = readl(stream_data->mmio +
+ XLNX_AUD_CH_STS_START + 0x4);
+
+ xlnx_parse_aes_params(aes_reg1_val, aes_reg2_val,
+ component->dev);
+ }
+ }
+
+ size = params_buffer_bytes(params);
+ status = snd_pcm_lib_malloc_pages(substream, size);
+ if (status < 0)
+ return status;
+
+ stream_data->buffer_size = size;
+
+ low = lower_32_bits(substream->dma_buffer.addr);
+ high = upper_32_bits(substream->dma_buffer.addr);
+ writel(low, stream_data->mmio + XLNX_AUD_BUFF_ADDR_LSB);
+ writel(high, stream_data->mmio + XLNX_AUD_BUFF_ADDR_MSB);
+
+ val = readl(stream_data->mmio + XLNX_AUD_CTRL);
+ bits_per_sample = params_width(params);
+ switch (bits_per_sample) {
+ case 8:
+ val |= (BIT_DEPTH_8 << AUD_CTRL_DATA_WIDTH_SHIFT);
+ break;
+ case 16:
+ val |= (BIT_DEPTH_16 << AUD_CTRL_DATA_WIDTH_SHIFT);
+ break;
+ case 20:
+ val |= (BIT_DEPTH_20 << AUD_CTRL_DATA_WIDTH_SHIFT);
+ break;
+ case 24:
+ val |= (BIT_DEPTH_24 << AUD_CTRL_DATA_WIDTH_SHIFT);
+ break;
+ case 32:
+ val |= (BIT_DEPTH_32 << AUD_CTRL_DATA_WIDTH_SHIFT);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val |= active_ch << AUD_CTRL_ACTIVE_CH_SHIFT;
+ writel(val, stream_data->mmio + XLNX_AUD_CTRL);
+
+ val = (params_periods(params) << PERIOD_CFG_PERIODS_SHIFT)
+ | params_period_bytes(params);
+ writel(val, stream_data->mmio + XLNX_AUD_PERIOD_CONFIG);
+ bytes_per_ch = DIV_ROUND_UP(params_period_bytes(params), active_ch);
+ writel(bytes_per_ch, stream_data->mmio + XLNX_BYTES_PER_CH);
+
+ return 0;
+}
+
+static int xlnx_formatter_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int xlnx_formatter_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ u32 val;
+ struct xlnx_pcm_stream_param *stream_data =
+ substream->runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ val = readl(stream_data->mmio + XLNX_AUD_CTRL);
+ val |= AUD_CTRL_DMA_EN_MASK;
+ writel(val, stream_data->mmio + XLNX_AUD_CTRL);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ val = readl(stream_data->mmio + XLNX_AUD_CTRL);
+ val &= ~AUD_CTRL_DMA_EN_MASK;
+ writel(val, stream_data->mmio + XLNX_AUD_CTRL);
+ break;
+ }
+
+ return 0;
+}
+
+static int xlnx_formatter_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd,
+ DRV_NAME);
+ return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
+ SNDRV_DMA_TYPE_DEV, component->dev,
+ xlnx_pcm_hardware.buffer_bytes_max,
+ xlnx_pcm_hardware.buffer_bytes_max);
+}
+
+static const struct snd_pcm_ops xlnx_formatter_pcm_ops = {
+ .open = xlnx_formatter_pcm_open,
+ .close = xlnx_formatter_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = xlnx_formatter_pcm_hw_params,
+ .hw_free = xlnx_formatter_pcm_hw_free,
+ .trigger = xlnx_formatter_pcm_trigger,
+ .pointer = xlnx_formatter_pcm_pointer,
+};
+
+static const struct snd_soc_component_driver xlnx_asoc_component = {
+ .name = DRV_NAME,
+ .ops = &xlnx_formatter_pcm_ops,
+ .pcm_new = xlnx_formatter_pcm_new,
+};
+
+static int xlnx_formatter_pcm_probe(struct platform_device *pdev)
+{
+ int ret;
+ u32 val;
+ struct xlnx_pcm_drv_data *aud_drv_data;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+
+ aud_drv_data = devm_kzalloc(dev, sizeof(*aud_drv_data), GFP_KERNEL);
+ if (!aud_drv_data)
+ return -ENOMEM;
+
+ aud_drv_data->axi_clk = devm_clk_get(dev, "s_axi_lite_aclk");
+ if (IS_ERR(aud_drv_data->axi_clk)) {
+ ret = PTR_ERR(aud_drv_data->axi_clk);
+ dev_err(dev, "failed to get s_axi_lite_aclk(%d)\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(aud_drv_data->axi_clk);
+ if (ret) {
+ dev_err(dev,
+ "failed to enable s_axi_lite_aclk(%d)\n", ret);
+ return ret;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "audio formatter node:addr to resource failed\n");
+ ret = -ENXIO;
+ goto clk_err;
+ }
+ aud_drv_data->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(aud_drv_data->mmio)) {
+ dev_err(dev, "audio formatter ioremap failed\n");
+ ret = PTR_ERR(aud_drv_data->mmio);
+ goto clk_err;
+ }
+
+ 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;
+ 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");
+ 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);
+
+ ret = devm_snd_soc_register_component(dev, &xlnx_asoc_component,
+ NULL, 0);
+ if (ret) {
+ dev_err(dev, "pcm platform device register failed\n");
+ goto clk_err;
+ }
+
+ return 0;
+
+clk_err:
+ clk_disable_unprepare(aud_drv_data->axi_clk);
+ return ret;
+}
+
+static int xlnx_formatter_pcm_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct xlnx_pcm_drv_data *adata = dev_get_drvdata(&pdev->dev);
+
+ if (adata->s2mm_presence)
+ ret = xlnx_formatter_pcm_reset(adata->mmio + XLNX_S2MM_OFFSET);
+
+ /* Try MM2S reset, even if S2MM reset fails */
+ if (adata->mm2s_presence)
+ ret = xlnx_formatter_pcm_reset(adata->mmio + XLNX_MM2S_OFFSET);
+
+ if (ret)
+ dev_err(&pdev->dev, "audio formatter reset failed\n");
+
+ clk_disable_unprepare(adata->axi_clk);
+ return ret;
+}
+
+static const struct of_device_id xlnx_formatter_pcm_of_match[] = {
+ { .compatible = "xlnx,audio-formatter-1.0"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, xlnx_formatter_pcm_of_match);
+
+static struct platform_driver xlnx_formatter_pcm_driver = {
+ .probe = xlnx_formatter_pcm_probe,
+ .remove = xlnx_formatter_pcm_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = xlnx_formatter_pcm_of_match,
+ },
+};
+
+module_platform_driver(xlnx_formatter_pcm_driver);
+MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/xilinx/xlnx_spdif.c b/sound/soc/xilinx/xlnx_spdif.c
new file mode 100644
index 000000000000..3b9000fd8c49
--- /dev/null
+++ b/sound/soc/xilinx/xlnx_spdif.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Xilinx ASoC SPDIF audio support
+//
+// Copyright (C) 2018 Xilinx, Inc.
+//
+// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>
+//
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define XLNX_SPDIF_RATES \
+ (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+#define XLNX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+#define XSPDIF_IRQ_STS_REG 0x20
+#define XSPDIF_IRQ_ENABLE_REG 0x28
+#define XSPDIF_SOFT_RESET_REG 0x40
+#define XSPDIF_CONTROL_REG 0x44
+#define XSPDIF_CHAN_0_STS_REG 0x4C
+#define XSPDIF_GLOBAL_IRQ_ENABLE_REG 0x1C
+#define XSPDIF_CH_A_USER_DATA_REG_0 0x64
+
+#define XSPDIF_CORE_ENABLE_MASK BIT(0)
+#define XSPDIF_FIFO_FLUSH_MASK BIT(1)
+#define XSPDIF_CH_STS_MASK BIT(5)
+#define XSPDIF_GLOBAL_IRQ_ENABLE BIT(31)
+#define XSPDIF_CLOCK_CONFIG_BITS_MASK GENMASK(5, 2)
+#define XSPDIF_CLOCK_CONFIG_BITS_SHIFT 2
+#define XSPDIF_SOFT_RESET_VALUE 0xA
+
+#define MAX_CHANNELS 2
+#define AES_SAMPLE_WIDTH 32
+#define CH_STATUS_UPDATE_TIMEOUT 40
+
+struct spdif_dev_data {
+ u32 mode;
+ u32 aclk;
+ bool rx_chsts_updated;
+ void __iomem *base;
+ struct clk *axi_clk;
+ wait_queue_head_t chsts_q;
+};
+
+static irqreturn_t xlnx_spdifrx_irq_handler(int irq, void *arg)
+{
+ u32 val;
+ struct spdif_dev_data *ctx = arg;
+
+ val = readl(ctx->base + XSPDIF_IRQ_STS_REG);
+ if (val & XSPDIF_CH_STS_MASK) {
+ writel(val & XSPDIF_CH_STS_MASK,
+ ctx->base + XSPDIF_IRQ_STS_REG);
+ val = readl(ctx->base +
+ XSPDIF_IRQ_ENABLE_REG);
+ writel(val & ~XSPDIF_CH_STS_MASK,
+ ctx->base + XSPDIF_IRQ_ENABLE_REG);
+
+ ctx->rx_chsts_updated = true;
+ wake_up_interruptible(&ctx->chsts_q);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int xlnx_spdif_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u32 val;
+ struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
+
+ val = readl(ctx->base + XSPDIF_CONTROL_REG);
+ val |= XSPDIF_FIFO_FLUSH_MASK;
+ writel(val, ctx->base + XSPDIF_CONTROL_REG);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ writel(XSPDIF_CH_STS_MASK,
+ ctx->base + XSPDIF_IRQ_ENABLE_REG);
+ writel(XSPDIF_GLOBAL_IRQ_ENABLE,
+ ctx->base + XSPDIF_GLOBAL_IRQ_ENABLE_REG);
+ }
+
+ return 0;
+}
+
+static void xlnx_spdif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
+
+ writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG);
+}
+
+static int xlnx_spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ u32 val, clk_div, clk_cfg;
+ struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
+
+ clk_div = DIV_ROUND_CLOSEST(ctx->aclk, MAX_CHANNELS * AES_SAMPLE_WIDTH *
+ params_rate(params));
+
+ switch (clk_div) {
+ case 4:
+ clk_cfg = 0;
+ break;
+ case 8:
+ clk_cfg = 1;
+ break;
+ case 16:
+ clk_cfg = 2;
+ break;
+ case 24:
+ clk_cfg = 3;
+ break;
+ case 32:
+ clk_cfg = 4;
+ break;
+ case 48:
+ clk_cfg = 5;
+ break;
+ case 64:
+ clk_cfg = 6;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = readl(ctx->base + XSPDIF_CONTROL_REG);
+ val &= ~XSPDIF_CLOCK_CONFIG_BITS_MASK;
+ val |= clk_cfg << XSPDIF_CLOCK_CONFIG_BITS_SHIFT;
+ writel(val, ctx->base + XSPDIF_CONTROL_REG);
+
+ return 0;
+}
+
+static int rx_stream_detect(struct snd_soc_dai *dai)
+{
+ int err;
+ struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
+ unsigned long jiffies = msecs_to_jiffies(CH_STATUS_UPDATE_TIMEOUT);
+
+ /* start capture only if stream is detected within 40ms timeout */
+ err = wait_event_interruptible_timeout(ctx->chsts_q,
+ ctx->rx_chsts_updated,
+ jiffies);
+ if (!err) {
+ dev_err(dai->dev, "No streaming audio detected!\n");
+ return -EINVAL;
+ }
+ ctx->rx_chsts_updated = false;
+
+ return 0;
+}
+
+static int xlnx_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ u32 val;
+ int ret = 0;
+ struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
+
+ val = readl(ctx->base + XSPDIF_CONTROL_REG);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ val |= XSPDIF_CORE_ENABLE_MASK;
+ writel(val, ctx->base + XSPDIF_CONTROL_REG);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ ret = rx_stream_detect(dai);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ val &= ~XSPDIF_CORE_ENABLE_MASK;
+ writel(val, ctx->base + XSPDIF_CONTROL_REG);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops xlnx_spdif_dai_ops = {
+ .startup = xlnx_spdif_startup,
+ .shutdown = xlnx_spdif_shutdown,
+ .trigger = xlnx_spdif_trigger,
+ .hw_params = xlnx_spdif_hw_params,
+};
+
+static struct snd_soc_dai_driver xlnx_spdif_tx_dai = {
+ .name = "xlnx_spdif_tx",
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = XLNX_SPDIF_RATES,
+ .formats = XLNX_SPDIF_FORMATS,
+ },
+ .ops = &xlnx_spdif_dai_ops,
+};
+
+static struct snd_soc_dai_driver xlnx_spdif_rx_dai = {
+ .name = "xlnx_spdif_rx",
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = XLNX_SPDIF_RATES,
+ .formats = XLNX_SPDIF_FORMATS,
+ },
+ .ops = &xlnx_spdif_dai_ops,
+};
+
+static const struct snd_soc_component_driver xlnx_spdif_component = {
+ .name = "xlnx-spdif",
+};
+
+static const struct of_device_id xlnx_spdif_of_match[] = {
+ { .compatible = "xlnx,spdif-2.0", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xlnx_spdif_of_match);
+
+static int xlnx_spdif_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+ struct snd_soc_dai_driver *dai_drv;
+ struct spdif_dev_data *ctx;
+
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->axi_clk = devm_clk_get(dev, "s_axi_aclk");
+ if (IS_ERR(ctx->axi_clk)) {
+ ret = PTR_ERR(ctx->axi_clk);
+ dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(ctx->axi_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable s_axi_aclk(%d)\n", ret);
+ return ret;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ctx->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(ctx->base)) {
+ ret = PTR_ERR(ctx->base);
+ goto clk_err;
+ }
+ ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode);
+ if (ret < 0) {
+ dev_err(dev, "cannot get SPDIF mode\n");
+ goto clk_err;
+ }
+ if (ctx->mode) {
+ dai_drv = &xlnx_spdif_tx_dai;
+ } else {
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(dev, "No IRQ resource found\n");
+ ret = -ENODEV;
+ goto clk_err;
+ }
+ ret = devm_request_irq(dev, res->start,
+ xlnx_spdifrx_irq_handler,
+ 0, "XLNX_SPDIF_RX", ctx);
+ if (ret) {
+ dev_err(dev, "spdif rx irq request failed\n");
+ ret = -ENODEV;
+ goto clk_err;
+ }
+
+ init_waitqueue_head(&ctx->chsts_q);
+ dai_drv = &xlnx_spdif_rx_dai;
+ }
+
+ ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk);
+ if (ret < 0) {
+ dev_err(dev, "cannot get aud_clk_i value\n");
+ goto clk_err;
+ }
+
+ dev_set_drvdata(dev, ctx);
+
+ ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component,
+ dai_drv, 1);
+ if (ret) {
+ dev_err(dev, "SPDIF component registration failed\n");
+ goto clk_err;
+ }
+
+ writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG);
+ dev_info(dev, "%s DAI registered\n", dai_drv->name);
+
+clk_err:
+ clk_disable_unprepare(ctx->axi_clk);
+ return ret;
+}
+
+static int xlnx_spdif_remove(struct platform_device *pdev)
+{
+ struct spdif_dev_data *ctx = dev_get_drvdata(&pdev->dev);
+
+ clk_disable_unprepare(ctx->axi_clk);
+ return 0;
+}
+
+static struct platform_driver xlnx_spdif_driver = {
+ .driver = {
+ .name = "xlnx-spdif",
+ .of_match_table = xlnx_spdif_of_match,
+ },
+ .probe = xlnx_spdif_probe,
+ .remove = xlnx_spdif_remove,
+};
+module_platform_driver(xlnx_spdif_driver);
+
+MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");
+MODULE_DESCRIPTION("XILINX SPDIF driver");
+MODULE_LICENSE("GPL v2");