4328 lines
119 KiB
Diff
4328 lines
119 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Walker Chen <walker.chen@starfivetech.com>
|
|
Date: Wed, 17 Nov 2021 15:50:50 +0800
|
|
Subject: ASoC: starfive: Add StarFive JH7100 audio drivers
|
|
|
|
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>
|
|
---
|
|
sound/soc/Kconfig | 1 +
|
|
sound/soc/Makefile | 1 +
|
|
sound/soc/starfive/Kconfig | 59 +
|
|
sound/soc/starfive/Makefile | 24 +
|
|
sound/soc/starfive/i2svad-pcm.c | 249 +++
|
|
sound/soc/starfive/i2svad.c | 1042 ++++++++++
|
|
sound/soc/starfive/i2svad.h | 246 +++
|
|
sound/soc/starfive/pdm.c | 362 ++++
|
|
sound/soc/starfive/pdm.h | 43 +
|
|
sound/soc/starfive/pwmdac-pcm.c | 233 +++
|
|
sound/soc/starfive/pwmdac-transmitter.c | 81 +
|
|
sound/soc/starfive/pwmdac.c | 862 ++++++++
|
|
sound/soc/starfive/pwmdac.h | 161 ++
|
|
sound/soc/starfive/spdif-pcm.c | 288 +++
|
|
sound/soc/starfive/spdif.c | 384 ++++
|
|
sound/soc/starfive/spdif.h | 154 ++
|
|
16 files changed, 4190 insertions(+)
|
|
|
|
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
|
|
index 848fbae26c3b..8d1d9401ecf2 100644
|
|
--- a/sound/soc/Kconfig
|
|
+++ b/sound/soc/Kconfig
|
|
@@ -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"
|
|
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
|
|
index 507eaed1d6a1..e4ad5fa85173 100644
|
|
--- a/sound/soc/Makefile
|
|
+++ b/sound/soc/Makefile
|
|
@@ -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
|
|
index 000000000000..2dfbf9d48886
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/Kconfig
|
|
@@ -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
|
|
index 000000000000..60d85f424907
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/Makefile
|
|
@@ -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
|
|
index 000000000000..61f6339d00d2
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/i2svad-pcm.c
|
|
@@ -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
|
|
index 000000000000..74978d30702c
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/i2svad.c
|
|
@@ -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
|
|
index 000000000000..cd14cb4ce813
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/i2svad.h
|
|
@@ -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
|
|
index 000000000000..af5efd8adfb7
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/pdm.c
|
|
@@ -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
|
|
index 000000000000..cbc0a9ecd378
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/pdm.h
|
|
@@ -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
|
|
index 000000000000..decd08bc289e
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/pwmdac-pcm.c
|
|
@@ -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
|
|
index 000000000000..46c2e5041ffb
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/pwmdac-transmitter.c
|
|
@@ -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
|
|
index 000000000000..78f2f9c62ec9
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/pwmdac.c
|
|
@@ -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
|
|
index 000000000000..b9f651fc59f1
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/pwmdac.h
|
|
@@ -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
|
|
index 000000000000..cd78ffd1e691
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/spdif-pcm.c
|
|
@@ -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
|
|
index 000000000000..910333638056
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/spdif.c
|
|
@@ -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
|
|
index 000000000000..b9b856db701b
|
|
--- /dev/null
|
|
+++ b/sound/soc/starfive/spdif.h
|
|
@@ -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 */
|
|
--
|
|
Armbian
|
|
|