summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorXingyu Wu <xingyu.wu@starfivetech.com>2023-06-15 06:32:50 +0300
committerXingyu Wu <xingyu.wu@starfivetech.com>2023-06-15 13:24:12 +0300
commit52159b42a877061873aa0c1e57408b47d6027c01 (patch)
tree06de97170b0c123e1f40da66db2079983af3ef69 /sound
parente6fe7b72f82d90502b2736573313d276ad66b32f (diff)
downloadlinux-52159b42a877061873aa0c1e57408b47d6027c01.tar.xz
ASoC: starfive: Add SPDIF and PCM driver
Add SPDIF and SPDIF-PCM driver for StarFive JH7110. Signed-off-by: Xingyu Wu <xingyu.wu@starfivetech.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/starfive/Kconfig17
-rw-r--r--sound/soc/starfive/Makefile4
-rw-r--r--sound/soc/starfive/jh7110_spdif.c568
-rw-r--r--sound/soc/starfive/jh7110_spdif.h196
-rw-r--r--sound/soc/starfive/jh7110_spdif_pcm.c339
5 files changed, 1124 insertions, 0 deletions
diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig
index 1e38a4fa2e99..a8ca01e9bd91 100644
--- a/sound/soc/starfive/Kconfig
+++ b/sound/soc/starfive/Kconfig
@@ -28,6 +28,23 @@ config SND_SOC_JH7110_PWMDAC_PCM
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_SOC_JH7110_SPDIF
+ tristate "JH7110 SPDIF module"
+ depends on HAVE_CLK && SND_SOC_STARFIVE
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y or M if you want to add support for SPDIF driver of StarFive
+ JH7110 SoC.
+
+config SND_SOC_JH7110_SPDIF_PCM
+ bool "PCM PIO extension for JH7110 SPDIF"
+ depends on SND_SOC_JH7110_SPDIF
+ default y if SND_SOC_JH7110_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_SOC_JH7110_TDM
tristate "JH7110 TDM device driver"
depends on HAVE_CLK && SND_SOC_STARFIVE
diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile
index 0394e02446e6..c31f1bf34e2c 100644
--- a/sound/soc/starfive/Makefile
+++ b/sound/soc/starfive/Makefile
@@ -3,4 +3,8 @@ obj-$(CONFIG_SND_SOC_JH7110_PWMDAC) += jh7110_pwmdac.o
obj-$(CONFIG_SND_SOC_JH7110_PWMDAC_TRANSMITTER) += jh7110_pwmdac_transmitter.o
pwmdac-$(CONFIG_SND_SOC_JH7110_PWMDAC_PCM) += jh7110_pwmdac_pcm.o
+obj-$(CONFIG_SND_SOC_JH7110_SPDIF) += spdif.o
+spdif-y := jh7110_spdif.o
+spdif-$(CONFIG_SND_SOC_JH7110_SPDIF_PCM) += jh7110_spdif_pcm.o
+
obj-$(CONFIG_SND_SOC_JH7110_TDM) += jh7110_tdm.o
diff --git a/sound/soc/starfive/jh7110_spdif.c b/sound/soc/starfive/jh7110_spdif.c
new file mode 100644
index 000000000000..2ac70345fe73
--- /dev/null
+++ b/sound/soc/starfive/jh7110_spdif.c
@@ -0,0 +1,568 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SPDIF driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 StarFive Technology Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "jh7110_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 = 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:
+ dev_err(dai->dev, "%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 = params_channels(params);
+ unsigned int rate = params_rate(params);
+ unsigned int format = params_format(params);
+ unsigned int tsamplerate;
+ unsigned int mclk;
+ unsigned int audio_root;
+ int ret;
+
+ switch (channels) {
+ case 1:
+ regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+ SPDIF_CHANNEL_MODE, SPDIF_CHANNEL_MODE);
+ regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+ SPDIF_DUPLICATE, SPDIF_DUPLICATE);
+ spdif->channels = false;
+ break;
+ case 2:
+ regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+ SPDIF_CHANNEL_MODE, 0);
+ spdif->channels = true;
+ 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_S24_3LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ break;
+ default:
+ dev_err(dai->dev, "invalid format\n");
+ return -EINVAL;
+ }
+
+ switch (rate) {
+ case 8000:
+ break;
+ case 11025:
+ audio_root = 148500000;
+ /* 11025 * 512 = 5644800 */
+ /* But now pll2 is 1188m and mclk should be 5711539 closely. */
+ mclk = 5711539;
+ break;
+ case 16000:
+ break;
+ case 22050:
+ audio_root = 148500000;
+ mclk = 11423077;
+ break;
+ default:
+ dev_err(dai->dev, "channel:%d sample rate:%d\n", channels, rate);
+ return -EINVAL;
+ }
+
+ /* use mclk_inner clock from 1188m PLL2 will be better about 11k and 22k*/
+ if ((rate == 11025) || (rate == 22050)) {
+ ret = clk_set_parent(spdif->mclk, spdif->mclk_inner);
+ if (ret) {
+ dev_err(dai->dev,
+ "failed to set parent to mclk_inner ret=%d\n", ret);
+ goto fail_ext;
+ }
+
+ ret = clk_set_rate(spdif->audio_root, audio_root);
+ if (ret) {
+ dev_err(dai->dev, "failed to set audio_root rate :%d\n", ret);
+ goto fail_ext;
+ }
+
+ ret = clk_set_rate(spdif->mclk_inner, mclk);
+ if (ret) {
+ dev_err(dai->dev, "failed to set mclk_inner rate :%d\n", ret);
+ goto fail_ext;
+ }
+
+ mclk = clk_get_rate(spdif->mclk_inner);
+ } else {
+ ret = clk_set_parent(spdif->mclk, spdif->mclk_ext);
+ if (ret) {
+ dev_err(dai->dev,
+ "failed to set parent to mclk_ext ret=%d\n", ret);
+ goto fail_ext;
+ }
+
+ mclk = clk_get_rate(spdif->mclk_ext);
+ }
+
+ /* (FCLK)4096000/128=32000 */
+ tsamplerate = (mclk / 128 + rate / 2) / rate - 1;
+ if (tsamplerate < 3)
+ tsamplerate = 3;
+
+ /* transmission sample rate */
+ regmap_update_bits(spdif->regmap, SPDIF_CTRL, 0xFF, tsamplerate);
+
+ return 0;
+
+fail_ext:
+ return ret;
+}
+
+static int sf_spdif_clks_get(struct platform_device *pdev,
+ struct sf_spdif_dev *spdif)
+{
+ static struct clk_bulk_data clks[] = {
+ { .id = "apb" }, /* clock-names in dts file */
+ { .id = "core" },
+ { .id = "audroot" },
+ { .id = "mclk_inner"},
+ { .id = "mclk_ext"},
+ { .id = "mclk"},
+ };
+ int ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(clks), clks);
+
+ spdif->spdif_apb = clks[0].clk;
+ spdif->spdif_core = clks[1].clk;
+ spdif->audio_root = clks[2].clk;
+ spdif->mclk_inner = clks[3].clk;
+ spdif->mclk_ext = clks[4].clk;
+ spdif->mclk = clks[5].clk;
+
+ return ret;
+}
+
+static int sf_spdif_resets_get(struct platform_device *pdev,
+ struct sf_spdif_dev *spdif)
+{
+ struct reset_control_bulk_data resets[] = {
+ { .id = "apb" },
+ };
+ int ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(resets), resets);
+
+ if (ret)
+ return ret;
+
+ spdif->rst_apb = resets[0].rstc;
+
+ return 0;
+}
+
+static int starfive_spdif_crg_enable(struct sf_spdif_dev *spdif, bool enable)
+{
+ int ret;
+
+ dev_dbg(spdif->dev, "starfive_spdif clk&rst %sable.\n", enable ? "en":"dis");
+ if (enable) {
+ ret = clk_prepare_enable(spdif->spdif_apb);
+ if (ret) {
+ dev_err(spdif->dev, "failed to prepare enable spdif_apb\n");
+ goto failed_apb_clk;
+ }
+
+ ret = clk_prepare_enable(spdif->spdif_core);
+ if (ret) {
+ dev_err(spdif->dev, "failed to prepare enable spdif_core\n");
+ goto failed_core_clk;
+ }
+
+ ret = reset_control_deassert(spdif->rst_apb);
+ if (ret) {
+ dev_err(spdif->dev, "failed to deassert apb\n");
+ goto failed_rst;
+ }
+ } else {
+ clk_disable_unprepare(spdif->spdif_core);
+ clk_disable_unprepare(spdif->spdif_apb);
+ }
+
+ return 0;
+
+failed_rst:
+ clk_disable_unprepare(spdif->spdif_core);
+failed_core_clk:
+ clk_disable_unprepare(spdif->spdif_apb);
+failed_apb_clk:
+ return ret;
+}
+
+static int sf_spdif_dai_probe(struct snd_soc_dai *dai)
+{
+ struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai);
+
+ pm_runtime_get_sync(spdif->dev);
+
+ /* 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,
+ BIT8TO20MASK<<SPDIF_PREAMBLEDEL, 0x3<<SPDIF_PREAMBLEDEL);
+
+ regmap_update_bits(spdif->regmap, SPDIF_FIFO_CTRL,
+ ALLBITMASK, 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);
+
+ pm_runtime_put_sync(spdif->dev);
+ return 0;
+}
+
+static const struct snd_soc_dai_ops sf_spdif_dai_ops = {
+ .trigger = sf_spdif_trigger,
+ .hw_params = sf_spdif_hw_params,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int spdif_system_suspend(struct device *dev)
+{
+ struct sf_spdif_dev *spdif = dev_get_drvdata(dev);
+
+ /* save the register value */
+ regmap_read(spdif->regmap, SPDIF_CTRL, &spdif->reg_spdif_ctrl);
+ regmap_read(spdif->regmap, SPDIF_INT_REG, &spdif->reg_spdif_int);
+ regmap_read(spdif->regmap, SPDIF_FIFO_CTRL, &spdif->reg_spdif_fifo_ctrl);
+
+ return pm_runtime_force_suspend(dev);
+}
+
+static int spdif_system_resume(struct device *dev)
+{
+ struct sf_spdif_dev *spdif = dev_get_drvdata(dev);
+ int ret = pm_runtime_force_resume(dev);
+
+ if (ret)
+ return ret;
+
+ /* restore the register value */
+ regmap_update_bits(spdif->regmap, SPDIF_CTRL,
+ ALLBITMASK, spdif->reg_spdif_ctrl);
+ regmap_update_bits(spdif->regmap, SPDIF_INT_REG,
+ ALLBITMASK, spdif->reg_spdif_int);
+ regmap_update_bits(spdif->regmap, SPDIF_FIFO_CTRL,
+ ALLBITMASK, spdif->reg_spdif_fifo_ctrl);
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int spdif_runtime_suspend(struct device *dev)
+{
+ struct sf_spdif_dev *spdif = dev_get_drvdata(dev);
+
+ return starfive_spdif_crg_enable(spdif, false);
+}
+
+static int spdif_runtime_resume(struct device *dev)
+{
+ struct sf_spdif_dev *spdif = dev_get_drvdata(dev);
+
+ return starfive_spdif_crg_enable(spdif, true);
+}
+#endif
+
+static const struct dev_pm_ops spdif_pm_ops = {
+ SET_RUNTIME_PM_OPS(spdif_runtime_suspend, spdif_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(spdif_system_suspend, spdif_system_resume)
+};
+
+#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 = 1,
+ .channels_max = 2,
+ .rates = SF_PCM_RATE_8000_22050,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &sf_spdif_dai_ops,
+ .symmetric_rate = 1,
+};
+
+static const struct snd_soc_component_driver sf_spdif_component = {
+ .name = "starfive-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;
+
+ ret = sf_spdif_clks_get(pdev, spdif);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get audio clock\n");
+ return ret;
+ }
+
+ ret = sf_spdif_resets_get(pdev, spdif);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get audio reset controls\n");
+ return ret;
+ }
+
+ ret = starfive_spdif_crg_enable(spdif, true);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable audio clock\n");
+ return ret;
+ }
+
+ 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;
+
+ starfive_spdif_crg_enable(spdif, false);
+ pm_runtime_enable(&pdev->dev);
+ dev_dbg(&pdev->dev, "spdif register done.\n");
+
+ return 0;
+
+err_clk_disable:
+ return ret;
+}
+
+static const struct of_device_id sf_spdif_of_match[] = {
+ { .compatible = "starfive,jh7110-spdif", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sf_spdif_of_match);
+
+static struct platform_driver sf_spdif_driver = {
+ .driver = {
+ .name = "starfive-spdif",
+ .of_match_table = sf_spdif_of_match,
+ .pm = &spdif_pm_ops,
+ },
+ .probe = sf_spdif_probe,
+};
+module_platform_driver(sf_spdif_driver);
+
+MODULE_AUTHOR("curry.zhang <curry.zhang@starfive.com>");
+MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>");
+MODULE_DESCRIPTION("starfive SPDIF driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/starfive/jh7110_spdif.h b/sound/soc/starfive/jh7110_spdif.h
new file mode 100644
index 000000000000..6fa62cdefd59
--- /dev/null
+++ b/sound/soc/starfive/jh7110_spdif.h
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SPDIF driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 StarFive Technology Co., Ltd.
+ */
+
+#ifndef __SND_SOC_JH7110_SPDIF_H
+#define __SND_SOC_JH7110_SPDIF_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.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] */
+/* 0:SFR reg reset to defualt value; auto set back to '1' after reset */
+#define SPDIF_SFR_ENABLE (1<<8)
+/* 0:reset of SPDIF block, SRF bits are unchanged; 1:enables SPDIF module */
+#define SPDIF_ENABLE (1<<9)
+/* 0:FIFO pointers are reset to zero,threshold levels for FIFO are unchaned; auto set back to 1 */
+#define SPDIF_FIFO_ENABLE (1<<10)
+/* 1:blocked and the modules are in power save mode; 0:block feeds the modules */
+#define SPDIF_CLK_ENABLE (1<<11)
+#define SPDIF_TR_MODE (1<<12) /* 0:rx; 1:tx */
+/* 0:party bit rx in a sub-frame is repeated on the parity; 1:check on a parity error */
+#define SPDIF_PARITCHECK (1<<13)
+/*
+ * 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_PARITYGEN (1<<14)
+/* 0:validity bit in frame isn't checked and all frame are written; 1:validity bit rx is checked */
+#define SPDIF_VALIDITYCHECK (1<<15)
+#define SPDIF_CHANNEL_MODE (1<<16) /* 0:two-channel; 1:single-channel */
+/* only tx -single-channel mode; 0:secondary channel; 1: left(primary) channel */
+#define SPDIF_DUPLICATE (1<<17)
+/*
+ * only tx;
+ * 0:first preamble B after reset tx valid sub-frame;
+ * 1:first preamble B is tx after preambleddel(INT_REG)
+ */
+#define SPDIF_SETPREAMBB (1<<18)
+/* 0:FIFO disabled ,APB accese FIFO; 1:FIFO enable, APB access to FIFO disable; */
+#define SPDIF_USE_FIFO_IF (1<<19)
+#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 */
+/* 1:left channel received and tx into FIFO; 0:right channel received and tx into FIFO */
+#define SPDIF_RIGHT_LEFT (1<<31)
+
+#define BIT8TO20MASK 0x1FFF
+#define ALLBITMASK 0xFFFFFFFF
+
+#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;
+ bool channels;
+ unsigned int tx_ptr;
+ unsigned int rx_ptr;
+ struct clk *spdif_apb;
+ struct clk *spdif_core;
+ struct clk *audio_root;
+ struct clk *mclk_inner;
+ struct clk *mclk;
+ struct clk *mclk_ext;
+ struct reset_control *rst_apb;
+ unsigned int reg_spdif_ctrl;
+ unsigned int reg_spdif_int;
+ unsigned int reg_spdif_fifo_ctrl;
+
+ struct snd_dmaengine_dai_dma_data dma_data;
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_JH7110_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
+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)
+{
+ return -EINVAL;
+}
+#endif
+
+#endif /* __SND_SOC_JH7110_SPDIF_H */
diff --git a/sound/soc/starfive/jh7110_spdif_pcm.c b/sound/soc/starfive/jh7110_spdif_pcm.c
new file mode 100644
index 000000000000..60cebc6c16fd
--- /dev/null
+++ b/sound/soc/starfive/jh7110_spdif_pcm.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SPDIF PCM driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 StarFive Technology Co., Ltd.
+ */
+
+#include <linux/io.h>
+#include <linux/rcupdate.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "jh7110_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)
+{
+ unsigned int period_pos = tx_ptr % runtime->period_size;
+ u32 data[2];
+ int i;
+
+ /* two- channel and signal-channel mode */
+ if (dev->channels) {
+ const u16 (*p16)[2] = (void *)runtime->dma_area;
+ const u32 (*p32)[2] = (void *)runtime->dma_area;
+
+ for (i = 0; i < dev->fifo_th; i++) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE) {
+ data[0] = p16[tx_ptr][0];
+ data[0] = data[0]<<8;
+ data[0] &= 0x00ffff00;
+ data[1] = p16[tx_ptr][1];
+ data[1] = data[1]<<8;
+ data[1] &= 0x00ffff00;
+ } else if (format == SNDRV_PCM_FORMAT_S24_LE) {
+ data[0] = p32[tx_ptr][0];
+ data[1] = p32[tx_ptr][1];
+
+ /*
+ * To adapt S24_3LE and ALSA pass parameter of S24_LE.
+ * operation of S24_LE should be same to S24_3LE.
+ * So it would wrong when playback S24_LE file.
+ * when want to playback S24_LE file, should add in there:
+ * data[0] = data[0]>>8;
+ * data[1] = data[1]>>8;
+ */
+
+ data[0] &= 0x00ffffff;
+ data[1] &= 0x00ffffff;
+ } else if (format == SNDRV_PCM_FORMAT_S24_3LE) {
+ data[0] = p32[tx_ptr][0];
+ data[1] = p32[tx_ptr][1];
+ data[0] &= 0x00ffffff;
+ data[1] &= 0x00ffffff;
+ } else if (format == SNDRV_PCM_FORMAT_S32_LE) {
+ data[0] = p32[tx_ptr][0];
+ data[0] = data[0]>>8;
+ data[1] = p32[tx_ptr][1];
+ 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;
+ }
+ } else {
+ const u16 (*p16) = (void *)runtime->dma_area;
+ const u32 (*p32) = (void *)runtime->dma_area;
+
+ for (i = 0; i < dev->fifo_th; i++) {
+ if (format == SNDRV_PCM_FORMAT_S16_LE) {
+ data[0] = p16[tx_ptr];
+ data[0] = data[0]<<8;
+ data[0] &= 0x00ffff00;
+ } else if (format == SNDRV_PCM_FORMAT_S24_LE ||
+ format == SNDRV_PCM_FORMAT_S24_3LE) {
+ data[0] = p32[tx_ptr];
+ data[0] &= 0x00ffffff;
+ } else if (format == SNDRV_PCM_FORMAT_S32_LE) {
+ data[0] = p32[tx_ptr];
+ data[0] = data[0]>>8;
+ }
+
+ iowrite32(data[0], 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;
+ unsigned int period_pos = rx_ptr % runtime->period_size;
+ u32 data[2];
+ 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 (format == SNDRV_PCM_FORMAT_S16_LE) {
+ p16[rx_ptr][0] = data[0]>>8;
+ p16[rx_ptr][1] = data[1]>>8;
+ } else if (format == SNDRV_PCM_FORMAT_S24_LE) {
+ p32[rx_ptr][0] = data[0];
+ p32[rx_ptr][1] = data[1];
+ } else if (format == SNDRV_PCM_FORMAT_S32_LE) {
+ 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 |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .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_S24_3LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 1,
+ .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 1:
+ 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_S24_3LE:
+ 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);
+}