diff options
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/starfive/Kconfig | 21 | ||||
-rw-r--r-- | sound/soc/starfive/Makefile | 4 | ||||
-rw-r--r-- | sound/soc/starfive/jh7110_pwmdac.c | 954 | ||||
-rw-r--r-- | sound/soc/starfive/jh7110_pwmdac.h | 160 | ||||
-rw-r--r-- | sound/soc/starfive/jh7110_pwmdac_pcm.c | 233 | ||||
-rw-r--r-- | sound/soc/starfive/jh7110_pwmdac_transmitter.c | 95 |
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"); |