summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/starfive/Kconfig21
-rw-r--r--sound/soc/starfive/Makefile4
-rw-r--r--sound/soc/starfive/jh7110_pwmdac.c954
-rw-r--r--sound/soc/starfive/jh7110_pwmdac.h160
-rw-r--r--sound/soc/starfive/jh7110_pwmdac_pcm.c233
-rw-r--r--sound/soc/starfive/jh7110_pwmdac_transmitter.c95
6 files changed, 1467 insertions, 0 deletions
diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig
index 737c956f7b93..1e38a4fa2e99 100644
--- a/sound/soc/starfive/Kconfig
+++ b/sound/soc/starfive/Kconfig
@@ -7,6 +7,27 @@ config SND_SOC_STARFIVE
the Starfive SoCs' Audio interfaces. You will also need to
select the audio interfaces to support below.
+config SND_SOC_JH7110_PWMDAC
+ tristate "JH7110 PWMDAC module"
+ depends on HAVE_CLK && SND_SOC_STARFIVE
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y or M if you want to add support for JH7110 pwmdac driver.
+
+config SND_SOC_JH7110_PWMDAC_TRANSMITTER
+ tristate "JH7110 PWMDAC transmitter dit codecs"
+ depends on SND_SOC_JH7110_PWMDAC
+ default SND_SOC_JH7110_PWMDAC
+ help
+ Say Y or M if you want to add support for JH7110 pwmdac transmitter dit driver.
+
+config SND_SOC_JH7110_PWMDAC_PCM
+ bool "PCM PIO extension for PWMDAC"
+ depends on SND_SOC_JH7110_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_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 f7d960211d72..0394e02446e6 100644
--- a/sound/soc/starfive/Makefile
+++ b/sound/soc/starfive/Makefile
@@ -1,2 +1,6 @@
# StarFive Platform Support
+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_TDM) += jh7110_tdm.o
diff --git a/sound/soc/starfive/jh7110_pwmdac.c b/sound/soc/starfive/jh7110_pwmdac.c
new file mode 100644
index 000000000000..0d4ad0602663
--- /dev/null
+++ b/sound/soc/starfive/jh7110_pwmdac.c
@@ -0,0 +1,954 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PWMDAC driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 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 <linux/kthread.h>
+#include <linux/reset.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include "jh7110_pwmdac.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_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 == PWMDAC_SHIFT_8) {
+ value = (~((~value) | SFC_PWMDAC_SHIFT));
+ pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value);
+ } else if (data == PWMDAC_SHIFT_10) {
+ value |= SFC_PWMDAC_SHIFT;
+ 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 = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL);
+
+ if (data == PWMDAC_CYCLE_LEFT) {
+ value = (~((~value) | (0x03<<PWMDAC_DUTY_CYCLE_LOW)));
+ pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value);
+ } else if (data == PWMDAC_CYCLE_RIGHT) {
+ value = (~((~value) | (0x01<<PWMDAC_DUTY_CYCLE_HIGH))) |
+ (0x01<<PWMDAC_DUTY_CYCLE_LOW);
+ pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value);
+ } else if (data == PWMDAC_CYCLE_CENTER) {
+ value = (~((~value) | (0x01<<PWMDAC_DUTY_CYCLE_LOW))) |
+ (0x01<<PWMDAC_DUTY_CYCLE_HIGH);
+ pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value);
+ }
+}
+
+static void pwmdac_set_ctrl_N(struct sf_pwmdac_dev *dev, u16 data)
+{
+ u32 value = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL);
+
+ pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL,
+ (value & PWMDAC_CTRL_DATA_MASK) | ((data - 1) << PWMDAC_CTRL_DATA_SHIFT));
+}
+
+static void pwmdac_LR_data_change(struct sf_pwmdac_dev *dev, u8 data)
+{
+ u32 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 = 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;
+
+ 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 << PWMDAC_DATA_LEFT_SHIFT));
+ value |= (data<<PWMDAC_DATA_LEFT_SHIFT);
+ pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value);
+ return 0;
+}
+
+static int get_pwmdac_fifo_state(struct sf_pwmdac_dev *dev)
+{
+ u32 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)
+{
+ return 0;
+}
+
+int pwmdac_tx_thread(void *dev)
+{
+ struct sf_pwmdac_dev *pwmdac_dev = (struct sf_pwmdac_dev *)dev;
+
+ if (!pwmdac_dev) {
+ dev_err(pwmdac_dev->dev, "%s L.%d dev is null.\n", __FILE__, __LINE__);
+ return -1;
+ }
+
+ while (!pwmdac_dev->tx_thread_exit) {
+ if (get_pwmdac_fifo_state(pwmdac_dev) == 0)
+ sf_pwmdac_pcm_push_tx(pwmdac_dev);
+ else
+ udelay(100);
+ }
+
+ 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;
+}
+
+static int sf_pwmdac_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+
+ dai_link->stop_dma_first = 1;
+
+ 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);
+ int ret;
+ unsigned long mclk_dac_value;
+
+ dev->play_dma_data.addr = dev->mapbase + PWMDAC_WDATA;
+
+ switch (params_rate(params)) {
+ case 8000:
+ dev->datan = PWMDAC_SAMPLE_CNT_3;
+ mclk_dac_value = 6144000;
+ break;
+ case 11025:
+ dev->datan = PWMDAC_SAMPLE_CNT_2;
+ mclk_dac_value = 5644800;
+ break;
+ case 16000:
+ dev->datan = PWMDAC_SAMPLE_CNT_3;
+ mclk_dac_value = 12288000;
+ break;
+ case 22050:
+ dev->datan = PWMDAC_SAMPLE_CNT_1;
+ mclk_dac_value = 5644800;
+ break;
+ case 32000:
+ dev->datan = PWMDAC_SAMPLE_CNT_1;
+ mclk_dac_value = 8192000;
+ break;
+ case 44100:
+ dev->datan = PWMDAC_SAMPLE_CNT_1;
+ mclk_dac_value = 11289600;
+ break;
+ case 48000:
+ dev->datan = PWMDAC_SAMPLE_CNT_1;
+ mclk_dac_value = 12288000;
+ break;
+ default:
+ dev_err(dai->dev, "%d rate not supported\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ /* The mclock for the clock driver always rounds down so add a little slack */
+ mclk_dac_value = mclk_dac_value + 64;
+ pwmdac_set(dev);
+
+ ret = clk_set_rate(dev->clk_pwmdac_core, mclk_dac_value);
+ if (ret) {
+ dev_err(dai->dev, "failed to set rate for clk_pwmdac_core %lu\n", mclk_dac_value);
+ goto err_clk_pwmdac;
+ }
+
+ 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;
+
+err_clk_pwmdac:
+ return ret;
+}
+
+static int sf_pwmdac_clks_get(struct platform_device *pdev,
+ struct sf_pwmdac_dev *dev)
+{
+ dev->clk_apb0 = devm_clk_get(&pdev->dev, "apb0");
+ if (IS_ERR(dev->clk_apb0))
+ return PTR_ERR(dev->clk_apb0);
+
+ dev->clk_pwmdac_apb = devm_clk_get(&pdev->dev, "apb");
+ if (IS_ERR(dev->clk_pwmdac_apb))
+ return PTR_ERR(dev->clk_pwmdac_apb);
+
+ dev->clk_pwmdac_core = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(dev->clk_pwmdac_core))
+ return PTR_ERR(dev->clk_pwmdac_core);
+
+ return 0;
+}
+
+static int sf_pwmdac_resets_get(struct platform_device *pdev,
+ struct sf_pwmdac_dev *dev)
+{
+ dev->rst_apb = devm_reset_control_get_exclusive(&pdev->dev, "apb");
+ if (IS_ERR(dev->rst_apb)) {
+ dev_err(&pdev->dev, "%s: failed to get pwmdac apb reset control\n", __func__);
+ return PTR_ERR(dev->rst_apb);
+ }
+
+ return 0;
+}
+
+static int starfive_pwmdac_crg_enable(struct sf_pwmdac_dev *dev, bool enable)
+{
+ int ret = 0;
+
+ dev_dbg(dev->dev, "starfive_pwmdac clk&rst %sable.\n", enable ? "en":"dis");
+ if (enable) {
+ ret = clk_prepare_enable(dev->clk_apb0);
+ if (ret) {
+ dev_err(dev->dev, "failed to prepare enable clk_apb0\n");
+ goto err_clk_apb0;
+ }
+
+ ret = clk_prepare_enable(dev->clk_pwmdac_apb);
+ if (ret) {
+ dev_err(dev->dev, "failed to prepare enable clk_pwmdac_apb\n");
+ goto err_clk_apb;
+ }
+
+ ret = clk_prepare_enable(dev->clk_pwmdac_core);
+ if (ret) {
+ dev_err(dev->dev, "failed to prepare enable clk_pwmdac_core\n");
+ goto err_clk_core;
+ }
+
+ ret = reset_control_deassert(dev->rst_apb);
+ if (ret) {
+ dev_err(dev->dev, "failed to deassert apb\n");
+ goto err_rst_apb;
+ }
+ } else {
+ clk_disable_unprepare(dev->clk_pwmdac_core);
+ clk_disable_unprepare(dev->clk_pwmdac_apb);
+ clk_disable_unprepare(dev->clk_apb0);
+ }
+
+ return 0;
+
+err_rst_apb:
+ clk_disable_unprepare(dev->clk_pwmdac_core);
+err_clk_core:
+ clk_disable_unprepare(dev->clk_pwmdac_apb);
+err_clk_apb:
+ clk_disable_unprepare(dev->clk_apb0);
+err_clk_apb0:
+ return ret;
+}
+
+static int sf_pwmdac_clk_init(struct platform_device *pdev,
+ struct sf_pwmdac_dev *dev)
+{
+ int ret = 0;
+
+ ret = starfive_pwmdac_crg_enable(dev, true);
+ if (ret)
+ goto err_clk_pwmdac;
+
+ ret = clk_set_rate(dev->clk_pwmdac_core, 4096000);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to set rate for clk_pwmdac_core\n");
+ goto err_clk_pwmdac;
+ }
+
+ dev_info(&pdev->dev, "clk_apb0 = %lu, clk_pwmdac_apb = %lu, clk_pwmdac_core = %lu\n",
+ clk_get_rate(dev->clk_apb0), clk_get_rate(dev->clk_pwmdac_apb),
+ clk_get_rate(dev->clk_pwmdac_core));
+
+err_clk_pwmdac:
+ return ret;
+}
+
+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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int starfive_pwmdac_system_suspend(struct device *dev)
+{
+ struct sf_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+
+ /* save the register value */
+ pwmdac->pwmdac_ctrl_data = pwmdc_read_reg(pwmdac->pwmdac_base, PWMDAC_CTRL);
+ return pm_runtime_force_suspend(dev);
+}
+
+static int starfive_pwmdac_system_resume(struct device *dev)
+{
+ struct sf_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ /* restore the register value */
+ pwmdc_write_reg(pwmdac->pwmdac_base, PWMDAC_CTRL, pwmdac->pwmdac_ctrl_data);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int starfive_pwmdac_runtime_suspend(struct device *dev)
+{
+ struct sf_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+
+ return starfive_pwmdac_crg_enable(pwmdac, false);
+}
+
+static int starfive_pwmdac_runtime_resume(struct device *dev)
+{
+ struct sf_pwmdac_dev *pwmdac = dev_get_drvdata(dev);
+
+ return starfive_pwmdac_crg_enable(pwmdac, true);
+}
+#endif
+
+static const struct dev_pm_ops starfive_pwmdac_pm_ops = {
+ SET_RUNTIME_PM_OPS(starfive_pwmdac_runtime_suspend, starfive_pwmdac_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(starfive_pwmdac_system_suspend, starfive_pwmdac_system_resume)
+};
+
+#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)
+{
+ 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 = {
+ .startup = sf_pwmdac_startup,
+ .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 = "starfive-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_8000_48000,
+ .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;
+
+ dev->pwmdac_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(dev->pwmdac_base))
+ return PTR_ERR(dev->pwmdac_base);
+
+ dev->mapbase = res->start;
+ dev->dev = &pdev->dev;
+
+ ret = sf_pwmdac_clks_get(pdev, dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get pwmdac clock\n");
+ return ret;
+ }
+
+ ret = sf_pwmdac_resets_get(pdev, dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get pwmdac reset controls\n");
+ return ret;
+ }
+
+ ret = sf_pwmdac_clk_init(pdev, dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable pwmdac clock\n");
+ return ret;
+ }
+
+ 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);
+
+#ifdef CONFIG_PM
+ starfive_pwmdac_crg_enable(dev, false);
+#endif
+
+ pm_runtime_enable(dev->dev);
+
+ 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,jh7110-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 = "starfive-pwmdac",
+ .of_match_table = of_match_ptr(sf_pwmdac_of_match),
+ .pm = &starfive_pwmdac_pm_ops,
+ },
+};
+
+static int __init pwmdac_driver_init(void)
+{
+ return platform_driver_register(&sf_pwmdac_driver);
+}
+
+static void pwmdac_driver_exit(void)
+{
+ platform_driver_unregister(&sf_pwmdac_driver);
+}
+
+late_initcall(pwmdac_driver_init);
+module_exit(pwmdac_driver_exit);
+
+MODULE_AUTHOR("curry.zhang <curry.zhang@starfivetech.com>");
+MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("starfive pwmdac SoC Interface");
+MODULE_ALIAS("platform:starfive-pwmdac");
diff --git a/sound/soc/starfive/jh7110_pwmdac.h b/sound/soc/starfive/jh7110_pwmdac.h
new file mode 100644
index 000000000000..13afcf18e0e7
--- /dev/null
+++ b/sound/soc/starfive/jh7110_pwmdac.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PWMDAC driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 StarFive Technology Co., Ltd.
+ */
+
+#ifndef __STARFIVE_PWMDAC_LOCAL_H
+#define __STARFIVE_PWMDAC_LOCAL_H
+
+#include <linux/clk.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
+
+#define PWMDAC_CTRL_DATA_SHIFT 4
+#define PWMDAC_CTRL_DATA_MASK 0xF
+#define PWMDAC_DATA_LEFT_SHIFT 15
+#define PWMDAC_DUTY_CYCLE_LOW 2
+#define PWMDAC_DUTY_CYCLE_HIGH 3
+
+#define PWMDAC_MCLK 4096000
+
+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,
+};
+
+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 *clk_apb0;
+ struct clk *clk_pwmdac_apb;
+ struct clk *clk_pwmdac_core;
+ struct reset_control *rst_apb;
+
+ 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;
+
+ struct clk *audio_src;
+ struct clk *pwmdac_apb;
+ struct clk *pwmdac_mclk;
+ unsigned int pwmdac_ctrl_data;
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_JH7110_PWMDAC_PCM)
+void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev);
+void sf_pwmdac_pcm_pop_rx(struct sf_pwmdac_dev *dev);
+int sf_pwmdac_pcm_register(struct platform_device *pdev);
+#else
+void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev) { }
+void sf_pwmdac_pcm_pop_rx(struct sf_pwmdac_dev *dev) { }
+int sf_pwmdac_pcm_register(struct platform_device *pdev)
+{
+ return -EINVAL;
+}
+#endif
+
+#endif
diff --git a/sound/soc/starfive/jh7110_pwmdac_pcm.c b/sound/soc/starfive/jh7110_pwmdac_pcm.c
new file mode 100644
index 000000000000..d4cb7d66994f
--- /dev/null
+++ b/sound/soc/starfive/jh7110_pwmdac_pcm.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PWMDAC 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_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;
+ u32 basedat = 0;
+ int i;
+
+ 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;
+ u32 basedat = 0;
+ int i;
+
+ 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;
+ bool active, period_elapsed;
+
+ 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;
+
+ 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/jh7110_pwmdac_transmitter.c b/sound/soc/starfive/jh7110_pwmdac_transmitter.c
new file mode 100644
index 000000000000..d8cab28e05af
--- /dev/null
+++ b/sound/soc/starfive/jh7110_pwmdac_transmitter.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PWMDAC dummy codec driver for the StarFive JH7110 SoC
+ *
+ * Copyright (C) 2022 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,
+ .legacy_dai_naming = 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 = "starfive,jh7110-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),
+ },
+};
+
+static int __init pwmdac_dit_driver_init(void)
+{
+ return platform_driver_register(&pwmdac_dit_driver);
+}
+
+static void pwmdac_dit_driver_exit(void)
+{
+ platform_driver_unregister(&pwmdac_dit_driver);
+}
+
+late_initcall(pwmdac_dit_driver_init);
+module_exit(pwmdac_dit_driver_exit);
+
+MODULE_AUTHOR("curry.zhang <curry.zhang@starfivetech.com>");
+MODULE_DESCRIPTION("pwmdac dummy codec driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform: starfive-pwmdac dummy codec");