Codec DAI endpoint for TXDAC. The codec does only a few things
- set up decimation
- enable codec and I2S output
- ensures the driver doesn't have dac overwrite enabled. (unmute the
output)
Signed-off-by: Anton D. Stavinskii <stavinsky@gmail.com>
---
sound/soc/sophgo/Kconfig | 11 +-
sound/soc/sophgo/Makefile | 1 +
sound/soc/sophgo/cv1800b-sound-dac.c | 204 +++++++++++++++++++++++++++++++++++
3 files changed, 215 insertions(+), 1 deletion(-)
diff --git a/sound/soc/sophgo/Kconfig b/sound/soc/sophgo/Kconfig
index 12d1a57ea308..e4786f087589 100644
--- a/sound/soc/sophgo/Kconfig
+++ b/sound/soc/sophgo/Kconfig
@@ -28,10 +28,19 @@ config SND_SOC_CV1800B_ADC_CODEC
help
This driver provides an ASoC codec DAI for capture and basic
control of the RXADC registers.
-
Say Y or M to build support for the Sophgo CV1800B
internal analog ADC codec block (RXADC).
The module will be called cv1800b-sound-adc
+config SND_SOC_CV1800B_DAC_CODEC
+ tristate "Sophgo CV1800B/SG2002 internal DAC codec"
+ depends on SND_SOC
+ help
+ This driver provides an ASoC codec DAI for playback and basic
+ control of the TXDAC registers.
+
+ Say Y or M to build support for the Sophgo CV1800B
+ internal analog DAC codec block (TXDAC).
+ The module will be called cv1800b-sound-dac
endmenu
diff --git a/sound/soc/sophgo/Makefile b/sound/soc/sophgo/Makefile
index c654d6059cbd..ec8dd31efddd 100644
--- a/sound/soc/sophgo/Makefile
+++ b/sound/soc/sophgo/Makefile
@@ -2,3 +2,4 @@
# Sophgo Platform Support
obj-$(CONFIG_SND_SOC_CV1800B_TDM) += cv1800b-tdm.o
obj-$(CONFIG_SND_SOC_CV1800B_ADC_CODEC) += cv1800b-sound-adc.o
+obj-$(CONFIG_SND_SOC_CV1800B_DAC_CODEC) += cv1800b-sound-dac.o
diff --git a/sound/soc/sophgo/cv1800b-sound-dac.c b/sound/soc/sophgo/cv1800b-sound-dac.c
new file mode 100644
index 000000000000..ccf386174639
--- /dev/null
+++ b/sound/soc/sophgo/cv1800b-sound-dac.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Internal DAC codec for cv1800b based CPUs
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <sound/soc.h>
+#include <linux/io.h>
+
+#define CV1800B_TXDAC_CTRL0 0x00
+#define CV1800B_TXDAC_CTRL1 0x04
+#define CV1800B_TXDAC_STATUS 0x08
+#define CV1800B_TXDAC_AFE0 0x0c
+#define CV1800B_TXDAC_AFE1 0x10
+#define CV1800B_TXDAC_ANA0 0x20
+#define CV1800B_TXDAC_ANA1 0x24
+#define CV1800B_TXDAC_ANA2 0x28
+
+/* cv1800b_TXDAC_CTRL0 */
+#define REG_TXDAC_EN GENMASK(0, 0)
+#define REG_I2S_RX_EN GENMASK(1, 1)
+
+/* cv1800b_TXDAC_CTRL1 */
+#define REG_TXDAC_CIC_OPT GENMASK(1, 0)
+
+/* cv1800b_TXDAC_AFE0 */
+#define REG_TXDAC_INIT_DLY_CNT GENMASK(5, 0)
+
+/* cv1800b_TXDAC_ANA2 */
+#define TXDAC_OW_VAL_L_MASK GENMASK(7, 0)
+#define TXDAC_OW_VAL_R_MASK GENMASK(15, 8)
+#define TXDAC_OW_EN_L_MASK GENMASK(16, 16)
+#define TXDAC_OW_EN_R_MASK GENMASK(17, 17)
+
+struct cv1800b_priv {
+ void __iomem *regs;
+ struct device *dev;
+};
+
+enum decimation_values {
+ DECIMATION_64 = 0,
+ DECIMATION_128,
+ DECIMATION_256,
+ DECIMATION_512,
+};
+
+static void cv1800b_dac_enable(struct cv1800b_priv *priv, bool enable)
+{
+ u32 val;
+
+ val = readl(priv->regs + CV1800B_TXDAC_CTRL0);
+ val = u32_replace_bits(val, enable, REG_TXDAC_EN);
+ val = u32_replace_bits(val, enable, REG_I2S_RX_EN);
+ writel(val, priv->regs + CV1800B_TXDAC_CTRL0);
+}
+
+static void cv1800b_dac_mute(struct cv1800b_priv *priv, bool enable)
+{
+ u32 val;
+
+ val = readl(priv->regs + CV1800B_TXDAC_ANA2);
+ val = u32_replace_bits(val, enable, TXDAC_OW_EN_L_MASK);
+ val = u32_replace_bits(val, enable, TXDAC_OW_EN_R_MASK);
+ writel(val, priv->regs + CV1800B_TXDAC_ANA2);
+}
+
+static int cv1800b_dac_decimation(struct cv1800b_priv *priv, u8 dec)
+{
+ u32 val;
+
+ if (dec > 3)
+ return -EINVAL;
+
+ val = readl(priv->regs + CV1800B_TXDAC_CTRL1);
+ val = u32_replace_bits(val, dec, REG_TXDAC_CIC_OPT);
+ writel(val, priv->regs + CV1800B_TXDAC_CTRL1);
+ return 0;
+}
+
+static int cv1800b_dac_dly(struct cv1800b_priv *priv, u32 dly)
+{
+ u32 val;
+
+ if (dly > 63)
+ return -EINVAL;
+
+ val = readl(priv->regs + CV1800B_TXDAC_AFE0);
+ val = u32_replace_bits(val, dly, REG_TXDAC_INIT_DLY_CNT);
+ writel(val, priv->regs + CV1800B_TXDAC_AFE0);
+ return 0;
+}
+
+static int cv1800b_dac_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct cv1800b_priv *priv = snd_soc_dai_get_drvdata(dai);
+ int ret;
+ unsigned int rate = params_rate(params);
+
+ if (rate != 48000) {
+ dev_err(priv->dev, "rate %u is not supported\n", rate);
+ return -EINVAL;
+ }
+
+ cv1800b_dac_mute(priv, false);
+ /* minimal decimation for 48kHz is 64*/
+ ret = cv1800b_dac_decimation(priv, DECIMATION_64);
+ if (ret)
+ return ret;
+
+ /* value is taken from vendors driver 48kHz
+ * tested on sg2000 and sg2002.
+ */
+ ret = cv1800b_dac_dly(priv, 0x19);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int cv1800b_dac_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct cv1800b_priv *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:
+ cv1800b_dac_enable(priv, true);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ cv1800b_dac_enable(priv, false);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cv1800b_dac_dai_ops = {
+ .hw_params = cv1800b_dac_hw_params,
+ .trigger = cv1800b_dac_dai_trigger,
+};
+
+static struct snd_soc_dai_driver cv1800b_dac_dai = {
+ .name = "dac-hifi",
+ .playback = { .stream_name = "DAC Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE },
+ .ops = &cv1800b_dac_dai_ops,
+};
+
+static const struct snd_soc_component_driver cv1800b_dac_component = {
+ .name = "cv1800b-dac-codec",
+};
+
+static int cv1800b_dac_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cv1800b_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ platform_set_drvdata(pdev, priv);
+ return devm_snd_soc_register_component(&pdev->dev,
+ &cv1800b_dac_component,
+ &cv1800b_dac_dai, 1);
+}
+
+static const struct of_device_id cv1800b_dac_of_match[] = {
+ { .compatible = "sophgo,cv1800b-sound-dac" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cv1800b_dac_of_match);
+
+static struct platform_driver cv1800b_dac_driver = {
+ .probe = cv1800b_dac_probe,
+ .driver = {
+ .name = "cv1800b-dac-codec",
+ .of_match_table = cv1800b_dac_of_match,
+ },
+};
+module_platform_driver(cv1800b_dac_driver);
+
+MODULE_DESCRIPTION("DAC codec for CV1800B");
+MODULE_AUTHOR("Anton D. Stavinskii <stavinsky@gmail.com>");
+MODULE_LICENSE("GPL");
--
2.43.0
On Tue, Jan 20, 2026 at 11:06:07PM +0400, Anton D. Stavinskii wrote:
> help
> This driver provides an ASoC codec DAI for capture and basic
> control of the RXADC registers.
> -
> Say Y or M to build support for the Sophgo CV1800B
> internal analog ADC codec block (RXADC).
> The module will be called cv1800b-sound-adc
Extra change here.
> +static int cv1800b_dac_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)
> +{
> + struct cv1800b_priv *priv = snd_soc_dai_get_drvdata(dai);
> + int ret;
> + unsigned int rate = params_rate(params);
> + cv1800b_dac_mute(priv, false);
> + /* minimal decimation for 48kHz is 64*/
Nothing ever mutes the DAC so this is a bit redundant. The mute should
probably be a mute_stream() operation.
On Tue, Jan 27, 2026 at 12:49:52PM +0400, Mark Brown wrote:
> On Tue, Jan 20, 2026 at 11:06:07PM +0400, Anton D. Stavinskii wrote:
>
> > help
> > This driver provides an ASoC codec DAI for capture and basic
> > control of the RXADC registers.
> > -
> > Say Y or M to build support for the Sophgo CV1800B
> > internal analog ADC codec block (RXADC).
> > The module will be called cv1800b-sound-adc
>
> Extra change here.
Will fix, thanks.
>
> > +static int cv1800b_dac_hw_params(struct snd_pcm_substream *substream,
> > + struct snd_pcm_hw_params *params,
> > + struct snd_soc_dai *dai)
> > +{
> > + struct cv1800b_priv *priv = snd_soc_dai_get_drvdata(dai);
> > + int ret;
> > + unsigned int rate = params_rate(params);
>
> > + cv1800b_dac_mute(priv, false);
> > + /* minimal decimation for 48kHz is 64*/
>
> Nothing ever mutes the DAC so this is a bit redundant. The mute should
> probably be a mute_stream() operation.
I'm not sure here. DAC mute feature was not implemented because I don't know how
exactly do that. The public documentation is very weak for my taste.
This call added here to be sure that override flag is
not set (override feature replaces the output by setting it to constant
value and ignoring the input, so it is some kind of mute from my understanding.
So ensuring this off is needed be sure that DAC will output our I2S data).
What do you think will be better to do here?
I'm sure that is needed, but may be better to move
it to some early stages, like probe function.
On Tue, Jan 27, 2026 at 07:11:24PM +0400, Anton D. Stavinskii wrote: > On Tue, Jan 27, 2026 at 12:49:52PM +0400, Mark Brown wrote: > > Nothing ever mutes the DAC so this is a bit redundant. The mute should > > probably be a mute_stream() operation. > I'm not sure here. DAC mute feature was not implemented because I don't know how > exactly do that. The public documentation is very weak for my taste. > This call added here to be sure that override flag is > not set (override feature replaces the output by setting it to constant > value and ignoring the input, so it is some kind of mute from my understanding. > So ensuring this off is needed be sure that DAC will output our I2S data). > What do you think will be better to do here? > I'm sure that is needed, but may be better to move > it to some early stages, like probe function. It's probably fine to leave the functional code as is but you should add some comments to the mute function explaining waht's going on here.
On Tue, Jan 27, 2026 at 03:13:59PM +0400, Mark Brown wrote: > On Tue, Jan 27, 2026 at 07:11:24PM +0400, Anton D. Stavinskii wrote: > > On Tue, Jan 27, 2026 at 12:49:52PM +0400, Mark Brown wrote: > > > > Nothing ever mutes the DAC so this is a bit redundant. The mute should > > > probably be a mute_stream() operation. > > > I'm not sure here. DAC mute feature was not implemented because I don't know how > > exactly do that. The public documentation is very weak for my taste. > > This call added here to be sure that override flag is > > not set (override feature replaces the output by setting it to constant > > value and ignoring the input, so it is some kind of mute from my understanding. > > So ensuring this off is needed be sure that DAC will output our I2S data). > > What do you think will be better to do here? > > I'm sure that is needed, but may be better to move > > it to some early stages, like probe function. > > It's probably fine to leave the functional code as is but you should add > some comments to the mute function explaining waht's going on here. Sounds good. Will add comments and prepare v5. Thanks a lot! > _______________________________________________ > linux-riscv mailing list > linux-riscv@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv
On Tue, Jan 27, 2026 at 07:30:27PM +0400, Anton D. Stavinskii wrote: > On Tue, Jan 27, 2026 at 03:13:59PM +0400, Mark Brown wrote: > > It's probably fine to leave the functional code as is but you should add > > some comments to the mute function explaining waht's going on here. > Sounds good. Will add comments and prepare v5. Like I said in reply to the cover letter please send an incremental change.
© 2016 - 2026 Red Hat, Inc.