diff --git a/Documentation/devicetree/bindings/sound/sophgo,cv1800b-codecs.yaml b/Documentation/devicetree/bindings/sound/sophgo,cv1800b-codecs.yaml new file mode 100644 index 000000000000..7293a98e98c5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/sophgo,cv1800b-codecs.yaml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/sophgo,cv1800b-codecs.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sophgo CV1800B Internal ADC/DAC Codec + +maintainers: + - Anton D. Stavinskii + +description: + Internal ADC and DAC audio codecs integrated in the Sophgo CV1800B SoC. + Codecs expose a single DAI and are intended to be connected + to an I2S/TDM controller via an ASoC machine driver. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + enum: + - sophgo,cv1800b-sound-adc + - sophgo,cv1800b-sound-dac + + reg: + maxItems: 1 + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + audio-codec@300a100 { + compatible = "sophgo,cv1800b-sound-adc"; + reg = <0x0300a100 0x100>; + #sound-dai-cells = <0>; + }; +... diff --git a/Documentation/devicetree/bindings/sound/sophgo,cv1800b-i2s.yaml b/Documentation/devicetree/bindings/sound/sophgo,cv1800b-i2s.yaml new file mode 100644 index 000000000000..f08362b0ca5e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/sophgo,cv1800b-i2s.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/sophgo,cv1800b-i2s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sophgo CV1800B I2S/TDM controller + +maintainers: + - Anton D. Stavinskii + +description: I2S/TDM controller found in CV1800B / Sophgo SG2002/SG2000 SoCs. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: sophgo,cv1800b-i2s + + reg: + maxItems: 1 + + "#sound-dai-cells": + const: 0 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: i2s + - const: mclk + + dmas: + minItems: 1 + maxItems: 2 + + dma-names: + minItems: 1 + items: + - enum: [rx, tx] + - const: tx + +required: + - compatible + - reg + - clocks + - clock-names + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + #include + + i2s@4110000 { + compatible = "sophgo,cv1800b-i2s"; + reg = <0x04110000 0x10000>; + clocks = <&clk CLK_APB_I2S1>, <&clk CLK_SDMA_AUD1>; + clock-names = "i2s", "mclk"; + dmas = <&dmamux 2 1>, <&dmamux 3 1>; + dma-names = "rx", "tx"; + #sound-dai-cells = <0>; + }; +... diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 36e0d443ba0e..edfdcbf734fe 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -127,6 +127,7 @@ source "sound/soc/renesas/Kconfig" source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/sdca/Kconfig" +source "sound/soc/sophgo/Kconfig" source "sound/soc/spacemit/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sprd/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 8c0480e6484e..21d8406767fc 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += sdca/ obj-$(CONFIG_SND_SOC) += sof/ +obj-$(CONFIG_SND_SOC) += sophgo/ obj-$(CONFIG_SND_SOC) += spacemit/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += sprd/ diff --git a/sound/soc/sophgo/Kconfig b/sound/soc/sophgo/Kconfig new file mode 100644 index 000000000000..9b454261bcfd --- /dev/null +++ b/sound/soc/sophgo/Kconfig @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# SoC audio configuration for cv1800b +# + +menu "Sophgo" + depends on COMPILE_TEST || ARCH_SOPHGO + +config SND_SOC_CV1800B_TDM + tristate "Sophgo CV1800B I2S/TDM support" + depends on SND_SOC && OF + select SND_SOC_GENERIC_DMAENGINE_PCM + help + This option enables the I2S/TDM audio controller found in Sophgo + CV1800B / SG2002 SoCs. The controller supports standard I2S + audio modes for playback and capture. + + The driver integrates with the ASoC framework and uses the DMA + engine for audio data transfer. It is intended to be configured + via Device Tree along with simple-audio-card module. + + To compile the driver as a module, choose M here: the module will + be called cv1800b_tdm. + +config SND_SOC_CV1800B_ADC_CODEC + tristate "Sophgo CV1800B/SG2002 internal ADC codec" + depends on SND_SOC + 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 new file mode 100644 index 000000000000..ec8dd31efddd --- /dev/null +++ b/sound/soc/sophgo/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +# 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-adc.c b/sound/soc/sophgo/cv1800b-sound-adc.c new file mode 100644 index 000000000000..794030b713e9 --- /dev/null +++ b/sound/soc/sophgo/cv1800b-sound-adc.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Internal adc codec for cv1800b compatible SoC + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CV1800B_RXADC_WORD_LEN 16 +#define CV1800B_RXADC_CHANNELS 2 + +#define CV1800B_RXADC_CTRL0 0x00 +#define CV1800B_RXADCC_CTRL1 0x04 +#define CV1800B_RXADC_STATUS 0x08 +#define CV1800B_RXADC_CLK 0x0c +#define CV1800B_RXADC_ANA0 0x10 +#define CV1800B_RXADC_ANA1 0x14 +#define CV1800B_RXADC_ANA2 0x18 +#define CV1800B_RXADC_ANA3 0x1c +#define CV1800B_RXADC_ANA4 0x20 + +/* CV1800B_RXADC_CTRL0 */ +#define REG_RXADC_EN GENMASK(0, 0) +#define REG_I2S_TX_EN GENMASK(1, 1) + +/* CV1800B_RXADCC_CTRL1 */ +#define REG_RXADC_CIC_OPT GENMASK(1, 0) +#define REG_RXADC_IGR_INIT GENMASK(8, 8) + +/* CV1800B_RXADC_ANA0 */ +#define REG_GSTEPL_RXPGA GENMASK(12, 0) +#define REG_G6DBL_RXPGA GENMASK(13, 13) +#define REG_GAINL_RXADC GENMASK(15, 14) +#define REG_GSTEPR_RXPGA GENMASK(28, 16) +#define REG_G6DBR_RXPGA GENMASK(29, 29) +#define REG_GAINR_RXADC GENMASK(31, 30) +#define REG_COMB_LEFT_VOLUME GENMASK(15, 0) +#define REG_COMB_RIGHT_VOLUME GENMASK(31, 16) + +/* CV1800B_RXADC_ANA2 */ +#define REG_MUTEL_RXPGA GENMASK(0, 0) +#define REG_MUTER_RXPGA GENMASK(1, 1) + +/* CV1800B_RXADC_CLK */ +#define REG_RXADC_CLK_INV GENMASK(0, 0) +#define REG_RXADC_SCK_DIV GENMASK(15, 8) +#define REG_RXADC_DLYEN GENMASK(23, 16) + +enum decimation_values { + DECIMATION_64 = 0, + DECIMATION_128, + DECIMATION_256, + DECIMATION_512, +}; + +static const u32 cv1800b_gains[] = { + 0x0001, /* 0dB */ + 0x0002, /* 2dB */ + 0x0004, /* 4dB */ + 0x0008, /* 6dB */ + 0x0010, /* 8dB */ + 0x0020, /* 10dB */ + 0x0040, /* 12dB */ + 0x0080, /* 14dB */ + 0x0100, /* 16dB */ + 0x0200, /* 18dB */ + 0x0400, /* 20dB */ + 0x0800, /* 22dB */ + 0x1000, /* 24dB */ + 0x2400, /* 26dB */ + 0x2800, /* 28dB */ + 0x3000, /* 30dB */ + 0x6400, /* 32dB */ + 0x6800, /* 34dB */ + 0x7000, /* 36dB */ + 0xA400, /* 38dB */ + 0xA800, /* 40dB */ + 0xB000, /* 42dB */ + 0xE400, /* 44dB */ + 0xE800, /* 46dB */ + 0xF000, /* 48dB */ +}; + +struct cv1800b_priv { + void __iomem *regs; + struct device *dev; + unsigned int mclk_rate; +}; + +static int cv1800b_adc_setbclk_div(struct cv1800b_priv *priv, unsigned int rate) +{ + u32 val; + u32 bclk_div; + u64 tmp; + + if (!priv->mclk_rate || !rate) + return -EINVAL; + + tmp = priv->mclk_rate; + tmp /= CV1800B_RXADC_WORD_LEN; + tmp /= CV1800B_RXADC_CHANNELS; + tmp /= rate; + tmp /= 2; + + if (!tmp) { + dev_err(priv->dev, "computed BCLK divider is zero\n"); + return -EINVAL; + } + + if (tmp > 256) { + dev_err(priv->dev, "BCLK divider %llu out of range\n", tmp); + return -EINVAL; + } + + bclk_div = tmp - 1; + val = readl(priv->regs + CV1800B_RXADC_CLK); + val = u32_replace_bits(val, bclk_div, REG_RXADC_SCK_DIV); + /* Vendor value for 48kHz, tested on SG2000/SG2002 */ + val = u32_replace_bits(val, 0x19, REG_RXADC_DLYEN); + writel(val, priv->regs + CV1800B_RXADC_CLK); + + return 0; +} + +static void cv1800b_adc_enable(struct cv1800b_priv *priv, bool enable) +{ + u32 val; + + val = readl(priv->regs + CV1800B_RXADC_CTRL0); + val = u32_replace_bits(val, enable, REG_RXADC_EN); + val = u32_replace_bits(val, enable, REG_I2S_TX_EN); + writel(val, priv->regs + CV1800B_RXADC_CTRL0); +} + +static unsigned int cv1800b_adc_calc_db(u32 ana0, bool right) +{ + u32 step_mask = right ? FIELD_GET(REG_GSTEPR_RXPGA, ana0) : + FIELD_GET(REG_GSTEPL_RXPGA, ana0); + u32 coarse = right ? FIELD_GET(REG_GAINR_RXADC, ana0) : + FIELD_GET(REG_GAINL_RXADC, ana0); + bool g6db = right ? FIELD_GET(REG_G6DBR_RXPGA, ana0) : + FIELD_GET(REG_G6DBL_RXPGA, ana0); + + u32 step = step_mask ? __ffs(step_mask) : 0; + + step = min(step, 12U); + coarse = min(coarse, 3U); + + return 2 * step + 6 * coarse + (g6db ? 6 : 0); +} + +static int cv1800b_adc_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); + unsigned int rate = params_rate(params); + u32 val; + int ret; + + ret = cv1800b_adc_setbclk_div(priv, rate); + if (ret) { + dev_err(priv->dev, + "could not set rate, check DT node for fixed clock\n"); + return ret; + } + + /* init adc */ + val = readl(priv->regs + CV1800B_RXADCC_CTRL1); + val = u32_replace_bits(val, 1, REG_RXADC_IGR_INIT); + val = u32_replace_bits(val, DECIMATION_64, REG_RXADC_CIC_OPT); + writel(val, priv->regs + CV1800B_RXADCC_CTRL1); + return 0; +} + +static int cv1800b_adc_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_adc_enable(priv, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + cv1800b_adc_enable(priv, false); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cv1800b_adc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct cv1800b_priv *priv = snd_soc_dai_get_drvdata(dai); + + priv->mclk_rate = freq; + dev_dbg(priv->dev, "mclk is set to %u\n", freq); + return 0; +} + +static const struct snd_soc_dai_ops cv1800b_adc_dai_ops = { + .hw_params = cv1800b_adc_hw_params, + .set_sysclk = cv1800b_adc_dai_set_sysclk, + .trigger = cv1800b_adc_dai_trigger, +}; + +static struct snd_soc_dai_driver cv1800b_adc_dai = { + .name = "adc-hifi", + .capture = { .stream_name = "ADC Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE }, + .ops = &cv1800b_adc_dai_ops, +}; + +static int cv1800b_adc_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cv1800b_priv *priv = snd_soc_component_get_drvdata(component); + u32 ana0 = readl(priv->regs + CV1800B_RXADC_ANA0); + + unsigned int left = cv1800b_adc_calc_db(ana0, false); + unsigned int right = cv1800b_adc_calc_db(ana0, true); + + ucontrol->value.integer.value[0] = min(left / 2, 24U); + ucontrol->value.integer.value[1] = min(right / 2, 24U); + return 0; +} + +static int cv1800b_adc_volume_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cv1800b_priv *priv = snd_soc_component_get_drvdata(component); + + u32 v_left = clamp_t(u32, ucontrol->value.integer.value[0], 0, 24); + u32 v_right = clamp_t(u32, ucontrol->value.integer.value[1], 0, 24); + u32 val; + + val = readl(priv->regs + CV1800B_RXADC_ANA0); + val = u32_replace_bits(val, cv1800b_gains[v_left], + REG_COMB_LEFT_VOLUME); + val = u32_replace_bits(val, cv1800b_gains[v_right], + REG_COMB_RIGHT_VOLUME); + writel(val, priv->regs + CV1800B_RXADC_ANA0); + + return 0; +} + +static DECLARE_TLV_DB_SCALE(cv1800b_volume_tlv, 0, 200, 0); + +static const struct snd_kcontrol_new cv1800b_adc_controls[] = { + SOC_DOUBLE_EXT_TLV("Internal I2S Capture Volume", SND_SOC_NOPM, 0, 16, 24, false, + cv1800b_adc_volume_get, cv1800b_adc_volume_set, + cv1800b_volume_tlv), +}; + +static const struct snd_soc_component_driver cv1800b_adc_component = { + .name = "cv1800b-adc-codec", + .controls = cv1800b_adc_controls, + .num_controls = ARRAY_SIZE(cv1800b_adc_controls), +}; + +static int cv1800b_adc_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_adc_component, + &cv1800b_adc_dai, 1); +} + +static const struct of_device_id cv1800b_adc_of_match[] = { + { .compatible = "sophgo,cv1800b-sound-adc" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, cv1800b_adc_of_match); + +static struct platform_driver cv1800b_adc_driver = { + .probe = cv1800b_adc_probe, + .driver = { + .name = "cv1800b-sound-adc", + .of_match_table = cv1800b_adc_of_match, + }, +}; + +module_platform_driver(cv1800b_adc_driver); + +MODULE_DESCRIPTION("ADC codec for CV1800B"); +MODULE_AUTHOR("Anton D. Stavinskii "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sophgo/cv1800b-sound-dac.c b/sound/soc/sophgo/cv1800b-sound-dac.c new file mode 100644 index 000000000000..135322bcf6ad --- /dev/null +++ b/sound/soc/sophgo/cv1800b-sound-dac.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Internal DAC codec for cv1800b based CPUs + */ + +#include +#include +#include +#include +#include +#include + +#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); +} + +/* + * Control the DAC overwrite bits. When enabled, the DAC outputs the fixed + * overwrite value instead of samples from the I2S input. + */ +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; + } + /* Clear DAC overwrite so playback uses I2S data. */ + 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 "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sophgo/cv1800b-tdm.c b/sound/soc/sophgo/cv1800b-tdm.c new file mode 100644 index 000000000000..4cbac8c1160f --- /dev/null +++ b/sound/soc/sophgo/cv1800b-tdm.c @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TX_FIFO_SIZE (1024) +#define RX_FIFO_SIZE (1024) +#define TX_MAX_BURST (8) +#define RX_MAX_BURST (8) + +#define CV1800B_DEF_FREQ 24576000 +#define CV1800B_DEF_MCLK_FS_RATIO 256 + +/* tdm registers */ +#define CV1800B_BLK_MODE_SETTING 0x000 +#define CV1800B_FRAME_SETTING 0x004 +#define CV1800B_SLOT_SETTING1 0x008 +#define CV1800B_SLOT_SETTING2 0x00C +#define CV1800B_DATA_FORMAT 0x010 +#define CV1800B_BLK_CFG 0x014 +#define CV1800B_I2S_ENABLE 0x018 +#define CV1800B_I2S_RESET 0x01C +#define CV1800B_I2S_INT_EN 0x020 +#define CV1800B_I2S_INT 0x024 +#define CV1800B_FIFO_THRESHOLD 0x028 +#define CV1800B_LRCK_MASTER 0x02C /* special clock only mode */ +#define CV1800B_FIFO_RESET 0x030 +#define CV1800B_RX_STATUS 0x040 +#define CV1800B_TX_STATUS 0x048 +#define CV1800B_CLK_CTRL0 0x060 +#define CV1800B_CLK_CTRL1 0x064 +#define CV1800B_PCM_SYNTH 0x068 +#define CV1800B_RX_RD_PORT 0x080 +#define CV1800B_TX_WR_PORT 0x0C0 + +/* CV1800B_BLK_MODE_SETTING (0x000) */ +#define BLK_TX_MODE_MASK GENMASK(0, 0) +#define BLK_MASTER_MODE_MASK GENMASK(1, 1) +#define BLK_DMA_MODE_MASK GENMASK(7, 7) + +/* CV1800B_CLK_CTRL1 (0x064) */ +#define CLK_MCLK_DIV_MASK GENMASK(15, 0) +#define CLK_BCLK_DIV_MASK GENMASK(31, 16) + +/* CV1800B_CLK_CTRL0 (0x060) */ +#define CLK_AUD_CLK_SEL_MASK GENMASK(0, 0) +#define CLK_BCLK_OUT_CLK_FORCE_EN_MASK GENMASK(6, 6) +#define CLK_MCLK_OUT_EN_MASK GENMASK(7, 7) +#define CLK_AUD_EN_MASK GENMASK(8, 8) + +/* CV1800B_I2S_RESET (0x01C) */ +#define RST_I2S_RESET_RX_MASK GENMASK(0, 0) +#define RST_I2S_RESET_TX_MASK GENMASK(1, 1) + +/* CV1800B_FIFO_RESET (0x030) */ +#define FIFO_RX_RESET_MASK GENMASK(0, 0) +#define FIFO_TX_RESET_MASK GENMASK(16, 16) + +/* CV1800B_I2S_ENABLE (0x018) */ +#define I2S_ENABLE_MASK GENMASK(0, 0) + +/* CV1800B_BLK_CFG (0x014) */ +#define BLK_AUTO_DISABLE_WITH_CH_EN_MASK GENMASK(4, 4) +#define BLK_RX_BLK_CLK_FORCE_EN_MASK GENMASK(8, 8) +#define BLK_RX_FIFO_DMA_CLK_FORCE_EN_MASK GENMASK(9, 9) +#define BLK_TX_BLK_CLK_FORCE_EN_MASK GENMASK(16, 16) +#define BLK_TX_FIFO_DMA_CLK_FORCE_EN_MASK GENMASK(17, 17) + +/* CV1800B_FRAME_SETTING (0x004) */ +#define FRAME_LENGTH_MASK GENMASK(8, 0) +#define FS_ACTIVE_LENGTH_MASK GENMASK(23, 16) + +/* CV1800B_I2S_INT_EN (0x020) */ +#define INT_I2S_INT_EN_MASK GENMASK(8, 8) + +/* CV1800B_SLOT_SETTING2 (0x00C) */ +#define SLOT_EN_MASK GENMASK(15, 0) + +/* CV1800B_LRCK_MASTER (0x02C) */ +#define LRCK_MASTER_ENABLE_MASK GENMASK(0, 0) + +/* CV1800B_DATA_FORMAT (0x010) */ +#define DF_WORD_LENGTH_MASK GENMASK(2, 1) +#define DF_TX_SOURCE_LEFT_ALIGN_MASK GENMASK(6, 6) + +/* CV1800B_FIFO_THRESHOLD (0x028) */ +#define FIFO_RX_THRESHOLD_MASK GENMASK(4, 0) +#define FIFO_TX_THRESHOLD_MASK GENMASK(20, 16) +#define FIFO_TX_HIGH_THRESHOLD_MASK GENMASK(28, 24) + +/* CV1800B_SLOT_SETTING1 (0x008) */ +#define SLOT_NUM_MASK GENMASK(3, 0) +#define SLOT_SIZE_MASK GENMASK(13, 8) +#define DATA_SIZE_MASK GENMASK(20, 16) +#define FB_OFFSET_MASK GENMASK(28, 24) + +enum cv1800b_tdm_word_length { + CV1800B_WORD_LENGTH_8_BIT = 0, + CV1800B_WORD_LENGTH_16_BIT = 1, + CV1800B_WORD_LENGTH_32_BIT = 2, +}; + +struct cv1800b_i2s { + void __iomem *base; + struct clk *clk; + struct clk *sysclk; + struct device *dev; + struct snd_dmaengine_dai_dma_data playback_dma; + struct snd_dmaengine_dai_dma_data capture_dma; + u32 mclk_rate; + bool bclk_ratio_fixed; + u32 bclk_ratio; + +}; + +static void cv1800b_setup_dma_struct(struct cv1800b_i2s *i2s, + phys_addr_t phys_base) +{ + i2s->playback_dma.addr = phys_base + CV1800B_TX_WR_PORT; + i2s->playback_dma.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma.fifo_size = TX_FIFO_SIZE; + i2s->playback_dma.maxburst = TX_MAX_BURST; + + i2s->capture_dma.addr = phys_base + CV1800B_RX_RD_PORT; + i2s->capture_dma.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma.fifo_size = RX_FIFO_SIZE; + i2s->capture_dma.maxburst = RX_MAX_BURST; +} + +static const struct snd_dmaengine_pcm_config cv1800b_i2s_pcm_config = { + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static void cv1800b_reset_fifo(struct cv1800b_i2s *i2s) +{ + u32 val; + + val = readl(i2s->base + CV1800B_FIFO_RESET); + val = u32_replace_bits(val, 1, FIFO_RX_RESET_MASK); + val = u32_replace_bits(val, 1, FIFO_TX_RESET_MASK); + writel(val, i2s->base + CV1800B_FIFO_RESET); + + usleep_range(10, 20); + + val = readl(i2s->base + CV1800B_FIFO_RESET); + val = u32_replace_bits(val, 0, FIFO_RX_RESET_MASK); + val = u32_replace_bits(val, 0, FIFO_TX_RESET_MASK); + writel(val, i2s->base + CV1800B_FIFO_RESET); +} + +static void cv1800b_reset_i2s(struct cv1800b_i2s *i2s) +{ + u32 val; + + val = readl(i2s->base + CV1800B_I2S_RESET); + val = u32_replace_bits(val, 1, RST_I2S_RESET_RX_MASK); + val = u32_replace_bits(val, 1, RST_I2S_RESET_TX_MASK); + writel(val, i2s->base + CV1800B_I2S_RESET); + + usleep_range(10, 20); + + val = readl(i2s->base + CV1800B_I2S_RESET); + val = u32_replace_bits(val, 0, RST_I2S_RESET_RX_MASK); + val = u32_replace_bits(val, 0, RST_I2S_RESET_TX_MASK); + writel(val, i2s->base + CV1800B_I2S_RESET); +} + +static void cv1800b_set_mclk_div(struct cv1800b_i2s *i2s, u32 mclk_div) +{ + u32 val; + + val = readl(i2s->base + CV1800B_CLK_CTRL1); + val = u32_replace_bits(val, mclk_div, CLK_MCLK_DIV_MASK); + writel(val, i2s->base + CV1800B_CLK_CTRL1); + dev_dbg(i2s->dev, "mclk_div is set to %u\n", mclk_div); +} + +static void cv1800b_set_tx_mode(struct cv1800b_i2s *i2s, bool is_tx) +{ + u32 val; + + val = readl(i2s->base + CV1800B_BLK_MODE_SETTING); + val = u32_replace_bits(val, is_tx, BLK_TX_MODE_MASK); + writel(val, i2s->base + CV1800B_BLK_MODE_SETTING); + dev_dbg(i2s->dev, "tx_mode is set to %u\n", is_tx); +} + +static int cv1800b_set_bclk_div(struct cv1800b_i2s *i2s, u32 bclk_div) +{ + u32 val; + + if (bclk_div == 0 || bclk_div > 0xFFFF) + return -EINVAL; + + val = readl(i2s->base + CV1800B_CLK_CTRL1); + val = u32_replace_bits(val, bclk_div, CLK_BCLK_DIV_MASK); + writel(val, i2s->base + CV1800B_CLK_CTRL1); + dev_dbg(i2s->dev, "bclk_div is set to %u\n", bclk_div); + return 0; +} + +/* set memory width of audio data , reg word_length */ +static int cv1800b_set_word_length(struct cv1800b_i2s *i2s, + unsigned int physical_width) +{ + u8 word_length_val; + u32 val; + + switch (physical_width) { + case 8: + word_length_val = CV1800B_WORD_LENGTH_8_BIT; + break; + case 16: + word_length_val = CV1800B_WORD_LENGTH_16_BIT; + break; + case 32: + word_length_val = CV1800B_WORD_LENGTH_32_BIT; + break; + default: + dev_dbg(i2s->dev, "can't set word_length field\n"); + return -EINVAL; + } + + val = readl(i2s->base + CV1800B_DATA_FORMAT); + val = u32_replace_bits(val, word_length_val, DF_WORD_LENGTH_MASK); + writel(val, i2s->base + CV1800B_DATA_FORMAT); + return 0; +} + +static void cv1800b_enable_clocks(struct cv1800b_i2s *i2s, bool enabled) +{ + u32 val; + + val = readl(i2s->base + CV1800B_CLK_CTRL0); + val = u32_replace_bits(val, enabled, CLK_AUD_EN_MASK); + writel(val, i2s->base + CV1800B_CLK_CTRL0); +} + +static int cv1800b_set_slot_settings(struct cv1800b_i2s *i2s, u32 slots, + u32 physical_width, u32 data_size) +{ + u32 slot_num; + u32 slot_size; + u32 frame_length; + u32 frame_active_length; + u32 val; + + if (!slots || !physical_width || !data_size) { + dev_err(i2s->dev, "frame or slot settings are not valid\n"); + return -EINVAL; + } + if (slots > 16 || physical_width > 64 || data_size > 32) { + dev_err(i2s->dev, "frame or slot settings are not valid\n"); + return -EINVAL; + } + + slot_num = slots - 1; + slot_size = physical_width - 1; + frame_length = (physical_width * slots) - 1; + frame_active_length = physical_width - 1; + + if (frame_length > 511 || frame_active_length > 255) { + dev_err(i2s->dev, "frame or slot settings are not valid\n"); + return -EINVAL; + } + + val = readl(i2s->base + CV1800B_SLOT_SETTING1); + val = u32_replace_bits(val, slot_size, SLOT_SIZE_MASK); + val = u32_replace_bits(val, data_size - 1, DATA_SIZE_MASK); + val = u32_replace_bits(val, slot_num, SLOT_NUM_MASK); + writel(val, i2s->base + CV1800B_SLOT_SETTING1); + + val = readl(i2s->base + CV1800B_FRAME_SETTING); + val = u32_replace_bits(val, frame_length, FRAME_LENGTH_MASK); + val = u32_replace_bits(val, frame_active_length, FS_ACTIVE_LENGTH_MASK); + writel(val, i2s->base + CV1800B_FRAME_SETTING); + + dev_dbg(i2s->dev, "slot settings num: %u width: %u\n", slots, physical_width); + return 0; +} + +/* + * calculate mclk_div. + * if requested value is bigger than optimal + * leave mclk_div as 1. cff clock is capable + * to handle it + */ +static int cv1800b_calc_mclk_div(unsigned int target_mclk, u32 *mclk_div) +{ + *mclk_div = 1; + + if (target_mclk == 0) + return -EINVAL; + + /* optimal parent frequency is close to CV1800B_DEF_FREQ */ + if (target_mclk < CV1800B_DEF_FREQ) { + *mclk_div = DIV_ROUND_CLOSEST(CV1800B_DEF_FREQ, target_mclk); + if (!*mclk_div || *mclk_div > 0xFFFF) + return -EINVAL; + } + return 0; +} + +/* + * set CCF clock and divider for this clock + * mclk_clock = ccf_clock / mclk_div + */ +static int cv1800b_i2s_set_rate_for_mclk(struct cv1800b_i2s *i2s, + unsigned int target_mclk) +{ + u32 mclk_div = 1; + u64 tmp; + int ret; + unsigned long clk_rate; + unsigned long actual; + + ret = cv1800b_calc_mclk_div(target_mclk, &mclk_div); + if (ret) { + dev_dbg(i2s->dev, "can't calc mclk_div for freq %u\n", + target_mclk); + return ret; + } + + tmp = (u64)target_mclk * mclk_div; + if (tmp > ULONG_MAX) { + dev_err(i2s->dev, "clk_rate overflow: freq=%u div=%u\n", + target_mclk, mclk_div); + return -ERANGE; + } + + clk_rate = (unsigned long)tmp; + + cv1800b_enable_clocks(i2s, false); + + ret = clk_set_rate(i2s->sysclk, clk_rate); + if (ret) + return ret; + + actual = clk_get_rate(i2s->sysclk); + if (clk_rate != actual) { + dev_err_ratelimited(i2s->dev, + "clk_set_rate failed %lu, actual is %lu\n", + clk_rate, actual); + } + + cv1800b_set_mclk_div(i2s, mclk_div); + cv1800b_enable_clocks(i2s, true); + + return 0; +} + +static int cv1800b_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + unsigned int channels = params_channels(params); + unsigned int physical_width = params_physical_width(params); + int data_width = params_width(params); + bool tx_mode = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0; + int ret; + u32 bclk_div; + u32 bclk_ratio; + u32 mclk_rate; + u32 tmp; + + if (data_width < 0) + return data_width; + + if (!channels || !rate || !physical_width) + return -EINVAL; + + ret = cv1800b_set_slot_settings(i2s, channels, physical_width, data_width); + if (ret) + return ret; + + if (i2s->mclk_rate) { + mclk_rate = i2s->mclk_rate; + } else { + dev_dbg(i2s->dev, "mclk is not set by machine driver\n"); + ret = cv1800b_i2s_set_rate_for_mclk(i2s, + rate * CV1800B_DEF_MCLK_FS_RATIO); + if (ret) + return ret; + mclk_rate = rate * CV1800B_DEF_MCLK_FS_RATIO; + } + + bclk_ratio = (i2s->bclk_ratio_fixed) ? i2s->bclk_ratio : + (physical_width * channels); + + if (check_mul_overflow(rate, bclk_ratio, &tmp)) + return -EOVERFLOW; + + if (!tmp) + return -EINVAL; + if (mclk_rate % tmp) + dev_warn(i2s->dev, "mclk rate is not aligned to bclk or rate\n"); + + bclk_div = DIV_ROUND_CLOSEST(mclk_rate, tmp); + + ret = cv1800b_set_bclk_div(i2s, bclk_div); + if (ret) + return ret; + + ret = cv1800b_set_word_length(i2s, physical_width); + if (ret) + return ret; + + cv1800b_set_tx_mode(i2s, tx_mode); + + cv1800b_reset_fifo(i2s); + cv1800b_reset_i2s(i2s); + return 0; +} + +static int cv1800b_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 val; + + val = readl(i2s->base + CV1800B_I2S_ENABLE); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = u32_replace_bits(val, 1, I2S_ENABLE_MASK); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = u32_replace_bits(val, 0, I2S_ENABLE_MASK); + break; + default: + return -EINVAL; + } + writel(val, i2s->base + CV1800B_I2S_ENABLE); + return 0; +} + +static int cv1800b_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai); + struct snd_soc_dai_link *dai_link = rtd->dai_link; + + dev_dbg(i2s->dev, "%s: dai=%s substream=%d\n", __func__, dai->name, + substream->stream); + /** + * Ensure DMA is stopped before DAI + * shutdown (prevents DW AXI DMAC stop/busy on next open). + */ + dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; + return 0; +} + +static int cv1800b_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + if (!i2s) { + dev_err(dai->dev, "no drvdata in DAI probe\n"); + return -ENODEV; + } + + snd_soc_dai_init_dma_data(dai, &i2s->playback_dma, &i2s->capture_dma); + return 0; +} + +static int cv1800b_i2s_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 val; + u32 master; + + /* only i2s format is supported */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) + return -EINVAL; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + dev_dbg(i2s->dev, "set to master mode\n"); + master = 1; + break; + + case SND_SOC_DAIFMT_CBC_CFC: + dev_dbg(i2s->dev, "set to slave mode\n"); + master = 0; + break; + default: + return -EINVAL; + } + + val = readl(i2s->base + CV1800B_BLK_MODE_SETTING); + val = u32_replace_bits(val, master, BLK_MASTER_MODE_MASK); + writel(val, i2s->base + CV1800B_BLK_MODE_SETTING); + return 0; +} + +static int cv1800b_i2s_dai_set_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + if (ratio == 0) + return -EINVAL; + i2s->bclk_ratio = ratio; + i2s->bclk_ratio_fixed = true; + return 0; +} + +static int cv1800b_i2s_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai); + int ret; + u32 val; + bool output_enable = (dir == SND_SOC_CLOCK_OUT) ? true : false; + + dev_dbg(i2s->dev, "%s called with %u\n", __func__, freq); + ret = cv1800b_i2s_set_rate_for_mclk(i2s, freq); + if (ret) + return ret; + + val = readl(i2s->base + CV1800B_CLK_CTRL0); + val = u32_replace_bits(val, output_enable, CLK_MCLK_OUT_EN_MASK); + writel(val, i2s->base + CV1800B_CLK_CTRL0); + + i2s->mclk_rate = freq; + return 0; +} + +static const struct snd_soc_dai_ops cv1800b_i2s_dai_ops = { + .probe = cv1800b_i2s_dai_probe, + .startup = cv1800b_i2s_startup, + .hw_params = cv1800b_i2s_hw_params, + .trigger = cv1800b_i2s_trigger, + .set_fmt = cv1800b_i2s_dai_set_fmt, + .set_bclk_ratio = cv1800b_i2s_dai_set_bclk_ratio, + .set_sysclk = cv1800b_i2s_dai_set_sysclk, +}; + +static const struct snd_soc_dai_driver cv1800b_i2s_dai_template = { + .name = "cv1800b-i2s", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &cv1800b_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver cv1800b_i2s_component = { + .name = "cv1800b-i2s", +}; + +static void cv1800b_i2s_hw_disable(struct cv1800b_i2s *i2s) +{ + u32 val; + + val = readl(i2s->base + CV1800B_I2S_ENABLE); + val = u32_replace_bits(val, 0, I2S_ENABLE_MASK); + writel(val, i2s->base + CV1800B_I2S_ENABLE); + + val = readl(i2s->base + CV1800B_CLK_CTRL0); + val = u32_replace_bits(val, 0, CLK_AUD_EN_MASK); + val = u32_replace_bits(val, 0, CLK_MCLK_OUT_EN_MASK); + writel(val, i2s->base + CV1800B_CLK_CTRL0); + + val = readl(i2s->base + CV1800B_I2S_RESET); + val = u32_replace_bits(val, 1, RST_I2S_RESET_RX_MASK); + val = u32_replace_bits(val, 1, RST_I2S_RESET_TX_MASK); + writel(val, i2s->base + CV1800B_I2S_RESET); + + val = readl(i2s->base + CV1800B_FIFO_RESET); + val = u32_replace_bits(val, 1, FIFO_RX_RESET_MASK); + val = u32_replace_bits(val, 1, FIFO_TX_RESET_MASK); + writel(val, i2s->base + CV1800B_FIFO_RESET); +} + +static void cv1800b_i2s_setup_tdm(struct cv1800b_i2s *i2s) +{ + u32 val; + + val = readl(i2s->base + CV1800B_BLK_MODE_SETTING); + val = u32_replace_bits(val, 1, BLK_DMA_MODE_MASK); + writel(val, i2s->base + CV1800B_BLK_MODE_SETTING); + + val = readl(i2s->base + CV1800B_CLK_CTRL0); + val = u32_replace_bits(val, 0, CLK_AUD_CLK_SEL_MASK); + val = u32_replace_bits(val, 0, CLK_MCLK_OUT_EN_MASK); + val = u32_replace_bits(val, 0, CLK_AUD_EN_MASK); + writel(val, i2s->base + CV1800B_CLK_CTRL0); + + val = readl(i2s->base + CV1800B_FIFO_THRESHOLD); + val = u32_replace_bits(val, 4, FIFO_RX_THRESHOLD_MASK); + val = u32_replace_bits(val, 4, FIFO_TX_THRESHOLD_MASK); + val = u32_replace_bits(val, 4, FIFO_TX_HIGH_THRESHOLD_MASK); + writel(val, i2s->base + CV1800B_FIFO_THRESHOLD); + + val = readl(i2s->base + CV1800B_I2S_ENABLE); + val = u32_replace_bits(val, 0, I2S_ENABLE_MASK); + writel(val, i2s->base + CV1800B_I2S_ENABLE); +} + +static int cv1800b_i2s_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cv1800b_i2s *i2s; + struct resource *res; + void __iomem *regs; + struct snd_soc_dai_driver *dai; + int ret; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + i2s->dev = &pdev->dev; + i2s->base = regs; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + cv1800b_setup_dma_struct(i2s, res->start); + + i2s->clk = devm_clk_get_enabled(dev, "i2s"); + if (IS_ERR(i2s->clk)) + return dev_err_probe(dev, PTR_ERR(i2s->clk), + "failed to get+enable i2s\n"); + i2s->sysclk = devm_clk_get_enabled(dev, "mclk"); + if (IS_ERR(i2s->sysclk)) + return dev_err_probe(dev, PTR_ERR(i2s->sysclk), + "failed to get+enable mclk\n"); + + platform_set_drvdata(pdev, i2s); + cv1800b_i2s_setup_tdm(i2s); + + dai = devm_kmemdup(dev, &cv1800b_i2s_dai_template, sizeof(*dai), + GFP_KERNEL); + if (!dai) + return -ENOMEM; + + ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, dai, + 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(dev, &cv1800b_i2s_pcm_config, 0); + if (ret) { + dev_err(dev, "dmaengine_pcm_register failed: %d\n", ret); + return ret; + } + + return 0; +} + +static void cv1800b_i2s_remove(struct platform_device *pdev) +{ + struct cv1800b_i2s *i2s = platform_get_drvdata(pdev); + + if (!i2s) + return; + cv1800b_i2s_hw_disable(i2s); +} + +static const struct of_device_id cv1800b_i2s_of_match[] = { + { .compatible = "sophgo,cv1800b-i2s" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, cv1800b_i2s_of_match); + +static struct platform_driver cv1800b_i2s_driver = { + .probe = cv1800b_i2s_probe, + .remove = cv1800b_i2s_remove, + .driver = { + .name = "cv1800b-i2s", + .of_match_table = cv1800b_i2s_of_match, + }, +}; +module_platform_driver(cv1800b_i2s_driver); + +MODULE_DESCRIPTION("Sophgo cv1800b I2S/TDM driver"); +MODULE_AUTHOR("Anton D. Stavinsky "); +MODULE_LICENSE("GPL");