ASoC: starfive: Add StarFive JH7100 audio drivers
authorWalker Chen <walker.chen@starfivetech.com>
Wed, 17 Nov 2021 07:50:50 +0000 (15:50 +0800)
committerŁukasz Stelmach <l.stelmach@samsung.com>
Thu, 9 Feb 2023 19:07:48 +0000 (20:07 +0100)
Signed-off-by: Michael Yan <michael.yan@starfivetech.com>
Signed-off-by: Jenny Zhang <jenny.zhang@starfivetech.com>
Signed-off-by: Walker Chen <walker.chen@starfivetech.com>
Signed-off-by: Emil Renner Berthing <kernel@esmil.dk>
16 files changed:
sound/soc/Kconfig
sound/soc/Makefile
sound/soc/starfive/Kconfig [new file with mode: 0644]
sound/soc/starfive/Makefile [new file with mode: 0644]
sound/soc/starfive/i2svad-pcm.c [new file with mode: 0644]
sound/soc/starfive/i2svad.c [new file with mode: 0644]
sound/soc/starfive/i2svad.h [new file with mode: 0644]
sound/soc/starfive/pdm.c [new file with mode: 0644]
sound/soc/starfive/pdm.h [new file with mode: 0644]
sound/soc/starfive/pwmdac-pcm.c [new file with mode: 0644]
sound/soc/starfive/pwmdac-transmitter.c [new file with mode: 0644]
sound/soc/starfive/pwmdac.c [new file with mode: 0644]
sound/soc/starfive/pwmdac.h [new file with mode: 0644]
sound/soc/starfive/spdif-pcm.c [new file with mode: 0644]
sound/soc/starfive/spdif.c [new file with mode: 0644]
sound/soc/starfive/spdif.h [new file with mode: 0644]

index 848fbae..8d1d940 100644 (file)
@@ -91,6 +91,7 @@ source "sound/soc/sh/Kconfig"
 source "sound/soc/sof/Kconfig"
 source "sound/soc/spear/Kconfig"
 source "sound/soc/sprd/Kconfig"
+source "sound/soc/starfive/Kconfig"
 source "sound/soc/sti/Kconfig"
 source "sound/soc/stm/Kconfig"
 source "sound/soc/sunxi/Kconfig"
index 507eaed..e4ad5fa 100644 (file)
@@ -55,6 +55,7 @@ obj-$(CONFIG_SND_SOC) += pxa/
 obj-$(CONFIG_SND_SOC)  += qcom/
 obj-$(CONFIG_SND_SOC)  += rockchip/
 obj-$(CONFIG_SND_SOC)  += samsung/
+obj-$(CONFIG_SND_SOC)  += starfive/
 obj-$(CONFIG_SND_SOC)  += sh/
 obj-$(CONFIG_SND_SOC)  += sof/
 obj-$(CONFIG_SND_SOC)  += spear/
diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig
new file mode 100644 (file)
index 0000000..2dfbf9d
--- /dev/null
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 StarFive Technology Co., Ltd.
+
+config SND_STARFIVE_SPDIF
+       tristate "starfive spdif"
+       depends on SOC_STARFIVE || COMPILE_TEST
+       select SND_SOC_GENERIC_DMAENGINE_PCM
+       select REGMAP_MMIO
+       help
+         Say Y or M if you want to add support for codecs attached to the
+         I2S interface on VIC vic_starlight board. You will also need to select
+         the drivers for the rest of VIC audio subsystem.
+
+config SND_STARFIVE_SPDIF_PCM
+       bool "PCM PIO extension for spdif driver"
+       depends on SND_STARFIVE_SPDIF
+       help
+        Say Y or N if you want to add a custom ALSA extension that registers
+        a PCM and uses PIO to transfer data.
+
+config SND_STARFIVE_PWMDAC
+       tristate "starfive pwmdac Device Driver"
+       depends on SOC_STARFIVE || COMPILE_TEST
+       select SND_SOC_GENERIC_DMAENGINE_PCM
+       help
+        Say Y or M if you want to add support for sf pwmdac driver.
+
+config SND_STARFIVE_PWMDAC_PCM
+       bool "PCM PIO extension for pwmdac driver"
+       depends on SND_STARFIVE_PWMDAC
+       help
+        Say Y or N if you want to add a custom ALSA extension that registers
+        a PCM and uses PIO to transfer data.
+
+config SND_STARFIVE_PDM
+       tristate "starfive pdm Device Driver"
+       depends on SOC_STARFIVE || COMPILE_TEST
+       select REGMAP_MMIO
+       help
+        Say Y or M if you want to add support for sf pdm driver.
+
+config SND_STARFIVE_I2SVAD
+       tristate "starfive I2SVAD Device Driver"
+       depends on SOC_STARFIVE || COMPILE_TEST
+       select SND_SOC_GENERIC_DMAENGINE_PCM
+       help
+        Say Y or M if you want to add support for I2SVAD driver for
+        starfive I2SVAD device.
+
+config SND_STARFIVE_I2SVAD_PCM
+       bool "PCM PIO extension for I2SVAD driver"
+       depends on SND_STARFIVE_I2SVAD
+       help
+        Say Y or N if you want to add a custom ALSA extension that registers
+        a PCM and uses PIO to transfer data.
+
+        This functionality is specially suited for I2SVAD devices that don't have
+        DMA support.
+
diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile
new file mode 100644 (file)
index 0000000..60d85f4
--- /dev/null
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2021 StarFive Technology Co., Ltd.
+#
+snd-soc-starfive-spdif-y := spdif.o
+snd-soc-starfive-spdif-$(CONFIG_SND_STARFIVE_SPDIF_PCM) += spdif-pcm.o
+
+obj-$(CONFIG_SND_STARFIVE_SPDIF) += snd-soc-starfive-spdif.o
+
+snd-soc-starfive-pwmdac-y := pwmdac.o
+snd-soc-starfive-pwmdac-$(CONFIG_SND_STARFIVE_PWMDAC_PCM) += pwmdac-pcm.o
+snd-soc-starfive-pwmdac-transmitter-y := pwmdac-transmitter.o
+
+obj-$(CONFIG_SND_STARFIVE_PWMDAC) += snd-soc-starfive-pwmdac.o
+obj-$(CONFIG_SND_STARFIVE_PWMDAC) += snd-soc-starfive-pwmdac-transmitter.o
+
+snd-soc-starfive-pdm-y := pdm.o
+
+obj-$(CONFIG_SND_STARFIVE_PDM) += snd-soc-starfive-pdm.o
+
+snd-soc-starfive-i2svad-y := i2svad.o
+snd-soc-starfive-i2svad-$(CONFIG_SND_STARFIVE_I2SVAD_PCM) += i2svad-pcm.o
+
+obj-$(CONFIG_SND_STARFIVE_I2SVAD) += snd-soc-starfive-i2svad.o
diff --git a/sound/soc/starfive/i2svad-pcm.c b/sound/soc/starfive/i2svad-pcm.c
new file mode 100644 (file)
index 0000000..61f6339
--- /dev/null
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/io.h>
+#include <linux/rcupdate.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "i2svad.h"
+
+#define BUFFER_BYTES_MAX       (3 * 2 * 8 * PERIOD_BYTES_MIN)
+#define PERIOD_BYTES_MIN       4096
+#define PERIODS_MIN            2
+
+#define i2svad_pcm_tx_fn(sample_bits) \
+static unsigned int i2svad_pcm_tx_##sample_bits(struct i2svad_dev *dev, \
+               struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \
+               bool *period_elapsed) \
+{ \
+       const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \
+       unsigned int period_pos = tx_ptr % runtime->period_size; \
+       int i; \
+\
+       for (i = 0; i < dev->fifo_th; i++) { \
+               iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \
+               iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \
+               period_pos++; \
+               if (++tx_ptr >= runtime->buffer_size) \
+                       tx_ptr = 0; \
+       } \
+       *period_elapsed = period_pos >= runtime->period_size; \
+       return tx_ptr; \
+}
+
+#define i2svad_pcm_rx_fn(sample_bits) \
+static unsigned int i2svad_pcm_rx_##sample_bits(struct i2svad_dev *dev, \
+               struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \
+               bool *period_elapsed) \
+{ \
+       u##sample_bits (*p)[2] = (void *)runtime->dma_area; \
+       unsigned int period_pos = rx_ptr % runtime->period_size; \
+       int i; \
+\
+       for (i = 0; i < dev->fifo_th; i++) { \
+               p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \
+               p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \
+               period_pos++; \
+               if (++rx_ptr >= runtime->buffer_size) \
+                       rx_ptr = 0; \
+       } \
+       *period_elapsed = period_pos >= runtime->period_size; \
+       return rx_ptr; \
+}
+
+i2svad_pcm_tx_fn(16);
+i2svad_pcm_rx_fn(16);
+
+#undef i2svad_pcm_tx_fn
+#undef i2svad_pcm_rx_fn
+
+static const struct snd_pcm_hardware i2svad_pcm_hardware = {
+       .info = SNDRV_PCM_INFO_INTERLEAVED |
+               SNDRV_PCM_INFO_MMAP |
+               SNDRV_PCM_INFO_MMAP_VALID |
+               SNDRV_PCM_INFO_BLOCK_TRANSFER,
+       .rates = SNDRV_PCM_RATE_32000 |
+               SNDRV_PCM_RATE_44100 |
+               SNDRV_PCM_RATE_48000,
+       .rate_min = 32000,
+       .rate_max = 48000,
+       .formats = SNDRV_PCM_FMTBIT_S16_LE,
+       .channels_min = 2,
+       .channels_max = 2,
+       .buffer_bytes_max = BUFFER_BYTES_MAX,
+       .period_bytes_min = PERIOD_BYTES_MIN,
+       .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+       .periods_min = PERIODS_MIN,
+       .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+       .fifo_size = 16,
+};
+
+static void i2svad_pcm_transfer(struct i2svad_dev *dev, bool push)
+{
+       struct snd_pcm_substream *substream;
+       bool active, period_elapsed;
+
+       rcu_read_lock();
+       if (push)
+               substream = rcu_dereference(dev->tx_substream);
+       else
+               substream = rcu_dereference(dev->rx_substream);
+       active = substream && snd_pcm_running(substream);
+       if (active) {
+               unsigned int ptr;
+               unsigned int new_ptr;
+
+               if (push) {
+                       ptr = READ_ONCE(dev->tx_ptr);
+                       new_ptr = dev->tx_fn(dev, substream->runtime, ptr,
+                                       &period_elapsed);
+                       cmpxchg(&dev->tx_ptr, ptr, new_ptr);
+               } else {
+                       ptr = READ_ONCE(dev->rx_ptr);
+                       new_ptr = dev->rx_fn(dev, substream->runtime, ptr,
+                                       &period_elapsed);
+                       cmpxchg(&dev->rx_ptr, ptr, new_ptr);
+               }
+
+               if (period_elapsed)
+                       snd_pcm_period_elapsed(substream);
+       }
+       rcu_read_unlock();
+}
+
+void i2svad_pcm_push_tx(struct i2svad_dev *dev)
+{
+       i2svad_pcm_transfer(dev, true);
+}
+
+void i2svad_pcm_pop_rx(struct i2svad_dev *dev)
+{
+       i2svad_pcm_transfer(dev, false);
+}
+
+static int i2svad_pcm_open(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+       struct i2svad_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+
+       snd_soc_set_runtime_hwparams(substream, &i2svad_pcm_hardware);
+       snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+       runtime->private_data = dev;
+
+       return 0;
+}
+
+static int i2svad_pcm_close(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream)
+{
+       synchronize_rcu();
+       return 0;
+}
+
+static int i2svad_pcm_hw_params(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream,
+                       struct snd_pcm_hw_params *hw_params)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct i2svad_dev *dev = runtime->private_data;
+
+       switch (params_channels(hw_params)) {
+       case 2:
+               break;
+       default:
+               dev_err(dev->dev, "invalid channels number\n");
+               return -EINVAL;
+       }
+
+       switch (params_format(hw_params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               dev->tx_fn = i2svad_pcm_tx_16;
+               dev->rx_fn = i2svad_pcm_rx_16;
+               break;
+       default:
+               dev_err(dev->dev, "invalid format\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int i2svad_pcm_trigger(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream, int cmd)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct i2svad_dev *dev = runtime->private_data;
+       int ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       WRITE_ONCE(dev->tx_ptr, 0);
+                       rcu_assign_pointer(dev->tx_substream, substream);
+               } else {
+                       WRITE_ONCE(dev->rx_ptr, 0);
+                       rcu_assign_pointer(dev->rx_substream, substream);
+               }
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       rcu_assign_pointer(dev->tx_substream, NULL);
+               else
+                       rcu_assign_pointer(dev->rx_substream, NULL);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static snd_pcm_uframes_t i2svad_pcm_pointer(struct snd_soc_component *component,
+                                       struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct i2svad_dev *dev = runtime->private_data;
+       snd_pcm_uframes_t pos;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               pos = READ_ONCE(dev->tx_ptr);
+       else
+               pos = READ_ONCE(dev->rx_ptr);
+
+       return pos < runtime->buffer_size ? pos : 0;
+}
+
+static int i2svad_pcm_new(struct snd_soc_component *component,
+                       struct snd_soc_pcm_runtime *rtd)
+{
+       size_t size = i2svad_pcm_hardware.buffer_bytes_max;
+
+       snd_pcm_set_managed_buffer_all(rtd->pcm,
+                       SNDRV_DMA_TYPE_CONTINUOUS,
+                       NULL, size, size);
+       return 0;
+}
+
+static const struct snd_soc_component_driver i2svad_pcm_component = {
+       .open           = i2svad_pcm_open,
+       .close          = i2svad_pcm_close,
+       .hw_params      = i2svad_pcm_hw_params,
+       .trigger        = i2svad_pcm_trigger,
+       .pointer        = i2svad_pcm_pointer,
+       .pcm_construct  = i2svad_pcm_new,
+};
+
+int i2svad_pcm_register(struct platform_device *pdev)
+{
+       return devm_snd_soc_register_component(&pdev->dev, &i2svad_pcm_component,
+                                       NULL, 0);
+}
diff --git a/sound/soc/starfive/i2svad.c b/sound/soc/starfive/i2svad.c
new file mode 100644 (file)
index 0000000..74978d3
--- /dev/null
@@ -0,0 +1,1042 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <sound/designware_i2s.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include <linux/kthread.h>
+
+#include "i2svad.h"
+
+/* vad control function*/
+static void vad_start(struct vad_params *vad)
+{
+       regmap_update_bits(vad->vad_map, VAD_MEM_SW,
+                       VAD_MEM_SW_MASK, VAD_MEM_SW_TO_VAD);
+       regmap_update_bits(vad->vad_map, VAD_SW,
+                       VAD_SW_MASK, VAD_SW_VAD_XMEM_ENABLE|VAD_SW_ADC_ENABLE);
+       regmap_update_bits(vad->vad_map, VAD_SPINT_EN,
+                       VAD_SPINT_EN_MASK, VAD_SPINT_EN_ENABLE);
+       regmap_update_bits(vad->vad_map, VAD_SLINT_EN,
+                       VAD_SLINT_EN_MASK, VAD_SLINT_EN_ENABLE);
+}
+
+static void vad_stop(struct vad_params *vad)
+{
+       regmap_update_bits(vad->vad_map, VAD_SPINT_EN,
+                       VAD_SPINT_EN_MASK, VAD_SLINT_EN_DISABLE);
+       regmap_update_bits(vad->vad_map, VAD_SLINT_EN,
+                       VAD_SLINT_EN_MASK, VAD_SLINT_EN_DISABLE);
+       regmap_update_bits(vad->vad_map, VAD_SW,
+                       VAD_SW_MASK, VAD_SW_VAD_XMEM_DISABLE|VAD_SW_ADC_DISABLE);
+       regmap_update_bits(vad->vad_map, VAD_MEM_SW,
+                       VAD_MEM_SW_MASK, VAD_MEM_SW_TO_AXI);
+}
+
+static void vad_status(struct vad_params *vad)
+{
+       u32 sp_value,sp_en;
+       u32 sl_value,sl_en;
+
+       regmap_read(vad->vad_map, VAD_SPINT,&sp_value);
+       regmap_read(vad->vad_map, VAD_SPINT_EN,&sp_en);
+       if (sp_value&sp_en){
+               regmap_update_bits(vad->vad_map, VAD_SPINT_CLR,
+                               VAD_SPINT_CLR_MASK, VAD_SPINT_CLR_VAD_SPINT);
+               vad->vstatus = VAD_STATUS_SPINT;
+               vad_stop(vad);
+               vad_start(vad);
+       }
+
+       regmap_read(vad->vad_map, VAD_SLINT,&sl_value);
+       regmap_read(vad->vad_map, VAD_SLINT_EN,&sl_en);
+       if (sl_value&sl_en){
+               regmap_update_bits(vad->vad_map, VAD_SLINT_CLR,
+                               VAD_SLINT_CLR_MASK, VAD_SLINT_CLR_VAD_SLINT);
+               vad->vstatus = VAD_STATUS_SLINT;
+       }
+}
+
+static int vad_trigger(struct vad_params *vad,int cmd)
+{
+       int ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if(vad->vswitch)
+               {
+                       vad_start(vad);
+               }
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               vad_stop(vad);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static void vad_init(struct vad_params *vad)
+{
+       /* left_margin */
+       regmap_update_bits(vad->vad_map, VAD_LEFT_MARGIN,
+                       VAD_LEFT_MARGIN_MASK, 0x0);
+       /* right_margin */
+       regmap_update_bits(vad->vad_map, VAD_RIGHT_MARGIN,
+                       VAD_RIGHT_MARGIN_MASK, 0x0);
+       /*low-energy transition range threshold ——NL*/
+       regmap_update_bits(vad->vad_map, VAD_N_LOW_CONT_FRAMES,
+                       VAD_N_LOW_CONT_FRAMES_MASK, 0x3);
+       /* low-energy transition range */
+       regmap_update_bits(vad->vad_map, VAD_N_LOW_SEEK_FRAMES,
+                       VAD_N_LOW_SEEK_FRAMES_MASK, 0x8);
+       /* high-energy transition range threshold——NH */
+       regmap_update_bits(vad->vad_map, VAD_N_HIGH_CONT_FRAMES,
+                       VAD_N_HIGH_CONT_FRAMES_MASK, 0x5);
+       /* high-energy transition range */
+       regmap_update_bits(vad->vad_map, VAD_N_HIGH_SEEK_FRAMES,
+                       VAD_N_HIGH_SEEK_FRAMES_MASK, 0x1E);
+       /*low-energy voice range threshold——NVL*/
+       regmap_update_bits(vad->vad_map, VAD_N_SPEECH_LOW_HIGH_FRAMES,
+                       VAD_N_SPEECH_LOW_HIGH_FRAMES_MASK, 0x2);
+       /*low-energy voice range*/
+       regmap_update_bits(vad->vad_map, VAD_N_SPEECH_LOW_SEEK_FRAMES,
+                       VAD_N_SPEECH_LOW_SEEK_FRAMES_MASK, 0x12);
+       /*mean silence frame range*/
+       regmap_update_bits(vad->vad_map, VAD_MEAN_SIL_FRAMES,
+                       VAD_MEAN_SIL_FRAMES_MASK, 0xA);
+       /*low-energy threshold scaling factor,12bit(0~0xFFF)*/
+       regmap_update_bits(vad->vad_map, VAD_N_ALPHA,
+                       VAD_N_ALPHA_MASK, 0x1A);
+       /*high-energy threshold scaling factor,12bit(0~0xFFF)*/
+       regmap_update_bits(vad->vad_map, VAD_N_BETA,
+                       VAD_N_BETA_MASK, 0x34);
+       regmap_update_bits(vad->vad_map, VAD_LEFT_WD,
+                       VAD_LEFT_WD_MASK, VAD_LEFT_WD_BIT_15_0);
+       regmap_update_bits(vad->vad_map, VAD_RIGHT_WD,
+                       VAD_RIGHT_WD_MASK, VAD_RIGHT_WD_BIT_15_0);
+       regmap_update_bits(vad->vad_map, VAD_LR_SEL,
+                       VAD_LR_SEL_MASK, VAD_LR_SEL_L);
+       regmap_update_bits(vad->vad_map, VAD_STOP_DELAY,
+                       VAD_STOP_DELAY_MASK, VAD_STOP_DELAY_0_SAMPLE);
+       regmap_update_bits(vad->vad_map, VAD_ADDR_START,
+                       VAD_ADDR_START_MASK, 0x0);
+       regmap_update_bits(vad->vad_map, VAD_ADDR_WRAP,
+                       VAD_ADDR_WRAP_MASK, 0x2000);
+       regmap_update_bits(vad->vad_map, VAD_MEM_SW,
+                       VAD_MEM_SW_MASK, VAD_MEM_SW_TO_AXI);
+       regmap_update_bits(vad->vad_map, VAD_SPINT_CLR,
+                       VAD_SPINT_CLR_MASK, VAD_SPINT_CLR_VAD_SPINT);
+       regmap_update_bits(vad->vad_map, VAD_SLINT_CLR,
+                       VAD_SLINT_CLR_MASK, VAD_SLINT_CLR_VAD_SLINT);
+}
+
+
+static int vad_switch_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 1;
+
+       return 0;
+}
+
+static int vad_switch_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct i2svad_dev *dev = snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.integer.value[0] = dev->vad.vswitch;
+
+       return 0;
+}
+
+static int vad_switch_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct i2svad_dev *dev = snd_soc_component_get_drvdata(component);
+       int val;
+
+       val = ucontrol->value.integer.value[0];
+       if (val && !dev->vad.vswitch) {
+               dev->vad.vswitch = true;
+       } else if (!val && dev->vad.vswitch) {
+               dev->vad.vswitch = false;
+               vad_stop(&(dev->vad));
+       }
+
+       return 0;
+}
+
+
+static int vad_status_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 2;
+
+       return 0;
+}
+
+static int vad_status_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct i2svad_dev *dev = snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.integer.value[0] = dev->vad.vstatus;
+       dev->vad.vstatus = VAD_STATUS_NORMAL;
+
+       return 0;
+}
+
+
+#define SOC_VAD_SWITCH_DECL(xname) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = vad_switch_info, .get = vad_switch_get, \
+       .put = vad_switch_put, }
+
+#define SOC_VAD_STATUS_DECL(xname) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = vad_status_info, .get = vad_status_get, }
+
+
+static const struct snd_kcontrol_new vad_snd_controls[] = {
+       SOC_VAD_SWITCH_DECL("vad switch"),
+       SOC_VAD_STATUS_DECL("vad status"),
+};
+
+static int vad_probe(struct snd_soc_component *component)
+{
+       struct i2svad_dev *priv = snd_soc_component_get_drvdata(component);
+
+       snd_soc_component_init_regmap(component, priv->vad.vad_map);
+       snd_soc_add_component_controls(component, vad_snd_controls,
+                               ARRAY_SIZE(vad_snd_controls));
+
+       return 0;
+}
+
+/* i2s control function*/
+static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
+{
+       writel(val, io_base + reg);
+}
+
+static inline u32 i2s_read_reg(void __iomem *io_base, int reg)
+{
+       return readl(io_base + reg);
+}
+
+static inline void i2s_disable_channels(struct i2svad_dev *dev, u32 stream)
+{
+       u32 i = 0;
+
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               for (i = 0; i < ALL_CHANNEL_NUM; i++)
+                       i2s_write_reg(dev->i2s_base, TER(i), 0);
+       } else {
+               for (i = 0; i < ALL_CHANNEL_NUM; i++)
+                       i2s_write_reg(dev->i2s_base, RER(i), 0);
+       }
+}
+
+static inline void i2s_clear_irqs(struct i2svad_dev *dev, u32 stream)
+{
+       u32 i = 0;
+
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               for (i = 0; i < ALL_CHANNEL_NUM; i++)
+                       i2s_read_reg(dev->i2s_base, TOR(i));
+       } else {
+               for (i = 0; i < ALL_CHANNEL_NUM; i++)
+                       i2s_read_reg(dev->i2s_base, ROR(i));
+       }
+}
+
+static inline void i2s_disable_irqs(struct i2svad_dev *dev, u32 stream,
+                               int chan_nr)
+{
+       u32 i, irq;
+
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               for (i = 0; i < (chan_nr / 2); i++) {
+                       irq = i2s_read_reg(dev->i2s_base, IMR(i));
+                       i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30);
+               }
+       } else {
+               for (i = 0; i < (chan_nr / 2); i++) {
+                       irq = i2s_read_reg(dev->i2s_base, IMR(i));
+                       i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03);
+               }
+       }
+}
+
+static inline void i2s_enable_irqs(struct i2svad_dev *dev, u32 stream,
+                               int chan_nr)
+{
+       u32 i, irq;
+
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               for (i = 0; i < (chan_nr / 2); i++) {
+                       irq = i2s_read_reg(dev->i2s_base, IMR(i));
+                       i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
+               }
+       } else {
+               for (i = 0; i < (chan_nr / 2); i++) {
+                       irq = i2s_read_reg(dev->i2s_base, IMR(i));
+                       i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
+               }
+       }
+}
+
+static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
+{
+       struct i2svad_dev *dev = dev_id;
+       bool irq_valid = false;
+       u32 isr[4];
+       int i;
+
+       for (i = 0; i < ALL_CHANNEL_NUM; i++)
+               isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
+
+       i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
+       i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
+
+       for (i = 0; i < 4; i++) {
+               /*
+                * Check if TX fifo is empty. If empty fill FIFO with samples
+                * NOTE: Only two channels supported
+                */
+               if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) {
+                       i2svad_pcm_push_tx(dev);
+                       irq_valid = true;
+               }
+
+               /*
+                * Data available. Retrieve samples from FIFO
+                * NOTE: Only two channels supported
+                */
+               if ((isr[i] & ISR_RXDA) && (i == 0) && dev->use_pio) {
+                       i2svad_pcm_pop_rx(dev);
+                       irq_valid = true;
+               }
+
+               /* Error Handling: TX */
+               if (isr[i] & ISR_TXFO) {
+                       //dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i);
+                       irq_valid = true;
+               }
+
+               /* Error Handling: TX */
+               if (isr[i] & ISR_RXFO) {
+                       //dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i);
+                       irq_valid = true;
+               }
+       }
+
+       vad_status(&(dev->vad));
+
+       if (irq_valid)
+               return IRQ_HANDLED;
+       else
+               return IRQ_NONE;
+}
+
+static void i2s_start(struct i2svad_dev *dev,
+                       struct snd_pcm_substream *substream)
+{
+       struct i2s_clk_config_data *config = &dev->config;
+
+       i2s_write_reg(dev->i2s_base, IER, 1);
+       i2s_enable_irqs(dev, substream->stream, config->chan_nr);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               i2s_write_reg(dev->i2s_base, ITER, 1);
+       else
+               i2s_write_reg(dev->i2s_base, IRER, 1);
+
+       i2s_write_reg(dev->i2s_base, CER, 1);
+}
+
+static void i2s_stop(struct i2svad_dev *dev,
+               struct snd_pcm_substream *substream)
+{
+
+       i2s_clear_irqs(dev, substream->stream);
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               i2s_write_reg(dev->i2s_base, ITER, 0);
+       else
+               i2s_write_reg(dev->i2s_base, IRER, 0);
+
+       i2s_disable_irqs(dev, substream->stream, 8);
+
+       if (!dev->active) {
+               i2s_write_reg(dev->i2s_base, CER, 0);
+               i2s_write_reg(dev->i2s_base, IER, 0);
+       }
+}
+
+static int dw_i2s_startup(struct snd_pcm_substream *substream,
+               struct snd_soc_dai *cpu_dai)
+{
+       struct i2svad_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+       union dw_i2s_snd_dma_data *dma_data = NULL;
+
+
+       if (!(dev->capability & DWC_I2S_RECORD) &&
+                       (substream->stream == SNDRV_PCM_STREAM_CAPTURE))
+               return -EINVAL;
+
+       if (!(dev->capability & DWC_I2S_PLAY) &&
+                       (substream->stream == SNDRV_PCM_STREAM_PLAYBACK))
+               return -EINVAL;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               dma_data = &dev->play_dma_data;
+       else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+               dma_data = &dev->capture_dma_data;
+
+       snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
+
+       return 0;
+}
+
+static void dw_i2s_config(struct i2svad_dev *dev, int stream)
+{
+       u32 ch_reg;
+       struct i2s_clk_config_data *config = &dev->config;
+
+
+       i2s_disable_channels(dev, stream);
+
+       for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) {
+               if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       i2s_write_reg(dev->i2s_base, TCR(ch_reg),
+                                       dev->xfer_resolution);
+                       i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
+                                       dev->fifo_th - 1);
+                       i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
+               } else {
+                       i2s_write_reg(dev->i2s_base, RCR(ch_reg),
+                                       dev->xfer_resolution);
+                       i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
+                                       dev->fifo_th - 1);
+                       i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
+               }
+
+       }
+}
+
+static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
+               struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+       struct i2svad_dev *dev = snd_soc_dai_get_drvdata(dai);
+       struct i2s_clk_config_data *config = &dev->config;
+       int ret;
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               config->data_width = 16;
+               dev->ccr = 0x00;
+               dev->xfer_resolution = 0x02;
+               break;
+
+       case SNDRV_PCM_FORMAT_S24_LE:
+               config->data_width = 24;
+               dev->ccr = 0x08;
+               dev->xfer_resolution = 0x04;
+               break;
+
+       case SNDRV_PCM_FORMAT_S32_LE:
+               config->data_width = 32;
+               dev->ccr = 0x10;
+               dev->xfer_resolution = 0x05;
+               break;
+
+       default:
+               dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
+               return -EINVAL;
+       }
+
+       config->chan_nr = params_channels(params);
+
+       switch (config->chan_nr) {
+       case EIGHT_CHANNEL_SUPPORT:
+       case SIX_CHANNEL_SUPPORT:
+       case FOUR_CHANNEL_SUPPORT:
+       case TWO_CHANNEL_SUPPORT:
+               break;
+       default:
+               dev_err(dev->dev, "channel not supported\n");
+               return -EINVAL;
+       }
+
+       dw_i2s_config(dev, substream->stream);
+
+       i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
+
+       config->sample_rate = params_rate(params);
+
+       if (dev->capability & DW_I2S_MASTER) {
+               if (dev->i2s_clk_cfg) {
+                       ret = dev->i2s_clk_cfg(config);
+                       if (ret < 0) {
+                               dev_err(dev->dev, "runtime audio clk config fail\n");
+                               return ret;
+                       }
+               } else {
+                       u32 bitclk = config->sample_rate *
+                                       config->data_width * 2;
+
+                       ret = clk_set_rate(dev->clk, bitclk);
+                       if (ret) {
+                               dev_err(dev->dev, "Can't set I2S clock rate: %d\n",
+                                       ret);
+                               return ret;
+                       }
+               }
+       }
+       return 0;
+}
+
+static void dw_i2s_shutdown(struct snd_pcm_substream *substream,
+               struct snd_soc_dai *dai)
+{
+       snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int dw_i2s_prepare(struct snd_pcm_substream *substream,
+                       struct snd_soc_dai *dai)
+{
+       struct i2svad_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               i2s_write_reg(dev->i2s_base, TXFFR, 1);
+       else
+               i2s_write_reg(dev->i2s_base, RXFFR, 1);
+
+       return 0;
+}
+
+static int dw_i2s_trigger(struct snd_pcm_substream *substream,
+               int cmd, struct snd_soc_dai *dai)
+{
+       struct i2svad_dev *dev = snd_soc_dai_get_drvdata(dai);
+       int ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               dev->active++;
+               i2s_start(dev, substream);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               dev->active--;
+               i2s_stop(dev, substream);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+       {
+               vad_trigger(&(dev->vad),cmd);
+       }
+       return ret;
+}
+
+static int dw_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+       struct i2svad_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+       int ret = 0;
+
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBM_CFM:
+               if (dev->capability & DW_I2S_SLAVE)
+                       ret = 0;
+               else
+                       ret = -EINVAL;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFS:
+               if (dev->capability & DW_I2S_MASTER)
+                       ret = 0;
+               else
+                       ret = -EINVAL;
+               break;
+       case SND_SOC_DAIFMT_CBM_CFS:
+       case SND_SOC_DAIFMT_CBS_CFM:
+               ret = -EINVAL;
+               break;
+       default:
+               dev_dbg(dev->dev, "dwc : Invalid master/slave format\n");
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static const struct snd_soc_dai_ops dw_i2s_dai_ops = {
+       .startup        = dw_i2s_startup,
+       .shutdown       = dw_i2s_shutdown,
+       .hw_params      = dw_i2s_hw_params,
+       .prepare        = dw_i2s_prepare,
+       .trigger        = dw_i2s_trigger,
+       .set_fmt        = dw_i2s_set_fmt,
+};
+
+#ifdef CONFIG_PM
+static int dw_i2s_runtime_suspend(struct device *dev)
+{
+       struct i2svad_dev *dw_dev = dev_get_drvdata(dev);
+
+       if (dw_dev->capability & DW_I2S_MASTER)
+               clk_disable(dw_dev->clk);
+       return 0;
+}
+
+static int dw_i2s_runtime_resume(struct device *dev)
+{
+       struct i2svad_dev *dw_dev = dev_get_drvdata(dev);
+
+       if (dw_dev->capability & DW_I2S_MASTER)
+               clk_enable(dw_dev->clk);
+       return 0;
+}
+
+static int dw_i2s_suspend(struct snd_soc_component *component)
+{
+       struct i2svad_dev *dev = snd_soc_component_get_drvdata(component);
+
+       if (dev->capability & DW_I2S_MASTER)
+               clk_disable(dev->clk);
+       return 0;
+}
+
+static int dw_i2s_resume(struct snd_soc_component *component)
+{
+       struct i2svad_dev *dev = snd_soc_component_get_drvdata(component);
+       struct snd_soc_dai *dai;
+       int stream;
+
+       if (dev->capability & DW_I2S_MASTER)
+               clk_enable(dev->clk);
+
+       for_each_component_dais(component, dai) {
+               for_each_pcm_streams(stream)
+                       if (snd_soc_dai_stream_active(dai, stream))
+                               dw_i2s_config(dev, stream);
+       }
+
+       return 0;
+}
+
+#else
+#define dw_i2s_suspend NULL
+#define dw_i2s_resume  NULL
+#endif
+
+static int dw_i2svad_probe(struct snd_soc_component *component)
+{
+       vad_probe(component);
+       return 0;
+}
+
+static const struct snd_soc_component_driver dw_i2s_component = {
+       .name           = "dw-i2s",
+       .probe          = dw_i2svad_probe,
+       .suspend        = dw_i2s_suspend,
+       .resume         = dw_i2s_resume,
+};
+
+/*
+ * The following tables allow a direct lookup of various parameters
+ * defined in the I2S block's configuration in terms of sound system
+ * parameters.  Each table is sized to the number of entries possible
+ * according to the number of configuration bits describing an I2S
+ * block parameter.
+ */
+
+/* Maximum bit resolution of a channel - not uniformly spaced */
+static const u32 fifo_width[COMP_MAX_WORDSIZE] = {
+       12, 16, 20, 24, 32, 0, 0, 0
+};
+
+/* Width of (DMA) bus */
+static const u32 bus_widths[COMP_MAX_DATA_WIDTH] = {
+       DMA_SLAVE_BUSWIDTH_1_BYTE,
+       DMA_SLAVE_BUSWIDTH_2_BYTES,
+       DMA_SLAVE_BUSWIDTH_4_BYTES,
+       DMA_SLAVE_BUSWIDTH_UNDEFINED
+};
+
+/* PCM format to support channel resolution */
+static const u32 formats[COMP_MAX_WORDSIZE] = {
+       SNDRV_PCM_FMTBIT_S16_LE,
+       SNDRV_PCM_FMTBIT_S16_LE,
+       SNDRV_PCM_FMTBIT_S24_LE,
+       SNDRV_PCM_FMTBIT_S24_LE,
+       SNDRV_PCM_FMTBIT_S32_LE,
+       0,
+       0,
+       0
+};
+
+static const struct regmap_config sf_i2s_regmap_cfg = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = 0x1000,
+};
+
+static int dw_configure_dai(struct i2svad_dev *dev,
+                               struct snd_soc_dai_driver *dw_i2s_dai,
+                               unsigned int rates)
+{
+       /*
+        * Read component parameter registers to extract
+        * the I2S block's configuration.
+        */
+       u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+       u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2);
+       u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
+       u32 idx;
+
+       if (dev->capability & DWC_I2S_RECORD &&
+                       dev->quirks & DW_I2S_QUIRK_COMP_PARAM1)
+               comp1 = comp1 & ~BIT(5);
+
+       if (dev->capability & DWC_I2S_PLAY &&
+                       dev->quirks & DW_I2S_QUIRK_COMP_PARAM1)
+               comp1 = comp1 & ~BIT(6);
+
+       if (COMP1_TX_ENABLED(comp1)) {
+               dev_dbg(dev->dev, " designware: play supported\n");
+               idx = COMP1_TX_WORDSIZE_0(comp1);
+               if (WARN_ON(idx >= ARRAY_SIZE(formats)))
+                       return -EINVAL;
+               if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+                       idx = 1;
+               dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM;
+               dw_i2s_dai->playback.channels_max =
+                               1 << (COMP1_TX_CHANNELS(comp1) + 1);
+               //dw_i2s_dai->playback.formats = formats[idx];
+               dw_i2s_dai->playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
+               dw_i2s_dai->playback.rates = rates;
+       }
+
+       if (COMP1_RX_ENABLED(comp1)) {
+               dev_dbg(dev->dev, "designware: record supported\n");
+               idx = COMP2_RX_WORDSIZE_0(comp2);
+               if (WARN_ON(idx >= ARRAY_SIZE(formats)))
+                       return -EINVAL;
+               if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+                       idx = 1;
+               dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM;
+               dw_i2s_dai->capture.channels_max =
+                               1 << (COMP1_RX_CHANNELS(comp1) + 1);
+               //dw_i2s_dai->capture.formats = formats[idx];
+               dw_i2s_dai->capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
+               dw_i2s_dai->capture.rates = rates;
+       }
+
+       if (COMP1_MODE_EN(comp1)) {
+               dev_dbg(dev->dev, "designware: i2s master mode supported\n");
+               dev->capability |= DW_I2S_MASTER;
+       } else {
+               dev_dbg(dev->dev, "designware: i2s slave mode supported\n");
+               dev->capability |= DW_I2S_SLAVE;
+       }
+
+       dev->fifo_th = fifo_depth / 2;
+       return 0;
+}
+
+static int dw_configure_dai_by_pd(struct i2svad_dev *dev,
+                               struct snd_soc_dai_driver *dw_i2s_dai,
+                               struct resource *res,
+                               const struct i2s_platform_data *pdata)
+{
+       u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+       u32 idx = COMP1_APB_DATA_WIDTH(comp1);
+       int ret;
+
+       if (WARN_ON(idx >= ARRAY_SIZE(bus_widths)))
+               return -EINVAL;
+
+       ret = dw_configure_dai(dev, dw_i2s_dai, pdata->snd_rates);
+       if (ret < 0)
+               return ret;
+
+       if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE)
+               idx = 1;
+       /* Set DMA slaves info */
+       dev->play_dma_data.pd.data = pdata->play_dma_data;
+       dev->capture_dma_data.pd.data = pdata->capture_dma_data;
+       dev->play_dma_data.pd.addr = res->start + I2S_TXDMA;
+       dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA;
+       dev->play_dma_data.pd.max_burst = 16;
+       dev->capture_dma_data.pd.max_burst = 16;
+       dev->play_dma_data.pd.addr_width = bus_widths[idx];
+       dev->capture_dma_data.pd.addr_width = bus_widths[idx];
+       dev->play_dma_data.pd.filter = pdata->filter;
+       dev->capture_dma_data.pd.filter = pdata->filter;
+
+       return 0;
+}
+
+static int dw_configure_dai_by_dt(struct i2svad_dev *dev,
+                               struct snd_soc_dai_driver *dw_i2s_dai,
+                               struct resource *res)
+{
+       u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1);
+       u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2);
+       u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
+       u32 idx = COMP1_APB_DATA_WIDTH(comp1);
+       u32 idx2;
+       int ret;
+
+       if (WARN_ON(idx >= ARRAY_SIZE(bus_widths)))
+               return -EINVAL;
+
+       ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000);
+       if (ret < 0)
+               return ret;
+
+       if (COMP1_TX_ENABLED(comp1)) {
+               idx2 = COMP1_TX_WORDSIZE_0(comp1);
+
+               dev->capability |= DWC_I2S_PLAY;
+               dev->play_dma_data.dt.addr = res->start + I2S_TXDMA;
+               dev->play_dma_data.dt.addr_width = bus_widths[idx];
+               dev->play_dma_data.dt.fifo_size = fifo_depth *
+                       (fifo_width[idx2]) >> 8;
+               dev->play_dma_data.dt.maxburst = 16;
+       }
+       if (COMP1_RX_ENABLED(comp1)) {
+               idx2 = COMP2_RX_WORDSIZE_0(comp2);
+
+               dev->capability |= DWC_I2S_RECORD;
+               dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA;
+               dev->capture_dma_data.dt.addr_width = bus_widths[idx];
+               dev->capture_dma_data.dt.fifo_size = fifo_depth *
+                       (fifo_width[idx2] >> 8);
+               dev->capture_dma_data.dt.maxburst = 16;
+       }
+
+       return 0;
+
+}
+
+static int dw_i2s_probe(struct platform_device *pdev)
+{
+       const struct i2s_platform_data *pdata = pdev->dev.platform_data;
+       struct i2svad_dev *dev;
+       struct resource *res;
+       int ret, irq;
+       struct snd_soc_dai_driver *dw_i2s_dai;
+       const char *clk_id;
+
+       dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL);
+       if (!dw_i2s_dai)
+               return -ENOMEM;
+
+       dw_i2s_dai->ops = &dw_i2s_dai_ops;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       dev->i2s_base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(dev->i2s_base))
+               return PTR_ERR(dev->i2s_base);
+
+       dev->vad.vad_base = dev->i2s_base;
+       dev->vad.vad_map = devm_regmap_init_mmio(&pdev->dev, dev->i2s_base, &sf_i2s_regmap_cfg);
+       if (IS_ERR(dev->vad.vad_map)) {
+               dev_err(&pdev->dev, "failed to init regmap: %ld\n",
+                       PTR_ERR(dev->vad.vad_map));
+               return PTR_ERR(dev->vad.vad_map);
+       }
+
+       dev->dev = &pdev->dev;
+
+       dev->clk_apb_i2svad = devm_clk_get(&pdev->dev, "i2svad_apb");
+       if (IS_ERR(dev->clk_apb_i2svad))
+               return dev_err_probe(&pdev->dev, PTR_ERR(dev->clk_apb_i2svad),
+                                    "failed to get apb clock\n");
+
+       dev->rst_apb_i2svad = devm_reset_control_get_exclusive(&pdev->dev, "apb_i2svad");
+       if (IS_ERR(dev->rst_apb_i2svad))
+               return dev_err_probe(&pdev->dev, PTR_ERR(dev->rst_apb_i2svad),
+                                    "failed to get apb reset\n");
+
+       dev->rst_i2svad_srst = devm_reset_control_get_exclusive(&pdev->dev, "i2svad_srst");
+       if (IS_ERR(dev->rst_i2svad_srst))
+               return dev_err_probe(&pdev->dev, PTR_ERR(dev->rst_i2svad_srst),
+                                    "failed to get source reset\n");
+
+       ret = clk_prepare_enable(dev->clk_apb_i2svad);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to enable apb clock\n");
+
+       ret = reset_control_deassert(dev->rst_apb_i2svad);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to deassert apb reset\n");
+
+       ret = reset_control_deassert(dev->rst_i2svad_srst);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to deassert source reset\n");
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq >= 0) {
+               ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0,
+                               pdev->name, dev);
+               if (ret < 0) {
+                       dev_err(&pdev->dev, "failed to request irq\n");
+                       return ret;
+               }
+       }
+
+       dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
+       dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
+       if (pdata) {
+               dev->capability = pdata->cap;
+               clk_id = NULL;
+               dev->quirks = pdata->quirks;
+               if (dev->quirks & DW_I2S_QUIRK_COMP_REG_OFFSET) {
+                       dev->i2s_reg_comp1 = pdata->i2s_reg_comp1;
+                       dev->i2s_reg_comp2 = pdata->i2s_reg_comp2;
+               }
+               ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata);
+       } else {
+               clk_id = "i2sclk";
+               ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res);
+       }
+       if (ret < 0)
+               return ret;
+
+       if (dev->capability & DW_I2S_MASTER) {
+               if (pdata) {
+                       dev->i2s_clk_cfg = pdata->i2s_clk_cfg;
+                       if (!dev->i2s_clk_cfg) {
+                               dev_err(&pdev->dev, "no clock configure method\n");
+                               return -ENODEV;
+                       }
+               }
+               dev->clk = devm_clk_get(&pdev->dev, clk_id);
+
+               if (IS_ERR(dev->clk))
+                       return PTR_ERR(dev->clk);
+
+               ret = clk_prepare_enable(dev->clk);
+               if (ret < 0)
+                       return ret;
+       }
+
+       dev_set_drvdata(&pdev->dev, dev);
+       ret = devm_snd_soc_register_component(&pdev->dev, &dw_i2s_component,
+                                        dw_i2s_dai, 1);
+       if (ret != 0) {
+               dev_err(&pdev->dev, "not able to register dai\n");
+               goto err_clk_disable;
+       }
+
+       if (!pdata) {
+               if (irq >= 0) {
+                       ret = i2svad_pcm_register(pdev);
+                       dev->use_pio = true;
+               } else {
+                       ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
+                                       0);
+                       dev->use_pio = false;
+               }
+
+               if (ret) {
+                       dev_err(&pdev->dev, "could not register pcm: %d\n",
+                                       ret);
+                       goto err_clk_disable;
+               }
+       }
+
+       vad_init(&(dev->vad));
+       pm_runtime_enable(&pdev->dev);
+
+       return 0;
+
+err_clk_disable:
+       if (dev->capability & DW_I2S_MASTER)
+               clk_disable_unprepare(dev->clk);
+       return ret;
+}
+
+static int dw_i2s_remove(struct platform_device *pdev)
+{
+       struct i2svad_dev *dev = dev_get_drvdata(&pdev->dev);
+
+       if (dev->capability & DW_I2S_MASTER)
+               clk_disable_unprepare(dev->clk);
+
+       pm_runtime_disable(&pdev->dev);
+       return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dw_i2s_of_match[] = {
+       { .compatible = "starfive,sf-i2svad", },
+       {},
+};
+
+MODULE_DEVICE_TABLE(of, dw_i2s_of_match);
+#endif
+
+static const struct dev_pm_ops dwc_pm_ops = {
+       SET_RUNTIME_PM_OPS(dw_i2s_runtime_suspend, dw_i2s_runtime_resume, NULL)
+};
+
+static struct platform_driver dw_i2s_driver = {
+       .probe          = dw_i2s_probe,
+       .remove         = dw_i2s_remove,
+       .driver         = {
+               .name   = "sf-i2svad",
+               .of_match_table = of_match_ptr(dw_i2s_of_match),
+               .pm = &dwc_pm_ops,
+       },
+};
+
+module_platform_driver(dw_i2s_driver);
+
+MODULE_AUTHOR("jenny zhang <jenny.zhang@starfivetech.com>");
+MODULE_DESCRIPTION("starfive I2SVAD SoC Interface");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sf-i2svad");
diff --git a/sound/soc/starfive/i2svad.h b/sound/soc/starfive/i2svad.h
new file mode 100644 (file)
index 0000000..cd14cb4
--- /dev/null
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#ifndef __SND_SOC_STARFIVE_I2SVAD_H
+#define __SND_SOC_STARFIVE_I2SVAD_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/designware_i2s.h>
+
+/* common register for all channel */
+#define IER            0x000
+#define IRER           0x004
+#define ITER           0x008
+#define CER            0x00C
+#define CCR            0x010
+#define RXFFR          0x014
+#define TXFFR          0x018
+
+/* Interrupt status register fields */
+#define ISR_TXFO       BIT(5)
+#define ISR_TXFE       BIT(4)
+#define ISR_RXFO       BIT(1)
+#define ISR_RXDA       BIT(0)
+
+/* I2STxRxRegisters for all channels */
+#define LRBR_LTHR(x)   (0x40 * x + 0x020)
+#define RRBR_RTHR(x)   (0x40 * x + 0x024)
+#define RER(x)         (0x40 * x + 0x028)
+#define TER(x)         (0x40 * x + 0x02C)
+#define RCR(x)         (0x40 * x + 0x030)
+#define TCR(x)         (0x40 * x + 0x034)
+#define ISR(x)         (0x40 * x + 0x038)
+#define IMR(x)         (0x40 * x + 0x03C)
+#define ROR(x)         (0x40 * x + 0x040)
+#define TOR(x)         (0x40 * x + 0x044)
+#define RFCR(x)                (0x40 * x + 0x048)
+#define TFCR(x)                (0x40 * x + 0x04C)
+#define RFF(x)         (0x40 * x + 0x050)
+#define TFF(x)         (0x40 * x + 0x054)
+
+/* I2SCOMPRegisters */
+#define I2S_COMP_PARAM_2       0x01F0
+#define I2S_COMP_PARAM_1       0x01F4
+#define I2S_COMP_VERSION       0x01F8
+#define I2S_COMP_TYPE          0x01FC
+
+/* VAD Registers */
+#define VAD_LEFT_MARGIN                                0x800  /* left_margin */
+#define VAD_RIGHT_MARGIN                       0x804  /* right_margin */
+#define VAD_N_LOW_CONT_FRAMES                  0x808  /* low-energy transition range threshold ——NL*/
+#define VAD_N_LOW_SEEK_FRAMES                  0x80C  /* low-energy transition range */
+#define VAD_N_HIGH_CONT_FRAMES                 0x810  /* high-energy transition range threshold——NH */
+#define VAD_N_HIGH_SEEK_FRAMES                 0x814  /* high-energy transition range */
+#define VAD_N_SPEECH_LOW_HIGH_FRAMES           0x818  /* low-energy voice range threshold——NVL*/
+#define VAD_N_SPEECH_LOW_SEEK_FRAMES           0x81C  /* low-energy voice range*/
+#define VAD_MEAN_SIL_FRAMES                    0x820  /* mean silence frame range*/
+#define VAD_N_ALPHA                            0x824  /* low-energy threshold scaling factor,12bit(0~0xFFF)*/
+#define VAD_N_BETA                             0x828  /* high-energy threshold scaling factor,12bit(0~0xFFF)*/
+#define VAD_FIFO_DEPTH                         0x82C  /* status register for VAD */
+#define VAD_LR_SEL                             0x840  /* L/R channel data selection for processing */
+#define VAD_SW                                 0x844  /* push enable signal*/
+#define VAD_LEFT_WD                            0x848  /* select left channel*/
+#define VAD_RIGHT_WD                           0x84C  /* select right channel*/
+#define VAD_STOP_DELAY                         0x850  /* delay stop for 0-3 samples*/
+#define VAD_ADDR_START                         0x854  /* vad memory start address, align with 64bit*/
+#define VAD_ADDR_WRAP                          0x858  /* vad memory highest address for Push, align with 64bit,(addr_wrap-1) is the max physical address*/
+#define VAD_MEM_SW                             0x85C  /* xmem switch */
+#define VAD_SPINT_CLR                          0x860  /* clear vad_spint interrup status*/
+#define VAD_SPINT_EN                           0x864  /* disable/enable vad_spint from vad_flag rising edge*/
+#define VAD_SLINT_CLR                          0x868  /* clear vad_slint interrup status*/
+#define VAD_SLINT_EN                           0x86C  /* disable/enable  vad_slint from vad_flag falling edge*/
+#define VAD_RAW_SPINT                          0x870  /* status of spint before vad_spint_en*/
+#define VAD_RAW_SLINT                          0x874  /* status of slint before vad_slint_en*/
+#define VAD_SPINT                              0x878  /* status of spint after vad_spint_en*/
+#define VAD_SLINT                              0x87C  /* status of slint before vad_slint_en*/
+#define VAD_XMEM_ADDR                          0x880  /* next xmem address ,align to 16bi*/
+#define VAD_I2S_CTRL_REG_ADDR                  0x884
+
+/*
+ * vad parameter register fields
+ */
+#define VAD_LEFT_MARGIN_MASK                   GENMASK(4, 0)
+#define VAD_RIGHT_MARGIN_MASK                  GENMASK(4, 0)
+#define VAD_N_LOW_CONT_FRAMES_MASK             GENMASK(4, 0)
+#define VAD_N_LOW_SEEK_FRAMES_MASK             GENMASK(4, 0)
+#define VAD_N_HIGH_CONT_FRAMES_MASK            GENMASK(4, 0)
+#define VAD_N_HIGH_SEEK_FRAMES_MASK            GENMASK(4, 0)
+#define VAD_N_SPEECH_LOW_HIGH_FRAMES_MASK      GENMASK(4, 0)
+#define VAD_N_SPEECH_LOW_SEEK_FRAMES_MASK      GENMASK(4, 0)
+#define VAD_MEAN_SIL_FRAMES_MASK               GENMASK(4, 0)
+#define VAD_N_ALPHA_MASK                       GENMASK(11, 0)
+#define VAD_N_BETA_MASK                                GENMASK(11, 0)
+#define VAD_LR_SEL_MASK                                GENMASK(0, 0)
+#define VAD_LR_SEL_L                           (0 << 0)
+#define VAD_LR_SEL_R                           (1 << 0)
+
+#define VAD_SW_MASK                            GENMASK(1, 0)
+#define VAD_SW_VAD_XMEM_ENABLE                 (1 << 0)
+#define VAD_SW_VAD_XMEM_DISABLE                        (0 << 0)
+#define VAD_SW_ADC_ENABLE                      (1 << 1)
+#define VAD_SW_ADC_DISABLE                     (0 << 1)
+
+
+#define VAD_LEFT_WD_MASK                       GENMASK(0, 0)
+#define VAD_LEFT_WD_BIT_31_16                  (1 << 1)
+#define VAD_LEFT_WD_BIT_15_0                   (0 << 1)
+
+
+#define VAD_RIGHT_WD_MASK                      GENMASK(0, 0)
+#define VAD_RIGHT_WD_BIT_31_16                 (1 << 1)
+#define VAD_RIGHT_WD_BIT_15_0                  (0 << 1)
+
+
+#define VAD_STOP_DELAY_MASK                    GENMASK(1, 0)
+#define VAD_STOP_DELAY_0_SAMPLE                        0
+#define VAD_STOP_DELAY_1_SAMPLE                        1
+#define VAD_STOP_DELAY_2_SAMPLE                        2
+#define VAD_STOP_DELAY_3_SAMPLE                        3
+
+#define VAD_ADDR_START_MASK                    GENMASK(12, 0)
+#define VAD_ADDR_WRAP_MASK                     GENMASK(13, 0)
+#define VAD_MEM_SW_MASK                                GENMASK(0, 0)
+#define VAD_SPINT_CLR_MASK                     GENMASK(0, 0)
+#define VAD_SPINT_EN_MASK                      GENMASK(0, 0)
+#define VAD_SLINT_CLR_MASK                     GENMASK(0, 0)
+#define VAD_SLINT_EN_MASK                      GENMASK(0, 0)
+#define VAD_I2S_CTRL_REG_ADDR_MASK             GENMASK(0, 0)
+
+#define VAD_MEM_SW_TO_VAD                      (1 << 0)
+#define VAD_MEM_SW_TO_AXI                      (0 << 0)
+
+#define VAD_SPINT_CLR_VAD_SPINT                        (1 << 0)
+
+#define VAD_SPINT_EN_ENABLE                    (1 << 0)
+#define VAD_SPINT_EN_DISABLE                   (0 << 0)
+
+#define VAD_SLINT_CLR_VAD_SLINT                        (1 << 0)
+
+#define VAD_SLINT_EN_ENABLE                    (1 << 0)
+#define VAD_SLINT_EN_DISABLE                   (0 << 0)
+
+#define VAD_STATUS_NORMAL                      0
+#define VAD_STATUS_SPINT                       1
+#define VAD_STATUS_SLINT                       2
+
+/*
+ * Component parameter register fields - define the I2S block's
+ * configuration.
+ */
+#define COMP1_TX_WORDSIZE_3(r)         (((r) & GENMASK(27, 25)) >> 25)
+#define COMP1_TX_WORDSIZE_2(r)         (((r) & GENMASK(24, 22)) >> 22)
+#define COMP1_TX_WORDSIZE_1(r)         (((r) & GENMASK(21, 19)) >> 19)
+#define COMP1_TX_WORDSIZE_0(r)         (((r) & GENMASK(18, 16)) >> 16)
+#define COMP1_TX_CHANNELS(r)           (((r) & GENMASK(10, 9)) >> 9)
+#define COMP1_RX_CHANNELS(r)           (((r) & GENMASK(8, 7)) >> 7)
+#define COMP1_RX_ENABLED(r)            (((r) & BIT(6)) >> 6)
+#define COMP1_TX_ENABLED(r)            (((r) & BIT(5)) >> 5)
+#define COMP1_MODE_EN(r)               (((r) & BIT(4)) >> 4)
+#define COMP1_FIFO_DEPTH_GLOBAL(r)     (((r) & GENMASK(3, 2)) >> 2)
+#define COMP1_APB_DATA_WIDTH(r)                (((r) & GENMASK(1, 0)) >> 0)
+
+#define COMP2_RX_WORDSIZE_3(r)         (((r) & GENMASK(12, 10)) >> 10)
+#define COMP2_RX_WORDSIZE_2(r)         (((r) & GENMASK(9, 7)) >> 7)
+#define COMP2_RX_WORDSIZE_1(r)         (((r) & GENMASK(5, 3)) >> 3)
+#define COMP2_RX_WORDSIZE_0(r)         (((r) & GENMASK(2, 0)) >> 0)
+
+/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
+#define COMP_MAX_WORDSIZE              (1 << 3)
+#define COMP_MAX_DATA_WIDTH            (1 << 2)
+
+#define MAX_CHANNEL_NUM                8
+#define MIN_CHANNEL_NUM                2
+#define ALL_CHANNEL_NUM                4
+
+
+union dw_i2s_snd_dma_data {
+       struct i2s_dma_data pd;
+       struct snd_dmaengine_dai_dma_data dt;
+};
+
+struct vad_params {
+       void __iomem *vad_base;
+       struct regmap *vad_map;
+       unsigned int vswitch;
+       unsigned int vstatus; /*vad detect status: 1:SPINT 2:SLINT 0:normal*/
+};
+
+struct i2svad_dev {
+       void __iomem *i2s_base;
+       struct clk *clk;
+       int active;
+       unsigned int capability;
+       unsigned int quirks;
+       unsigned int i2s_reg_comp1;
+       unsigned int i2s_reg_comp2;
+       struct device *dev;
+       u32 ccr;
+       u32 xfer_resolution;
+       u32 fifo_th;
+
+       struct clk *clk_apb_i2svad;
+       struct reset_control *rst_apb_i2svad;
+       struct reset_control *rst_i2svad_srst;
+
+       /* data related to DMA transfers b/w i2s and DMAC */
+       union dw_i2s_snd_dma_data play_dma_data;
+       union dw_i2s_snd_dma_data capture_dma_data;
+       struct i2s_clk_config_data config;
+       int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
+
+       /* data related to PIO transfers */
+       bool use_pio;
+       struct snd_pcm_substream __rcu *tx_substream;
+       struct snd_pcm_substream __rcu *rx_substream;
+       unsigned int (*tx_fn)(struct i2svad_dev *dev,
+                       struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+                       bool *period_elapsed);
+       unsigned int (*rx_fn)(struct i2svad_dev *dev,
+                       struct snd_pcm_runtime *runtime, unsigned int rx_ptr,
+                       bool *period_elapsed);
+       unsigned int tx_ptr;
+       unsigned int rx_ptr;
+
+       struct vad_params vad;
+};
+
+#if IS_ENABLED(CONFIG_SND_STARFIVE_I2SVAD_PCM)
+void i2svad_pcm_push_tx(struct i2svad_dev *dev);
+void i2svad_pcm_pop_rx(struct i2svad_dev *dev);
+int i2svad_pcm_register(struct platform_device *pdev);
+#else
+static inline void i2svad_pcm_push_tx(struct i2svad_dev *dev) { }
+static inline void i2svad_pcm_pop_rx(struct i2svad_dev *dev) { }
+static inline int i2svad_pcm_register(struct platform_device *pdev)
+{
+       return -EINVAL;
+}
+#endif
+
+#endif
diff --git a/sound/soc/starfive/pdm.c b/sound/soc/starfive/pdm.c
new file mode 100644 (file)
index 0000000..af5efd8
--- /dev/null
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "pdm.h"
+
+#define AUDIOC_CLK     (12288000)
+#define PDM_MUL        (128)
+
+struct sf_pdm {
+       struct regmap *pdm_map;
+       struct regmap *clk_map;
+       struct clk *clk;
+};
+
+static const DECLARE_TLV_DB_SCALE(volume_tlv, -9450, 150, 0);
+
+static const struct snd_kcontrol_new sf_pdm_snd_controls[] = {
+       SOC_SINGLE("DC compensation Control", PDM_DMIC_CTRL0, 30, 1, 0),
+       SOC_SINGLE("High Pass Filter Control", PDM_DMIC_CTRL0, 28, 1, 0),
+       SOC_SINGLE("Left Channel Volume Control", PDM_DMIC_CTRL0, 23, 1, 0),
+       SOC_SINGLE("Right Channel Volume Control", PDM_DMIC_CTRL0, 22, 1, 0),
+       SOC_SINGLE_TLV("Volume", PDM_DMIC_CTRL0, 16, 0x3F, 1, volume_tlv),
+       SOC_SINGLE("Data MSB Shift", PDM_DMIC_CTRL0, 1, 7, 0),
+       SOC_SINGLE("SCALE", PDM_DC_SCALE0, 0, 0x3F, 0),
+       SOC_SINGLE("DC offset", PDM_DC_SCALE0, 8, 0xFFFFF, 0),
+};
+
+static int sf_pdm_set_mclk(struct regmap *map, unsigned int clk, unsigned int weight)
+{
+       int mclk_div,bclk_div,lrclk_div;
+       u32     pdm_div;
+
+       /*
+       audio source clk:12288000, mclk_div:4, mclk:3M
+       support 8K/16K/32K/48K sample reate
+       suapport 16/24/32 bit weight
+       bit weight 32
+       mclk bclk  lrclk
+       3M   1.5M  48K
+       3M   1M    32K
+       3M   0.5M  16K
+       3M   0.25M  8K
+
+       bit weight 24,set lrclk_div as 32
+       mclk bclk  lrclk
+       3M   1.5M  48K
+       3M   1M    32K
+       3M   0.5M  16K
+       3M   0.25M  8K
+
+       bit weight 16
+       mclk bclk   lrclk
+       3M   0.75M  48K
+       3M   0.5M   32K
+       3M   0.25M  16K
+       3M   0.125M 8K
+       */
+
+       switch (clk) {
+       case 8000:
+       case 16000:
+       case 32000:
+       case 48000:
+               break;
+       default:
+               printk(KERN_ERR "sample rate:%d\n", clk);
+               return -EINVAL;
+       }
+
+       switch (weight) {
+       case 16:
+       case 24:
+       case 32:
+               break;
+       default:
+               printk(KERN_ERR "bit weight:%d\n", weight);
+               return -EINVAL;
+       }
+
+       if (24 == weight) {
+               weight = 32;
+       }
+
+       mclk_div = 4;
+       bclk_div = AUDIOC_CLK/mclk_div/(clk*weight);
+       lrclk_div = weight;
+
+       /* PDM MCLK = 128*LRCLK */
+       pdm_div = AUDIOC_CLK/(PDM_MUL*clk);
+
+       regmap_update_bits(map, AUDIO_CLK_ADC_MCLK, 0x0F, mclk_div);
+       regmap_update_bits(map, AUDIO_CLK_I2SADC_BCLK, 0x1F, bclk_div);
+       regmap_update_bits(map, AUDIO_CLK_ADC_LRCLK, 0x3F, lrclk_div);
+       regmap_update_bits(map, AUDIO_CLK_PDM_CLK, 0x0F, pdm_div);
+
+       return 0;
+}
+
+static void sf_pdm_enable(struct regmap *map)
+{
+       /* Enable PDM */
+       regmap_update_bits(map, PDM_DMIC_CTRL0, 0x01<<PDM_DMIC_RVOL_OFFSET, 0);
+       regmap_update_bits(map, PDM_DMIC_CTRL0, 0x01<<PDM_DMIC_LVOL_OFFSET, 0);
+}
+
+static void sf_pdm_disable(struct regmap *map)
+{
+       regmap_update_bits(map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_RVOL_OFFSET, 0x01<<PDM_DMIC_RVOL_OFFSET);
+       regmap_update_bits(map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_LVOL_OFFSET, 0x01<<PDM_DMIC_LVOL_OFFSET);
+}
+
+static int sf_pdm_trigger(struct snd_pcm_substream *substream, int cmd,
+                       struct snd_soc_dai *dai)
+{
+       struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               sf_pdm_enable(priv->pdm_map);
+               return 0;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               sf_pdm_disable(priv->pdm_map);
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int sf_pdm_hw_params(struct snd_pcm_substream *substream,
+                       struct snd_pcm_hw_params *params,
+                       struct snd_soc_dai *dai)
+{
+       struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai);
+       unsigned int rate = params_rate(params);
+       unsigned int width;
+       int ret;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               return 0;
+
+       width = params_width(params);
+       switch (width) {
+       case 16:
+       case 24:
+       case 32:
+               break;
+       default:
+               dev_err(dai->dev, "unsupported sample width\n");
+               return -EINVAL;
+       }
+
+       ret = sf_pdm_set_mclk(priv->clk_map, rate, width);
+       if (ret < 0) {
+               dev_err(dai->dev, "unsupported sample rate\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct snd_soc_dai_ops sf_pdm_dai_ops = {
+       .trigger        = sf_pdm_trigger,
+       .hw_params      = sf_pdm_hw_params,
+};
+
+static int sf_pdm_dai_probe(struct snd_soc_dai *dai)
+{
+       struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai);
+
+       /* Reset */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_SW_RSTN_OFFSET, 0x00);
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_SW_RSTN_OFFSET, 0x01<<PDM_DMIC_SW_RSTN_OFFSET);
+
+       /* Make sure the device is initially disabled */
+       sf_pdm_disable(priv->pdm_map);
+
+       /* MUTE */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x3F<<PDM_DMIC_VOL_OFFSET, 0x3F<<PDM_DMIC_VOL_OFFSET);
+
+       /* UNMUTE */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x3F<<PDM_DMIC_VOL_OFFSET, 0);
+
+       /* enable high pass filter */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_ENHPF_OFFSET, 0x01<<PDM_DMIC_ENHPF_OFFSET);
+
+       /* i2s slaver mode */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_I2SMODE_OFFSET, 0x01<<PDM_DMIC_I2SMODE_OFFSET);
+
+       /* disable fast mode */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_FASTMODE_OFFSET, 0);
+
+       /* enable dc bypass mode */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x01<<PDM_DMIC_DCBPS_OFFSET, 0);
+
+       /* dmic msb shift 0 */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x07<<PDM_DMIC_MSB_SHIFT_OFFSET, 0);
+
+       /* scale:0 */
+       regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0, 0x3F, 0x08);
+
+       /* DC offset:0 */
+       regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0,
+               0xFFFFF<<PDM_DMIC_DCOFF1_OFFSET, 0xC0005<<PDM_DMIC_DCOFF1_OFFSET);
+
+       return 0;
+}
+
+static int sf_pdm_dai_remove(struct snd_soc_dai *dai)
+{
+       struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai);
+
+       /* MUTE */
+       regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0,
+               0x3F<<PDM_DMIC_VOL_OFFSET, 0x3F<<PDM_DMIC_VOL_OFFSET);
+
+       return 0;
+}
+
+#define SF_PCM_RATE (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
+                                       SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_48000)
+
+static struct snd_soc_dai_driver sf_pdm_dai_drv = {
+       .name = "PDM",
+       .id = 0,
+       .capture = {
+               .stream_name    = "Capture",
+               .channels_min   = 2,
+               .channels_max   = 2,
+               .rates          =       SF_PCM_RATE,
+               .formats        =       SNDRV_PCM_FMTBIT_S16_LE|\
+                                               SNDRV_PCM_FMTBIT_S24_LE|\
+                                               SNDRV_PCM_FMTBIT_S32_LE,
+       },
+       .ops            = &sf_pdm_dai_ops,
+       .probe          = sf_pdm_dai_probe,
+       .remove         = sf_pdm_dai_remove,
+       .symmetric_rate = 1,
+};
+
+static int pdm_probe(struct snd_soc_component *component)
+{
+       struct sf_pdm *priv = snd_soc_component_get_drvdata(component);
+
+       snd_soc_component_init_regmap(component, priv->pdm_map);
+       snd_soc_add_component_controls(component, sf_pdm_snd_controls,
+                               ARRAY_SIZE(sf_pdm_snd_controls));
+
+       return 0;
+}
+
+static const struct snd_soc_component_driver sf_pdm_component_drv = {
+       .name = "sf-pdm",
+       .probe = pdm_probe,
+};
+
+static const struct regmap_config sf_pdm_regmap_cfg = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = 0x20,
+};
+
+static const struct regmap_config sf_audio_clk_regmap_cfg = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = 0x100,
+};
+
+static int sf_pdm_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct sf_pdm *priv;
+       struct resource *res;
+       void __iomem *regs;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, priv);
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdm");
+       regs = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(regs))
+               return PTR_ERR(regs);
+
+       priv->pdm_map = devm_regmap_init_mmio(dev, regs, &sf_pdm_regmap_cfg);
+       if (IS_ERR(priv->pdm_map)) {
+               dev_err(dev, "failed to init regmap: %ld\n",
+                       PTR_ERR(priv->pdm_map));
+               return PTR_ERR(priv->pdm_map);
+       }
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-clk");
+       regs = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(regs))
+               return PTR_ERR(regs);
+
+       priv->clk_map = devm_regmap_init_mmio(dev, regs, &sf_audio_clk_regmap_cfg);
+       if (IS_ERR(priv->clk_map)) {
+               dev_err(dev, "failed to init regmap: %ld\n",
+                       PTR_ERR(priv->clk_map));
+               return PTR_ERR(priv->clk_map);
+       }
+
+       return devm_snd_soc_register_component(dev, &sf_pdm_component_drv,
+                                       &sf_pdm_dai_drv, 1);
+}
+
+static int sf_pdm_dev_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+static const struct of_device_id sf_pdm_of_match[] = {
+       {.compatible = "starfive,sf-pdm",},
+       {}
+};
+MODULE_DEVICE_TABLE(of, sf_pdm_of_match);
+
+static struct platform_driver sf_pdm_driver = {
+
+       .driver = {
+               .name = "sf-pdm",
+               .of_match_table = sf_pdm_of_match,
+       },
+       .probe = sf_pdm_probe,
+       .remove = sf_pdm_dev_remove,
+};
+module_platform_driver(sf_pdm_driver);
+
+MODULE_AUTHOR("michael.yan <michael.yan@starfivetech.com>");
+MODULE_DESCRIPTION("starfive PDM Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/starfive/pdm.h b/sound/soc/starfive/pdm.h
new file mode 100644 (file)
index 0000000..cbc0a9e
--- /dev/null
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#ifndef __SND_SOC_STARFIVE_PDM_H
+#define __SND_SOC_STARFIVE_PDM_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <linux/dmaengine.h>
+#include <linux/types.h>
+
+#define PDM_DMIC_CTRL0                 (0x00)
+#define PDM_DC_SCALE0                  (0x04)
+#define PDM_DMIC_CTRL1                 (0x10)
+#define PDM_DC_SCALE1                  (0x14)
+
+/* PDM CTRL OFFSET */
+#define PDM_DMIC_MSB_SHIFT_OFFSET      (1)
+#define PDM_DMIC_VOL_OFFSET            (16)
+#define PDM_DMIC_RVOL_OFFSET           (22)
+#define PDM_DMIC_LVOL_OFFSET           (23)
+#define PDM_DMIC_I2SMODE_OFFSET                (24)
+#define PDM_DMIC_ENHPF_OFFSET          (28)
+#define PDM_DMIC_FASTMODE_OFFSET       (29)
+#define PDM_DMIC_DCBPS_OFFSET          (30)
+#define PDM_DMIC_SW_RSTN_OFFSET                (31)
+
+/* PDM SCALE OFFSET */
+#define PDM_DMIC_DCOFF3_OFFSET         (24)
+#define PDM_DMIC_DCOFF2_OFFSET         (16)
+#define PDM_DMIC_DCOFF1_OFFSET         (8)
+#define PDM_DMIC_SCALE_OFFSET          (0)
+
+#define AUDIO_CLK_ADC_MCLK             0x0
+#define AUDIO_CLK_I2SADC_BCLK          0xC
+#define AUDIO_CLK_ADC_LRCLK            0x14
+#define AUDIO_CLK_PDM_CLK              0x1C
+
+#endif /* __SND_SOC_STARFIVE_PDM_H */
diff --git a/sound/soc/starfive/pwmdac-pcm.c b/sound/soc/starfive/pwmdac-pcm.c
new file mode 100644 (file)
index 0000000..decd08b
--- /dev/null
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/io.h>
+#include <linux/rcupdate.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pwmdac.h"
+
+#define BUFFER_BYTES_MAX       (3 * 2 * 8 * PERIOD_BYTES_MIN)
+#define PERIOD_BYTES_MIN       4096
+#define PERIODS_MIN            2
+
+static unsigned int sf_pwmdac_pcm_tx_8(struct sf_pwmdac_dev *dev,
+               struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+               bool *period_elapsed)
+{
+       const u8 (*p)[2] = (void *)runtime->dma_area;
+       unsigned int period_pos = tx_ptr % runtime->period_size;
+       int i;
+       u32 basedat = 0;
+
+       for (i = 0; i < dev->fifo_th; i++) {
+               basedat = (p[tx_ptr][0]<<8)|(p[tx_ptr][1] << 24);
+               iowrite32(basedat,dev->pwmdac_base + PWMDAC_WDATA);
+               period_pos++;
+               if (++tx_ptr >= runtime->buffer_size)
+                       tx_ptr = 0;
+       }
+
+       *period_elapsed = period_pos >= runtime->period_size;
+
+       return tx_ptr;
+}
+
+
+static unsigned int sf_pwmdac_pcm_tx_16(struct sf_pwmdac_dev *dev,
+               struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+               bool *period_elapsed)
+{
+       const u16 (*p)[2] = (void *)runtime->dma_area;
+       unsigned int period_pos = tx_ptr % runtime->period_size;
+       int i;
+       u32 basedat = 0;
+
+       for (i = 0; i < dev->fifo_th; i++) {
+               basedat = (p[tx_ptr][0])|(p[tx_ptr][1] << 16);
+               iowrite32(basedat,dev->pwmdac_base + PWMDAC_WDATA);
+               period_pos++;
+               if (++tx_ptr >= runtime->buffer_size)
+                       tx_ptr = 0;
+       }
+
+       *period_elapsed = period_pos >= runtime->period_size;
+       return tx_ptr;
+}
+
+static const struct snd_pcm_hardware sf_pcm_hardware = {
+       .info = SNDRV_PCM_INFO_INTERLEAVED |
+               SNDRV_PCM_INFO_MMAP |
+               SNDRV_PCM_INFO_MMAP_VALID |
+               SNDRV_PCM_INFO_BLOCK_TRANSFER,
+       .rates = SNDRV_PCM_RATE_16000,
+       .rate_min = 16000,
+       .rate_max = 16000,
+       .formats = SNDRV_PCM_FMTBIT_S16_LE,
+       .channels_min = 2,
+       .channels_max = 2,
+       .buffer_bytes_max = BUFFER_BYTES_MAX,
+       .period_bytes_min = PERIOD_BYTES_MIN,
+       .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+       .periods_min = PERIODS_MIN,
+       .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+       .fifo_size = 2,
+};
+
+static void sf_pcm_transfer(struct sf_pwmdac_dev *dev, bool push)
+{
+       struct snd_pcm_substream *substream = NULL;
+       bool period_elapsed = false;
+       bool active;
+
+       rcu_read_lock();
+       if (push)
+               substream = rcu_dereference(dev->tx_substream);
+
+       active = substream && snd_pcm_running(substream);
+       if (active) {
+               unsigned int ptr;
+               unsigned int new_ptr;
+
+               if (push) {
+                       ptr = READ_ONCE(dev->tx_ptr);
+                       new_ptr = dev->tx_fn(dev, substream->runtime, ptr,
+                                       &period_elapsed);
+                       cmpxchg(&dev->tx_ptr, ptr, new_ptr);
+               }
+
+               if (period_elapsed)
+                       snd_pcm_period_elapsed(substream);
+       }
+       rcu_read_unlock();
+}
+
+void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev)
+{
+       sf_pcm_transfer(dev, true);
+}
+
+
+static int sf_pcm_open(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+       struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+
+       snd_soc_set_runtime_hwparams(substream, &sf_pcm_hardware);
+       snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+       runtime->private_data = dev;
+
+       return 0;
+}
+
+
+static int sf_pcm_close(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream)
+{
+       synchronize_rcu();
+       return 0;
+}
+
+static int sf_pcm_hw_params(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream,
+                       struct snd_pcm_hw_params *hw_params)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct sf_pwmdac_dev *dev = runtime->private_data;
+
+       switch (params_channels(hw_params)) {
+       case 2:
+               break;
+       default:
+               dev_err(dev->dev, "invalid channels number\n");
+               return -EINVAL;
+       }
+
+       switch (params_format(hw_params)) {
+       case SNDRV_PCM_FORMAT_U8:
+       case SNDRV_PCM_FORMAT_S8:
+               dev->tx_fn = sf_pwmdac_pcm_tx_8;
+               break;
+       case SNDRV_PCM_FORMAT_S16_LE:
+               dev->tx_fn = sf_pwmdac_pcm_tx_16;
+               break;
+       default:
+               dev_err(dev->dev, "invalid format\n");
+               return -EINVAL;
+       }
+
+               return 0;
+}
+
+
+static int sf_pcm_trigger(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream, int cmd)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct sf_pwmdac_dev *dev = runtime->private_data;
+       int ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       WRITE_ONCE(dev->tx_ptr, 0);
+                       rcu_assign_pointer(dev->tx_substream, substream);
+               }
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       rcu_assign_pointer(dev->tx_substream, NULL);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static snd_pcm_uframes_t sf_pcm_pointer(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct sf_pwmdac_dev *dev = runtime->private_data;
+       snd_pcm_uframes_t pos = 0;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               pos = READ_ONCE(dev->tx_ptr);
+
+       return pos < runtime->buffer_size ? pos : 0;
+}
+
+static int sf_pcm_new(struct snd_soc_component *component,
+                       struct snd_soc_pcm_runtime *rtd)
+{
+       size_t size = sf_pcm_hardware.buffer_bytes_max;
+
+       snd_pcm_set_managed_buffer_all(rtd->pcm,
+                       SNDRV_DMA_TYPE_CONTINUOUS,
+                       NULL, size, size);
+       return 0;
+}
+
+static const struct snd_soc_component_driver dw_pcm_component = {
+       .open           = sf_pcm_open,
+       .close          = sf_pcm_close,
+       .hw_params      = sf_pcm_hw_params,
+       .trigger        = sf_pcm_trigger,
+       .pointer        = sf_pcm_pointer,
+       .pcm_construct  = sf_pcm_new,
+};
+
+int sf_pwmdac_pcm_register(struct platform_device *pdev)
+{
+       return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component,
+                                       NULL, 0);
+}
diff --git a/sound/soc/starfive/pwmdac-transmitter.c b/sound/soc/starfive/pwmdac-transmitter.c
new file mode 100644 (file)
index 0000000..46c2e50
--- /dev/null
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <linux/of.h>
+
+#define DRV_NAME "pwmdac-dit"
+
+#define STUB_RATES     SNDRV_PCM_RATE_8000_192000
+#define STUB_FORMATS   (SNDRV_PCM_FMTBIT_S8|\
+                       SNDRV_PCM_FMTBIT_U8      |\
+                       SNDRV_PCM_FMTBIT_S16_LE  | \
+                       SNDRV_PCM_FMTBIT_S20_3LE | \
+                       SNDRV_PCM_FMTBIT_S24_LE  | \
+                       SNDRV_PCM_FMTBIT_S32_LE)
+
+static const struct snd_soc_dapm_widget dit_widgets[] = {
+       SND_SOC_DAPM_OUTPUT("pwmdac-out"),
+};
+
+static const struct snd_soc_dapm_route dit_routes[] = {
+       { "pwmdac-out", NULL, "Playback" },
+};
+
+static struct snd_soc_component_driver soc_codec_pwmdac_dit = {
+       .dapm_widgets           = dit_widgets,
+       .num_dapm_widgets       = ARRAY_SIZE(dit_widgets),
+       .dapm_routes            = dit_routes,
+       .num_dapm_routes        = ARRAY_SIZE(dit_routes),
+       .idle_bias_on           = 1,
+       .use_pmdown_time        = 1,
+       .endianness             = 1,
+};
+
+static struct snd_soc_dai_driver dit_stub_dai = {
+       .name           = "pwmdac-dit-hifi",
+       .playback       = {
+               .stream_name    = "Playback",
+               .channels_min   = 1,
+               .channels_max   = 384,
+               .rates          = STUB_RATES,
+               .formats        = STUB_FORMATS,
+       },
+};
+
+static int pwmdac_dit_probe(struct platform_device *pdev)
+{
+
+       return devm_snd_soc_register_component(&pdev->dev,
+                       &soc_codec_pwmdac_dit,
+                       &dit_stub_dai, 1);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id pwmdac_dit_dt_ids[] = {
+       { .compatible = "linux,pwmdac-dit", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, pwmdac_dit_dt_ids);
+#endif
+
+static struct platform_driver pwmdac_dit_driver = {
+       .probe          = pwmdac_dit_probe,
+       .driver         = {
+               .name   = DRV_NAME,
+               .of_match_table = of_match_ptr(pwmdac_dit_dt_ids),
+       },
+};
+
+module_platform_driver(pwmdac_dit_driver);
+
+MODULE_AUTHOR("jenny.zhang <jenny.zhang@starfivetech.com>");
+MODULE_DESCRIPTION("pwmdac dummy codec driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform: starfive-pwmdac dummy codec");
diff --git a/sound/soc/starfive/pwmdac.c b/sound/soc/starfive/pwmdac.c
new file mode 100644 (file)
index 0000000..78f2f9c
--- /dev/null
@@ -0,0 +1,862 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PWMDAC driver for the StarFive JH7100 SoC
+ *
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include "pwmdac.h"
+#include <linux/kthread.h>
+
+struct ct_pwmdac {
+       char *name;
+       unsigned int vals;
+};
+
+static const struct ct_pwmdac pwmdac_ct_shift_bit[] = {
+       { .name = "8bit", .vals = PWMDAC_SHIFT_8 },
+       { .name = "10bit", .vals = PWMDAC_SHIFT_10 }
+};
+
+static const struct ct_pwmdac pwmdac_ct_duty_cycle[] = {
+       { .name = "left", .vals = PWMDAC_CYCLE_LEFT },
+       { .name = "right", .vals = PWMDAC_CYCLE_RIGHT },
+       { .name = "center", .vals = PWMDAC_CYCLE_CENTER }
+};
+
+static const struct ct_pwmdac pwmdac_ct_data_mode[] = {
+       { .name = "unsinged", .vals = UNSINGED_DATA },
+       { .name = "inverter", .vals = INVERTER_DATA_MSB }
+};
+
+static const struct ct_pwmdac pwmdac_ct_lr_change[] = {
+       { .name = "no_change", .vals = NO_CHANGE },
+       { .name = "change", .vals = CHANGE }
+};
+
+static const struct ct_pwmdac pwmdac_ct_shift[] = {
+       { .name = "left 0 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_0 },
+       { .name = "left 1 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_1 },
+       { .name = "left 2 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_2 },
+       { .name = "left 3 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_3 },
+       { .name = "left 4 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_4 },
+       { .name = "left 5 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_5 },
+       { .name = "left 6 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_6 }
+};
+
+static int pwmdac_shift_bit_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_shift_bit);
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->count = 1;
+       uinfo->value.enumerated.items = items;
+       if (uinfo->value.enumerated.item >= items) {
+               uinfo->value.enumerated.item = items - 1;
+       }
+       strcpy(uinfo->value.enumerated.name,
+                       pwmdac_ct_shift_bit[uinfo->value.enumerated.item].name);
+
+       return 0;
+}
+static int pwmdac_shift_bit_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       unsigned int item;
+
+       if (dev->shift_bit == pwmdac_ct_shift_bit[0].vals)
+               item = 0;
+       else
+               item = 1;
+
+       ucontrol->value.enumerated.item[0] = item;
+
+       return 0;
+}
+
+static int pwmdac_shift_bit_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       int sel = ucontrol->value.enumerated.item[0];
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_shift_bit);
+
+       if (sel > items)
+               return 0;
+
+       switch (sel) {
+       case 1:
+               dev->shift_bit = pwmdac_ct_shift_bit[1].vals;
+               break;
+       default:
+               dev->shift_bit = pwmdac_ct_shift_bit[0].vals;
+               break;
+       }
+
+       return 0;
+}
+
+static int pwmdac_duty_cycle_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_duty_cycle);
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->count = 1;
+       uinfo->value.enumerated.items = items;
+       if (uinfo->value.enumerated.item >= items)
+               uinfo->value.enumerated.item = items - 1;
+       strcpy(uinfo->value.enumerated.name,
+               pwmdac_ct_duty_cycle[uinfo->value.enumerated.item].name);
+
+       return 0;
+}
+
+static int pwmdac_duty_cycle_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.enumerated.item[0] = dev->duty_cycle;
+       return 0;
+}
+
+static int pwmdac_duty_cycle_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       int sel = ucontrol->value.enumerated.item[0];
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_duty_cycle);
+
+       if (sel > items)
+               return 0;
+
+       dev->duty_cycle = pwmdac_ct_duty_cycle[sel].vals;
+       return 0;
+}
+
+/*
+static int pwmdac_datan_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 1;
+       uinfo->value.integer.max = PWMDAC_SAMPLE_CNT_511;
+       uinfo->value.integer.step = 1;
+       return 0;
+}
+
+static int pwmdac_datan_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.integer.value[0] = dev->datan;
+
+       return 0;
+}
+
+static int pwmdac_datan_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel > PWMDAC_SAMPLE_CNT_511)
+               return 0;
+
+       dev->datan = sel;
+
+       return 0;
+}
+*/
+
+static int pwmdac_data_mode_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_data_mode);
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->count = 1;
+       uinfo->value.enumerated.items = items;
+       if (uinfo->value.enumerated.item >= items)
+               uinfo->value.enumerated.item = items - 1;
+       strcpy(uinfo->value.enumerated.name,
+               pwmdac_ct_data_mode[uinfo->value.enumerated.item].name);
+
+       return 0;
+}
+
+static int pwmdac_data_mode_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.enumerated.item[0] = dev->data_mode;
+       return 0;
+}
+
+static int pwmdac_data_mode_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       int sel = ucontrol->value.enumerated.item[0];
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_data_mode);
+
+       if (sel > items)
+               return 0;
+
+       dev->data_mode = pwmdac_ct_data_mode[sel].vals;
+       return 0;
+}
+
+static int pwmdac_shift_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_shift);
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->count = 1;
+       uinfo->value.enumerated.items = items;
+       if (uinfo->value.enumerated.item >= items)
+               uinfo->value.enumerated.item = items - 1;
+       strcpy(uinfo->value.enumerated.name,
+               pwmdac_ct_shift[uinfo->value.enumerated.item].name);
+
+       return 0;
+}
+
+static int pwmdac_shift_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       unsigned int item = dev->shift;
+
+       ucontrol->value.enumerated.item[0] = pwmdac_ct_shift[item].vals;
+       return 0;
+}
+
+static int pwmdac_shift_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       int sel = ucontrol->value.enumerated.item[0];
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_shift);
+
+       if (sel > items)
+               return 0;
+
+       dev->shift = pwmdac_ct_shift[sel].vals;
+       return 0;
+}
+
+static int pwmdac_lr_change_info(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_info *uinfo)
+{
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_lr_change);
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+       uinfo->count = 1;
+       uinfo->value.enumerated.items = items;
+       if (uinfo->value.enumerated.item >= items)
+               uinfo->value.enumerated.item = items - 1;
+       strcpy(uinfo->value.enumerated.name,
+               pwmdac_ct_lr_change[uinfo->value.enumerated.item].name);
+
+       return 0;
+}
+
+static int pwmdac_lr_change_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+
+       ucontrol->value.enumerated.item[0] = dev->lr_change;
+       return 0;
+}
+
+static int pwmdac_lr_change_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component);
+       int sel = ucontrol->value.enumerated.item[0];
+       unsigned int items = ARRAY_SIZE(pwmdac_ct_lr_change);
+
+       if (sel > items)
+               return 0;
+
+       dev->lr_change = pwmdac_ct_lr_change[sel].vals;
+       return 0;
+}
+
+static inline void pwmdc_write_reg(void __iomem *io_base, int reg, u32 val)
+{
+       writel(val, io_base + reg);
+}
+
+static inline u32 pwmdc_read_reg(void __iomem *io_base, int reg)
+{
+       return readl(io_base + reg);
+}
+
+/*
+ * 32bit-4byte
+*/
+static void pwmdac_set_ctrl_enable(struct sf_pwmdac_dev *dev)
+{
+       u32 date;
+       date = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL);
+       pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, date | BIT(0) );
+}
+
+/*
+ * 32bit-4byte
+*/
+static void pwmdac_set_ctrl_disable(struct sf_pwmdac_dev *dev)
+{
+       u32 date;
+       date = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL);
+       pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, date & ~ BIT(0));
+}
+
+/*
+ * 8:8-bit
+ * 10:10-bit
+*/
+static void pwmdac_set_ctrl_shift(struct sf_pwmdac_dev *dev, u8 data)
+{
+       u32 value = 0;
+
+       if (data == 8) {
+               value = (~((~value) | 0x02));
+               pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value);
+       }
+       else if(data == 10){
+               value |= 0x02;
+               pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value);
+       }
+}
+
+/*
+ * 00:left
+ * 01:right
+ * 10:center
+*/
+static void pwmdac_set_ctrl_dutyCycle(struct sf_pwmdac_dev *dev, u8 data)
+{
+       u32 value = 0;
+
+       value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL);
+       if (data == 0) { //left
+               value = (~((~value) | (0x03<<2)));
+               pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value);
+       }
+       else if (data == 1) { //right
+               value = (~((~value) | (0x01<<3))) | (0x01<<2);
+               pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value);
+       }
+       else if (data == 2) { //center
+               value = (~((~value) | (0x01<<2))) | (0x01<<3);
+               pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value);
+       }
+}
+
+
+static void pwmdac_set_ctrl_N(struct sf_pwmdac_dev *dev, u16 data)
+{
+       u32 value = 0;
+
+       value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL);
+       pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, (value & 0xF) | ((data - 1) << 4));
+}
+
+
+static void pwmdac_LR_data_change(struct sf_pwmdac_dev *dev, u8 data)
+{
+       u32 value = 0;
+
+       value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL);
+       switch (data) {
+       case NO_CHANGE:
+               value &= (~SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE);
+               break;
+       case CHANGE:
+               value |= SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE;
+               break;
+       }
+       pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value);
+}
+
+static void pwmdac_data_mode(struct sf_pwmdac_dev *dev, u8 data)
+{
+       u32 value = 0;
+
+       value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL);
+       if (data == UNSINGED_DATA) {
+               value &= (~SFC_PWMDAC_DATA_MODE);
+       }
+       else if (data == INVERTER_DATA_MSB) {
+               value |= SFC_PWMDAC_DATA_MODE;
+       }
+       pwmdc_write_reg(dev->pwmdac_base,PWMDAC_CTRL, value);
+}
+
+static int pwmdac_data_shift(struct sf_pwmdac_dev *dev,u8 data)
+{
+       u32 value = 0;
+
+       if ((data < PWMDAC_DATA_LEFT_SHIFT_BIT_0) || (data > PWMDAC_DATA_LEFT_SHIFT_BIT_7)) {
+               return -1;
+       }
+
+       value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL);
+       value &= ( ~ ( PWMDAC_DATA_LEFT_SHIFT_BIT_ALL << 15 ) );
+       value |= (data<<15);
+       pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value);
+       return 0;
+}
+
+static int get_pwmdac_fifo_state(struct sf_pwmdac_dev *dev)
+{
+       u32 value;
+
+       value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_SATAE);
+       if ((value & 0x02) == 0)
+               return FIFO_UN_FULL;
+
+       return FIFO_FULL;
+}
+
+
+static void pwmdac_set(struct sf_pwmdac_dev *dev)
+{
+       ///8-bit + left + N=16
+       pwmdac_set_ctrl_shift(dev, dev->shift_bit);
+       pwmdac_set_ctrl_dutyCycle(dev, dev->duty_cycle);
+       pwmdac_set_ctrl_N(dev, dev->datan);
+       pwmdac_set_ctrl_enable(dev);
+
+       pwmdac_LR_data_change(dev, dev->lr_change);
+       pwmdac_data_mode(dev, dev->data_mode);
+       if (dev->shift) {
+               pwmdac_data_shift(dev, dev->shift);
+       }
+}
+
+static void pwmdac_stop(struct sf_pwmdac_dev *dev)
+{
+       pwmdac_set_ctrl_disable(dev);
+}
+
+static int pwmdac_config(struct sf_pwmdac_dev *dev)
+{
+       switch (dev->mode) {
+       case shift_8Bit_unsigned:
+       case shift_8Bit_unsigned_dataShift:
+               /* 8 bit, unsigned */
+               dev->shift_bit  = PWMDAC_SHIFT_8;
+               dev->duty_cycle = PWMDAC_CYCLE_CENTER;
+               dev->datan              = PWMDAC_SAMPLE_CNT_8;
+               dev->data_mode  = UNSINGED_DATA;
+               break;
+
+       case shift_8Bit_inverter:
+       case shift_8Bit_inverter_dataShift:
+               /* 8 bit, invert */
+               dev->shift_bit  = PWMDAC_SHIFT_8;
+               dev->duty_cycle = PWMDAC_CYCLE_CENTER;
+               dev->datan              = PWMDAC_SAMPLE_CNT_8;
+               dev->data_mode  = INVERTER_DATA_MSB;
+               break;
+
+       case shift_10Bit_unsigned:
+       case shift_10Bit_unsigned_dataShift:
+               /* 10 bit, unsigend */
+               dev->shift_bit  = PWMDAC_SHIFT_10;
+               dev->duty_cycle = PWMDAC_CYCLE_CENTER;
+               dev->datan              = PWMDAC_SAMPLE_CNT_8;
+               dev->data_mode  = UNSINGED_DATA;
+               break;
+
+       case shift_10Bit_inverter:
+       case shift_10Bit_inverter_dataShift:
+               /* 10 bit, invert */
+               dev->shift_bit  = PWMDAC_SHIFT_10;
+               dev->duty_cycle = PWMDAC_CYCLE_CENTER;
+               dev->datan              = PWMDAC_SAMPLE_CNT_8;
+               dev->data_mode  = INVERTER_DATA_MSB;
+               break;
+
+       default:
+               return -1;
+       }
+
+       if ((dev->mode == shift_8Bit_unsigned_dataShift) || (dev->mode == shift_8Bit_inverter_dataShift)
+               || (dev->mode == shift_10Bit_unsigned_dataShift) || (dev->mode == shift_10Bit_inverter_dataShift)) {
+               dev->shift = 4; /*0~7*/
+       } else {
+               dev->shift = 0;
+       }
+       dev->lr_change = NO_CHANGE;
+       return 0;
+}
+
+static int sf_pwmdac_prepare(struct snd_pcm_substream *substream,
+                       struct snd_soc_dai *dai)
+{
+       //struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai);
+       //pwmdac_set(dev);
+       return 0;
+}
+
+static int pwmdac_tx_thread(void *dev)
+{
+       struct sf_pwmdac_dev *pwmdac_dev = (struct sf_pwmdac_dev *)dev;
+
+       set_current_state(TASK_INTERRUPTIBLE);
+       while (!schedule_timeout(usecs_to_jiffies(50))) {
+               if (pwmdac_dev->tx_thread_exit)
+                       break;
+               if (get_pwmdac_fifo_state(pwmdac_dev)==0) {
+                       sf_pwmdac_pcm_push_tx(pwmdac_dev);
+               }
+
+               set_current_state(TASK_INTERRUPTIBLE);
+       }
+
+       pwmdac_dev->tx_thread = NULL;
+       return 0;
+}
+
+static int sf_pwmdac_trigger(struct snd_pcm_substream *substream,
+               int cmd, struct snd_soc_dai *dai)
+{
+       struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai);
+       int ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               dev->active++;
+               pwmdac_set(dev);
+               if (dev->use_pio) {
+                       dev->tx_thread = kthread_create(pwmdac_tx_thread, (void *)dev, "pwmdac");
+                       if (IS_ERR(dev->tx_thread)) {
+                               return PTR_ERR(dev->tx_thread);
+                       }
+                       wake_up_process(dev->tx_thread);
+                       dev->tx_thread_exit = 0;
+               }
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               dev->active--;
+               pwmdac_stop(dev);
+               if (dev->use_pio) {
+                       if(dev->tx_thread) {
+                               dev->tx_thread_exit = 1;
+                       }
+               }
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+
+       return 0;
+}
+
+static int sf_pwmdac_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+       struct sf_pwmdac_dev *dev = dev_get_drvdata(dai->dev);
+
+       dev->play_dma_data.addr = dev->mapbase + PWMDAC_WDATA;
+
+       switch (params_channels(params)) {
+       case 2:
+               dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+               break;
+       case 1:
+               dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+               break;
+       default:
+               dev_err(dai->dev, "%d channels not supported\n",
+                               params_channels(params));
+               return -EINVAL;
+       }
+
+       dev->play_dma_data.fifo_size = 1;
+       dev->play_dma_data.maxburst = 16;
+
+       snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL);
+       snd_soc_dai_set_drvdata(dai, dev);
+
+       return 0;
+}
+
+static int sf_pwmdac_clks_get(struct platform_device *pdev,
+                             struct sf_pwmdac_dev *dev)
+{
+       static const char *const clock_names[PWMDAC_CLK_NUM] = {
+               [PWMDAC_CLK_AUDIO_ROOT]  = "audio_root",
+               [PWMDAC_CLK_AUDIO_SRC]   = "audio_src",
+               [PWMDAC_CLK_AUDIO_12288] = "audio_12288",
+               [PWMDAC_CLK_DMA1P_AHB]   = "dma1p_ahb",
+               [PWMDAC_CLK_PWMDAC_APB]  = "pwmdac_apb",
+               [PWMDAC_CLK_DAC_MCLK]    = "dac_mclk",
+       };
+       int i;
+
+       for (i = 0; i < PWMDAC_CLK_NUM; i++)
+               dev->clk[i].id = clock_names[i];
+
+       return devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(dev->clk), dev->clk);
+}
+
+static int sf_pwmdac_resets_get(struct platform_device *pdev,
+                               struct sf_pwmdac_dev *dev)
+{
+       static const char *const reset_names[PWMDAC_RST_NUM] = {
+               [PWMDAC_RST_APB_BUS]    = "apb_bus",
+               [PWMDAC_RST_DMA1P_AHB]  = "dma1p_ahb",
+               [PWMDAC_RST_APB_PWMDAC] = "apb_pwmdac",
+       };
+       int i;
+
+       for (i = 0; i < PWMDAC_RST_NUM; i++)
+               dev->rst[i].id = reset_names[i];
+
+       return devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(dev->rst), dev->rst);
+}
+
+static int sf_pwmdac_clk_init(struct platform_device *pdev,
+                               struct sf_pwmdac_dev *dev)
+{
+       int ret;
+       int i;
+
+       for (i = 0; i <= PWMDAC_CLK_DMA1P_AHB; i++) {
+               ret = clk_prepare_enable(dev->clk[i].clk);
+               if (ret)
+                       return dev_err_probe(&pdev->dev, ret,
+                                            "failed to enable %s\n", dev->clk[i].id);
+       }
+
+       for (i = 0; i <= PWMDAC_RST_DMA1P_AHB; i++) {
+               ret = reset_control_deassert(dev->rst[i].rstc);
+               if (ret)
+                       return dev_err_probe(&pdev->dev, ret,
+                                            "failed to deassert %s\n", dev->rst[i].id);
+       }
+
+       ret = clk_set_rate(dev->clk[PWMDAC_CLK_AUDIO_SRC].clk, 12288000);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret,
+                                    "failed to set 12.288 MHz rate for clk_audio_src\n");
+
+       ret = reset_control_assert(dev->rst[PWMDAC_RST_APB_PWMDAC].rstc);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to assert apb_pwmdac\n");
+
+       ret = clk_prepare_enable(dev->clk[PWMDAC_CLK_DAC_MCLK].clk);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to prepare enable clk_dac_mclk\n");
+
+       /* we want 4096kHz but the clock driver always rounds down so add a little slack */
+       ret = clk_set_rate(dev->clk[PWMDAC_CLK_DAC_MCLK].clk, 4096000 + 64);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to set 4096kHz rate for clk_dac_mclk\n");
+
+       ret = clk_prepare_enable(dev->clk[PWMDAC_CLK_PWMDAC_APB].clk);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to prepare enable clk_pwmdac_apb\n");
+
+       ret = reset_control_deassert(dev->rst[PWMDAC_RST_APB_PWMDAC].rstc);
+       if (ret)
+               return dev_err_probe(&pdev->dev, ret, "failed to deassert apb_pwmdac\n");
+
+       return 0;
+}
+
+static int sf_pwmdac_dai_probe(struct snd_soc_dai *dai)
+{
+       struct sf_pwmdac_dev *dev = dev_get_drvdata(dai->dev);
+
+       dev->play_dma_data.addr = dev->mapbase + PWMDAC_WDATA;
+       dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+       dev->play_dma_data.fifo_size = 1;
+       dev->play_dma_data.maxburst = 16;
+
+       snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL);
+       snd_soc_dai_set_drvdata(dai, dev);
+
+       return 0;
+}
+#define SOC_PWMDAC_ENUM_DECL(xname, xinfo, xget, xput) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = xinfo, .get = xget, \
+       .put = xput,}
+static const struct snd_kcontrol_new pwmdac_snd_controls[] = {
+       SOC_PWMDAC_ENUM_DECL("shift_bit", pwmdac_shift_bit_info,
+               pwmdac_shift_bit_get, pwmdac_shift_bit_put),
+       SOC_PWMDAC_ENUM_DECL("duty_cycle", pwmdac_duty_cycle_info,
+               pwmdac_duty_cycle_get, pwmdac_duty_cycle_put),
+       SOC_PWMDAC_ENUM_DECL("data_mode", pwmdac_data_mode_info,
+               pwmdac_data_mode_get, pwmdac_data_mode_put),
+       SOC_PWMDAC_ENUM_DECL("shift", pwmdac_shift_info,
+               pwmdac_shift_get, pwmdac_shift_put),
+       SOC_PWMDAC_ENUM_DECL("lr_change", pwmdac_lr_change_info,
+               pwmdac_lr_change_get, pwmdac_lr_change_put),
+};
+static int pwmdac_probe(struct snd_soc_component *component)
+{
+//     struct sf_pwmdac_dev *priv = snd_soc_component_get_drvdata(component);
+       snd_soc_add_component_controls(component, pwmdac_snd_controls,
+                               ARRAY_SIZE(pwmdac_snd_controls));
+       return 0;
+}
+
+
+static const struct snd_soc_dai_ops sf_pwmdac_dai_ops = {
+       .hw_params      = sf_pwmdac_hw_params,
+       .prepare        = sf_pwmdac_prepare,
+       .trigger        = sf_pwmdac_trigger,
+};
+
+static const struct snd_soc_component_driver sf_pwmdac_component = {
+       .name           = "sf-pwmdac",
+       .probe          = pwmdac_probe,
+};
+
+static struct snd_soc_dai_driver pwmdac_dai = {
+       .name = "pwmdac",
+       .id = 0,
+       .probe  = sf_pwmdac_dai_probe,
+       .playback = {
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_16000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE,
+       },
+       .ops = &sf_pwmdac_dai_ops,
+};
+
+static int sf_pwmdac_probe(struct platform_device *pdev)
+{
+       struct sf_pwmdac_dev *dev;
+       struct resource *res;
+       int ret;
+
+       dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       dev->mapbase = res->start;
+       dev->pwmdac_base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(dev->pwmdac_base))
+               return PTR_ERR(dev->pwmdac_base);
+
+       ret = sf_pwmdac_clks_get(pdev, dev);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to get audio clock\n");
+               return ret;
+       }
+
+       ret = sf_pwmdac_resets_get(pdev, dev);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to get audio reset controls\n");
+               return ret;
+        }
+
+       ret = sf_pwmdac_clk_init(pdev, dev);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to enable audio clock\n");
+               return ret;
+       }
+
+       dev->dev = &pdev->dev;
+       dev->mode = shift_8Bit_inverter;
+       dev->fifo_th = 1;//8byte
+       pwmdac_config(dev);
+
+       dev->use_pio = false;
+       dev_set_drvdata(&pdev->dev, dev);
+       ret = devm_snd_soc_register_component(&pdev->dev, &sf_pwmdac_component,
+                                        &pwmdac_dai, 1);
+       if (ret != 0) {
+               dev_err(&pdev->dev, "not able to register dai\n");
+               return ret;
+       }
+
+       if (dev->use_pio) {
+               ret = sf_pwmdac_pcm_register(pdev);
+       } else {
+               ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
+                               0);
+       }
+       return 0;
+}
+
+
+static int sf_pwmdac_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id sf_pwmdac_of_match[] = {
+       { .compatible = "starfive,pwmdac",       },
+       {},
+};
+
+MODULE_DEVICE_TABLE(of, sf_pwmdac_of_match);
+#endif
+
+
+static struct platform_driver sf_pwmdac_driver = {
+       .probe          = sf_pwmdac_probe,
+       .remove         = sf_pwmdac_remove,
+       .driver         = {
+               .name   = "sf-pwmdac",
+               .of_match_table = of_match_ptr(sf_pwmdac_of_match),
+       },
+};
+
+module_platform_driver(sf_pwmdac_driver);
+
+MODULE_AUTHOR("jenny.zhang <jenny.zhang@starfivetech.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("starfive pwmdac SoC Interface");
+MODULE_ALIAS("platform:starfive-pwmdac");
diff --git a/sound/soc/starfive/pwmdac.h b/sound/soc/starfive/pwmdac.h
new file mode 100644 (file)
index 0000000..b9f651f
--- /dev/null
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#ifndef __SND_SOC_STARFIVE_PWMDAC_H
+#define __SND_SOC_STARFIVE_PWMDAC_H
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+
+#define PWMDAC_WDATA   0       // PWMDAC_BASE_ADDR
+#define PWMDAC_CTRL    0x04    // PWMDAC_BASE_ADDR + 0x04
+#define PWMDAC_SATAE   0x08    // PWMDAC_BASE_ADDR + 0x08
+#define PWMDAC_RESERVED        0x0C    // PWMDAC_BASE_ADDR + 0x0C
+
+#define SFC_PWMDAC_SHIFT       BIT(1)
+#define SFC_PWMDAC_DUTY_CYCLE  BIT(2)
+#define SFC_PWMDAC_CNT_N       BIT(4)
+
+#define SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE      BIT(13)
+#define SFC_PWMDAC_DATA_MODE                   BIT(14)
+
+#define FIFO_UN_FULL   0
+#define FIFO_FULL      1
+
+enum pwmdac_lr_change{
+       NO_CHANGE = 0,
+       CHANGE,
+};
+
+enum pwmdac_d_mode{
+       UNSINGED_DATA = 0,
+       INVERTER_DATA_MSB,
+};
+
+enum pwmdac_shift_bit{
+       PWMDAC_SHIFT_8 = 8,     /* pwmdac shift 8 bit */
+       PWMDAC_SHIFT_10 = 10,   /* pwmdac shift 10 bit */
+};
+
+enum pwmdac_duty_cycle{
+       PWMDAC_CYCLE_LEFT = 0,          /* pwmdac duty cycle left */
+       PWMDAC_CYCLE_RIGHT = 1,         /* pwmdac duty cycle right */
+       PWMDAC_CYCLE_CENTER = 2,        /* pwmdac duty cycle center */
+};
+
+/*sample count [12:4] <511*/
+enum pwmdac_sample_count{
+       PWMDAC_SAMPLE_CNT_1 = 1,
+       PWMDAC_SAMPLE_CNT_2,
+       PWMDAC_SAMPLE_CNT_3,
+       PWMDAC_SAMPLE_CNT_4,
+       PWMDAC_SAMPLE_CNT_5,
+       PWMDAC_SAMPLE_CNT_6,
+       PWMDAC_SAMPLE_CNT_7,
+       PWMDAC_SAMPLE_CNT_8 = 1,        //(32.468/8) == (12.288/3) == 4.096
+       PWMDAC_SAMPLE_CNT_9,
+       PWMDAC_SAMPLE_CNT_10,
+       PWMDAC_SAMPLE_CNT_11,
+       PWMDAC_SAMPLE_CNT_12,
+       PWMDAC_SAMPLE_CNT_13,
+       PWMDAC_SAMPLE_CNT_14,
+       PWMDAC_SAMPLE_CNT_15,
+       PWMDAC_SAMPLE_CNT_16,
+       PWMDAC_SAMPLE_CNT_17,
+       PWMDAC_SAMPLE_CNT_18,
+       PWMDAC_SAMPLE_CNT_19,
+       PWMDAC_SAMPLE_CNT_20 = 20,
+       PWMDAC_SAMPLE_CNT_30 = 30,
+       PWMDAC_SAMPLE_CNT_511 = 511,
+};
+
+
+enum data_shift{
+       PWMDAC_DATA_LEFT_SHIFT_BIT_0 = 0,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_1,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_2,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_3,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_4,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_5,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_6,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_7,
+       PWMDAC_DATA_LEFT_SHIFT_BIT_ALL,
+};
+
+enum pwmdac_config_list{
+       shift_8Bit_unsigned = 0,
+       shift_8Bit_unsigned_dataShift,
+       shift_10Bit_unsigned,
+       shift_10Bit_unsigned_dataShift,
+
+       shift_8Bit_inverter,
+       shift_8Bit_inverter_dataShift,
+       shift_10Bit_inverter,
+       shift_10Bit_inverter_dataShift,
+};
+
+enum pwmdac_clocks {
+       PWMDAC_CLK_AUDIO_ROOT,
+       PWMDAC_CLK_AUDIO_SRC,
+       PWMDAC_CLK_AUDIO_12288,
+       PWMDAC_CLK_DMA1P_AHB,
+       PWMDAC_CLK_PWMDAC_APB,
+       PWMDAC_CLK_DAC_MCLK,
+       PWMDAC_CLK_NUM,
+};
+
+enum pwmdac_resets {
+       PWMDAC_RST_APB_BUS,
+       PWMDAC_RST_DMA1P_AHB,
+       PWMDAC_RST_APB_PWMDAC,
+       PWMDAC_RST_NUM,
+};
+
+struct sf_pwmdac_dev {
+       void __iomem *pwmdac_base;
+       resource_size_t mapbase;
+       u8  mode;
+       u8 shift_bit;
+       u8 duty_cycle;
+       u8 datan;
+       u8 data_mode;
+       u8 lr_change;
+       u8 shift;
+       u8 fifo_th;
+       bool use_pio;
+       spinlock_t lock;
+       int active;
+
+       struct clk_bulk_data clk[PWMDAC_CLK_NUM];
+       struct reset_control_bulk_data rst[PWMDAC_RST_NUM];
+
+       struct device *dev;
+       struct snd_dmaengine_dai_dma_data play_dma_data;
+       struct snd_pcm_substream __rcu *tx_substream;
+       unsigned int (*tx_fn)(struct sf_pwmdac_dev *dev,
+                       struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+                       bool *period_elapsed);
+       unsigned int tx_ptr;
+       struct task_struct *tx_thread;
+       bool tx_thread_exit;
+};
+
+
+
+#if IS_ENABLED(CONFIG_SND_STARFIVE_PWMDAC_PCM)
+void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev);
+int sf_pwmdac_pcm_register(struct platform_device *pdev);
+#else
+static void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev) { }
+static int sf_pwmdac_pcm_register(struct platform_device *pdev)
+{
+       return -EINVAL;
+}
+#endif
+
+#endif
diff --git a/sound/soc/starfive/spdif-pcm.c b/sound/soc/starfive/spdif-pcm.c
new file mode 100644 (file)
index 0000000..cd78ffd
--- /dev/null
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/io.h>
+#include <linux/rcupdate.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "spdif.h"
+
+#define BUFFER_BYTES_MAX       (3 * 2 * 8 * PERIOD_BYTES_MIN)
+#define PERIOD_BYTES_MIN       4096
+#define PERIODS_MIN                    2
+
+static unsigned int sf_spdif_pcm_tx(struct sf_spdif_dev *dev,
+               struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+               bool *period_elapsed, snd_pcm_format_t format)
+{
+       const u16 (*p16)[2] = (void *)runtime->dma_area;
+       const u32 (*p32)[2] = (void *)runtime->dma_area;
+       u32 data[2];
+       unsigned int period_pos = tx_ptr % runtime->period_size;
+       int i;
+
+       for (i = 0; i < dev->fifo_th; i++) {
+               if (SNDRV_PCM_FORMAT_S16_LE == format) {
+                       data[0] = p16[tx_ptr][0];
+                       data[1] = p16[tx_ptr][1];
+                       data[0] = data[0]<<8;
+                       data[1] = data[1]<<8;
+               } else if (SNDRV_PCM_FORMAT_S24_LE == format) {
+                       data[0] = p32[tx_ptr][0];
+                       data[1] = p32[tx_ptr][1];
+               } else if (SNDRV_PCM_FORMAT_S32_LE == format) {
+                       data[0] = p32[tx_ptr][0];
+                       data[1] = p32[tx_ptr][1];
+                       data[0] = data[0]>>8;
+                       data[1] = data[1]>>8;
+               }
+
+               iowrite32(data[0], dev->spdif_base + SPDIF_FIFO_ADDR);
+               iowrite32(data[1], dev->spdif_base + SPDIF_FIFO_ADDR);
+               period_pos++;
+               if (++tx_ptr >= runtime->buffer_size) {
+                       tx_ptr = 0;
+               }
+       }
+
+       *period_elapsed = period_pos >= runtime->period_size;
+       return tx_ptr;
+}
+
+static unsigned int sf_spdif_pcm_rx(struct sf_spdif_dev *dev,
+               struct snd_pcm_runtime *runtime, unsigned int rx_ptr,
+               bool *period_elapsed, snd_pcm_format_t format)
+{
+       u16 (*p16)[2] = (void *)runtime->dma_area;
+       u32 (*p32)[2] = (void *)runtime->dma_area;
+       u32 data[2];
+       unsigned int period_pos = rx_ptr % runtime->period_size;
+       int i;
+
+       for (i = 0; i < dev->fifo_th; i++) {
+               data[0] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR);
+               data[1] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR);
+               if (SNDRV_PCM_FORMAT_S16_LE == format) {
+                       p16[rx_ptr][0] = data[0]>>8;
+                       p16[rx_ptr][1] = data[1]>>8;
+               } else if (SNDRV_PCM_FORMAT_S24_LE == format) {
+                       p32[rx_ptr][0] = data[0];
+                       p32[rx_ptr][1] = data[1];
+               } else if (SNDRV_PCM_FORMAT_S32_LE == format) {
+                       p32[rx_ptr][0] = data[0]<<8;
+                       p32[rx_ptr][1] = data[1]<<8;
+               }
+
+               period_pos++;
+               if (++rx_ptr >= runtime->buffer_size)
+                       rx_ptr = 0;
+       }
+
+       *period_elapsed = period_pos >= runtime->period_size;
+       return rx_ptr;
+}
+
+static const struct snd_pcm_hardware sf_pcm_hardware = {
+       .info = SNDRV_PCM_INFO_INTERLEAVED |
+               SNDRV_PCM_INFO_MMAP |
+               SNDRV_PCM_INFO_MMAP_VALID |
+               SNDRV_PCM_INFO_BLOCK_TRANSFER,
+       .rates = SNDRV_PCM_RATE_8000 |
+               SNDRV_PCM_RATE_11025 |
+               SNDRV_PCM_RATE_16000 |
+               SNDRV_PCM_RATE_22050 |
+               SNDRV_PCM_RATE_32000 |
+               SNDRV_PCM_RATE_44100 |
+               SNDRV_PCM_RATE_48000,
+       .rate_min = 8000,
+       .rate_max = 48000,
+       .formats = SNDRV_PCM_FMTBIT_S16_LE |
+               SNDRV_PCM_FMTBIT_S24_LE |
+               SNDRV_PCM_FMTBIT_S32_LE,
+       .channels_min = 2,
+       .channels_max = 2,
+       .buffer_bytes_max = BUFFER_BYTES_MAX,
+       .period_bytes_min = PERIOD_BYTES_MIN,
+       .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+       .periods_min = PERIODS_MIN,
+       .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+       .fifo_size = 16,
+};
+
+static void sf_spdif_pcm_transfer(struct sf_spdif_dev *dev, bool push)
+{
+       struct snd_pcm_substream *substream;
+       bool active, period_elapsed;
+
+       rcu_read_lock();
+       if (push)
+               substream = rcu_dereference(dev->tx_substream);
+       else
+               substream = rcu_dereference(dev->rx_substream);
+       active = substream && snd_pcm_running(substream);
+       if (active) {
+               unsigned int ptr;
+               unsigned int new_ptr;
+
+               if (push) {
+                       ptr = READ_ONCE(dev->tx_ptr);
+                       new_ptr = dev->tx_fn(dev, substream->runtime, ptr,
+                                       &period_elapsed, dev->format);
+                       cmpxchg(&dev->tx_ptr, ptr, new_ptr);
+               } else {
+                       ptr = READ_ONCE(dev->rx_ptr);
+                       new_ptr = dev->rx_fn(dev, substream->runtime, ptr,
+                                       &period_elapsed, dev->format);
+                       cmpxchg(&dev->rx_ptr, ptr, new_ptr);
+               }
+
+               if (period_elapsed)
+                       snd_pcm_period_elapsed(substream);
+       }
+       rcu_read_unlock();
+}
+
+void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev)
+{
+       sf_spdif_pcm_transfer(dev, true);
+}
+
+void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev)
+{
+       sf_spdif_pcm_transfer(dev, false);
+}
+
+static int sf_pcm_open(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+       struct sf_spdif_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+
+       snd_soc_set_runtime_hwparams(substream, &sf_pcm_hardware);
+       snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+       runtime->private_data = dev;
+
+       return 0;
+}
+
+static int sf_pcm_close(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream)
+{
+       synchronize_rcu();
+       return 0;
+}
+
+static int sf_pcm_hw_params(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream,
+                       struct snd_pcm_hw_params *hw_params)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct sf_spdif_dev *dev = runtime->private_data;
+
+       switch (params_channels(hw_params)) {
+       case 2:
+               break;
+       default:
+               dev_err(dev->dev, "invalid channels number\n");
+               return -EINVAL;
+       }
+
+       dev->format = params_format(hw_params);
+       switch (dev->format) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+       case SNDRV_PCM_FORMAT_S24_LE:
+       case SNDRV_PCM_FORMAT_S32_LE:
+               break;
+       default:
+               dev_err(dev->dev, "invalid format\n");
+               return -EINVAL;
+       }
+
+       dev->tx_fn = sf_spdif_pcm_tx;
+       dev->rx_fn = sf_spdif_pcm_rx;
+
+       return 0;
+}
+
+static int sf_pcm_trigger(struct snd_soc_component *component,
+                       struct snd_pcm_substream *substream, int cmd)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct sf_spdif_dev *dev = runtime->private_data;
+       int ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       WRITE_ONCE(dev->tx_ptr, 0);
+                       rcu_assign_pointer(dev->tx_substream, substream);
+               } else {
+                       WRITE_ONCE(dev->rx_ptr, 0);
+                       rcu_assign_pointer(dev->rx_substream, substream);
+               }
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       rcu_assign_pointer(dev->tx_substream, NULL);
+               else
+                       rcu_assign_pointer(dev->rx_substream, NULL);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static snd_pcm_uframes_t sf_pcm_pointer(struct snd_soc_component *component,
+                                       struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct sf_spdif_dev *dev = runtime->private_data;
+       snd_pcm_uframes_t pos;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               pos = READ_ONCE(dev->tx_ptr);
+       }
+       else {
+               pos = READ_ONCE(dev->rx_ptr);
+       }
+
+       return pos < runtime->buffer_size ? pos : 0;
+}
+
+static int sf_pcm_new(struct snd_soc_component *component,
+                       struct snd_soc_pcm_runtime *rtd)
+{
+       size_t size = sf_pcm_hardware.buffer_bytes_max;
+
+       snd_pcm_set_managed_buffer_all(rtd->pcm,
+                       SNDRV_DMA_TYPE_CONTINUOUS,
+                       NULL, size, size);
+
+       return 0;
+}
+
+static const struct snd_soc_component_driver sf_pcm_component = {
+       .open           = sf_pcm_open,
+       .close          = sf_pcm_close,
+       .hw_params      = sf_pcm_hw_params,
+       .trigger        = sf_pcm_trigger,
+       .pointer        = sf_pcm_pointer,
+       .pcm_construct  = sf_pcm_new,
+};
+
+int sf_spdif_pcm_register(struct platform_device *pdev)
+{
+       return devm_snd_soc_register_component(&pdev->dev, &sf_pcm_component,
+                                       NULL, 0);
+}
+
diff --git a/sound/soc/starfive/spdif.c b/sound/soc/starfive/spdif.c
new file mode 100644 (file)
index 0000000..9103336
--- /dev/null
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "spdif.h"
+
+static irqreturn_t spdif_irq_handler(int irq, void *dev_id)
+{
+       struct sf_spdif_dev *dev = dev_id;
+       bool irq_valid = false;
+       unsigned int intr;
+       unsigned int stat;
+
+       regmap_read(dev->regmap, SPDIF_INT_REG, &intr);
+       regmap_read(dev->regmap, SPDIF_STAT_REG, &stat);
+       regmap_update_bits(dev->regmap, SPDIF_CTRL,
+               SPDIF_MASK_ENABLE, 0);
+       regmap_update_bits(dev->regmap, SPDIF_INT_REG,
+               SPDIF_INT_REG_BIT, 0);
+
+       if ((stat & SPDIF_EMPTY_FLAG) || (stat & SPDIF_AEMPTY_FLAG)) {
+               sf_spdif_pcm_push_tx(dev);
+               irq_valid = true;
+       }
+
+       if ((stat & SPDIF_FULL_FLAG) || (stat & SPDIF_AFULL_FLAG)) {
+               sf_spdif_pcm_pop_rx(dev);
+               irq_valid = true;
+       }
+
+       if (stat & SPDIF_PARITY_FLAG) {
+               irq_valid = true;
+       }
+
+       if (stat & SPDIF_UNDERR_FLAG) {
+               irq_valid = true;
+       }
+
+       if (stat & SPDIF_OVRERR_FLAG) {
+               irq_valid = true;
+       }
+
+       if (stat & SPDIF_SYNCERR_FLAG) {
+               irq_valid = true;
+       }
+
+       if (stat & SPDIF_LOCK_FLAG) {
+               irq_valid = true;
+       }
+
+       if (stat & SPDIF_BEGIN_FLAG) {
+               irq_valid = true;
+       }
+
+       if (stat & SPDIF_RIGHT_LEFT) {
+               irq_valid = true;
+       }
+
+       regmap_update_bits(dev->regmap, SPDIF_CTRL,
+               SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE);
+
+       if (irq_valid)
+               return IRQ_HANDLED;
+       else
+               return IRQ_NONE;
+}
+
+static int sf_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+       struct snd_soc_dai *dai)
+{
+       struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai);
+       bool tx;
+
+       tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+       if (tx) {
+               /* tx mode */
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_TR_MODE, SPDIF_TR_MODE);
+
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_MASK_FIFO, SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK);
+       } else {
+               /* rx mode */
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_TR_MODE, 0);
+
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_MASK_FIFO, SPDIF_FULL_MASK | SPDIF_AFULL_MASK);
+       }
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               /* clock recovery form the SPDIF data stream  0:clk_enable */
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_CLK_ENABLE, 0);
+
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_ENABLE, SPDIF_ENABLE);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               /* clock recovery form the SPDIF data stream  1:power save mode */
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE);
+
+               regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+                       SPDIF_ENABLE, 0);
+               break;
+       default:
+               printk(KERN_ERR "%s L.%d cmd:%d\n", __func__, __LINE__, cmd);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int sf_spdif_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+       struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai);
+       unsigned int channels;
+       unsigned int rate;
+       unsigned int format;
+       unsigned int tsamplerate;
+
+       channels = params_channels(params);
+       rate = params_rate(params);
+       format = params_format(params);
+
+       switch (channels) {
+       case 2:
+               break;
+       default:
+               dev_err(dai->dev, "invalid channels number\n");
+               return -EINVAL;
+       }
+
+       switch (format) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+       case SNDRV_PCM_FORMAT_S24_LE:
+       case SNDRV_PCM_FORMAT_S32_LE:
+               break;
+       default:
+               dev_err(spdif->dev, "invalid format\n");
+               return -EINVAL;
+       }
+
+       switch (rate) {
+       case 8000:
+       case 11025:
+       case 16000:
+       case 22050:
+               break;
+       default:
+               printk(KERN_ERR "channel:%d sample rate:%d\n", channels, rate);
+               return -EINVAL;
+       }
+
+       /* 12288000/128=96000 */
+       tsamplerate = (96000 + rate/2)/rate - 1;
+
+       if (rate < 3) {
+               return -EINVAL;
+       }
+
+       /* transmission sample rate */
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL, 0xFF, tsamplerate);
+
+       return 0;
+}
+
+static int sf_spdif_dai_probe(struct snd_soc_dai *dai)
+{
+       struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai);
+
+       #if 0
+       spdif->play_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR;
+       spdif->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+       spdif->play_dma_data.fifo_size = 16;
+       spdif->play_dma_data.maxburst = 16;
+       spdif->capture_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR;
+       spdif->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+       spdif->capture_dma_data.fifo_size = 16;
+       spdif->capture_dma_data.maxburst = 16;
+       snd_soc_dai_init_dma_data(dai, &spdif->play_dma_data, &spdif->capture_dma_data);
+       snd_soc_dai_set_drvdata(dai, spdif);
+       #endif
+
+       /* reset */
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_ENABLE | SPDIF_SFR_ENABLE | SPDIF_FIFO_ENABLE, 0);
+
+       /* clear irq */
+       regmap_update_bits(spdif->regmap, SPDIF_INT_REG,
+               SPDIF_INT_REG_BIT, 0);
+
+       /* power save mode */
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE);
+
+       /* power save mode */
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE);
+
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE,
+               SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE);
+
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_SETPREAMBB, SPDIF_SETPREAMBB);
+
+       regmap_update_bits(spdif->regmap, SPDIF_INT_REG,
+               0x1FFF<<SPDIF_PREAMBLEDEL, 0x3<<SPDIF_PREAMBLEDEL);
+
+       regmap_update_bits(spdif->regmap, SPDIF_FIFO_CTRL,
+               0xFFFFFFFF, 0x20|(0x20<<SPDIF_AFULL_THRESHOLD));
+
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_PARITYGEN, SPDIF_PARITYGEN);
+
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE);
+
+       /* APB access to FIFO enable, disable if use DMA/FIFO */
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_USE_FIFO_IF, 0);
+
+       /* two channel */
+       regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+               SPDIF_CHANNEL_MODE, 0);
+
+       return 0;
+}
+
+static const struct snd_soc_dai_ops sf_spdif_dai_ops = {
+       .trigger = sf_spdif_trigger,
+       .hw_params = sf_spdif_hw_params,
+};
+
+#define SF_PCM_RATE_44100_192000  (SNDRV_PCM_RATE_44100 | \
+                                                                       SNDRV_PCM_RATE_48000 | \
+                                                                       SNDRV_PCM_RATE_96000 | \
+                                                                       SNDRV_PCM_RATE_192000)
+
+#define SF_PCM_RATE_8000_22050  (SNDRV_PCM_RATE_8000 | \
+                                                                       SNDRV_PCM_RATE_11025 | \
+                                                                       SNDRV_PCM_RATE_16000 | \
+                                                                       SNDRV_PCM_RATE_22050)
+
+static struct snd_soc_dai_driver sf_spdif_dai = {
+       .name = "spdif",
+       .id = 0,
+       .probe = sf_spdif_dai_probe,
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SF_PCM_RATE_8000_22050,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE \
+                                       |SNDRV_PCM_FMTBIT_S24_LE \
+                                       |SNDRV_PCM_FMTBIT_S32_LE,
+       },
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SF_PCM_RATE_8000_22050,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE \
+                                       |SNDRV_PCM_FMTBIT_S24_LE \
+                                       |SNDRV_PCM_FMTBIT_S32_LE,
+       },
+       .ops = &sf_spdif_dai_ops,
+       .symmetric_rate = 1,
+};
+
+static const struct snd_soc_component_driver sf_spdif_component = {
+       .name = "sf-spdif",
+};
+
+static const struct regmap_config sf_spdif_regmap_config = {
+       .reg_bits = 32,
+       .reg_stride = 4,
+       .val_bits = 32,
+       .max_register = 0x200,
+};
+
+static int sf_spdif_probe(struct platform_device *pdev)
+{
+       struct sf_spdif_dev *spdif;
+       struct resource *res;
+       void __iomem *base;
+       int ret;
+       int irq;
+
+       spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
+       if (!spdif)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, spdif);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(base))
+               return PTR_ERR(base);
+
+       spdif->spdif_base = base;
+       spdif->regmap = devm_regmap_init_mmio(&pdev->dev, spdif->spdif_base,
+                                       &sf_spdif_regmap_config);
+       if (IS_ERR(spdif->regmap))
+               return PTR_ERR(spdif->regmap);
+
+       spdif->dev = &pdev->dev;
+       spdif->fifo_th = 16;
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq >= 0) {
+               ret = devm_request_irq(&pdev->dev, irq, spdif_irq_handler, 0,
+                               pdev->name, spdif);
+               if (ret < 0) {
+                       dev_err(&pdev->dev, "failed to request irq\n");
+                       return ret;
+               }
+       }
+
+       ret = devm_snd_soc_register_component(&pdev->dev, &sf_spdif_component,
+                                        &sf_spdif_dai, 1);
+       if (ret)
+               goto err_clk_disable;
+
+       if (irq >= 0) {
+               ret = sf_spdif_pcm_register(pdev);
+               spdif->use_pio = true;
+       } else {
+               ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
+                                       0);
+               spdif->use_pio = false;
+       }
+
+       if (ret)
+               goto err_clk_disable;
+
+       return 0;
+
+err_clk_disable:
+       return ret;
+}
+
+static const struct of_device_id sf_spdif_of_match[] = {
+       { .compatible = "starfive,sf-spdif", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, sf_spdif_of_match);
+
+static struct platform_driver sf_spdif_driver = {
+       .driver = {
+               .name = "sf-spdif",
+               .of_match_table = sf_spdif_of_match,
+       },
+       .probe = sf_spdif_probe,
+};
+module_platform_driver(sf_spdif_driver);
+
+MODULE_AUTHOR("michael.yan <michael.yan@starfive.com>");
+MODULE_DESCRIPTION("starfive SPDIF driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/starfive/spdif.h b/sound/soc/starfive/spdif.h
new file mode 100644 (file)
index 0000000..b9b856d
--- /dev/null
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#ifndef __SND_SOC_STARFIVE_SPDIF_H
+#define __SND_SOC_STARFIVE_SPDIF_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <linux/dmaengine.h>
+#include <linux/types.h>
+
+#define SPDIF_CTRL             (0x0)
+#define SPDIF_INT_REG          (0x4)
+#define SPDIF_FIFO_CTRL                (0x8)
+#define SPDIF_STAT_REG         (0xC)
+
+#define SPDIF_FIFO_ADDR                (0x100)
+#define DMAC_SPDIF_POLLING_LEN (256)
+
+///ctrl: sampled on the rising clock edge
+#define SPDIF_TSAMPLERATE      0///[SRATEW-1:0]
+#define SPDIF_SFR_ENABLE       (1<<8)  ///0:SFR reg reset to defualt value; auto set back to '1' after reset
+#define SPDIF_ENABLE           (1<<9)  ///0:reset of SPDIF block, SRF bits are unchanged; 1:enables SPDIF module
+#define SPDIF_FIFO_ENABLE      (1<<10) ///0:FIFO pointers are reset to zero,threshold levels for FIFO are unchaned; auto set back to '1'
+#define SPDIF_CLK_ENABLE       (1<<11) ///1:blocked and the modules are in power save mode; 0:block feeds the modules
+#define SPDIF_TR_MODE          (1<<12) ///0:rx; 1:tx
+#define SPDIF_PARITCHECK       (1<<13) ///0:party bit rx in a sub-frame is repeated on the parity; 1:check on a parity error
+#define SPDIF_PARITYGEN                (1<<14) ///0:parity bit from FIFO is transmitted in sub-frame;1:parity bit generated inside the core and added to a transmitted sub-frame
+#define SPDIF_VALIDITYCHECK    (1<<15) ///0:validity bit in frame isn't checked and all frame are written; 1:validity bit rx is checked
+#define SPDIF_CHANNEL_MODE     (1<<16) ///0:two-channel; 1:single-channel
+#define SPDIF_DUPLICATE                (1<<17) ///only tx -single-channel mode; 0:secondary channel; 1: left(primary) channel
+#define SPDIF_SETPREAMBB       (1<<18) ///only tx; 0:first preamble B after reset tx valid sub-frame; 1:first preamble B is tx after preambleddel(INT_REG)
+#define SPDIF_USE_FIFO_IF      (1<<19) ///0:FIFO disabled ,APB accese FIFO; 1:FIFO enable, APB access to FIFO disable;
+///#define RESERVED            (1<<20)
+#define SPDIF_PARITY_MASK      (1<<21)
+#define SPDIF_UNDERR_MASK      (1<<22)
+#define SPDIF_OVRERR_MASK      (1<<23)
+#define SPDIF_EMPTY_MASK       (1<<24)
+#define SPDIF_AEMPTY_MASK      (1<<25)
+#define SPDIF_FULL_MASK                (1<<26)
+#define SPDIF_AFULL_MASK       (1<<27)
+#define SPDIF_SYNCERR_MASK     (1<<28)
+#define SPDIF_LOCK_MASK                (1<<29)
+#define SPDIF_BEGIN_MASK       (1<<30)
+#define SPDIF_INTEREQ_MAKS     (1<<31)
+
+#define SPDIF_MASK_ENABLE      (SPDIF_PARITY_MASK | SPDIF_UNDERR_MASK | SPDIF_OVRERR_MASK | SPDIF_EMPTY_MASK | \
+                                                        SPDIF_AEMPTY_MASK | SPDIF_FULL_MASK | SPDIF_AFULL_MASK | SPDIF_SYNCERR_MASK |  \
+                                                        SPDIF_LOCK_MASK | SPDIF_BEGIN_MASK | SPDIF_INTEREQ_MAKS)
+
+#define SPDIF_MASK_FIFO                (SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK | SPDIF_FULL_MASK | SPDIF_AFULL_MASK)
+
+////INT_REG
+#define SPDIF_RSAMPLERATE      0               ///[SRATEW-1:0]
+#define SPDIF_PREAMBLEDEL      8               ///[PDELAYW+7:8]        first B delay
+#define SPDIF_PARITYO          (1<<21) ///0:clear parity error
+#define SPDIF_TDATA_UNDERR     (1<<22) ///tx data underrun error;0:clear
+#define SPDIF_RDATA_OVRERR     (1<<23) ///rx data overrun error; 0:clear
+#define SPDIF_FIFO_EMPTY       (1<<24) ///empty; 0:clear
+#define SPDIF_FIOF_AEMPTY      (1<<25) ///almost empty; 0:clear
+#define SPDIF_FIFO_FULL                (1<<26) ///FIFO full; 0:clear
+#define SPDIF_FIFO_AFULL       (1<<27) ///FIFO almost full; 0:clear
+#define SPDIF_SYNCERR          (1<<28) ///sync error; 0:clear
+#define SPDIF_LOCK                     (1<<29) ///sync; 0:clear
+#define SPDIF_BLOCK_BEGIN      (1<<30) ///new start block rx data
+
+#define SPDIF_INT_REG_BIT      (SPDIF_PARITYO | SPDIF_TDATA_UNDERR | SPDIF_RDATA_OVRERR | SPDIF_FIFO_EMPTY |   \
+                                                        SPDIF_FIOF_AEMPTY | SPDIF_FIFO_FULL | SPDIF_FIFO_AFULL | SPDIF_SYNCERR |       \
+                                                        SPDIF_LOCK | SPDIF_BLOCK_BEGIN)
+
+#define SPDIF_ERROR_INT_STATUS (SPDIF_PARITYO | SPDIF_TDATA_UNDERR | SPDIF_RDATA_OVRERR)
+#define SPDIF_FIFO_INT_STATUS  (SPDIF_FIFO_EMPTY | SPDIF_FIOF_AEMPTY | SPDIF_FIFO_FULL | SPDIF_FIFO_AFULL)
+
+#define SPDIF_INT_PARITY_ERROR (-1)
+#define SPDIF_INT_TDATA_UNDERR (-2)
+#define SPDIF_INT_RDATA_OVRERR (-3)
+#define SPDIF_INT_FIFO_EMPTY   1
+#define SPDIF_INT_FIFO_AEMPTY  2
+#define SPDIF_INT_FIFO_FULL    3
+#define SPDIF_INT_FIFO_AFULL   4
+#define SPDIF_INT_SYNCERR      (-4)
+#define SPDIF_INT_LOCK         5       // reciever has become synchronized with input data stream
+#define SPDIF_INT_BLOCK_BEGIN  6       // start a new block in recieve data, written into FIFO
+
+///FIFO_CTRL
+#define SPDIF_AEMPTY_THRESHOLD 0       // [depth-1:0]
+#define SPDIF_AFULL_THRESHOLD  16      // [depth+15:16]
+
+///STAT_REG
+#define SPDIF_FIFO_LEVEL       (1<<0)
+#define SPDIF_PARITY_FLAG      (1<<21) // 1:error; 0:repeated
+#define SPDIF_UNDERR_FLAG      (1<<22) // 1:error
+#define SPDIF_OVRERR_FLAG      (1<<23) // 1:error
+#define SPDIF_EMPTY_FLAG       (1<<24) // 1:fifo empty
+#define SPDIF_AEMPTY_FLAG      (1<<25) // 1:fifo almost empty
+#define SPDIF_FULL_FLAG                (1<<26) // 1:fifo full
+#define SPDIF_AFULL_FLAG       (1<<27) // 1:fifo almost full
+#define SPDIF_SYNCERR_FLAG     (1<<28) // 1:rx sync error
+#define SPDIF_LOCK_FLAG                (1<<29) // 1:RX sync
+#define SPDIF_BEGIN_FLAG       (1<<30) // 1:start a new block
+#define SPDIF_RIGHT_LEFT       (1<<31) // 1:left channel received and tx into FIFO; 0:right channel received and tx into FIFO
+
+#define SPDIF_STAT             (SPDIF_PARITY_FLAG | SPDIF_UNDERR_FLAG | SPDIF_OVRERR_FLAG | SPDIF_EMPTY_FLAG |         \
+                                                SPDIF_AEMPTY_FLAG | SPDIF_FULL_FLAG | SPDIF_AFULL_FLAG | SPDIF_SYNCERR_FLAG |          \
+                                                SPDIF_LOCK_FLAG | SPDIF_BEGIN_FLAG | SPDIF_RIGHT_LEFT)
+struct sf_spdif_dev {
+       void __iomem *spdif_base;
+       struct regmap *regmap;
+       struct device *dev;
+       u32 fifo_th;
+       int active;
+
+       /* data related to DMA transfers b/w i2s and DMAC */
+       struct snd_dmaengine_dai_dma_data play_dma_data;
+       struct snd_dmaengine_dai_dma_data capture_dma_data;
+
+       bool use_pio;
+       struct snd_pcm_substream __rcu *tx_substream;
+       struct snd_pcm_substream __rcu *rx_substream;
+
+       unsigned int (*tx_fn)(struct sf_spdif_dev *dev,
+                       struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
+                       bool *period_elapsed, snd_pcm_format_t format);
+       unsigned int (*rx_fn)(struct sf_spdif_dev *dev,
+                       struct snd_pcm_runtime *runtime, unsigned int rx_ptr,
+                       bool *period_elapsed, snd_pcm_format_t format);
+
+       snd_pcm_format_t format;
+       //unsigned int sample_bits;
+       unsigned int tx_ptr;
+       unsigned int rx_ptr;
+
+       struct snd_dmaengine_dai_dma_data dma_data;
+};
+
+#if IS_ENABLED(CONFIG_SND_STARFIVE_SPDIF_PCM)
+void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev);
+void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev);
+int sf_spdif_pcm_register(struct platform_device *pdev);
+#else
+static inline void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev) { }
+static inline void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev) { }
+static inline int sf_spdif_pcm_register(struct platform_device *pdev)
+{
+       return -EINVAL;
+}
+#endif
+
+
+#endif /* __SND_SOC_STARFIVE_SPDIF_H */