From 1a8ee4f49169293e1144c2297db537a1e7de63ed Mon Sep 17 00:00:00 2001 From: Stephen Graf Date: Thu, 9 May 2024 20:59:34 -0700 Subject: Sound for H616, H618 Allwinner SOCs Signed-off-by: Stephen Graf --- .../allwinner/sun50i-h616-orangepi-zero.dtsi | 18 + .../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 83 + drivers/clk/sunxi-ng/ccu-sun50i-h616.c | 33 +- include/sound/soc-dai.h | 13 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/soc-core.c | 25 + sound/soc/sunxi/Kconfig | 8 + sound/soc/sunxi/Makefile | 1 + sound/soc/sunxi/sun50iw9-codec.c | 1093 ++++++++++++ sound/soc/sunxi_v2/Kconfig | 48 + sound/soc/sunxi_v2/Makefile | 11 + sound/soc/sunxi_v2/drv_hdmi.h | 63 + sound/soc/sunxi_v2/snd_sunxi_ahub.c | 1477 +++++++++++++++++ sound/soc/sunxi_v2/snd_sunxi_ahub.h | 67 + sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c | 534 ++++++ sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h | 291 ++++ sound/soc/sunxi_v2/snd_sunxi_common.c | 267 +++ sound/soc/sunxi_v2/snd_sunxi_common.h | 67 + sound/soc/sunxi_v2/snd_sunxi_log.h | 29 + sound/soc/sunxi_v2/snd_sunxi_mach.c | 479 ++++++ sound/soc/sunxi_v2/snd_sunxi_mach.h | 17 + sound/soc/sunxi_v2/snd_sunxi_mach_utils.c | 422 +++++ sound/soc/sunxi_v2/snd_sunxi_mach_utils.h | 116 ++ 24 files changed, 5147 insertions(+), 17 deletions(-) create mode 100644 sound/soc/sunxi/sun50iw9-codec.c create mode 100644 sound/soc/sunxi_v2/Kconfig create mode 100644 sound/soc/sunxi_v2/Makefile create mode 100644 sound/soc/sunxi_v2/drv_hdmi.h create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub.c create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub.h create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h create mode 100644 sound/soc/sunxi_v2/snd_sunxi_common.c create mode 100644 sound/soc/sunxi_v2/snd_sunxi_common.h create mode 100644 sound/soc/sunxi_v2/snd_sunxi_log.h create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach.c create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach.h create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach_utils.c create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach_utils.h diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi index ce3dc6d9cd66..23553f2249c2 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi @@ -105,6 +105,24 @@ &de { status = "okay"; }; +&codec { + allwinner,audio-routing = + "Line Out", "LINEOUT"; + status = "okay"; +}; + +&ahub_dam_plat { + status = "okay"; +}; + +&ahub1_plat { + status = "okay"; +}; + +&ahub1_mach { + status = "okay"; +}; + &ehci1 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi index 90e55a6aef1d..3df5b74bf306 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi @@ -182,6 +182,78 @@ dma: dma-controller@3002000 { #dma-cells = <1>; }; + codec: codec@05096000 { + #sound-dai-cells = <0>; + compatible = "allwinner,sun50i-h616-codec"; + reg = <0x05096000 0x31c>; + interrupts = ; + clocks = <&ccu CLK_BUS_AUDIO_CODEC>, + <&ccu CLK_AUDIO_CODEC_1X>, + <&ccu CLK_AUDIO_CODEC_4X>; + clock-names = "apb", "audio-codec-1x", "audio-codec-4x"; + resets = <&ccu RST_BUS_AUDIO_CODEC>; + dmas = <&dma 6>; + dma-names = "tx"; + status = "disabled"; + }; + + ahub_dam_plat:ahub_dam_plat@5097000 { + #sound-dai-cells = <0>; + /* sound card without pcm for hardware mix setting */ + compatible = "allwinner,sunxi-snd-plat-ahub_dam"; + reg = <0x05097000 0x1000>; + resets = <&ccu RST_BUS_AUDIO_HUB>; + clocks = <&ccu CLK_AUDIO_CODEC_1X>, + <&ccu CLK_AUDIO_CODEC_4X>, + <&ccu CLK_AUDIO_HUB>, + <&ccu CLK_BUS_AUDIO_HUB>; + clock-names = "clk_pll_audio", + "clk_pll_audio_4x", + "clk_audio_hub", + "clk_bus_audio_hub"; + status = "disabled"; + }; + + ahub1_plat:ahub1_plat { + #sound-dai-cells = <0>; + compatible = "allwinner,sunxi-snd-plat-ahub"; + apb_num = <1>; /* for dma port 4 */ + dmas = <&dma 4>, <&dma 4>; + dma-names = "tx", "rx"; + playback_cma = <128>; + capture_cma = <128>; + tx_fifo_size = <128>; + rx_fifo_size = <128>; + + tdm_num = <1>; + tx_pin = <0>; + rx_pin = <0>; + status = "disabled"; + }; + + ahub1_mach:ahub1_mach { + compatible = "allwinner,sunxi-snd-mach"; + soundcard-mach,name = "HDMI"; + + soundcard-mach,format = "i2s"; + soundcard-mach,frame-master = <&ahub1_cpu>; + soundcard-mach,bitclock-master = <&ahub1_cpu>; + /* soundcard-mach,frame-inversion; */ + /* soundcard-mach,bitclock-inversion; */ + soundcard-mach,slot-num = <2>; + soundcard-mach,slot-width = <32>; + status = "disabled"; + ahub1_cpu: soundcard-mach,cpu { + sound-dai = <&ahub1_plat>; + soundcard-mach,pll-fs = <4>; + soundcard-mach,mclk-fs = <0>; + }; + + ahub1_codec: soundcard-mach,codec { + sound-dai = <&hdmi>; + }; + }; + gpu: gpu@1800000 { compatible = "allwinner,sun50i-h616-mali", "arm,mali-bifrost"; @@ -457,6 +529,17 @@ gic: interrupt-controller@3021000 { #interrupt-cells = <3>; }; + iommu: iommu@30f0000 { + compatible = "allwinner,sun50i-h616-iommu", + "allwinner,sun50i-h6-iommu"; + reg = <0x030f0000 0x10000>; + interrupts = ; + clocks = <&ccu CLK_BUS_IOMMU>; + resets = <&ccu RST_BUS_IOMMU>; + #iommu-cells = <1>; + status = "okay"; + }; + mmc0: mmc@4020000 { compatible = "allwinner,sun50i-h616-mmc", "allwinner,sun50i-a100-mmc"; diff --git a/drivers/clk/sunxi-ng/ccu-sun50i-h616.c b/drivers/clk/sunxi-ng/ccu-sun50i-h616.c index 21e918582aa5..ab2628596b36 100644 --- a/drivers/clk/sunxi-ng/ccu-sun50i-h616.c +++ b/drivers/clk/sunxi-ng/ccu-sun50i-h616.c @@ -215,20 +215,22 @@ static struct ccu_nkmp pll_de_clk = { }, }; -/* - * TODO: Determine SDM settings for the audio PLL. The manual suggests - * PLL_FACTOR_N=16, PLL_POST_DIV_P=2, OUTPUT_DIV=2, pattern=0xe000c49b - * for 24.576 MHz, and PLL_FACTOR_N=22, PLL_POST_DIV_P=3, OUTPUT_DIV=2, - * pattern=0xe001288c for 22.5792 MHz. - * This clashes with our fixed PLL_POST_DIV_P. - */ #define SUN50I_H616_PLL_AUDIO_REG 0x078 + +static struct ccu_sdm_setting pll_audio_sdm_table[] = { + { .rate = 90316800, .pattern = 0xc001288d, .m = 3, .n = 22 }, + { .rate = 98304000, .pattern = 0xc001eb85, .m = 5, .n = 40 }, +}; + static struct ccu_nm pll_audio_hs_clk = { .enable = BIT(31), .lock = BIT(28), .n = _SUNXI_CCU_MULT_MIN(8, 8, 12), - .m = _SUNXI_CCU_DIV(1, 1), /* input divider */ + .m = _SUNXI_CCU_DIV(16, 6), + .sdm = _SUNXI_CCU_SDM(pll_audio_sdm_table, + BIT(24), 0x178, BIT(31)), .common = { + .features = CCU_FEATURE_SIGMA_DELTA_MOD, .reg = 0x078, .hw.init = CLK_HW_INIT("pll-audio-hs", "osc24M", &ccu_nm_ops, @@ -688,13 +690,13 @@ static const struct clk_hw *clk_parent_pll_audio[] = { */ static CLK_FIXED_FACTOR_HWS(pll_audio_1x_clk, "pll-audio-1x", clk_parent_pll_audio, - 96, 1, CLK_SET_RATE_PARENT); + 4, 1, CLK_SET_RATE_PARENT); static CLK_FIXED_FACTOR_HWS(pll_audio_2x_clk, "pll-audio-2x", clk_parent_pll_audio, - 48, 1, CLK_SET_RATE_PARENT); + 2, 1, CLK_SET_RATE_PARENT); static CLK_FIXED_FACTOR_HWS(pll_audio_4x_clk, "pll-audio-4x", clk_parent_pll_audio, - 24, 1, CLK_SET_RATE_PARENT); + 1, 1, CLK_SET_RATE_PARENT); static const struct clk_hw *pll_periph0_parents[] = { &pll_periph0_clk.common.hw @@ -1130,13 +1132,10 @@ static int sun50i_h616_ccu_probe(struct platform_device *pdev) writel(val, reg + usb2_clk_regs[i]); } - /* - * Force the post-divider of pll-audio to 12 and the output divider - * of it to 2, so 24576000 and 22579200 rates can be set exactly. - */ val = readl(reg + SUN50I_H616_PLL_AUDIO_REG); - val &= ~(GENMASK(21, 16) | BIT(0)); - writel(val | (11 << 16) | BIT(0), reg + SUN50I_H616_PLL_AUDIO_REG); + val &= ~BIT(1); + val |= BIT(0); + writel(val, reg + SUN50I_H616_PLL_AUDIO_REG); /* * First clock parent (osc32K) is unusable for CEC. But since there diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index adcd8719d343..778751096c23 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -415,6 +415,15 @@ struct snd_soc_dai_driver { struct snd_soc_dobj dobj; struct of_phandle_args *dai_args; + /* DAI driver callbacks */ + int (*probe)(struct snd_soc_dai *dai); + int (*remove)(struct snd_soc_dai *dai); + /* compress dai */ + int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num); + /* Optional Callback used at pcm creation*/ + int (*pcm_new)(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai); + /* ops */ const struct snd_soc_dai_ops *ops; const struct snd_soc_cdai_ops *cops; @@ -425,6 +434,10 @@ struct snd_soc_dai_driver { unsigned int symmetric_rate:1; unsigned int symmetric_channels:1; unsigned int symmetric_sample_bits:1; + + /* probe ordering - for components with runtime dependencies */ + int probe_order; + int remove_order; }; /* for Playback/Capture */ diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 439fa631c342..7dc3967ccb27 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -108,6 +108,7 @@ source "sound/soc/starfive/Kconfig" source "sound/soc/sti/Kconfig" source "sound/soc/stm/Kconfig" source "sound/soc/sunxi/Kconfig" +source "sound/soc/sunxi_v2/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/ti/Kconfig" source "sound/soc/uniphier/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 8376fdb217ed..79b48e8b3657 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_SND_SOC) += starfive/ obj-$(CONFIG_SND_SOC) += sti/ obj-$(CONFIG_SND_SOC) += stm/ obj-$(CONFIG_SND_SOC) += sunxi/ +obj-$(CONFIG_SND_SOC) += sunxi_v2/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += ti/ obj-$(CONFIG_SND_SOC) += uniphier/ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e65fe3a7c3e4..f43402e66020 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2523,6 +2523,7 @@ struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, { struct device *dev = component->dev; struct snd_soc_dai *dai; + struct snd_soc_dai_ops *ops; /* REMOVE ME */ lockdep_assert_held(&client_mutex); @@ -2551,6 +2552,30 @@ struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, if (!dai->name) return NULL; + /* REMOVE ME */ + if (dai_drv->probe || + dai_drv->remove || + dai_drv->compress_new || + dai_drv->pcm_new || + dai_drv->probe_order || + dai_drv->remove_order) { + + ops = devm_kzalloc(dev, sizeof(struct snd_soc_dai_ops), GFP_KERNEL); + if (!ops) + return NULL; + if (dai_drv->ops) + memcpy(ops, dai_drv->ops, sizeof(struct snd_soc_dai_ops)); + + ops->probe = dai_drv->probe; + ops->remove = dai_drv->remove; + ops->compress_new = dai_drv->compress_new; + ops->pcm_new = dai_drv->pcm_new; + ops->probe_order = dai_drv->probe_order; + ops->remove_order = dai_drv->remove_order; + + dai_drv->ops = ops; + } + dai->component = component; dai->dev = dev; dai->driver = dai_drv; diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 753c38c5d554..0f6579ea7143 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -10,6 +10,14 @@ config SND_SUN4I_CODEC Select Y or M to add support for the Codec embedded in the Allwinner A10 and affiliated SoCs. +config SND_SUN50IW9_CODEC + tristate "Allwinner H616 Codec Support" + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Select Y or M to add support for the Codec embedded in the Allwinner + H616 and affiliated SoCs. + config SND_SUN8I_CODEC tristate "Allwinner SUN8I audio codec" depends on OF diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile index b0b976a3d069..f72d94f3004c 100644 --- a/sound/soc/sunxi/Makefile +++ b/sound/soc/sunxi/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o +obj-$(CONFIG_SND_SUN50IW9_CODEC) += sun50iw9-codec.o obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o obj-$(CONFIG_SND_SUN50I_CODEC_ANALOG) += sun50i-codec-analog.o diff --git a/sound/soc/sunxi/sun50iw9-codec.c b/sound/soc/sunxi/sun50iw9-codec.c new file mode 100644 index 000000000000..38b1d3824c20 --- /dev/null +++ b/sound/soc/sunxi/sun50iw9-codec.c @@ -0,0 +1,1093 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2014 Emilio López + * Copyright 2014 Jon Smirl + * Copyright 2015 Maxime Ripard + * Copyright 2015 Adam Sampson + * Copyright 2016 Chen-Yu Tsai + * Copyright 2021 gryzun + * + * Based on the Allwinner SDK driver, released under the GPL. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SUNXI_DAC_DPC (0x00) +#define SUNXI_DAC_DPC_EN_DA (31) +#define SUNXI_DAC_DPC_DVOL (12) +#define SUNXI_DAC_DPC_HPF_EN (18) + +#define SUNXI_DAC_FIFOC (0x10) +#define SUNXI_DAC_FIFOC_DAC_FS (29) +#define SUNXI_DAC_FIFOC_TX_FIFO_MODE (24) +#define SUNXI_DAC_FIFOC_DRQ_CLR_CNT (21) +#define SUNXI_DAC_FIFOC_MONO_EN (6) +#define SUNXI_DAC_FIFOC_TX_SAMPLE_BITS (5) +#define SUNXI_DAC_FIFOC_DAC_DRQ_EN (4) +#define SUNXI_DAC_FIFOC_FIFO_FLUSH (0) + +#define SUNXI_DAC_FIFO_STA (0x14) +#define SUNXI_DAC_TXE_INT (3) +#define SUNXI_DAC_TXU_INT (2) +#define SUNXI_DAC_TXO_INT (1) +#define SUNXI_DAC_TXDATA (0x20) +#define SUNXI_DAC_CNT (0x24) +#define SUNXI_DAC_DG_REG (0x28) +#define SUNXI_DAC_DAP_CTL (0xf0) + +#define SUNXI_DAC_AC_DAC_REG (0x310) +#define SUNXI_DAC_LEN (15) +#define SUNXI_DAC_REN (14) +#define SUNXI_LINEOUTL_EN (13) +#define SUNXI_LMUTE (12) +#define SUNXI_LINEOUTR_EN (11) +#define SUNXI_RMUTE (10) +#define SUNXI_RSWITCH (9) +#define SUNXI_RAMPEN (8) +#define SUNXI_LINEOUTL_SEL (6) +#define SUNXI_LINEOUTR_SEL (5) +#define SUNXI_LINEOUT_VOL (0) + +#define SUNXI_DAC_AC_MIXER_REG (0x314) +#define SUNXI_LMIX_LDAC (21) +#define SUNXI_LMIX_RDAC (20) +#define SUNXI_RMIX_RDAC (17) +#define SUNXI_RMIX_LDAC (16) +#define SUNXI_LMIXEN (11) +#define SUNXI_RMIXEN (10) + +#define SUNXI_DAC_AC_RAMP_REG (0x31c) +#define SUNXI_RAMP_STEP (4) +#define SUNXI_RDEN (0) + +#define LABEL(constant) \ + { \ +#constant, constant, 0 \ + } +#define LABEL_END \ + { \ + NULL, 0, -1 \ + } + +struct audiocodec_reg_label +{ + const char *name; + const unsigned int address; + int value; +}; + +static struct audiocodec_reg_label reg_labels[] = { + LABEL(SUNXI_DAC_DPC), + LABEL(SUNXI_DAC_FIFOC), + LABEL(SUNXI_DAC_FIFO_STA), + LABEL(SUNXI_DAC_CNT), + LABEL(SUNXI_DAC_DG_REG), + LABEL(SUNXI_DAC_DAP_CTL), + LABEL(SUNXI_DAC_AC_DAC_REG), + LABEL(SUNXI_DAC_AC_MIXER_REG), + LABEL(SUNXI_DAC_AC_RAMP_REG), + LABEL_END, +}; + +struct regmap *codec_regmap_debug = NULL; + +struct sun50i_h616_codec +{ + unsigned char *name; + struct device *dev; + struct regmap *regmap; + struct clk *clk_apb; + struct clk *clk_module; + struct reset_control *rst; + struct gpio_desc *gpio_pa; + + /* ADC_FIFOC register is at different offset on different SoCs */ + struct regmap_field *reg_adc_fifoc; + + struct snd_dmaengine_dai_dma_data playback_dma_data; +}; + +static void sun50i_h616_codec_start_playback(struct sun50i_h616_codec *scodec) +{ + /* Flush TX FIFO */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_FIFO_FLUSH), + BIT(SUNXI_DAC_FIFOC_FIFO_FLUSH)); + + /* Enable DAC DRQ */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_DAC_DRQ_EN), + BIT(SUNXI_DAC_FIFOC_DAC_DRQ_EN)); +} + +static void sun50i_h616_codec_stop_playback(struct sun50i_h616_codec *scodec) +{ + /* Disable DAC DRQ */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_DAC_DRQ_EN), + 0); +} + +static int sun50i_h616_codec_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + 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) + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x1 << SUNXI_DAC_FIFOC_DAC_DRQ_EN), + (0x1 << SUNXI_DAC_FIFOC_DAC_DRQ_EN)); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x1 << SUNXI_DAC_FIFOC_DAC_DRQ_EN), + (0x0 << SUNXI_DAC_FIFOC_DAC_DRQ_EN)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int sun50i_h616_codec_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + (0x1 << SUNXI_DAC_FIFOC_FIFO_FLUSH), + (0x1 << SUNXI_DAC_FIFOC_FIFO_FLUSH)); + regmap_write(scodec->regmap, SUNXI_DAC_FIFO_STA, + (0x1 << SUNXI_DAC_TXE_INT | 1 << SUNXI_DAC_TXU_INT | 0x1 << SUNXI_DAC_TXO_INT)); + regmap_write(scodec->regmap, SUNXI_DAC_CNT, 0); + + return 0; +} + +static unsigned long sun50i_h616_codec_get_mod_freq(struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + + switch (rate) + { + case 176400: + case 88200: + case 44100: + case 33075: + case 22050: + case 14700: + case 11025: + case 7350: + return 22579200; + + case 192000: + case 96000: + case 48000: + case 32000: + case 24000: + case 16000: + case 12000: + case 8000: + return 24576000; + + default: + return 0; + } +} + +static int sun50i_h616_codec_get_hw_rate(struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + + switch (rate) + { + case 192000: + case 176400: + return 6; + + case 96000: + case 88200: + return 7; + + case 48000: + case 44100: + return 0; + + case 32000: + case 33075: + return 1; + + case 24000: + case 22050: + return 2; + + case 16000: + case 14700: + return 3; + + case 12000: + case 11025: + return 4; + + case 8000: + case 7350: + return 5; + + default: + return -EINVAL; + } +} + +static int sun50i_h616_codec_hw_params_playback(struct sun50i_h616_codec *scodec, + struct snd_pcm_hw_params *params, + unsigned int hwrate) +{ + u32 val; + + /* Set DAC sample rate */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + 7 << SUNXI_DAC_FIFOC_DAC_FS, + hwrate << SUNXI_DAC_FIFOC_DAC_FS); + + /* Set the number of channels we want to use */ + if (params_channels(params) == 1) + val = BIT(SUNXI_DAC_FIFOC_MONO_EN); + else + val = 0; + + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_MONO_EN), + val); + + /* Set the number of sample bits to either 16 or 24 bits */ + if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) + { + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_TX_SAMPLE_BITS), + BIT(SUNXI_DAC_FIFOC_TX_SAMPLE_BITS)); + + /* Set TX FIFO mode to padding the LSBs with 0 */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_TX_FIFO_MODE), + 0); + + scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + } + else + { + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_TX_SAMPLE_BITS), + 0); + + /* Set TX FIFO mode to repeat the MSB */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + BIT(SUNXI_DAC_FIFOC_TX_FIFO_MODE), + BIT(SUNXI_DAC_FIFOC_TX_FIFO_MODE)); + + scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + } + + return 0; +} + +struct sample_rate +{ + unsigned int samplerate; + unsigned int rate_bit; +}; + +static const struct sample_rate sample_rate_conv[] = { + {44100, 0}, + {48000, 0}, + {8000, 5}, + {32000, 1}, + {22050, 2}, + {24000, 2}, + {16000, 3}, + {11025, 4}, + {12000, 4}, + {192000, 6}, + {96000, 7}, +}; + +static int sun50i_h616_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + unsigned long clk_freq; + int ret, hwrate; + int i; + + clk_freq = sun50i_h616_codec_get_mod_freq(params); + if (!clk_freq) + return -EINVAL; + + ret = clk_set_rate(scodec->clk_module, clk_freq * 2); + if (ret) + return ret; + + hwrate = sun50i_h616_codec_get_hw_rate(params); + if (hwrate < 0) + return hwrate; + + switch (params_format(params)) + { + case SNDRV_PCM_FORMAT_S16_LE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + { + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x3 << SUNXI_DAC_FIFOC_TX_FIFO_MODE), + (0x3 << SUNXI_DAC_FIFOC_TX_FIFO_MODE)); + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x1 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS), + (0x0 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS)); + } + break; + case SNDRV_PCM_FORMAT_S24_LE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + { + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x3 << SUNXI_DAC_FIFOC_TX_FIFO_MODE), + (0x0 << SUNXI_DAC_FIFOC_TX_FIFO_MODE)); + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x1 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS), + (0x1 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS)); + } + break; + default: + break; + } + + for (i = 0; i < ARRAY_SIZE(sample_rate_conv); i++) + { + if (sample_rate_conv[i].samplerate == params_rate(params)) + { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + { + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x7 << SUNXI_DAC_FIFOC_DAC_FS), + (sample_rate_conv[i].rate_bit << SUNXI_DAC_FIFOC_DAC_FS)); + } + } + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + { + switch (params_channels(params)) + { + case 1: + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x1 << SUNXI_DAC_FIFOC_MONO_EN), + (0x1 << SUNXI_DAC_FIFOC_MONO_EN)); + break; + case 2: + regmap_update_bits(scodec->regmap, + SUNXI_DAC_FIFOC, + (0x1 << SUNXI_DAC_FIFOC_MONO_EN), + (0x0 << SUNXI_DAC_FIFOC_MONO_EN)); + break; + default: + pr_err("[%s] Playback cannot support %d channels.\n", + __func__, params_channels(params)); + return -EINVAL; + } + } + + return 0; +} + +static unsigned int sun50i_h616_codec_src_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, + 44100, 48000, 96000, 192000}; + +static struct snd_pcm_hw_constraint_list sun50i_h616_codec_constraints = { + .count = ARRAY_SIZE(sun50i_h616_codec_src_rates), + .list = sun50i_h616_codec_src_rates, +}; + +static int sun50i_h616_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &sun50i_h616_codec_constraints); + + /* + * Stop issuing DRQ when we have room for less than 16 samples + * in our TX FIFO + */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC, + 3 << SUNXI_DAC_FIFOC_DRQ_CLR_CNT, + 3 << SUNXI_DAC_FIFOC_DRQ_CLR_CNT); + + return clk_prepare_enable(scodec->clk_module); +} + +static void sun50i_h616_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + clk_disable_unprepare(scodec->clk_module); +} + +static const struct snd_soc_dai_ops sun50i_h616_codec_dai_ops = { + .startup = sun50i_h616_codec_startup, + .shutdown = sun50i_h616_codec_shutdown, + .trigger = sun50i_h616_codec_trigger, + .hw_params = sun50i_h616_codec_hw_params, + .prepare = sun50i_h616_codec_prepare, +}; + +static struct snd_soc_dai_driver sun50i_h616_codec_dai = { + .name = "Codec", + .ops = &sun50i_h616_codec_dai_ops, + .playback = { + .stream_name = "Codec Playback", + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 24, + }, +}; + +static int sunxi_lineout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(w->dapm->card); + + switch (event) + { + case SND_SOC_DAPM_POST_PMU: + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG, + (0x1 << SUNXI_RDEN), (0x1 << SUNXI_RDEN)); + msleep(25); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_LINEOUTL_EN) | (0x1 << SUNXI_LINEOUTR_EN), + (0x1 << SUNXI_LINEOUTL_EN) | (0x1 << SUNXI_LINEOUTR_EN)); + break; + case SND_SOC_DAPM_PRE_PMD: + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG, + (0x1 << SUNXI_RDEN), (0x0 << SUNXI_RDEN)); + msleep(25); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_LINEOUTL_EN) | (0x1 << SUNXI_LINEOUTR_EN), + (0x0 << SUNXI_LINEOUTL_EN) | (0x0 << SUNXI_LINEOUTR_EN)); + + break; + default: + break; + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(digital_tlv, 0, -116, -7424); +static const DECLARE_TLV_DB_SCALE(linein_to_l_r_mix_vol_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(fmin_to_l_r_mix_vol_tlv, -450, 150, 0); + +static const unsigned int lineout_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, + 0, + TLV_DB_SCALE_ITEM(0, 0, 1), + 1, + 31, + TLV_DB_SCALE_ITEM(-4350, 150, 1), +}; + +/*lineoutL mux select */ +const char *const left_lineoutl_text[] = { + "LOMixer", + "LROMixer", +}; + +static const struct soc_enum left_lineout_enum = + SOC_ENUM_SINGLE(SUNXI_DAC_AC_DAC_REG, SUNXI_LINEOUTL_SEL, + ARRAY_SIZE(left_lineoutl_text), left_lineoutl_text); + +static const struct snd_kcontrol_new left_lineout_mux = + SOC_DAPM_ENUM("Left LINEOUT Mux", left_lineout_enum); + +/*lineoutR mux select */ +const char *const right_lineoutr_text[] = { + "ROMixer", + "LROMixer", +}; + +static const struct soc_enum right_lineout_enum = + SOC_ENUM_SINGLE(SUNXI_DAC_AC_DAC_REG, SUNXI_LINEOUTR_SEL, + ARRAY_SIZE(right_lineoutr_text), right_lineoutr_text); + +static const struct snd_kcontrol_new right_lineout_mux = + SOC_DAPM_ENUM("Right LINEOUT Mux", right_lineout_enum); + +static const struct snd_kcontrol_new sun50i_h616_codec_codec_controls[] = { + + SOC_SINGLE_TLV("digital volume", SUNXI_DAC_DPC, + SUNXI_DAC_DPC_DVOL, 0x3F, 0, digital_tlv), + + SOC_SINGLE_TLV("LINEOUT volume", SUNXI_DAC_AC_DAC_REG, + SUNXI_LINEOUT_VOL, 0x1F, 0, lineout_tlv), +}; + +static const struct snd_kcontrol_new left_output_mixer[] = { + SOC_DAPM_SINGLE("DACL Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_LMIX_LDAC, 1, 0), + SOC_DAPM_SINGLE("DACR Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_LMIX_RDAC, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { + SOC_DAPM_SINGLE("DACL Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_RMIX_LDAC, 1, 0), + SOC_DAPM_SINGLE("DACR Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_RMIX_RDAC, 1, 0), +}; + +static const struct snd_soc_dapm_widget sun50i_h616_codec_codec_widgets[] = { + + /* Digital parts of the DACs */ + SND_SOC_DAPM_SUPPLY("DAC Enable", SUNXI_DAC_DPC, + SUNXI_DAC_DPC_EN_DA, 0, NULL, 0), + + SND_SOC_DAPM_AIF_IN_E("DACL", "Codec Playback", 0, SUNXI_DAC_AC_DAC_REG, SUNXI_DAC_LEN, 0, + NULL, 0), + SND_SOC_DAPM_AIF_IN_E("DACR", "Codec Playback", 0, SUNXI_DAC_AC_DAC_REG, SUNXI_DAC_REN, 0, + NULL, 0), + + SND_SOC_DAPM_MIXER("Left Output Mixer", SUNXI_DAC_AC_MIXER_REG, SUNXI_LMIXEN, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), + SND_SOC_DAPM_MIXER("Right Output Mixer", SUNXI_DAC_AC_MIXER_REG, SUNXI_RMIXEN, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + + SND_SOC_DAPM_MUX("Left LINEOUT Mux", SND_SOC_NOPM, + 0, 0, &left_lineout_mux), + SND_SOC_DAPM_MUX("Right LINEOUT Mux", SND_SOC_NOPM, + 0, 0, &right_lineout_mux), + + SND_SOC_DAPM_OUTPUT("LINEOUTL"), + SND_SOC_DAPM_OUTPUT("LINEOUTR"), + + SND_SOC_DAPM_LINE("LINEOUT", sunxi_lineout_event), +}; + +static const struct snd_soc_component_driver sun50i_h616_codec_codec = { + .controls = sun50i_h616_codec_codec_controls, + .num_controls = ARRAY_SIZE(sun50i_h616_codec_codec_controls), + .dapm_widgets = sun50i_h616_codec_codec_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun50i_h616_codec_codec_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct snd_soc_component_driver sun50i_h616_codec_component = { + .name = "sun50i_h616-codec", + .legacy_dai_naming = 1, +#ifdef CONFIG_DEBUG_FS + .debugfs_prefix = "cpu", +#endif +}; + +#define SUN50IW9_CODEC_RATES (SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT) +#define SUN50IW9_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +static int sun50i_h616_codec_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(card); + + snd_soc_dai_init_dma_data(dai, &scodec->playback_dma_data, + NULL); + + return 0; +} + +static struct snd_soc_dai_driver dummy_cpu_dai = { + .name = "sun50i_h616-codec-cpu-dai", + .probe = sun50i_h616_codec_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SUN50IW9_CODEC_RATES, + .formats = SUN50IW9_CODEC_FORMATS, + .sig_bits = 24, + }, +}; + +static struct snd_soc_dai_link *sun50i_h616_codec_create_link(struct device *dev, + int *num_links) +{ + struct snd_soc_dai_link *link = devm_kzalloc(dev, sizeof(*link), + GFP_KERNEL); + struct snd_soc_dai_link_component *dlc = devm_kzalloc(dev, + 3 * sizeof(*dlc), GFP_KERNEL); + if (!link || !dlc) + return NULL; + + link->cpus = &dlc[0]; + link->codecs = &dlc[1]; + link->platforms = &dlc[2]; + + link->num_cpus = 1; + link->num_codecs = 1; + link->num_platforms = 1; + + link->name = "cdc"; + link->stream_name = "CDC PCM"; + link->codecs->dai_name = "Codec"; + link->cpus->dai_name = dev_name(dev); + link->codecs->name = dev_name(dev); + link->platforms->name = dev_name(dev); + link->dai_fmt = SND_SOC_DAIFMT_I2S; + link->playback_only = true; + link->capture_only = false; + + *num_links = 1; + + return link; +}; + +static int sun50i_h616_codec_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(w->dapm->card); + + gpiod_set_value_cansleep(scodec->gpio_pa, + !!SND_SOC_DAPM_EVENT_ON(event)); + + if (SND_SOC_DAPM_EVENT_ON(event)) + { + /* + * Need a delay to wait for DAC to push the data. 700ms seems + * to be the best compromise not to feel this delay while + * playing a sound. + */ + msleep(700); + } + + return 0; +} + +static const struct snd_soc_dapm_widget sun6i_codec_card_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_SPK("Speaker", sun50i_h616_codec_spk_event), +}; + +/* Connect digital side enables to analog side widgets */ +static const struct snd_soc_dapm_route sun8i_codec_card_routes[] = { + /* DAC Routes */ + {"DACR", NULL, "DAC Enable"}, + {"DACL", NULL, "DAC Enable"}, + + {"Left Output Mixer", "DACR Switch", "DACR"}, + {"Left Output Mixer", "DACL Switch", "DACL"}, + + {"Right Output Mixer", "DACL Switch", "DACL"}, + {"Right Output Mixer", "DACR Switch", "DACR"}, + + {"Left LINEOUT Mux", "LOMixer", "Left Output Mixer"}, + {"Left LINEOUT Mux", "LROMixer", "Right Output Mixer"}, + {"Right LINEOUT Mux", "ROMixer", "Right Output Mixer"}, + {"Right LINEOUT Mux", "LROMixer", "Left Output Mixer"}, + + {"LINEOUTL", NULL, "Left LINEOUT Mux"}, + {"LINEOUTR", NULL, "Right LINEOUT Mux"}, + + {"LINEOUT", NULL, "LINEOUTL"}, + {"LINEOUT", NULL, "LINEOUTR"}, + + {"Speaker", NULL, "LINEOUTL"}, + {"Speaker", NULL, "LINEOUTR"}, +}; + +static const struct snd_kcontrol_new sunxi_card_controls[] = { + SOC_DAPM_PIN_SWITCH("LINEOUT"), +}; + +static struct snd_soc_card *sun50i_h616_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + card->dai_link = sun50i_h616_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = "audiocodec"; + card->controls = sunxi_card_controls; + card->num_controls = ARRAY_SIZE(sunxi_card_controls), + card->dapm_widgets = sun6i_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); + card->dapm_routes = sun8i_codec_card_routes; + card->num_dapm_routes = ARRAY_SIZE(sun8i_codec_card_routes); + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + +static const struct regmap_config sun50i_h616_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUNXI_DAC_AC_RAMP_REG, + .cache_type = REGCACHE_NONE, +}; + +struct sun50i_h616_codec_quirks +{ + const struct regmap_config *regmap_config; + const struct snd_soc_component_driver *codec; + struct snd_soc_card *(*create_card)(struct device *dev); + struct reg_field reg_adc_fifoc; /* used for regmap_field */ + unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */ + unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */ + bool has_reset; +}; + +static const struct sun50i_h616_codec_quirks sun50i_h616_codec_quirks = { + .regmap_config = &sun50i_h616_codec_regmap_config, + .codec = &sun50i_h616_codec_codec, + .create_card = sun50i_h616_codec_create_card, + .reg_dac_txdata = SUNXI_DAC_TXDATA, + .has_reset = true, +}; + +static const struct of_device_id sun50i_h616_codec_of_match[] = { + { + .compatible = "allwinner,sun50i-h616-codec", + .data = &sun50i_h616_codec_quirks, + }, + {}}; +MODULE_DEVICE_TABLE(of, sun50i_h616_codec_of_match); + +static ssize_t show_audio_reg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0, i = 0; + unsigned int reg_val; + unsigned int size = ARRAY_SIZE(reg_labels); + + count += sprintf(buf, "dump audiocodec reg:\n"); + + while ((i < size) && (reg_labels[i].name != NULL)) + { + regmap_read(codec_regmap_debug, + reg_labels[i].address, ®_val); + count += sprintf(buf + count, "%-20s [0x%03x]: 0x%-10x save_val:0x%x\n", + reg_labels[i].name, (reg_labels[i].address), + reg_val, reg_labels[i].value); + i++; + } + + return count; +} + +static DEVICE_ATTR(audio_reg, 0644, show_audio_reg, NULL); + +static struct attribute *audio_debug_attrs[] = { + &dev_attr_audio_reg.attr, + NULL, +}; + +static struct attribute_group audio_debug_attr_group = { + .name = "audio_reg_debug", + .attrs = audio_debug_attrs, +}; + +static void sunxi_codec_init(struct sun50i_h616_codec *scodec) +{ + /* Disable DRC function for playback */ + regmap_write(scodec->regmap, SUNXI_DAC_DAP_CTL, 0); + + /* Enable HPF(high passed filter) */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_DPC, + (0x1 << SUNXI_DAC_DPC_HPF_EN), (0x1 << SUNXI_DAC_DPC_HPF_EN)); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1f << SUNXI_LINEOUT_VOL), + (0x1a << SUNXI_LINEOUT_VOL)); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_DPC, + (0x3f << SUNXI_DAC_DPC_DVOL), (0 << SUNXI_DAC_DPC_DVOL)); + + /* Mixer to channel LINEOUT MUTE control init */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_LMUTE), (0x1 << SUNXI_LMUTE)); + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_RMUTE), (0x1 << SUNXI_RMUTE)); + + /* ramp func about */ + if (0) + { + /* Not used the ramp func cause there is the MUTE to avoid pop noise */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_RSWITCH), (0x1 << SUNXI_RSWITCH)); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_RAMPEN), (0x0 << SUNXI_RAMPEN)); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG, + (0x7 << SUNXI_RAMP_STEP), (0x0 << SUNXI_RAMP_STEP)); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG, + (0x1 << SUNXI_RDEN), (0x0 << SUNXI_RDEN)); + } + else + { + /* If no MUTE to avoid pop, just use the ramp func to avoid it */ + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_RSWITCH), (0x0 << SUNXI_RSWITCH)); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG, + (0x1 << SUNXI_RAMPEN), (0x1 << SUNXI_RAMPEN)); + + regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG, + (0x7 << SUNXI_RAMP_STEP), (0x1 << SUNXI_RAMP_STEP)); + } +} + +static int sun50i_h616_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct sun50i_h616_codec *scodec; + const struct sun50i_h616_codec_quirks *quirks; + struct resource *res; + void __iomem *base; + int ret; + + scodec = devm_kzalloc(&pdev->dev, sizeof(struct sun50i_h616_codec), GFP_KERNEL); + if (!scodec) + return -ENOMEM; + + scodec->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + quirks = of_device_get_match_data(&pdev->dev); + if (quirks == NULL) + { + dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); + return -ENODEV; + } + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + quirks->regmap_config); + if (IS_ERR(scodec->regmap)) + { + dev_err(&pdev->dev, "Failed to create our regmap\n"); + return PTR_ERR(scodec->regmap); + } + + /* Get the clocks from the DT */ + scodec->clk_apb = devm_clk_get(&pdev->dev, "apb"); + if (IS_ERR(scodec->clk_apb)) + { + dev_err(&pdev->dev, "Failed to get the APB clock\n"); + return PTR_ERR(scodec->clk_apb); + } + + scodec->clk_module = devm_clk_get(&pdev->dev, "audio-codec-1x"); + if (IS_ERR(scodec->clk_module)) + { + dev_err(&pdev->dev, "Failed to get the codec module clock\n"); + return PTR_ERR(scodec->clk_module); + } + + if (quirks->has_reset) + { + scodec->rst = devm_reset_control_get_exclusive(&pdev->dev, + NULL); + if (IS_ERR(scodec->rst)) + { + dev_err(&pdev->dev, "Failed to get reset control\n"); + return PTR_ERR(scodec->rst); + } + } + + scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", + GPIOD_OUT_LOW); + if (IS_ERR(scodec->gpio_pa)) + { + ret = PTR_ERR(scodec->gpio_pa); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get pa gpio: %d\n", ret); + return ret; + } + + /* Enable the bus clock */ + if (clk_prepare_enable(scodec->clk_apb)) + { + dev_err(&pdev->dev, "Failed to enable the APB clock\n"); + return -EINVAL; + } + + /* Deassert the reset control */ + if (scodec->rst) + { + ret = reset_control_deassert(scodec->rst); + if (ret) + { + dev_err(&pdev->dev, + "Failed to deassert the reset control\n"); + goto err_clk_disable; + } + } + + /* DMA configuration for TX FIFO */ + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; + scodec->playback_dma_data.maxburst = 8; + scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, + &sun50i_h616_codec_dai, 1); + if (ret) + { + dev_err(&pdev->dev, "Failed to register our codec\n"); + goto err_assert_reset; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun50i_h616_codec_component, + &dummy_cpu_dai, 1); + if (ret) + { + dev_err(&pdev->dev, "Failed to register our DAI\n"); + goto err_assert_reset; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + { + dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); + goto err_assert_reset; + } + + card = quirks->create_card(&pdev->dev); + if (IS_ERR(card)) + { + ret = PTR_ERR(card); + dev_err(&pdev->dev, "Failed to create our card\n"); + goto err_assert_reset; + } + + snd_soc_card_set_drvdata(card, scodec); + + codec_regmap_debug = scodec->regmap; + + ret = snd_soc_register_card(card); + if (ret) + { + dev_err(&pdev->dev, "Failed to register our card\n"); + goto err_assert_reset; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &audio_debug_attr_group); + if (ret) + dev_warn(&pdev->dev, "failed to create attr group\n"); + + sunxi_codec_init(scodec); + + return 0; + +err_assert_reset: + if (scodec->rst) + reset_control_assert(scodec->rst); +err_clk_disable: + clk_disable_unprepare(scodec->clk_apb); + return ret; +} + +static int sun50i_h616_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + if (scodec->rst) + reset_control_assert(scodec->rst); + clk_disable_unprepare(scodec->clk_apb); + + return 0; +} + +static struct platform_driver sun50i_h616_codec_driver = { + .driver = { + .name = "sun50i-h616-codec", + .of_match_table = sun50i_h616_codec_of_match, + }, + .probe = sun50i_h616_codec_probe, + .remove = sun50i_h616_codec_remove, +}; +module_platform_driver(sun50i_h616_codec_driver); + +MODULE_DESCRIPTION("Allwinner H616 codec driver"); +MODULE_AUTHOR("Emilio López "); +MODULE_AUTHOR("Jon Smirl "); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_AUTHOR("Chen-Yu Tsai "); +MODULE_AUTHOR("Leeboby "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sunxi_v2/Kconfig b/sound/soc/sunxi_v2/Kconfig new file mode 100644 index 000000000000..37fc579ba9db --- /dev/null +++ b/sound/soc/sunxi_v2/Kconfig @@ -0,0 +1,48 @@ +# common +config SND_SOC_SUNXI_MACH + tristate + +# ahub dam +config SND_SOC_SUNXI_AHUB_DAM + tristate + +config SND_SOC_SUNXI_INTERNALCODEC + tristate + +config SND_SOC_SUNXI_SUN50IW9_CODEC + tristate + +# menu select +menu "Allwinner SoC Audio support V2" + depends on ARCH_SUNXI + +# aaudio +config SND_SOC_SUNXI_AAUDIO + tristate "Allwinner AAUDIO support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_SUNXI_MACH + select SND_SOC_SUNXI_INTERNALCODEC + select SND_SOC_SUNXI_SUN50IW9_CODEC + depends on ARCH_SUNXI + help + Select Y or M to support analog-audio Module in the Allwinner SoCs. + +# ahub +config SND_SOC_SUNXI_AHUB + tristate "Allwinner AHUB Support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_SUNXI_MACH + select SND_SOC_SUNXI_AHUB_DAM + depends on ARCH_SUNXI + help + Select Y or M to support audio-hub Module in Allwinner SoCs. + +config SND_SOC_SUNXI_DEBUG + tristate "Components Debug" + depends on SND_SOC_SUNXI_COMPONENTS + help + Select Y or M to support debug components. + +endmenu diff --git a/sound/soc/sunxi_v2/Makefile b/sound/soc/sunxi_v2/Makefile new file mode 100644 index 000000000000..c7c2ef8f9fe9 --- /dev/null +++ b/sound/soc/sunxi_v2/Makefile @@ -0,0 +1,11 @@ +# platform -> ahub +snd_soc_sunxi_ahub_dam-objs += snd_sunxi_ahub_dam.o +obj-$(CONFIG_SND_SOC_SUNXI_AHUB_DAM) += snd_soc_sunxi_ahub_dam.o + +snd_soc_sunxi_ahub-objs += snd_sunxi_ahub.o +obj-$(CONFIG_SND_SOC_SUNXI_AHUB) += snd_soc_sunxi_ahub.o + +# common -> machine (note: Finally compile, save system startup time) +snd_soc_sunxi_machine-objs += snd_sunxi_mach.o +snd_soc_sunxi_machine-objs += snd_sunxi_mach_utils.o +obj-$(CONFIG_SND_SOC_SUNXI_MACH) += snd_soc_sunxi_machine.o diff --git a/sound/soc/sunxi_v2/drv_hdmi.h b/sound/soc/sunxi_v2/drv_hdmi.h new file mode 100644 index 000000000000..2e05489b01e1 --- /dev/null +++ b/sound/soc/sunxi_v2/drv_hdmi.h @@ -0,0 +1,63 @@ +/* + * Allwinner SoCs hdmi driver. + * + * Copyright (C) 2016 Allwinner. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __DRV_HDMI_H__ +#define __DRV_HDMI_H__ + +typedef struct { + __u8 hw_intf; /* 0:iis 1:spdif 2:pcm */ + __u16 fs_between; /* fs */ + __u32 sample_rate; /*sample rate*/ + __u8 clk_edge; /* 0:*/ + __u8 ch0_en; /* 1 */ + __u8 ch1_en; /* 0 */ + __u8 ch2_en; /* 0 */ + __u8 ch3_en; /* 0 */ + __u8 word_length; /* 32 */ + __u8 shift_ctl; /* 0 */ + __u8 dir_ctl; /* 0 */ + __u8 ws_pol; + __u8 just_pol; + __u8 channel_num; + __u8 data_raw; + __u8 sample_bit; + __u8 ca; /* channel allocation */ +} hdmi_audio_t; + +typedef struct { + __s32 (*hdmi_audio_enable)(__u8 mode, __u8 channel); + __s32 (*hdmi_set_audio_para)(hdmi_audio_t *audio_para); + __s32 (*hdmi_is_playback)(void); +} __audio_hdmi_func; + +enum hdmi_hpd_status { + STATUE_CLOSE = 0, + STATUE_OPEN = 1, +}; + +void audio_set_hdmi_func(__audio_hdmi_func *hdmi_func); +#if defined(CONFIG_SND_SUNXI_SOC_AUDIOHUB_INTERFACE) +void audio_set_muti_hdmi_func(__audio_hdmi_func *hdmi_func); +#endif + +/******************** SND_HDMI for sunxi_v2 begain ***************************/ +#if IS_ENABLED(CONFIG_HDMI2_DISP2_SUNXI) +extern int snd_hdmi_get_func(__audio_hdmi_func *hdmi_func); +#else +static inline int snd_hdmi_get_func(__audio_hdmi_func *hdmi_func) +{ + pr_err("HDMI Audio API is disable\n"); + + return -1; +} +#endif +/******************** SND_HDMI for sunxi_v2 end ******************************/ + +#endif diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub.c b/sound/soc/sunxi_v2/snd_sunxi_ahub.c new file mode 100644 index 000000000000..8a1065e9183e --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_ahub.c @@ -0,0 +1,1477 @@ +/* + * sound\soc\sunxi\snd_sunxi_ahub.c + * (C) Copyright 2021-2025 + * AllWinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snd_sunxi_log.h" +#include "snd_sunxi_ahub.h" + +#define HLOG "AHUB" +#define DRV_NAME "sunxi-snd-plat-ahub" + +static int sunxi_ahub_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int apb_num, tdm_num; + + SND_LOG_DEBUG(HLOG, "\n"); + + regmap = ahub_info->mem_info.regmap; + apb_num = ahub_info->dts_info.apb_num; + tdm_num = ahub_info->dts_info.tdm_num; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + snd_soc_dai_set_dma_data(dai, substream, + &ahub_info->playback_dma_param); + } else { + snd_soc_dai_set_dma_data(dai, substream, + &ahub_info->capture_dma_param); + } + + /* APBIF & I2S of RST and GAT */ + if (tdm_num > 3 || apb_num > 2) { + SND_LOG_ERR(HLOG, "unspport tdm num or apbif num\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap, SUNXI_AHUB_RST, + 0x1 << (APBIF_TXDIF0_RST - apb_num), + 0x1 << (APBIF_TXDIF0_RST - apb_num)); + regmap_update_bits(regmap, SUNXI_AHUB_GAT, + 0x1 << (APBIF_TXDIF0_GAT - apb_num), + 0x1 << (APBIF_TXDIF0_GAT - apb_num)); + } else { + regmap_update_bits(regmap, SUNXI_AHUB_RST, + 0x1 << (APBIF_RXDIF0_RST - apb_num), + 0x1 << (APBIF_RXDIF0_RST - apb_num)); + regmap_update_bits(regmap, SUNXI_AHUB_GAT, + 0x1 << (APBIF_RXDIF0_GAT - apb_num), + 0x1 << (APBIF_RXDIF0_GAT - apb_num)); + } + + return 0; +} + +static int sunxi_ahub_dai_set_pll(struct snd_soc_dai *dai, + int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct sunxi_ahub_clk_info *clk_info = NULL; + + SND_LOG_DEBUG(HLOG, "stream -> %s, freq_in ->%u, freq_out ->%u\n", + pll_id ? "IN" : "OUT", freq_in, freq_out); + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null.\n"); + return -ENOMEM; + } + clk_info = &ahub_info->clk_info; + + if (freq_in > 24576000) { + //if (clk_set_parent(clk_info->clk_module, clk_info->clk_pllx4)) { + // SND_LOG_ERR(HLOG, "set parent of clk_module to pllx4 failed\n"); + // return -EINVAL; + //} + + if (clk_set_rate(clk_info->clk_pll, freq_in)) { + SND_LOG_ERR(HLOG, "freq : %u pllx4 clk unsupport\n", freq_in); + return -EINVAL; + } + } else { + //if (clk_set_parent(clk_info->clk_module, clk_info->clk_pll)) { + // SND_LOG_ERR(HLOG, "set parent of clk_module to pll failed\n"); + // return -EINVAL; + //} + if (clk_set_rate(clk_info->clk_pll, freq_in)) { + SND_LOG_ERR(HLOG, "freq : %u pll clk unsupport\n", freq_in); + return -EINVAL; + } + } + if (clk_set_rate(clk_info->clk_module, freq_out / 2)) { + SND_LOG_ERR(HLOG, "freq : %u module clk unsupport\n", freq_out); + return -EINVAL; + } + + ahub_info->pllclk_freq = freq_in; + ahub_info->mclk_freq = freq_out; + + return 0; +} + +static int sunxi_ahub_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int tdm_num; + unsigned int mclk_ratio, mclk_ratio_map; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null.\n"); + return -ENOMEM; + } + regmap = ahub_info->mem_info.regmap; + tdm_num = ahub_info->dts_info.tdm_num; + + if (freq == 0) { + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), + 0x1 << I2S_CLKD_MCLK, 0x0 << I2S_CLKD_MCLK); + SND_LOG_DEBUG(HLOG, "mclk freq: 0\n"); + return 0; + } + if (ahub_info->pllclk_freq == 0) { + SND_LOG_ERR(HLOG, "pllclk freq is invalid\n"); + return -ENOMEM; + } + mclk_ratio = ahub_info->pllclk_freq / freq; + + switch (mclk_ratio) { + case 1: + mclk_ratio_map = 1; + break; + case 2: + mclk_ratio_map = 2; + break; + case 4: + mclk_ratio_map = 3; + break; + case 6: + mclk_ratio_map = 4; + break; + case 8: + mclk_ratio_map = 5; + break; + case 12: + mclk_ratio_map = 6; + break; + case 16: + mclk_ratio_map = 7; + break; + case 24: + mclk_ratio_map = 8; + break; + case 32: + mclk_ratio_map = 9; + break; + case 48: + mclk_ratio_map = 10; + break; + case 64: + mclk_ratio_map = 11; + break; + case 96: + mclk_ratio_map = 12; + break; + case 128: + mclk_ratio_map = 13; + break; + case 176: + mclk_ratio_map = 14; + break; + case 192: + mclk_ratio_map = 15; + break; + default: + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), + 0x1 << I2S_CLKD_MCLK, 0x0 << I2S_CLKD_MCLK); + SND_LOG_ERR(HLOG, "mclk freq div unsupport\n"); + return -EINVAL; + } + + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), + 0xf << I2S_CLKD_MCLKDIV, + mclk_ratio_map << I2S_CLKD_MCLKDIV); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), + 0x1 << I2S_CLKD_MCLK, 0x1 << I2S_CLKD_MCLK); + + return 0; +} + +static int sunxi_ahub_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int tdm_num; + unsigned int bclk_ratio; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null.\n"); + return -ENOMEM; + } + regmap = ahub_info->mem_info.regmap; + tdm_num = ahub_info->dts_info.tdm_num; + + /* ratio -> cpudai pllclk / pcm rate */ + switch (ratio) { + case 1: + bclk_ratio = 1; + break; + case 2: + bclk_ratio = 2; + break; + case 4: + bclk_ratio = 3; + break; + case 6: + bclk_ratio = 4; + break; + case 8: + bclk_ratio = 5; + break; + case 12: + bclk_ratio = 6; + break; + case 16: + bclk_ratio = 7; + break; + case 24: + bclk_ratio = 8; + break; + case 32: + bclk_ratio = 9; + break; + case 48: + bclk_ratio = 10; + break; + case 64: + bclk_ratio = 11; + break; + case 96: + bclk_ratio = 12; + break; + case 128: + bclk_ratio = 13; + break; + case 176: + bclk_ratio = 14; + break; + case 192: + bclk_ratio = 15; + break; + default: + SND_LOG_ERR(HLOG, "bclk freq div unsupport\n"); + return -EINVAL; + } + + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num), + 0xf << I2S_CLKD_BCLKDIV, + (bclk_ratio - 2) << I2S_CLKD_BCLKDIV); + + return 0; +} + +static int sunxi_ahub_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int tdm_num, tx_pin, rx_pin; + unsigned int mode, offset; + unsigned int lrck_polarity, brck_polarity; + + SND_LOG_DEBUG(HLOG, "\n"); + + ahub_info->fmt = fmt; + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null.\n"); + return -ENOMEM; + } + regmap = ahub_info->mem_info.regmap; + tdm_num = ahub_info->dts_info.tdm_num; + tx_pin = ahub_info->dts_info.tx_pin; + rx_pin = ahub_info->dts_info.rx_pin; + + /* set TDM format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode = 1; + offset = 1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + mode = 2; + offset = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode = 1; + offset = 0; + break; + case SND_SOC_DAIFMT_DSP_A: + mode = 0; + offset = 1; + /* L data MSB after FRM LRC (short frame) */ + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x1 << I2S_FMT0_LRCK_WIDTH, + 0x0 << I2S_FMT0_LRCK_WIDTH); + break; + case SND_SOC_DAIFMT_DSP_B: + mode = 0; + offset = 0; + /* L data MSB during FRM LRC (long frame) */ + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x1 << I2S_FMT0_LRCK_WIDTH, + 0x1 << I2S_FMT0_LRCK_WIDTH); + break; + default: + SND_LOG_ERR(HLOG, "format setting failed\n"); + return -EINVAL; + } + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x3 << I2S_CTL_MODE, mode << I2S_CTL_MODE); + /* regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin), + * 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); + */ + regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 0), + 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 1), + 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 2), + 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 3), + 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_IN_SLOT(tdm_num), + 0x3 << I2S_IN_OFFSET, offset << I2S_IN_OFFSET); + + /* set lrck & bclk polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + lrck_polarity = 0; + brck_polarity = 0; + break; + case SND_SOC_DAIFMT_NB_IF: + lrck_polarity = 1; + brck_polarity = 0; + break; + case SND_SOC_DAIFMT_IB_NF: + lrck_polarity = 0; + brck_polarity = 1; + break; + case SND_SOC_DAIFMT_IB_IF: + lrck_polarity = 1; + brck_polarity = 1; + break; + default: + SND_LOG_ERR(HLOG, "invert clk setting failed\n"); + return -EINVAL; + } + if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) || + ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_B)) + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x1 << I2S_FMT0_LRCK_POLARITY, + (lrck_polarity^1) << I2S_FMT0_LRCK_POLARITY); + else + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x1 << I2S_FMT0_LRCK_POLARITY, + lrck_polarity << I2S_FMT0_LRCK_POLARITY); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x1 << I2S_FMT0_BCLK_POLARITY, + brck_polarity << I2S_FMT0_BCLK_POLARITY); + + /* set master/slave */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* lrck & bclk dir output */ + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_CLK_OUT, 0x0 << I2S_CTL_CLK_OUT); + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* lrck & bclk dir input */ + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_CLK_OUT, 0x1 << I2S_CTL_CLK_OUT); + break; + default: + SND_LOG_ERR(HLOG, "unknown master/slave format\n"); + return -EINVAL; + } + + return 0; +} + +static int sunxi_ahub_dai_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int tdm_num, tx_pin, rx_pin; + unsigned int slot_width_map, lrck_width_map; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null\n"); + return -ENOMEM; + } + regmap = ahub_info->mem_info.regmap; + tdm_num = ahub_info->dts_info.tdm_num; + tx_pin = ahub_info->dts_info.tx_pin; + rx_pin = ahub_info->dts_info.rx_pin; + + switch (slot_width) { + case 8: + slot_width_map = 1; + break; + case 12: + slot_width_map = 2; + break; + case 16: + slot_width_map = 3; + break; + case 20: + slot_width_map = 4; + break; + case 24: + slot_width_map = 5; + break; + case 28: + slot_width_map = 6; + break; + case 32: + slot_width_map = 7; + break; + default: + SND_LOG_ERR(HLOG, "unknown slot width\n"); + return -EINVAL; + } + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x7 << I2S_FMT0_SW, slot_width_map << I2S_FMT0_SW); + + /* bclk num of per channel + * I2S/RIGHT_J/LEFT_J -> lrck long total is lrck_width_map * 2 + * DSP_A/DAP_B -> lrck long total is lrck_width_map * 1 + */ + switch (ahub_info->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + slots /= 2; + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + break; + default: + SND_LOG_ERR(HLOG, "unsupoort format\n"); + return -EINVAL; + } + lrck_width_map = slots * slot_width - 1; + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x3ff << I2S_FMT0_LRCK_PERIOD, + lrck_width_map << I2S_FMT0_LRCK_PERIOD); + + return 0; +} + +static int sunxi_ahub_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int apb_num, tdm_num, tx_pin, rx_pin; + unsigned int channels; + unsigned int channels_en[16] = { + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff + }; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null.\n"); + return -ENOMEM; + } + regmap = ahub_info->mem_info.regmap; + apb_num = ahub_info->dts_info.apb_num; + tdm_num = ahub_info->dts_info.tdm_num; + tx_pin = ahub_info->dts_info.tx_pin; + rx_pin = ahub_info->dts_info.rx_pin; + + /* configure DMA */ + switch (params_physical_width(params)) { + case 16: + ahub_info->playback_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + ahub_info->capture_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 24: + case 32: + ahub_info->playback_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ahub_info->capture_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + dev_err(dai->dev, "Unsupported physical sample width: %d\n", + params_physical_width(params)); + return -EINVAL; + } + + /* set bits */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + /* apbifn bits */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap, + SUNXI_AHUB_APBIF_TX_CTL(apb_num), + 0x7 << APBIF_TX_WS, + 0x3 << APBIF_TX_WS); + regmap_update_bits(regmap, + SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), + 0x1 << APBIF_TX_TXIM, + 0x1 << APBIF_TX_TXIM); + } else { + regmap_update_bits(regmap, + SUNXI_AHUB_APBIF_RX_CTL(apb_num), + 0x7 << APBIF_RX_WS, + 0x3 << APBIF_RX_WS); + regmap_update_bits(regmap, + SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), + 0x3 << APBIF_RX_RXOM, + 0x1 << APBIF_RX_RXOM); + } + + regmap_update_bits(regmap, + SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x7 << I2S_FMT0_SR, + 0x3 << I2S_FMT0_SR); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S24_LE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), + 0x7 << APBIF_TX_WS, 0x5 << APBIF_TX_WS); + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), + 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM); + } else { + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), + 0x7 << APBIF_RX_WS, 0x5 << APBIF_RX_WS); + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), + 0x3 << APBIF_RX_RXOM, 0x1 << APBIF_RX_RXOM); + } + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x7 << I2S_FMT0_SR, 0x5 << I2S_FMT0_SR); + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), + 0x7 << APBIF_TX_WS, 0x7 << APBIF_TX_WS); + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), + 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM); + } else { + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), + 0x7 << APBIF_RX_WS, 0x7 << APBIF_RX_WS); + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), + 0x3 << APBIF_RX_RXOM, 0x1 << APBIF_RX_RXOM); + } + regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num), + 0x7 << I2S_FMT0_SR, 0x7 << I2S_FMT0_SR); + break; + default: + SND_LOG_ERR(HLOG, "unrecognized format bits\n"); + return -EINVAL; + } + + /* set channels */ + channels = params_channels(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* apbifn channels */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), + 0xf << APBIF_TX_CHAN_NUM, + (channels - 1) << APBIF_TX_CHAN_NUM); + /* tdmn channels */ + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CHCFG(tdm_num), + 0xf << I2S_CHCFG_TX_CHANNUM, + (channels - 1) << I2S_CHCFG_TX_CHANNUM); + + regmap_update_bits(regmap, + SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin), + 0xf << I2S_OUT_SLOT_NUM, + (channels - 1) << I2S_OUT_SLOT_NUM); + regmap_update_bits(regmap, + SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin), + 0xffff << I2S_OUT_SLOT_EN, + channels_en[channels - 1] << I2S_OUT_SLOT_EN); + } else { + /* apbifn channels */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), + 0xf << APBIF_RX_CHAN_NUM, + (channels - 1) << APBIF_RX_CHAN_NUM); + /* tdmn channels */ + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CHCFG(tdm_num), + 0xf << I2S_CHCFG_RX_CHANNUM, + (channels - 1) << I2S_CHCFG_RX_CHANNUM); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_IN_SLOT(tdm_num), + 0xf << I2S_IN_SLOT_NUM, + (channels - 1) << I2S_IN_SLOT_NUM); + } + + return 0; +} + +static void sunxi_ahub_dai_tx_route(struct sunxi_ahub_info *ahub_info, + bool enable) +{ + struct regmap *regmap = NULL; + unsigned int tdm_num, tx_pin; + unsigned int apb_num; + + SND_LOG_DEBUG(HLOG, "%s\n", enable ? "on" : "off"); + + regmap = ahub_info->mem_info.regmap; + tdm_num = ahub_info->dts_info.tdm_num; + tx_pin = ahub_info->dts_info.tx_pin; + apb_num = ahub_info->dts_info.apb_num; + + if (enable) + goto tx_route_enable; + else + goto tx_route_disable; + +tx_route_enable: + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << (I2S_CTL_SDO0_EN + tx_pin), + 0x1 << (I2S_CTL_SDO0_EN + tx_pin)); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_TXEN, 0x1 << I2S_CTL_TXEN); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_OUT_MUTE, 0x0 << I2S_CTL_OUT_MUTE); + /* start apbif tx */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), + 0x1 << APBIF_TX_START, 0x1 << APBIF_TX_START); + /* enable tx drq */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_IRQ_CTL(apb_num), + 0x1 << APBIF_TX_DRQ, 0x1 << APBIF_TX_DRQ); + return; + +tx_route_disable: + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_OUT_MUTE, 0x1 << I2S_CTL_OUT_MUTE); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_TXEN, 0x0 << I2S_CTL_TXEN); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << (I2S_CTL_SDO0_EN + tx_pin), + 0x0 << (I2S_CTL_SDO0_EN + tx_pin)); + /* stop apbif tx */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num), + 0x1 << APBIF_TX_START, 0x0 << APBIF_TX_START); + /* disable tx drq */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_IRQ_CTL(apb_num), + 0x1 << APBIF_TX_DRQ, 0x0 << APBIF_TX_DRQ); + return; +} + +static void sunxi_ahub_dai_rx_route(struct sunxi_ahub_info *ahub_info, + bool enable) +{ + struct regmap *regmap = NULL; + unsigned int tdm_num, rx_pin; + unsigned int apb_num; + + SND_LOG_DEBUG(HLOG, "%s\n", enable ? "on" : "off"); + + regmap = ahub_info->mem_info.regmap; + tdm_num = ahub_info->dts_info.tdm_num; + rx_pin = ahub_info->dts_info.rx_pin; + apb_num = ahub_info->dts_info.apb_num; + + if (enable) + goto rx_route_enable; + else + goto rx_route_disable; + +rx_route_enable: + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << (I2S_CTL_SDI0_EN + rx_pin), + 0x1 << (I2S_CTL_SDI0_EN + rx_pin)); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_RXEN, 0x1 << I2S_CTL_RXEN); + /* start apbif rx */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), + 0x1 << APBIF_RX_START, 0x1 << APBIF_RX_START); + /* enable rx drq */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_IRQ_CTL(apb_num), + 0x1 << APBIF_RX_DRQ, 0x1 << APBIF_RX_DRQ); + return; + +rx_route_disable: + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_RXEN, 0x0 << I2S_CTL_RXEN); + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << (I2S_CTL_SDI0_EN + rx_pin), + 0x0 << (I2S_CTL_SDI0_EN + rx_pin)); + /* stop apbif rx */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num), + 0x1 << APBIF_RX_START, 0x0 << APBIF_RX_START); + /* disable rx drq */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_IRQ_CTL(apb_num), + 0x1 << APBIF_RX_DRQ, 0x0 << APBIF_RX_DRQ); + return; +} + +static int sunxi_ahub_dai_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null.\n"); + return -ENOMEM; + } + + 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) { + sunxi_ahub_dai_tx_route(ahub_info, true); + } else { + sunxi_ahub_dai_rx_route(ahub_info, true); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sunxi_ahub_dai_tx_route(ahub_info, false); + } else { + sunxi_ahub_dai_rx_route(ahub_info, false); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sunxi_ahub_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int apb_num; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "ahub_info is null.\n"); + return -ENOMEM; + } + regmap = ahub_info->mem_info.regmap; + apb_num = ahub_info->dts_info.apb_num; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* clear txfifo */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), + 0x1 << APBIF_TX_FTX, 0x1 << APBIF_TX_FTX); + /* clear tx o/u irq */ + regmap_write(regmap, SUNXI_AHUB_APBIF_TX_IRQ_STA(apb_num), + (0x1 << APBIF_TX_OV_PEND) | (0x1 << APBIF_TX_EM_PEND)); + /* clear tx fifo cnt */ + regmap_write(regmap, SUNXI_AHUB_APBIF_TXFIFO_CNT(apb_num), 0); + } else { + /* clear rxfifo */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), + 0x1 << APBIF_RX_FRX, 0x1 << APBIF_RX_FRX); + /* clear rx o/u irq */ + regmap_write(regmap, SUNXI_AHUB_APBIF_RX_IRQ_STA(apb_num), + (0x1 << APBIF_RX_UV_PEND) | (0x1 << APBIF_RX_AV_PEND)); + /* clear rx fifo cnt */ + regmap_write(regmap, SUNXI_AHUB_APBIF_RXFIFO_CNT(apb_num), 0); + } + + return 0; +} + +static void sunxi_ahub_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int apb_num, tdm_num; + + SND_LOG_DEBUG(HLOG, "\n"); + + regmap = ahub_info->mem_info.regmap; + apb_num = ahub_info->dts_info.apb_num; + tdm_num = ahub_info->dts_info.tdm_num; + + /* APBIF & I2S of RST and GAT */ + if (tdm_num > 3 || apb_num > 2) { + SND_LOG_ERR(HLOG, "unspport tdm num or apbif num\n"); + return; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap, SUNXI_AHUB_RST, + 0x1 << (APBIF_TXDIF0_RST - apb_num), + 0x0 << (APBIF_TXDIF0_RST - apb_num)); + regmap_update_bits(regmap, SUNXI_AHUB_GAT, + 0x1 << (APBIF_TXDIF0_GAT - apb_num), + 0x0 << (APBIF_TXDIF0_GAT - apb_num)); + } else { + regmap_update_bits(regmap, SUNXI_AHUB_RST, + 0x1 << (APBIF_RXDIF0_RST - apb_num), + 0x0 << (APBIF_RXDIF0_RST - apb_num)); + regmap_update_bits(regmap, SUNXI_AHUB_GAT, + 0x1 << (APBIF_RXDIF0_GAT - apb_num), + 0x0 << (APBIF_RXDIF0_GAT - apb_num)); + } +} + +static const struct snd_soc_dai_ops sunxi_ahub_dai_ops = { + /* call by machine */ + .set_pll = sunxi_ahub_dai_set_pll, // set pllclk + .set_sysclk = sunxi_ahub_dai_set_sysclk, // set mclk + .set_bclk_ratio = sunxi_ahub_dai_set_bclk_ratio,// set bclk freq + .set_tdm_slot = sunxi_ahub_dai_set_tdm_slot, // set slot num and width + .set_fmt = sunxi_ahub_dai_set_fmt, // set tdm fmt + /* call by asoc */ + .startup = sunxi_ahub_dai_startup, + .hw_params = sunxi_ahub_dai_hw_params, // set hardware params + .prepare = sunxi_ahub_dai_prepare, // clean irq and fifo + .trigger = sunxi_ahub_dai_trigger, // set drq + .shutdown = sunxi_ahub_dai_shutdown, +}; + +static void snd_soc_sunxi_ahub_init(struct sunxi_ahub_info *ahub_info) +{ + struct regmap *regmap = NULL; + unsigned int apb_num, tdm_num, tx_pin, rx_pin; + unsigned int reg_val = 0; + unsigned int rx_pin_map = 0; + unsigned int tdm_to_apb = 0; + unsigned int apb_to_tdm = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + regmap = ahub_info->mem_info.regmap; + apb_num = ahub_info->dts_info.apb_num; + tdm_num = ahub_info->dts_info.tdm_num; + tx_pin = ahub_info->dts_info.tx_pin; + rx_pin = ahub_info->dts_info.rx_pin; + + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_GEN, 0x1 << I2S_CTL_GEN); + regmap_update_bits(regmap, SUNXI_AHUB_RST, + 0x1 << (I2S0_RST - tdm_num), + 0x1 << (I2S0_RST - tdm_num)); + regmap_update_bits(regmap, SUNXI_AHUB_GAT, + 0x1 << (I2S0_GAT - tdm_num), + 0x1 << (I2S0_GAT - tdm_num)); + + /* tdm tx channels map */ + regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, tx_pin), 0x76543210); + regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP1(tdm_num, tx_pin), 0xFEDCBA98); + + /* tdm rx channels map */ + rx_pin_map = (rx_pin << 4) | (rx_pin << 12) | (rx_pin << 20) | (rx_pin << 28); + reg_val = 0x03020100 | rx_pin_map; + regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP0(tdm_num), reg_val); + reg_val = 0x07060504 | rx_pin_map; + regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP1(tdm_num), reg_val); + reg_val = 0x0B0A0908 | rx_pin_map; + regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP2(tdm_num), reg_val); + reg_val = 0x0F0E0D0C | rx_pin_map; + regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP3(tdm_num), reg_val); + + /* tdm tx & rx data fmt + * 1. MSB first + * 2. transfer 0 after each sample in each slot + * 3. linear PCM + */ + regmap_write(regmap, SUNXI_AHUB_I2S_FMT1(tdm_num), 0x30); + + /* apbif tx & rx data fmt + * 1. MSB first + * 2. trigger level tx -> 0x20, rx -> 0x40 + */ + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), + 0x1 << APBIF_TX_TXIM, 0x0 << APBIF_TX_TXIM); + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num), + 0x3f << APBIF_TX_LEVEL, 0x20 << APBIF_TX_LEVEL); + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), + 0x3 << APBIF_RX_RXOM, 0x0 << APBIF_RX_RXOM); + regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num), + 0x7f << APBIF_RX_LEVEL, 0x40 << APBIF_RX_LEVEL); + + /* apbif <-> tdm */ + switch (tdm_num) + { + case 0: + tdm_to_apb = APBIF_RX_I2S0_TXDIF; + break; + case 1: + tdm_to_apb = APBIF_RX_I2S1_TXDIF; + break; + case 2: + tdm_to_apb = APBIF_RX_I2S2_TXDIF; + break; + case 3: + tdm_to_apb = APBIF_RX_I2S3_TXDIF; + break; + default: + SND_LOG_ERR(HLOG, "unspport tdm num\n"); + return; + } + regmap_write(regmap, SUNXI_AHUB_APBIF_RXFIFO_CONT(apb_num), 0x1 << tdm_to_apb); + + switch (apb_num) + { + case 0: + apb_to_tdm = I2S_RX_APBIF_TXDIF0; + break; + case 1: + apb_to_tdm = I2S_RX_APBIF_TXDIF1; + break; + case 2: + apb_to_tdm = I2S_RX_APBIF_TXDIF2; + break; + default: + SND_LOG_ERR(HLOG, "unspport apb num\n"); + return; + } + regmap_write(regmap, SUNXI_AHUB_I2S_RXCONT(tdm_num), 0x1 << apb_to_tdm); + + return; +} + +static int sunxi_ahub_dai_probe(struct snd_soc_dai *dai) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + + SND_LOG_DEBUG(HLOG, "\n"); + + /* pcm_new will using the dma_param about the cma and fifo params. */ + snd_soc_dai_init_dma_data(dai, + &ahub_info->playback_dma_param, + &ahub_info->capture_dma_param); + + snd_soc_sunxi_ahub_init(ahub_info); + + return 0; +} + +static int sunxi_ahub_dai_remove(struct snd_soc_dai *dai) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap = NULL; + unsigned int tdm_num; + + SND_LOG_DEBUG(HLOG, "\n"); + + regmap = ahub_info->mem_info.regmap; + tdm_num = ahub_info->dts_info.tdm_num; + + regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num), + 0x1 << I2S_CTL_GEN, 0x0 << I2S_CTL_GEN); + regmap_update_bits(regmap, SUNXI_AHUB_RST, + 0x1 << (I2S0_RST - tdm_num), + 0x0 << (I2S0_RST - tdm_num)); + regmap_update_bits(regmap, SUNXI_AHUB_GAT, + 0x1 << (I2S0_GAT - tdm_num), + 0x0 << (I2S0_GAT - tdm_num)); + + return 0; +} + +static struct snd_soc_dai_driver sunxi_ahub_dai = { + .name = "ahub_plat", + .probe = sunxi_ahub_dai_probe, + .remove = sunxi_ahub_dai_remove, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000 + | SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000 + | SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &sunxi_ahub_dai_ops, +}; + +static int sunxi_ahub_probe(struct snd_soc_component *component) +{ + SND_LOG_DEBUG(HLOG, "\n"); + + return 0; +} + +static int sunxi_ahub_suspend(struct snd_soc_component *component) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component); + struct regmap *regmap = NULL; + unsigned int apb_num, tdm_num; + + SND_LOG_DEBUG(HLOG, "\n"); + + regmap = ahub_info->mem_info.regmap; + apb_num = ahub_info->dts_info.apb_num; + tdm_num = ahub_info->dts_info.tdm_num; + + return 0; +} + +static int sunxi_ahub_resume(struct snd_soc_component *component) +{ + struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component); + + SND_LOG_DEBUG(HLOG, "\n"); + + snd_soc_sunxi_ahub_init(ahub_info); + + return 0; +} + +int sunxi_loopback_debug_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int reg_val; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component); + struct sunxi_ahub_mem_info *mem_info = &ahub_info->mem_info; + struct sunxi_ahub_dts_info *dts_info = &ahub_info->dts_info; + + regmap_read(mem_info->regmap, SUNXI_AHUB_I2S_CTL(dts_info->tdm_num), ®_val); + ucontrol->value.integer.value[0] = ((reg_val & (1 << I2S_CTL_LOOP0)) ? 1 : 0); + + return 0; +} + +int sunxi_loopback_debug_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component); + struct sunxi_ahub_mem_info *mem_info = &ahub_info->mem_info; + struct sunxi_ahub_dts_info *dts_info = &ahub_info->dts_info; + + switch (ucontrol->value.integer.value[0]) { + case 0: + regmap_update_bits(mem_info->regmap, + SUNXI_AHUB_I2S_CTL(dts_info->tdm_num), + 1 << I2S_CTL_LOOP0, 0 << I2S_CTL_LOOP0); + break; + case 1: + regmap_update_bits(mem_info->regmap, + SUNXI_AHUB_I2S_CTL(dts_info->tdm_num), + 1 << I2S_CTL_LOOP0, 1 << I2S_CTL_LOOP0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_kcontrol_new sunxi_ahub_controls[] = { + SOC_SINGLE_EXT("loopback debug", SND_SOC_NOPM, 0, 1, 0, + sunxi_loopback_debug_get, sunxi_loopback_debug_set), +}; + +static struct snd_soc_component_driver sunxi_ahub_component = { + .name = DRV_NAME, + .probe = sunxi_ahub_probe, + .suspend = sunxi_ahub_suspend, + .resume = sunxi_ahub_resume, + .controls = sunxi_ahub_controls, + .num_controls = ARRAY_SIZE(sunxi_ahub_controls), +}; + +/******************************************************************************* + * for kernel source + ******************************************************************************/ +static int snd_soc_sunxi_ahub_pin_init(struct platform_device *pdev, + struct device_node *np, + struct sunxi_ahub_pinctl_info *pin_info) +{ + int ret = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (of_property_read_bool(np, "pinctrl_used")) { + pin_info->pinctrl_used = 1; + } else { + pin_info->pinctrl_used = 0; + SND_LOG_DEBUG(HLOG, "unused pinctrl\n"); + return 0; + } + + pin_info->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR_OR_NULL(pin_info->pinctrl)) { + SND_LOG_ERR(HLOG, "pinctrl get failed\n"); + ret = -EINVAL; + return ret; + } + pin_info->pinstate = pinctrl_lookup_state(pin_info->pinctrl, + PINCTRL_STATE_DEFAULT); + if (IS_ERR_OR_NULL(pin_info->pinstate)) { + SND_LOG_ERR(HLOG, "pinctrl default state get fail\n"); + ret = -EINVAL; + goto err_loopup_pinstate; + } + pin_info->pinstate_sleep = pinctrl_lookup_state(pin_info->pinctrl, + PINCTRL_STATE_SLEEP); + if (IS_ERR_OR_NULL(pin_info->pinstate_sleep)) { + SND_LOG_ERR(HLOG, "pinctrl sleep state get failed\n"); + ret = -EINVAL; + goto err_loopup_pin_sleep; + } + ret = pinctrl_select_state(pin_info->pinctrl, pin_info->pinstate); + if (ret < 0) { + SND_LOG_ERR(HLOG, "daudio set pinctrl default state fail\n"); + ret = -EBUSY; + goto err_pinctrl_select_default; + } + + return 0; + +err_pinctrl_select_default: +err_loopup_pin_sleep: +err_loopup_pinstate: + devm_pinctrl_put(pin_info->pinctrl); + return ret; +} + +static int snd_soc_sunxi_ahub_dts_params_init(struct platform_device *pdev, + struct device_node *np, + struct sunxi_ahub_dts_info *dts_info) +{ + int ret = 0; + unsigned int temp_val = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + /* get tdm fmt of apb_num & tdm_num & tx/rx_pin */ + ret = of_property_read_u32(np, "apb_num", &temp_val); + if (ret < 0) { + SND_LOG_WARN(HLOG, "apb_num config missing\n"); + dts_info->apb_num = 0; + } else { + if (temp_val > 2) { /* APBIFn (n = 0~2) */ + dts_info->apb_num = 0; + SND_LOG_WARN(HLOG, "apb_num config invalid\n"); + } else { + dts_info->apb_num = temp_val; + } + } + ret = of_property_read_u32(np, "tdm_num", &temp_val); + if (ret < 0) { + SND_LOG_WARN(HLOG, "tdm_num config missing\n"); + dts_info->tdm_num = 0; + } else { + if (temp_val > 3) { /* I2Sn (n = 0~3) */ + dts_info->tdm_num = 0; + SND_LOG_WARN(HLOG, "tdm_num config invalid\n"); + } else { + dts_info->tdm_num = temp_val; + } + } + ret = of_property_read_u32(np, "tx_pin", &temp_val); + if (ret < 0) { + SND_LOG_WARN(HLOG, "tx_pin config missing\n"); + dts_info->tx_pin = 0; + } else { + if (temp_val > 3) { /* I2S_DOUTn (n = 0~3) */ + dts_info->tx_pin = 0; + SND_LOG_WARN(HLOG, "tx_pin config invalid\n"); + } else { + dts_info->tx_pin = temp_val; + } + } + ret = of_property_read_u32(np, "rx_pin", &temp_val); + if (ret < 0) { + SND_LOG_WARN(HLOG, "rx_pin config missing\n"); + dts_info->rx_pin = 0; + } else { + if (temp_val > 3) { /* I2S_DINTn (n = 0~3) */ + dts_info->rx_pin = 0; + SND_LOG_WARN(HLOG, "rx_pin config invalid\n"); + } else { + dts_info->rx_pin = temp_val; + } + } + + SND_LOG_DEBUG(HLOG, "playback_cma : %lu\n", dts_info->playback_cma); + SND_LOG_DEBUG(HLOG, "capture_cma : %lu\n", dts_info->capture_cma); + SND_LOG_DEBUG(HLOG, "tx_fifo_size : %lu\n", dts_info->playback_fifo_size); + SND_LOG_DEBUG(HLOG, "rx_fifo_size : %lu\n", dts_info->capture_fifo_size); + SND_LOG_DEBUG(HLOG, "apb_num : %u\n", dts_info->apb_num); + SND_LOG_DEBUG(HLOG, "tdm_num : %u\n", dts_info->tdm_num); + SND_LOG_DEBUG(HLOG, "tx_pin : %u\n", dts_info->tx_pin); + SND_LOG_DEBUG(HLOG, "rx_pin : %u\n", dts_info->rx_pin); + + return 0; +}; + +static int snd_soc_sunxi_ahub_regulator_init(struct platform_device *pdev, + struct device_node *np, + struct sunxi_ahub_regulator_info *regulator_info) +{ + int ret = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + regulator_info->regulator_name = NULL; + if (of_property_read_string(np, "ahub_regulator", ®ulator_info->regulator_name)) { + SND_LOG_DEBUG(HLOG, "regulator missing\n"); + regulator_info->regulator = NULL; + return 0; + } + + regulator_info->regulator = regulator_get(NULL, regulator_info->regulator_name); + if (IS_ERR_OR_NULL(regulator_info->regulator)) { + SND_LOG_ERR(HLOG, "get duaido vcc-pin failed\n"); + ret = -EFAULT; + goto err_regulator_get; + } + ret = regulator_set_voltage(regulator_info->regulator, 3300000, 3300000); + if (ret < 0) { + SND_LOG_ERR(HLOG, "set duaido voltage failed\n"); + ret = -EFAULT; + goto err_regulator_set_vol; + } + ret = regulator_enable(regulator_info->regulator); + if (ret < 0) { + SND_LOG_ERR(HLOG, "enable duaido vcc-pin failed\n"); + ret = -EFAULT; + goto err_regulator_enable; + } + + return 0; + +err_regulator_enable: +err_regulator_set_vol: + if (regulator_info->regulator) + regulator_put(regulator_info->regulator); +err_regulator_get: + return ret; +}; + +static void snd_soc_sunxi_dma_params_init(struct sunxi_ahub_info *ahub_info) +{ + struct resource *res = ahub_info->mem_info.res; + struct sunxi_ahub_dts_info *dts_info = &ahub_info->dts_info; + + SND_LOG_DEBUG(HLOG, "\n"); + + ahub_info->playback_dma_param.addr = + res->start + SUNXI_AHUB_APBIF_TXFIFO(dts_info->apb_num); + ahub_info->playback_dma_param.maxburst = 8; + //ahub_info->playback_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + ahub_info->capture_dma_param.addr = + res->start + SUNXI_AHUB_APBIF_RXFIFO(dts_info->apb_num); + ahub_info->capture_dma_param.maxburst = 8; + //ahub_info->capture_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +}; + +static int sunxi_ahub_dev_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *np = pdev->dev.of_node; + struct sunxi_ahub_info *ahub_info = NULL; + struct sunxi_ahub_mem_info *mem_info = NULL; + struct sunxi_ahub_clk_info *clk_info = NULL; + struct sunxi_ahub_pinctl_info *pin_info = NULL; + struct sunxi_ahub_dts_info *dts_info = NULL; + struct sunxi_ahub_regulator_info *regulator_info = NULL; + + SND_LOG_DEBUG(HLOG, "\n"); + + ahub_info = devm_kzalloc(&pdev->dev, + sizeof(struct sunxi_ahub_info), + GFP_KERNEL); + if (IS_ERR_OR_NULL(ahub_info)) { + SND_LOG_ERR(HLOG, "alloc sunxi_ahub_info failed\n"); + ret = -ENOMEM; + goto err_devm_malloc_sunxi_daudio; + } + dev_set_drvdata(&pdev->dev, ahub_info); + ahub_info->dev = &pdev->dev; + mem_info = &ahub_info->mem_info; + clk_info = &ahub_info->clk_info; + pin_info = &ahub_info->pin_info; + dts_info = &ahub_info->dts_info; + regulator_info = &ahub_info->regulator_info; + + ret = snd_soc_sunxi_ahub_mem_get(mem_info); + if (ret) { + SND_LOG_ERR(HLOG, "remap get failed\n"); + ret = -EINVAL; + goto err_snd_soc_sunxi_ahub_mem_get; + } + + ret = snd_soc_sunxi_ahub_clk_get(clk_info); + if (ret) { + SND_LOG_ERR(HLOG, "clk get failed\n"); + ret = -EINVAL; + goto err_snd_soc_sunxi_ahub_clk_get; + } + + ret = snd_soc_sunxi_ahub_dts_params_init(pdev, np, dts_info); + if (ret) { + SND_LOG_ERR(HLOG, "dts init failed\n"); + ret = -EINVAL; + goto err_snd_soc_sunxi_ahub_dts_params_init; + } + + ret = snd_soc_sunxi_ahub_pin_init(pdev, np, pin_info); + if (ret) { + SND_LOG_ERR(HLOG, "pinctrl init failed\n"); + ret = -EINVAL; + goto err_snd_soc_sunxi_ahub_pin_init; + } + + ret = snd_soc_sunxi_ahub_regulator_init(pdev, np, regulator_info); + if (ret) { + SND_LOG_ERR(HLOG, "regulator_info init failed\n"); + ret = -EINVAL; + goto err_snd_soc_sunxi_ahub_regulator_init; + } + + snd_soc_sunxi_dma_params_init(ahub_info); + + ret = snd_soc_register_component(&pdev->dev, + &sunxi_ahub_component, + &sunxi_ahub_dai, 1); + if (ret) { + SND_LOG_ERR(HLOG, "component register failed\n"); + ret = -ENOMEM; + goto err_snd_soc_register_component; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + SND_LOG_ERR(HLOG, "register ASoC platform failed\n"); + ret = -ENOMEM; + goto err_snd_soc_sunxi_dma_platform_register; + } + + SND_LOG_DEBUG(HLOG, "register ahub platform success\n"); + + return 0; + +err_snd_soc_sunxi_dma_platform_register: + snd_soc_unregister_component(&pdev->dev); +err_snd_soc_register_component: +err_snd_soc_sunxi_ahub_regulator_init: +err_snd_soc_sunxi_ahub_dts_params_init: +err_snd_soc_sunxi_ahub_pin_init: +err_snd_soc_sunxi_ahub_clk_get: +err_snd_soc_sunxi_ahub_mem_get: + devm_kfree(&pdev->dev, ahub_info); +err_devm_malloc_sunxi_daudio: + of_node_put(np); + return ret; +} + +static int sunxi_ahub_dev_remove(struct platform_device *pdev) +{ + struct sunxi_ahub_info *ahub_info = dev_get_drvdata(&pdev->dev); + struct sunxi_ahub_pinctl_info *pin_info = &ahub_info->pin_info; + struct sunxi_ahub_regulator_info *regulator_info = &ahub_info->regulator_info; + + SND_LOG_DEBUG(HLOG, "\n"); + + snd_soc_unregister_component(&pdev->dev); + + if (regulator_info->regulator) { + if (!IS_ERR_OR_NULL(regulator_info->regulator)) { + regulator_disable(regulator_info->regulator); + regulator_put(regulator_info->regulator); + } + } + if (pin_info->pinctrl_used) { + devm_pinctrl_put(pin_info->pinctrl); + } + + devm_kfree(&pdev->dev, ahub_info); + + SND_LOG_DEBUG(HLOG, "unregister ahub platform success\n"); + + return 0; +} + +static const struct of_device_id sunxi_ahub_of_match[] = { + { .compatible = "allwinner," DRV_NAME, }, + {}, +}; +MODULE_DEVICE_TABLE(of, sunxi_ahub_of_match); + +static struct platform_driver sunxi_ahub_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = sunxi_ahub_of_match, + }, + .probe = sunxi_ahub_dev_probe, + .remove = sunxi_ahub_dev_remove, +}; + +int __init sunxi_ahub_dev_init(void) +{ + int ret; + + ret = platform_driver_register(&sunxi_ahub_driver); + if (ret != 0) { + SND_LOG_ERR(HLOG, "platform driver register failed\n"); + return -EINVAL; + } + + return ret; +} + +void __exit sunxi_ahub_dev_exit(void) +{ + platform_driver_unregister(&sunxi_ahub_driver); +} + +late_initcall(sunxi_ahub_dev_init); +module_exit(sunxi_ahub_dev_exit); + +MODULE_AUTHOR("Dby@allwinnertech.com"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("sunxi soundcard platform of ahub"); diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub.h b/sound/soc/sunxi_v2/snd_sunxi_ahub.h new file mode 100644 index 000000000000..b3c1cc592844 --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_ahub.h @@ -0,0 +1,67 @@ +/* sound\soc\sunxi\snd_sunxi_ahub.h + * (C) Copyright 2021-2025 + * Allwinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef __SND_SUNXI_AHUB_H +#define __SND_SUNXI_AHUB_H + +#include "snd_sunxi_ahub_dam.h" + +struct sunxi_ahub_pinctl_info { + struct pinctrl *pinctrl; + struct pinctrl_state *pinstate; + struct pinctrl_state *pinstate_sleep; + + bool pinctrl_used; +}; + +struct sunxi_ahub_dts_info { + unsigned int dai_type; + unsigned int apb_num; + unsigned int tdm_num; + unsigned int tx_pin; + unsigned int rx_pin; + + /* value must be (2^n)Kbyte */ + size_t playback_cma; + size_t playback_fifo_size; + size_t capture_cma; + size_t capture_fifo_size; +}; + +struct sunxi_ahub_regulator_info { + struct regulator *regulator; + const char *regulator_name; +}; + +struct sunxi_ahub_info { + struct device *dev; + + struct sunxi_ahub_mem_info mem_info; + struct sunxi_ahub_clk_info clk_info; + struct sunxi_ahub_pinctl_info pin_info; + struct sunxi_ahub_dts_info dts_info; + struct sunxi_ahub_regulator_info regulator_info; + + //struct sunxi_dma_params playback_dma_param; + //struct sunxi_dma_params capture_dma_param; + struct snd_dmaengine_dai_dma_data playback_dma_param; + struct snd_dmaengine_dai_dma_data capture_dma_param; + + /* for Hardware param setting */ + unsigned int fmt; + unsigned int pllclk_freq; + unsigned int moduleclk_freq; + unsigned int mclk_freq; + unsigned int lrck_freq; + unsigned int bclk_freq; +}; + +#endif /* __SND_SUNXI_AHUB_H */ diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c new file mode 100644 index 000000000000..1fcc8aefd50c --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c @@ -0,0 +1,534 @@ +/* + * sound\soc\sunxi\snd_sunxi_ahub_dam.c + * (C) Copyright 2021-2025 + * AllWinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snd_sunxi_log.h" +#include "snd_sunxi_ahub_dam.h" + +#define HLOG "AHUB_DAM" +#define DRV_NAME "sunxi-snd-plat-ahub_dam" + +static struct resource g_res; +struct sunxi_ahub_mem_info g_mem_info = { + .res = &g_res, +}; +static struct sunxi_ahub_clk_info g_clk_info; +static struct regmap_config g_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUNXI_AHUB_MAX_REG, + .cache_type = REGCACHE_NONE, +}; + +static struct snd_soc_dai_driver sunxi_ahub_dam_dai = { + .name = "ahub_dam", +}; + +static int sunxi_ahub_dam_probe(struct snd_soc_component *component) +{ + SND_LOG_DEBUG(HLOG, "\n"); + + return 0; +} + +static int sunxi_ahub_dam_suspend(struct snd_soc_component *component) +{ + struct sunxi_ahub_clk_info *clk_info = &g_clk_info; + + SND_LOG_DEBUG(HLOG, "\n"); + + clk_disable_unprepare(clk_info->clk_module); + clk_disable_unprepare(clk_info->clk_pll); + //clk_disable_unprepare(clk_info->clk_pllx4); + clk_disable_unprepare(clk_info->clk_bus); + reset_control_assert(clk_info->clk_rst); + + return 0; +} + +static int sunxi_ahub_dam_resume(struct snd_soc_component *component) +{ + struct sunxi_ahub_clk_info *clk_info = &g_clk_info; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (reset_control_deassert(clk_info->clk_rst)) { + SND_LOG_ERR(HLOG, "clk rst deassert failed\n"); + return -EINVAL; + } + if (clk_prepare_enable(clk_info->clk_bus)) { + SND_LOG_ERR(HLOG, "clk bus enable failed\n"); + return -EBUSY; + } + if (clk_prepare_enable(clk_info->clk_pll)) { + SND_LOG_ERR(HLOG, "clk_pll enable failed\n"); + return -EBUSY; + } + //if (clk_prepare_enable(clk_info->clk_pllx4)) { + // SND_LOG_ERR(HLOG, "clk_pllx4 enable failed\n"); + // return -EBUSY; + //} + if (clk_prepare_enable(clk_info->clk_module)) { + SND_LOG_ERR(HLOG, "clk_module enable failed\n"); + return -EBUSY; + } + + return 0; +} + +struct str_conv { + char *str; + unsigned int reg; +}; +static struct str_conv ahub_mux_name[] = { + {"APBIF0 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(0)}, + {"APBIF1 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(1)}, + {"APBIF2 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(2)}, + {"I2S0 Src Select", SUNXI_AHUB_I2S_RXCONT(0)}, + {"I2S1 Src Select", SUNXI_AHUB_I2S_RXCONT(1)}, + {"I2S2 Src Select", SUNXI_AHUB_I2S_RXCONT(2)}, + {"I2S3 Src Select", SUNXI_AHUB_I2S_RXCONT(3)}, + {"DAM0C0 Src Select", SUNXI_AHUB_DAM_RX0_SRC(0)}, + {"DAM0C1 Src Select", SUNXI_AHUB_DAM_RX1_SRC(0)}, + {"DAM0C2 Src Select", SUNXI_AHUB_DAM_RX2_SRC(0)}, + {"DAM1C0 Src Select", SUNXI_AHUB_DAM_RX0_SRC(1)}, + {"DAM1C1 Src Select", SUNXI_AHUB_DAM_RX1_SRC(1)}, + {"DAM1C2 Src Select", SUNXI_AHUB_DAM_RX2_SRC(1)}, +}; +static const char *ahub_mux_text[] = { + "NONE", + "APBIF_TXDIF0", + "APBIF_TXDIF1", + "APBIF_TXDIF2", + "I2S0_TXDIF", + "I2S1_TXDIF", + "I2S2_TXDIF", + "I2S3_TXDIF", + "DAM0_TXDIF", + "DAM1_TXDIF", +}; +static const unsigned int ahub_mux_values[] = { + 0, + 1 << I2S_RX_APBIF_TXDIF0, + 1 << I2S_RX_APBIF_TXDIF1, + 1 << I2S_RX_APBIF_TXDIF2, + 1 << I2S_RX_I2S0_TXDIF, + 1 << I2S_RX_I2S1_TXDIF, + 1 << I2S_RX_I2S2_TXDIF, + 1 << I2S_RX_I2S3_TXDIF, + 1 << I2S_RX_DAM0_TXDIF, + 1 << I2S_RX_DAM1_TXDIF, +}; +static SOC_ENUM_SINGLE_EXT_DECL(ahub_mux, ahub_mux_text); + +static int sunxi_ahub_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + unsigned int reg_val; + unsigned int src_reg; + struct regmap *regmap = g_mem_info.regmap; + + for (i = 0; i < ARRAY_SIZE(ahub_mux_name); i++) { + if (!strncmp(ahub_mux_name[i].str, kcontrol->id.name, + strlen(ahub_mux_name[i].str))) { + src_reg = ahub_mux_name[i].reg; + regmap_read(regmap, src_reg, ®_val); + reg_val &= 0xffffc000; + break; + } + } + + for (i = 1; i < ARRAY_SIZE(ahub_mux_values); i++) { + if (reg_val & ahub_mux_values[i]) { + ucontrol->value.integer.value[0] = i; + return 0; + } + } + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int sunxi_ahub_mux_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + unsigned int src_reg, src_regbit; + struct regmap *regmap = g_mem_info.regmap; + + if (ucontrol->value.integer.value[0] > ARRAY_SIZE(ahub_mux_name)) + return -EINVAL; + + src_regbit = ahub_mux_values[ucontrol->value.integer.value[0]]; + for (i = 0; i < ARRAY_SIZE(ahub_mux_name); i++) { + if (!strncmp(ahub_mux_name[i].str, kcontrol->id.name, + strlen(ahub_mux_name[i].str))) { + src_reg = ahub_mux_name[i].reg; + regmap_update_bits(regmap, src_reg, 0xffffc000, src_regbit); + break; + } + } + + return 0; +} + +static const struct snd_kcontrol_new sunxi_ahub_dam_controls[] = { + SOC_ENUM_EXT("APBIF0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("APBIF1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("APBIF2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("I2S0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("I2S1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("I2S2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("I2S3 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("DAM0C0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("DAM0C1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("DAM0C2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("DAM1C0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("DAM1C1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), + SOC_ENUM_EXT("DAM1C2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set), +}; + +static struct snd_soc_component_driver sunxi_ahub_dam_dev = { + .name = DRV_NAME, + .probe = sunxi_ahub_dam_probe, + .suspend = sunxi_ahub_dam_suspend, + .resume = sunxi_ahub_dam_resume, + .controls = sunxi_ahub_dam_controls, + .num_controls = ARRAY_SIZE(sunxi_ahub_dam_controls), +}; + +/******************************************************************************* + * for kernel source + ******************************************************************************/ +int snd_soc_sunxi_ahub_mem_get(struct sunxi_ahub_mem_info *mem_info) +{ + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(g_mem_info.regmap)) { + SND_LOG_ERR(HLOG, "regmap is invalid\n"); + return -EINVAL; + } + if (IS_ERR_OR_NULL(g_mem_info.res)) { + SND_LOG_ERR(HLOG, "res is invalid\n"); + return -EINVAL; + } + + mem_info->regmap = g_mem_info.regmap; + mem_info->res = g_mem_info.res; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_sunxi_ahub_mem_get); + +int snd_soc_sunxi_ahub_clk_get(struct sunxi_ahub_clk_info *clk_info) +{ + SND_LOG_DEBUG(HLOG, "\n"); + + if (IS_ERR_OR_NULL(g_clk_info.clk_pll)) { + SND_LOG_ERR(HLOG, "clk_pll is invalid\n"); + return -EINVAL; + } + //if (IS_ERR_OR_NULL(g_clk_info.clk_pllx4)) { + // SND_LOG_ERR(HLOG, "clk_pllx4 is invalid\n"); + // return -EINVAL; + //} + if (IS_ERR_OR_NULL(g_clk_info.clk_module)) { + SND_LOG_ERR(HLOG, "clk_module is invalid\n"); + return -EINVAL; + } + + clk_info->clk_pll = g_clk_info.clk_pll; + //clk_info->clk_pllx4 = g_clk_info.clk_pllx4; + clk_info->clk_module = g_clk_info.clk_module; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_sunxi_ahub_clk_get); + +static int snd_soc_sunxi_ahub_mem_init(struct platform_device *pdev, + struct device_node *np, + struct sunxi_ahub_mem_info *mem_info) +{ + int ret = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + ret = of_address_to_resource(np, 0, mem_info->res); + if (ret) { + SND_LOG_ERR(HLOG, "parse device node resource failed\n"); + ret = -EINVAL; + goto err_of_addr_to_resource; + } + + mem_info->memregion = devm_request_mem_region(&pdev->dev, + mem_info->res->start, + resource_size(mem_info->res), + DRV_NAME); + if (IS_ERR_OR_NULL(mem_info->memregion)) { + SND_LOG_ERR(HLOG, "memory region already claimed\n"); + ret = -EBUSY; + goto err_devm_request_region; + } + + mem_info->membase = devm_ioremap(&pdev->dev, + mem_info->memregion->start, + resource_size(mem_info->memregion)); + if (IS_ERR_OR_NULL(mem_info->membase)) { + SND_LOG_ERR(HLOG, "ioremap failed\n"); + ret = -EBUSY; + goto err_devm_ioremap; + } + + mem_info->regmap = devm_regmap_init_mmio(&pdev->dev, + mem_info->membase, + &g_regmap_config); + if (IS_ERR_OR_NULL(mem_info->regmap)) { + SND_LOG_ERR(HLOG, "regmap init failed\n"); + ret = -EINVAL; + goto err_devm_regmap_init; + } + + return 0; + +err_devm_regmap_init: + devm_iounmap(&pdev->dev, mem_info->membase); +err_devm_ioremap: + devm_release_mem_region(&pdev->dev, mem_info->memregion->start, + resource_size(mem_info->memregion)); +err_devm_request_region: +err_of_addr_to_resource: + return ret; +}; + +static int snd_soc_sunxi_ahub_clk_init(struct platform_device *pdev, + struct device_node *np, + struct sunxi_ahub_clk_info *clk_info) +{ + int ret = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + /* deassert rst clk */ + clk_info->clk_rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR_OR_NULL(clk_info->clk_rst)) { + SND_LOG_ERR(HLOG, "clk rst get failed\n"); + ret = -EBUSY; + goto err_rst_clk; + } + if (reset_control_deassert(clk_info->clk_rst)) { + SND_LOG_ERR(HLOG, "deassert reset clk failed\n"); + ret = -EBUSY; + goto err_rst_clk; + } + + /* enable ahub bus clk */ + clk_info->clk_bus = of_clk_get_by_name(np, "clk_bus_audio_hub"); + if (IS_ERR_OR_NULL(clk_info->clk_bus)) { + SND_LOG_ERR(HLOG, "clk bus get failed\n"); + ret = -EBUSY; + goto err_bus_clk; + } + if (clk_prepare_enable(clk_info->clk_bus)) { + SND_LOG_ERR(HLOG, "ahub clk bus enable failed\n"); + ret = -EBUSY; + goto err_bus_clk; + } + + /* get clk of ahub */ + clk_info->clk_module = of_clk_get_by_name(np, "clk_audio_hub"); + if (IS_ERR_OR_NULL(clk_info->clk_module)) { + SND_LOG_ERR(HLOG, "clk module get failed\n"); + ret = -EBUSY; + goto err_module_clk; + } + clk_info->clk_pll = of_clk_get_by_name(np, "clk_pll_audio"); + if (IS_ERR_OR_NULL(clk_info->clk_pll)) { + SND_LOG_ERR(HLOG, "clk pll get failed\n"); + ret = -EBUSY; + goto err_pll_clk; + } + //clk_info->clk_pllx4 = of_clk_get_by_name(np, "clk_pll_audio_4x"); + //if (IS_ERR_OR_NULL(clk_info->clk_pllx4)) { + // SND_LOG_ERR(HLOG, "clk pllx4 get failed\n"); + // ret = -EBUSY; + // goto err_pllx4_clk; + //} + + /* set ahub clk parent */ + //if (clk_set_parent(clk_info->clk_module, clk_info->clk_pllx4)) { + // SND_LOG_ERR(HLOG, "set parent of clk_module to pllx4 failed\n"); + // ret = -EINVAL; + // goto err_set_parent_clk; + //} + + /* enable clk of ahub */ + if (clk_prepare_enable(clk_info->clk_pll)) { + SND_LOG_ERR(HLOG, "clk_pll enable failed\n"); + ret = -EBUSY; + goto err_pll_clk_enable; + } + //if (clk_prepare_enable(clk_info->clk_pllx4)) { + // SND_LOG_ERR(HLOG, "clk_pllx4 enable failed\n"); + // ret = -EBUSY; + // goto err_pllx4_clk_enable; + //} + if (clk_prepare_enable(clk_info->clk_module)) { + SND_LOG_ERR(HLOG, "clk_module enable failed\n"); + ret = -EBUSY; + goto err_module_clk_enable; + } + + return 0; + +err_module_clk_enable: +// clk_disable_unprepare(clk_info->clk_pllx4); +//err_pllx4_clk_enable: + clk_disable_unprepare(clk_info->clk_pll); +err_pll_clk_enable: +//err_set_parent_clk: +// clk_put(clk_info->clk_pllx4); +//err_pllx4_clk: +// clk_put(clk_info->clk_pll); +err_pll_clk: + clk_put(clk_info->clk_module); +err_module_clk: + clk_disable_unprepare(clk_info->clk_bus); + clk_put(clk_info->clk_bus); +err_bus_clk: + reset_control_assert(clk_info->clk_rst); +err_rst_clk: + return ret; +} + +static int sunxi_ahub_dam_dev_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *np = pdev->dev.of_node; + + SND_LOG_DEBUG(HLOG, "\n"); + + ret = snd_soc_sunxi_ahub_mem_init(pdev, np, &g_mem_info); + if (ret) { + SND_LOG_ERR(HLOG, "remap init failed\n"); + ret = -EINVAL; + goto err_snd_soc_sunxi_ahub_mem_init; + } + + ret = snd_soc_sunxi_ahub_clk_init(pdev, np, &g_clk_info); + if (ret) { + SND_LOG_ERR(HLOG, "clk init failed\n"); + ret = -EINVAL; + goto err_snd_soc_sunxi_ahub_clk_init; + } + + ret = snd_soc_register_component(&pdev->dev, + &sunxi_ahub_dam_dev, + &sunxi_ahub_dam_dai, 1); + if (ret) { + SND_LOG_ERR(HLOG, "component register failed\n"); + ret = -ENOMEM; + goto err_snd_soc_register_component; + } + + SND_LOG_DEBUG(HLOG, "register ahub_dam platform success\n"); + + return 0; + +err_snd_soc_register_component: +err_snd_soc_sunxi_ahub_clk_init: +err_snd_soc_sunxi_ahub_mem_init: + of_node_put(np); + return ret; +} + +static int sunxi_ahub_dam_dev_remove(struct platform_device *pdev) +{ + struct sunxi_ahub_mem_info *mem_info = &g_mem_info; + struct sunxi_ahub_clk_info *clk_info = &g_clk_info; + + SND_LOG_DEBUG(HLOG, "\n"); + + snd_soc_unregister_component(&pdev->dev); + + devm_iounmap(&pdev->dev, mem_info->membase); + devm_release_mem_region(&pdev->dev, mem_info->memregion->start, + resource_size(mem_info->memregion)); + + clk_disable_unprepare(clk_info->clk_module); + clk_put(clk_info->clk_module); + clk_disable_unprepare(clk_info->clk_pll); + clk_put(clk_info->clk_pll); + //clk_disable_unprepare(clk_info->clk_pllx4); + //clk_put(clk_info->clk_pllx4); + clk_disable_unprepare(clk_info->clk_bus); + clk_put(clk_info->clk_bus); + reset_control_assert(clk_info->clk_rst); + + SND_LOG_DEBUG(HLOG, "unregister ahub_dam platform success\n"); + + return 0; +} + +static const struct of_device_id sunxi_ahub_dam_of_match[] = { + { .compatible = "allwinner," DRV_NAME, }, + {}, +}; +MODULE_DEVICE_TABLE(of, sunxi_ahub_dam_of_match); + +static struct platform_driver sunxi_ahub_dam_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = sunxi_ahub_dam_of_match, + }, + .probe = sunxi_ahub_dam_dev_probe, + .remove = sunxi_ahub_dam_dev_remove, +}; + +int __init sunxi_ahub_dam_dev_init(void) +{ + int ret; + + ret = platform_driver_register(&sunxi_ahub_dam_driver); + if (ret != 0) { + SND_LOG_ERR(HLOG, "platform driver register failed\n"); + return -EINVAL; + } + + return ret; +} + +void __exit sunxi_ahub_dam_dev_exit(void) +{ + platform_driver_unregister(&sunxi_ahub_dam_driver); +} + +late_initcall(sunxi_ahub_dam_dev_init); +module_exit(sunxi_ahub_dam_dev_exit); + +MODULE_AUTHOR("Dby@allwinnertech.com"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("sunxi soundcard platform of ahub_dam"); diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h new file mode 100644 index 000000000000..b7679bf545e9 --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h @@ -0,0 +1,291 @@ +/* sound\soc\sunxi\snd_sunxi_ahub_dam.h + * (C) Copyright 2021-2025 + * Allwinner Technology Co., Ltd. + * Dby + * + * some simple description for this code + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + */ +#ifndef __SND_SUNXI_AHUB_DAM_H +#define __SND_SUNXI_AHUB_DAM_H + +/* SUNXI Audio Hub registers list */ +#define SUNXI_AHUB_CTL 0x00 +#define SUNXI_AHUB_VER 0x04 +#define SUNXI_AHUB_RST 0x08 +#define SUNXI_AHUB_GAT 0x0c + +#define SUNXI_AHUB_APBIF_TX_CTL(n) (0x10 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_TX_IRQ_CTL(n) (0x14 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_TX_IRQ_STA(n) (0x18 + ((n) * 0x30)) + +#define SUNXI_AHUB_APBIF_TXFIFO_CTL(n) (0x20 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_TXFIFO_STA(n) (0x24 + ((n) * 0x30)) + +#define SUNXI_AHUB_APBIF_TXFIFO(n) (0x30 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_TXFIFO_CNT(n) (0x34 + ((n) * 0x30)) + +#define SUNXI_AHUB_APBIF_RX_CTL(n) (0x100 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_RX_IRQ_CTL(n) (0x104 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_RX_IRQ_STA(n) (0x108 + ((n) * 0x30)) + +#define SUNXI_AHUB_APBIF_RXFIFO_CTL(n) (0x110 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_RXFIFO_STA(n) (0x114 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_RXFIFO_CONT(n) (0x118 + ((n) * 0x30)) + +#define SUNXI_AHUB_APBIF_RXFIFO(n) (0x120 + ((n) * 0x30)) +#define SUNXI_AHUB_APBIF_RXFIFO_CNT(n) (0x124 + ((n) * 0x30)) + +#define SUNXI_AHUB_I2S_CTL(n) (0x200 + ((n) << 8)) +#define SUNXI_AHUB_I2S_FMT0(n) (0x204 + ((n) << 8)) +#define SUNXI_AHUB_I2S_FMT1(n) (0x208 + ((n) << 8)) +#define SUNXI_AHUB_I2S_CLKD(n) (0x20c + ((n) << 8)) + +#define SUNXI_AHUB_I2S_RXCONT(n) (0x220 + ((n) << 8)) +#define SUNXI_AHUB_I2S_CHCFG(n) (0x224 + ((n) << 8)) +#define SUNXI_AHUB_I2S_IRQ_CTL(n) (0x228 + ((n) << 8)) +#define SUNXI_AHUB_I2S_IRQ_STA(n) (0x22C + ((n) << 8)) +#define SUNXI_AHUB_I2S_OUT_SLOT(n, m) (0x230 + ((n) << 8) + ((m) << 4)) +#define SUNXI_AHUB_I2S_OUT_CHMAP0(n, m) (0x234 + ((n) << 8) + ((m) << 4)) +#define SUNXI_AHUB_I2S_OUT_CHMAP1(n, m) (0x238 + ((n) << 8) + ((m) << 4)) + +#define SUNXI_AHUB_I2S_IN_SLOT(n) (0x270 + ((n) << 8)) +#define SUNXI_AHUB_I2S_IN_CHMAP0(n) (0x274 + ((n) << 8)) +#define SUNXI_AHUB_I2S_IN_CHMAP1(n) (0x278 + ((n) << 8)) +#define SUNXI_AHUB_I2S_IN_CHMAP2(n) (0x27C + ((n) << 8)) +#define SUNXI_AHUB_I2S_IN_CHMAP3(n) (0x280 + ((n) << 8)) + +#define SUNXI_AHUB_DAM_CTL(n) (0xA00 + ((n) << 7)) + +#define SUNXI_AHUB_DAM_RX0_SRC(n) (0xA10 + ((n) << 7)) +#define SUNXI_AHUB_DAM_RX1_SRC(n) (0xA14 + ((n) << 7)) +#define SUNXI_AHUB_DAM_RX2_SRC(n) (0xA18 + ((n) << 7)) + +#define SUNXI_AHUB_DAM_MIX_CTL0(n) (0xA30 + ((n) << 7)) +#define SUNXI_AHUB_DAM_MIX_CTL1(n) (0xA34 + ((n) << 7)) +#define SUNXI_AHUB_DAM_MIX_CTL2(n) (0xA38 + ((n) << 7)) +#define SUNXI_AHUB_DAM_MIX_CTL3(n) (0xA3C + ((n) << 7)) +#define SUNXI_AHUB_DAM_MIX_CTL4(n) (0xA40 + ((n) << 7)) +#define SUNXI_AHUB_DAM_MIX_CTL5(n) (0xA44 + ((n) << 7)) +#define SUNXI_AHUB_DAM_MIX_CTL6(n) (0xA48 + ((n) << 7)) +#define SUNXI_AHUB_DAM_MIX_CTL7(n) (0xA4C + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL0(n) (0xA50 + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL1(n) (0xA54 + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL2(n) (0xA58 + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL3(n) (0xA5C + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL4(n) (0xA60 + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL5(n) (0xA64 + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL6(n) (0xA68 + ((n) << 7)) +#define SUNXI_AHUB_DAM_GAIN_CTL7(n) (0xA6C + ((n) << 7)) + +#define SUNXI_AHUB_MAX_REG SUNXI_AHUB_DAM_GAIN_CTL7(1) + +/* SUNXI_AHUB_CTL */ +#define HDMI_SRC_SEL 0x04 + +/* SUNXI_AHUB_RST */ +#define APBIF_TXDIF0_RST 31 +#define APBIF_TXDIF1_RST 30 +#define APBIF_TXDIF2_RST 29 +#define APBIF_RXDIF0_RST 27 +#define APBIF_RXDIF1_RST 26 +#define APBIF_RXDIF2_RST 25 +#define I2S0_RST 23 +#define I2S1_RST 22 +#define I2S2_RST 21 +#define I2S3_RST 20 +#define DAM0_RST 15 +#define DAM1_RST 14 + +/* SUNXI_AHUB_GAT */ +#define APBIF_TXDIF0_GAT 31 +#define APBIF_TXDIF1_GAT 30 +#define APBIF_TXDIF2_GAT 29 +#define APBIF_RXDIF0_GAT 27 +#define APBIF_RXDIF1_GAT 26 +#define APBIF_RXDIF2_GAT 25 +#define I2S0_GAT 23 +#define I2S1_GAT 22 +#define I2S2_GAT 21 +#define I2S3_GAT 20 +#define DAM0_GAT 15 +#define DAM1_GAT 14 + +/* SUNXI_AHUB_APBIF_TX_CTL */ +#define APBIF_TX_WS 16 +#define APBIF_TX_CHAN_NUM 8 +#define APBIF_TX_START 4 + +/* SUNXI_AHUB_APBIF_TX_IRQ_CTL */ +#define APBIF_TX_DRQ 3 +#define APBIF_TX_OVEN 1 +#define APBIF_TX_EMEN 0 + +/* SUNXI_AHUB_APBIF_TX_IRQ_STA */ +#define APBIF_TX_OV_PEND 2 +#define APBIF_TX_EM_PEND 0 + +/* SUNXI_AHUB_APBIF_TXFIFO_CTL */ +#define APBIF_TX_FTX 12 +#define APBIF_TX_LEVEL 4 +#define APBIF_TX_TXIM 0 + +/* SUNXI_AHUB_APBIF_TXFIFO_STA */ +#define APBIF_TX_EMPTY 8 +#define APBIF_TX_EMCNT 0 + +/* SUNXI_AHUB_APBIF_RX_CTL */ +#define APBIF_RX_WS 16 +#define APBIF_RX_CHAN_NUM 8 +#define APBIF_RX_START 4 + +/* SUNXI_AHUB_APBIF_RX_IRQ_CTL */ +#define APBIF_RX_DRQ 3 +#define APBIF_RX_UVEN 2 +#define APBIF_RX_AVEN 0 + +/* SUNXI_AHUB_APBIF_RX_IRQ_STA */ +#define APBIF_RX_UV_PEND 2 +#define APBIF_RX_AV_PEND 0 + +/* SUNXI_AHUB_APBIF_RXFIFO_CTL */ +#define APBIF_RX_FRX 12 +#define APBIF_RX_LEVEL 4 +#define APBIF_RX_RXOM 0 + +/* SUNXI_AHUB_APBIF_RXFIFO_STA */ +#define APBIF_RX_AVAIL 8 +#define APBIF_RX_AVCNT 0 + +/* SUNXI_AHUB_APBIF_RXFIFO_CONT */ +#define APBIF_RX_APBIF_TXDIF0 31 +#define APBIF_RX_APBIF_TXDIF1 30 +#define APBIF_RX_APBIF_TXDIF2 29 +#define APBIF_RX_I2S0_TXDIF 27 +#define APBIF_RX_I2S1_TXDIF 26 +#define APBIF_RX_I2S2_TXDIF 25 +#define APBIF_RX_I2S3_TXDIF 23 +#define APBIF_RX_DAM0_TXDIF 19 +#define APBIF_RX_DAM1_TXDIF 15 + +/* SUNXI_AHUB_I2S_CTL */ +#define I2S_CTL_LOOP3 23 +#define I2S_CTL_LOOP2 22 +#define I2S_CTL_LOOP1 21 +#define I2S_CTL_LOOP0 20 +#define I2S_CTL_SDI3_EN 15 +#define I2S_CTL_SDI2_EN 14 +#define I2S_CTL_SDI1_EN 13 +#define I2S_CTL_SDI0_EN 12 +#define I2S_CTL_CLK_OUT 18 +#define I2S_CTL_SDO3_EN 11 +#define I2S_CTL_SDO2_EN 10 +#define I2S_CTL_SDO1_EN 9 +#define I2S_CTL_SDO0_EN 8 +#define I2S_CTL_OUT_MUTE 6 +#define I2S_CTL_MODE 4 +#define I2S_CTL_TXEN 2 +#define I2S_CTL_RXEN 1 +#define I2S_CTL_GEN 0 + +/* SUNXI_AHUB_I2S_FMT0 */ +#define I2S_FMT0_LRCK_WIDTH 30 +#define I2S_FMT0_LRCK_POLARITY 19 +#define I2S_FMT0_LRCK_PERIOD 8 +#define I2S_FMT0_BCLK_POLARITY 7 +#define I2S_FMT0_SR 4 +#define I2S_FMT0_EDGE 3 +#define I2S_FMT0_SW 0 + +/* SUNXI_AHUB_I2S_FMT1 */ +#define I2S_FMT1_RX_LSB 7 +#define I2S_FMT1_TX_LSB 6 +#define I2S_FMT1_EXT 4 +#define I2S_FMT1_RX_PDM 2 +#define I2S_FMT1_TX_PDM 0 + +/* SUNXI_AHUB_I2S_CLKD */ +#define I2S_CLKD_MCLK 8 +#define I2S_CLKD_BCLKDIV 4 +#define I2S_CLKD_MCLKDIV 0 + +/* SUNXI_AHUB_I2S_RXCONT */ +#define I2S_RX_APBIF_TXDIF0 31 +#define I2S_RX_APBIF_TXDIF1 30 +#define I2S_RX_APBIF_TXDIF2 29 +#define I2S_RX_I2S0_TXDIF 27 +#define I2S_RX_I2S1_TXDIF 26 +#define I2S_RX_I2S2_TXDIF 25 +#define I2S_RX_I2S3_TXDIF 23 +#define I2S_RX_DAM0_TXDIF 19 +#define I2S_RX_DAM1_TXDIF 15 + +/* SUNXI_AHUB_I2S_CHCFG */ +#define I2S_CHCFG_HIZ 9 +#define I2S_CHCFG_TX_STATE 8 +#define I2S_CHCFG_RX_CHANNUM 4 +#define I2S_CHCFG_TX_CHANNUM 0 + +/* SUNXI_AHUB_I2S_IRQ_CTL */ +#define I2S_IRQ_RXOV_EN 1 +#define I2S_IRQ_TXUV_EN 0 + +/* SUNXI_AHUB_I2S_IRQ_STA */ +#define I2S_IRQ_RXOV_PEND 1 +#define I2S_IRQ_TXUV_PEND 0 + +/* SUNXI_AHUB_I2S_OUT_SLOT */ +#define I2S_OUT_OFFSET 20 +#define I2S_OUT_SLOT_NUM 16 +#define I2S_OUT_SLOT_EN 0 + +/* SUNXI_AHUB_I2S_IN_SLOT */ +#define I2S_IN_OFFSET 20 +#define I2S_IN_SLOT_NUM 16 + +/* SUNXI_AHUB_DAM_CTL */ +#define DAM_CTL_RX2_NUM 24 +#define DAM_CTL_RX1_NUM 20 +#define DAM_CTL_RX0_NUM 16 +#define DAM_CTL_TX_NUM 8 +#define DAM_CTL_RX2EN 6 +#define DAM_CTL_RX1EN 5 +#define DAM_CTL_RX0EN 4 +#define DAM_CTL_TXEN 0 + +/* SUNXI_AHUB_DAM_RX##chan##_SRC */ +#define DAM_RX_APBIF_TXDIF0 31 +#define DAM_RX_APBIF_TXDIF1 30 +#define DAM_RX_APBIF_TXDIF2 29 +#define DAM_RX_I2S0_TXDIF 27 +#define DAM_RX_I2S1_TXDIF 26 +#define DAM_RX_I2S2_TXDIF 25 +#define DAM_RX_I2S3_TXDIF 23 +#define DAM_RX_DAM0_TXDIF 19 +#define DAM_RX_DAM1_TXDIF 15 + +struct sunxi_ahub_mem_info { + char *dev_name; + struct resource *res; + void __iomem *membase; + struct resource *memregion; + struct regmap *regmap; +}; + +struct sunxi_ahub_clk_info { + struct clk *clk_pll; + struct clk *clk_pllx4; + struct clk *clk_module; + struct clk *clk_bus; + struct reset_control *clk_rst; +}; + +extern int snd_soc_sunxi_ahub_mem_get(struct sunxi_ahub_mem_info *mem_info); +extern int snd_soc_sunxi_ahub_clk_get(struct sunxi_ahub_clk_info *clk_info); + +#endif /* __SND_SUNXI_AHUB_DAM_H */ \ No newline at end of file diff --git a/sound/soc/sunxi_v2/snd_sunxi_common.c b/sound/soc/sunxi_v2/snd_sunxi_common.c new file mode 100644 index 000000000000..410ab75aea5a --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_common.c @@ -0,0 +1,267 @@ +/* + * sound\soc\sunxi\snd_sunxi_common.c + * (C) Copyright 2021-2025 + * AllWinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snd_sunxi_log.h" +#include "snd_sunxi_common.h" + +#define HLOG "COMMON" + +/* for regmap */ +int snd_sunxi_mem_init(struct platform_device *pdev, + struct sunxi_mem_info *mem_info) +{ + int ret = 0; + struct device_node *np = pdev->dev.of_node; + + SND_LOG_DEBUG(HLOG, "\n"); + + ret = of_address_to_resource(np, 0, mem_info->res); + if (ret) { + SND_LOG_ERR(HLOG, "parse device node resource failed\n"); + ret = -EINVAL; + goto err_of_addr_to_resource; + } + + mem_info->memregion = devm_request_mem_region(&pdev->dev, + mem_info->res->start, + resource_size(mem_info->res), + mem_info->dev_name); + if (IS_ERR_OR_NULL(mem_info->memregion)) { + SND_LOG_ERR(HLOG, "memory region already claimed\n"); + ret = -EBUSY; + goto err_devm_request_region; + } + + mem_info->membase = devm_ioremap(&pdev->dev, + mem_info->memregion->start, + resource_size(mem_info->memregion)); + if (IS_ERR_OR_NULL(mem_info->membase)) { + SND_LOG_ERR(HLOG, "ioremap failed\n"); + ret = -EBUSY; + goto err_devm_ioremap; + } + + mem_info->regmap = devm_regmap_init_mmio(&pdev->dev, + mem_info->membase, + mem_info->regmap_config); + if (IS_ERR_OR_NULL(mem_info->regmap)) { + SND_LOG_ERR(HLOG, "regmap init failed\n"); + ret = -EINVAL; + goto err_devm_regmap_init; + } + + return 0; + +err_devm_regmap_init: + devm_iounmap(&pdev->dev, mem_info->membase); +err_devm_ioremap: + devm_release_mem_region(&pdev->dev, mem_info->memregion->start, + resource_size(mem_info->memregion)); +err_devm_request_region: +err_of_addr_to_resource: + return ret; +} + +void snd_sunxi_mem_exit(struct platform_device *pdev, + struct sunxi_mem_info *mem_info) +{ + SND_LOG_DEBUG(HLOG, "\n"); + + devm_iounmap(&pdev->dev, mem_info->membase); + devm_release_mem_region(&pdev->dev, mem_info->memregion->start, + resource_size(mem_info->memregion)); +} + +/* for reg labels */ +int snd_sunxi_save_reg(struct regmap *regmap, struct reg_label *reg_labels) +{ + int i = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + while (reg_labels[i].name != NULL) { + regmap_read(regmap, + reg_labels[i].address, &(reg_labels[i].value)); + i++; + } + + return i; +} + +int snd_sunxi_echo_reg(struct regmap *regmap, struct reg_label *reg_labels) +{ + int i = 0; + + SND_LOG_DEBUG(HLOG, "\n"); + + while (reg_labels[i].name != NULL) { + regmap_write(regmap, + reg_labels[i].address, reg_labels[i].value); + i++; + } + + return i; +} + +/* for pa config */ +struct pa_config *snd_sunxi_pa_pin_init(struct platform_device *pdev, + u32 *pa_pin_max) +{ + int ret, i; + u32 pin_max; + u32 gpio_tmp; + u32 temp_val; + char str[20] = {0}; + struct pa_config *pa_cfg; + struct device_node *np = pdev->dev.of_node; + + SND_LOG_DEBUG(HLOG, "\n"); + + *pa_pin_max = 0; + ret = of_property_read_u32(np, "pa_pin_max", &temp_val); + if (ret < 0) { + SND_LOG_WARN(HLOG, "pa_pin_max get failed, default 0\n"); + return NULL; + } else { + pin_max = temp_val; + } + + pa_cfg = kzalloc(sizeof(struct pa_config) * pin_max, GFP_KERNEL); + if (!pa_cfg) { + SND_LOG_ERR(HLOG, "can't pa_config memory\n"); + return NULL; + } + + for (i = 0; i < pin_max; i++) { + sprintf(str, "pa_pin_%d", i); + ret = of_get_named_gpio(np, str, 0); + if (ret < 0) { + SND_LOG_ERR(HLOG, "pa_pin_%u get failed\n", i); + pa_cfg[i].used = 0; + continue; + } + gpio_tmp = ret; + if (!gpio_is_valid(gpio_tmp)) { + SND_LOG_ERR(HLOG, "pa_pin_%u (%u) is invalid\n", + i, gpio_tmp); + pa_cfg[i].used = 0; + continue; + } + ret = devm_gpio_request(&pdev->dev, gpio_tmp, str); + if (ret) { + SND_LOG_ERR(HLOG, "pa_pin_%u (%u) request failed\n", + i, gpio_tmp); + pa_cfg[i].used = 0; + continue; + } + pa_cfg[i].used = 1; + pa_cfg[i].pin = gpio_tmp; + + sprintf(str, "pa_pin_level_%d", i); + ret = of_property_read_u32(np, str, &temp_val); + if (ret < 0) { + SND_LOG_WARN(HLOG, "%s get failed, default low\n", str); + pa_cfg[i].level = 0; + } else { + if (temp_val > 0) + pa_cfg[i].level = 1; + } + sprintf(str, "pa_pin_msleep_%d", i); + ret = of_property_read_u32(np, str, &temp_val); + if (ret < 0) { + SND_LOG_WARN(HLOG, "%s get failed, default 0\n", str); + pa_cfg[i].msleep = 0; + } else { + pa_cfg[i].msleep = temp_val; + } + } + + *pa_pin_max = pin_max; + snd_sunxi_pa_pin_disable(pa_cfg, pin_max); + + return pa_cfg; +} + +void snd_sunxi_pa_pin_exit(struct platform_device *pdev, + struct pa_config *pa_cfg, u32 pa_pin_max) +{ + int i; + + SND_LOG_DEBUG(HLOG, "\n"); + + snd_sunxi_pa_pin_disable(pa_cfg, pa_pin_max); + + for (i = 0; i < pa_pin_max; i++) { + if (!pa_cfg[i].used) + continue; + + gpio_free(pa_cfg[i].pin); + } + + if (pa_cfg) + kfree(pa_cfg); +} + +int snd_sunxi_pa_pin_enable(struct pa_config *pa_cfg, u32 pa_pin_max) +{ + int i; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (pa_pin_max < 1) { + SND_LOG_DEBUG(HLOG, "no pa pin config\n"); + return 0; + } + + for (i = 0; i < pa_pin_max; i++) { + if (!pa_cfg[i].used) + continue; + + gpio_direction_output(pa_cfg[i].pin, 1); + gpio_set_value(pa_cfg[i].pin, pa_cfg[i].level); + } + + return 0; +} + +void snd_sunxi_pa_pin_disable(struct pa_config *pa_cfg, u32 pa_pin_max) +{ + int i; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (pa_pin_max < 1) { + SND_LOG_DEBUG(HLOG, "no pa pin config\n"); + return; + } + + for (i = 0; i < pa_pin_max; i++) { + if (!pa_cfg[i].used) + continue; + + gpio_direction_output(pa_cfg[i].pin, 1); + gpio_set_value(pa_cfg[i].pin, !pa_cfg[i].level); + } +} diff --git a/sound/soc/sunxi_v2/snd_sunxi_common.h b/sound/soc/sunxi_v2/snd_sunxi_common.h new file mode 100644 index 000000000000..7b88d20c25e0 --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_common.h @@ -0,0 +1,67 @@ +/* sound\soc\sunxi\snd_sunxi_common.h + * (C) Copyright 2021-2025 + * Allwinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef __SND_SUNXI_COMMON_H +#define __SND_SUNXI_COMMON_H + +/* for regmap */ +struct sunxi_mem_info { + char *dev_name; + struct resource *res; + struct regmap_config *regmap_config; + + void __iomem *membase; + struct resource *memregion; + struct regmap *regmap; +}; + +int snd_sunxi_mem_init(struct platform_device *pdev, + struct sunxi_mem_info *mem_info); +void snd_sunxi_mem_exit(struct platform_device *pdev, + struct sunxi_mem_info *mem_info); + +/* for reg debug */ +#define REG_LABEL(constant) {#constant, constant, 0} +#define REG_LABEL_END {NULL, 0, 0} + +struct reg_label { + const char *name; + const unsigned int address; + unsigned int value; +}; + +/* EX: + * static struct reg_label reg_labels[] = { + * REG_LABEL(SUNXI_REG_0), + * REG_LABEL(SUNXI_REG_1), + * REG_LABEL(SUNXI_REG_n), + * REG_LABEL_END, + * }; + */ +int snd_sunxi_save_reg(struct regmap *regmap, struct reg_label *reg_labels); +int snd_sunxi_echo_reg(struct regmap *regmap, struct reg_label *reg_labels); + +/* for pa config */ +struct pa_config { + u32 pin; + u32 msleep; + bool used; + bool level; +}; + +struct pa_config *snd_sunxi_pa_pin_init(struct platform_device *pdev, + u32 *pa_pin_max); +void snd_sunxi_pa_pin_exit(struct platform_device *pdev, + struct pa_config *pa_cfg, u32 pa_pin_max); +int snd_sunxi_pa_pin_enable(struct pa_config *pa_cfg, u32 pa_pin_max); +void snd_sunxi_pa_pin_disable(struct pa_config *pa_cfg, u32 pa_pin_max); + +#endif /* __SND_SUNXI_COMMON_H */ \ No newline at end of file diff --git a/sound/soc/sunxi_v2/snd_sunxi_log.h b/sound/soc/sunxi_v2/snd_sunxi_log.h new file mode 100644 index 000000000000..89ad9fe71936 --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_log.h @@ -0,0 +1,29 @@ +/* + * sound\soc\sunxi\snd_sunxi_log.h + * (C) Copyright 2021-2025 + * allwinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef __SND_SUNXI_LOG_H +#define __SND_SUNXI_LOG_H +#include + +#define SND_LOG_ERR(head, fmt, arg...) \ + pr_err("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg) + +#define SND_LOG_WARN(head, fmt, arg...) \ + pr_warn("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg) + +#define SND_LOG_INFO(head, fmt, arg...) \ + pr_info("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg) + +#define SND_LOG_DEBUG(head, fmt, arg...) \ + pr_debug("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg) + +#endif /* __SND_SUNXI_LOG_H */ diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach.c b/sound/soc/sunxi_v2/snd_sunxi_mach.c new file mode 100644 index 000000000000..27449ad6b843 --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_mach.c @@ -0,0 +1,479 @@ +/* + * sound\soc\sunxi\snd_sunxi_mach.c + * (C) Copyright 2021-2025 + * AllWinner Technology Co., Ltd. + * Dby + * + * based on ${LINUX}/sound/soc/generic/simple-card.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include + +#include "snd_sunxi_log.h" +#include "snd_sunxi_mach.h" + +#define HLOG "MACH" +#define DAI "sound-dai" +#define CELL "#sound-dai-cells" +#define PREFIX "soundcard-mach," + +#define DRV_NAME "sunxi-snd-mach" + +static void asoc_simple_shutdown(struct snd_pcm_substream *substream) +{ +} + +static int asoc_simple_startup(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int asoc_simple_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, rtd->num); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num); + struct asoc_simple_dai *dais = priv->dais; + unsigned int mclk; + unsigned int cpu_pll_clk, codec_pll_clk; + unsigned int cpu_bclk_ratio, codec_bclk_ratio; + unsigned int freq_point; + int cpu_clk_div, codec_clk_div; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + case 192000: + freq_point = 24576000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + freq_point = 22579200; + break; + default: + SND_LOG_ERR(HLOG, "Invalid rate %d\n", params_rate(params)); + return -EINVAL; + } + + /* for cpudai pll clk */ + cpu_pll_clk = freq_point * dai_props->cpu_pll_fs; + codec_pll_clk = freq_point * dai_props->codec_pll_fs; + cpu_clk_div = cpu_pll_clk / params_rate(params); + codec_clk_div = codec_pll_clk / params_rate(params); + SND_LOG_DEBUG(HLOG, "freq point : %u\n", freq_point); + SND_LOG_DEBUG(HLOG, "cpu pllclk : %u\n", cpu_pll_clk); + SND_LOG_DEBUG(HLOG, "codec pllclk : %u\n", codec_pll_clk); + SND_LOG_DEBUG(HLOG, "cpu clk_div : %u\n", cpu_clk_div); + SND_LOG_DEBUG(HLOG, "codec clk_div: %u\n", codec_clk_div); + + if (cpu_dai->driver->ops->set_pll) { + ret = snd_soc_dai_set_pll(cpu_dai, substream->stream, 0, + cpu_pll_clk, cpu_pll_clk); + if (ret) { + SND_LOG_ERR(HLOG, "cpu_dai set pllclk failed\n"); + return ret; + } + } + if (codec_dai->driver->ops->set_pll) { + ret = snd_soc_dai_set_pll(codec_dai, substream->stream, 0, + codec_pll_clk, codec_pll_clk); + if (ret) { + SND_LOG_ERR(HLOG, "codec_dai set pllclk failed\n"); + return ret; + } + } + + if (cpu_dai->driver->ops->set_clkdiv) { + ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, cpu_clk_div); + if (ret) { + SND_LOG_ERR(HLOG, "cpu_dai set clk_div failed\n"); + return ret; + } + } + if (codec_dai->driver->ops->set_clkdiv) { + ret = snd_soc_dai_set_clkdiv(codec_dai, 0, codec_clk_div); + if (ret) { + SND_LOG_ERR(HLOG, "cadec_dai set clk_div failed.\n"); + return ret; + } + } + + /* use for tdm only */ + if (!(dais->slots && dais->slot_width)) + return 0; + + /* for cpudai & codecdai mclk */ + if (dai_props->mclk_fp) + mclk = (freq_point >> 1) * dai_props->mclk_fs; + else + mclk = params_rate(params) * dai_props->mclk_fs; + cpu_bclk_ratio = cpu_pll_clk / (params_rate(params) * dais->slot_width * dais->slots); + codec_bclk_ratio = codec_pll_clk / (params_rate(params) * dais->slot_width * dais->slots); + SND_LOG_DEBUG(HLOG, "mclk : %u\n", mclk); + SND_LOG_DEBUG(HLOG, "cpu_bclk_ratio : %u\n", cpu_bclk_ratio); + SND_LOG_DEBUG(HLOG, "codec_bclk_ratio: %u\n", codec_bclk_ratio); + + if (cpu_dai->driver->ops->set_sysclk) { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT); + if (ret) { + SND_LOG_ERR(HLOG, "cpu_dai set sysclk(mclk) failed\n"); + return ret; + } + } + if (codec_dai->driver->ops->set_sysclk) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN); + if (ret) { + SND_LOG_ERR(HLOG, "cadec_dai set sysclk(mclk) failed\n"); + return ret; + } + } + + if (cpu_dai->driver->ops->set_bclk_ratio) { + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, cpu_bclk_ratio); + if (ret) { + SND_LOG_ERR(HLOG, "cpu_dai set bclk failed\n"); + return ret; + } + } + if (codec_dai->driver->ops->set_bclk_ratio) { + ret = snd_soc_dai_set_bclk_ratio(codec_dai, codec_bclk_ratio); + if (ret) { + SND_LOG_ERR(HLOG, "codec_dai set bclk failed\n"); + return ret; + } + } + + if (cpu_dai->driver->ops->set_fmt) { + ret = snd_soc_dai_set_fmt(cpu_dai, dai_link->dai_fmt); + if (ret) { + SND_LOG_ERR(HLOG, "cpu dai set fmt failed\n"); + return ret; + } + } + if (codec_dai->driver->ops->set_fmt) { + ret = snd_soc_dai_set_fmt(codec_dai, dai_link->dai_fmt); + if (ret) { + SND_LOG_ERR(HLOG, "codec dai set fmt failed\n"); + return ret; + } + } + + if (cpu_dai->driver->ops->set_tdm_slot) { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, dais->slots, dais->slot_width); + if (ret) { + SND_LOG_ERR(HLOG, "cpu dai set tdm slot failed\n"); + return ret; + } + } + if (codec_dai->driver->ops->set_tdm_slot) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, dais->slots, dais->slot_width); + if (ret) { + SND_LOG_ERR(HLOG, "codec dai set tdm slot failed\n"); + return ret; + } + } + + return 0; +} + +static struct snd_soc_ops simple_ops = { + .startup = asoc_simple_startup, + .shutdown = asoc_simple_shutdown, + .hw_params = asoc_simple_hw_params, +}; + +static int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + int i; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = &card->dapm; + + const struct snd_kcontrol_new *controls = card->controls; + + for (i = 0; i < card->num_controls; i++) + if (controls[i].info == snd_soc_dapm_info_pin_switch) + snd_soc_dapm_disable_pin(dapm, + (const char *)controls[i].private_value); + + if (card->num_controls) + snd_soc_dapm_sync(dapm); + + /* snd_soc_dai_set_sysclk(); */ + /* snd_soc_dai_set_tdm_slot(); */ + + return 0; +} + +static int simple_dai_link_of(struct device_node *node, + struct asoc_simple_priv *priv) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, 0); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, 0); + struct device_node *top_np = NULL; + struct device_node *cpu = NULL; + struct device_node *plat = NULL; + struct device_node *codec = NULL; + char prop[128]; + char *prefix = ""; + int ret, single_cpu; + + prefix = PREFIX; + top_np = node; + + snprintf(prop, sizeof(prop), "%scpu", prefix); + cpu = of_get_child_by_name(top_np, prop); + if (!cpu) { + ret = -EINVAL; + SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop); + goto dai_link_of_err; + } + snprintf(prop, sizeof(prop), "%splat", prefix); + plat = of_get_child_by_name(top_np, prop); + + snprintf(prop, sizeof(prop), "%scodec", prefix); + codec = of_get_child_by_name(top_np, prop); + if (!codec) { + ret = -EINVAL; + SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop); + goto dai_link_of_err; + } + + ret = asoc_simple_parse_daifmt(top_np, codec, prefix, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + /* sunxi: parse stream direction + * ex1) + * top_node { + * PREFIXplayback-only; + * } + * ex2) + * top_node { + * PREFIXcapture-only; + * } + */ + ret = asoc_simple_parse_daistream(top_np, prefix, dai_link); + if (ret < 0) + goto dai_link_of_err; + /* sunxi: parse slot-num & slot-width + * ex) + * top_node { + * PREFIXplayslot-num = ; + * PREFIXplayslot-width = ; + * } + */ + ret = asoc_simple_parse_tdm_slot(top_np, prefix, priv->dais); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_parse_cpu(cpu, dai_link, DAI, CELL, &single_cpu); + if (ret < 0) + goto dai_link_of_err; + ret = asoc_simple_parse_codec(codec, dai_link, DAI, CELL); + if (ret < 0) { + if (ret == -EPROBE_DEFER) + goto dai_link_of_err; + dai_link->codecs->name = "snd-soc-dummy"; + dai_link->codecs->dai_name = "snd-soc-dummy-dai"; + /* dai_link->codecs->name = "sunxi-dummy-codec"; */ + /* dai_link->codecs->dai_name = "sunxi-dummy-codec-dai"; */ + SND_LOG_DEBUG(HLOG, "use dummy codec for simple card.\n"); + } + ret = asoc_simple_parse_platform(plat, dai_link, DAI, CELL); + if (ret < 0) + goto dai_link_of_err; + + /* sunxi: parse pll-fs & mclk-fs + * ex) + * top_node { + * PREFIXcpu { + * PREFIXpll-fs = ; + * PREFIXmclk-fs = ; + * } + * } + */ + ret = asoc_simple_parse_tdm_clk(cpu, codec, prefix, dai_props); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpus->dai_name, + dai_link->codecs->dai_name); + if (ret < 0) + goto dai_link_of_err; + + dai_link->ops = &simple_ops; + dai_link->init = asoc_simple_dai_init; + + SND_LOG_DEBUG(HLOG, "name : %s\n", dai_link->stream_name); + SND_LOG_DEBUG(HLOG, "format : %x\n", dai_link->dai_fmt); + SND_LOG_DEBUG(HLOG, "cpu : %s\n", dai_link->cpus->name); + SND_LOG_DEBUG(HLOG, "codec : %s\n", dai_link->codecs->name); + + asoc_simple_canonicalize_cpu(dai_link, single_cpu); + asoc_simple_canonicalize_platform(dai_link); + +dai_link_of_err: + of_node_put(cpu); + of_node_put(plat); + of_node_put(codec); + + return ret; +} + +static int simple_parse_of(struct asoc_simple_priv *priv) +{ + int ret; + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_card *card = simple_priv_to_card(priv); + struct device_node *top_np = dev->of_node; + + SND_LOG_DEBUG(HLOG, "\n"); + + if (!top_np) + return -EINVAL; + + /* DAPM widgets */ + ret = asoc_simple_parse_widgets(card, PREFIX); + if (ret < 0) + return ret; + + /* DAPM routes */ + ret = asoc_simple_parse_routing(card, PREFIX); + if (ret < 0) + return ret; + + /* DAPM pin_switches */ + ret = asoc_simple_parse_pin_switches(card, PREFIX); + if (ret < 0) + return ret; + + /* For single DAI link & old style of DT node */ + ret = simple_dai_link_of(top_np, priv); + if (ret < 0) + return ret; + + ret = asoc_simple_parse_card_name(card, PREFIX); + return ret; +} + +static int simple_soc_probe(struct snd_soc_card *card) +{ + return 0; +} + +static int asoc_simple_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *top_np = dev->of_node; + struct asoc_simple_priv *priv; + struct snd_soc_card *card; + int ret; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card = simple_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->probe = simple_soc_probe; + + ret = asoc_simple_init_priv(priv); + if (ret < 0) + return ret; + + if (top_np && of_device_is_available(top_np)) { + ret = simple_parse_of(priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + SND_LOG_ERR(HLOG, "parse error %d\n", ret); + goto err; + } + } else { + SND_LOG_ERR(HLOG, "simple card dts available\n"); + } + + snd_soc_card_set_drvdata(card, priv); + + /* asoc_simple_debug_info(priv); */ + ret = devm_snd_soc_register_card(dev, card); + if (ret >= 0) + return ret; +err: + asoc_simple_clean_reference(card); + + return ret; +} + +static int asoc_simple_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + return asoc_simple_clean_reference(card); +} + +static const struct of_device_id snd_soc_sunxi_of_match[] = { + { .compatible = "allwinner," DRV_NAME, }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_soc_sunxi_of_match); + +static struct platform_driver sunxi_soundcard_machine_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = snd_soc_sunxi_of_match, + }, + .probe = asoc_simple_probe, + .remove = asoc_simple_remove, +}; + +int __init sunxi_soundcard_machine_dev_init(void) +{ + int ret; + + ret = platform_driver_register(&sunxi_soundcard_machine_driver); + if (ret != 0) { + SND_LOG_ERR(HLOG, "platform driver register failed\n"); + return -EINVAL; + } + + return ret; +} + +void __exit sunxi_soundcard_machine_dev_exit(void) +{ + platform_driver_unregister(&sunxi_soundcard_machine_driver); +} + +late_initcall(sunxi_soundcard_machine_dev_init); +module_exit(sunxi_soundcard_machine_dev_exit); + +MODULE_AUTHOR("Dby@allwinnertech.com"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("sunxi soundcard machine"); diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach.h b/sound/soc/sunxi_v2/snd_sunxi_mach.h new file mode 100644 index 000000000000..ab429c8841ab --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_mach.h @@ -0,0 +1,17 @@ +/* sound\soc\sunxi\snd_sunxi_mach.h + * (C) Copyright 2021-2025 + * Allwinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef __SND_SUNXI_MACH_H +#define __SND_SUNXI_MACH_H + +#include "snd_sunxi_mach_utils.h" + +#endif /* __SND_SUNXI_MACH_H */ diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach_utils.c b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.c new file mode 100644 index 000000000000..15f474e5cbeb --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.c @@ -0,0 +1,422 @@ +/* + * sound\soc\sunxi\snd_sunxi_mach_utils.c + * (C) Copyright 2021-2025 + * AllWinner Technology Co., Ltd. + * Dby + * + * based on ${LINUX}/sound/soc/generic/simple-card.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include +#include + +#include "snd_sunxi_log.h" +#include "snd_sunxi_mach_utils.h" + +#define HLOG "mach_utils" + +int asoc_simple_clean_reference(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + int i; + + for_each_card_prelinks(card, i, dai_link) { + of_node_put(dai_link->cpus->of_node); + of_node_put(dai_link->codecs->of_node); + } + return 0; +} + +int asoc_simple_init_priv(struct asoc_simple_priv *priv) +{ + struct snd_soc_card *card = simple_priv_to_card(priv); + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link; + struct simple_dai_props *dai_props; + struct asoc_simple_dai *dais; + struct snd_soc_codec_conf *cconf = NULL; + + dai_props = devm_kcalloc(dev, 1, sizeof(*dai_props), GFP_KERNEL); + dai_link = devm_kcalloc(dev, 1, sizeof(*dai_link), GFP_KERNEL); + dais = devm_kcalloc(dev, 1, sizeof(*dais), GFP_KERNEL); + if (!dai_props || !dai_link || !dais) + return -ENOMEM; + + /* + if (li->conf) { + cconf = devm_kcalloc(dev, li->conf, sizeof(*cconf), GFP_KERNEL); + if (!cconf) + return -ENOMEM; + } + */ + + /* + * Use snd_soc_dai_link_component instead of legacy style + * It is codec only. but cpu/platform will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + * + * "platform" might be removed + * see + * simple-card-utils.c :: asoc_simple_canonicalize_platform() + */ + dai_link->cpus = &dai_props->cpus; + dai_link->num_cpus = 1; + dai_link->codecs = &dai_props->codecs; + dai_link->num_codecs = 1; + dai_link->platforms = &dai_props->platforms; + dai_link->num_platforms = 1; + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + priv->dais = dais; + priv->codec_conf = cconf; + + card->dai_link = priv->dai_link; + card->num_links = 1; + card->codec_conf = cconf; + card->num_configs = 0; + + return 0; +} + +int asoc_simple_parse_widgets(struct snd_soc_card *card, char *prefix) +{ + struct device_node *node = card->dev->of_node; + char prop[128]; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); + + if (of_property_read_bool(node, prop)) + return snd_soc_of_parse_audio_simple_widgets(card, prop); + + /* no widgets is not error */ + return 0; +} + +int asoc_simple_parse_routing(struct snd_soc_card *card, char *prefix) +{ + struct device_node *node = card->dev->of_node; + char prop[128]; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); + + if (!of_property_read_bool(node, prop)) + return 0; + + return snd_soc_of_parse_audio_routing(card, prop); +} + +int asoc_simple_parse_pin_switches(struct snd_soc_card *card, char *prefix) +{ + const unsigned int nb_controls_max = 16; + const char **strings, *control_name; + struct snd_kcontrol_new *controls; + struct device *dev = card->dev; + unsigned int i, nb_controls; + char prop[128]; + int ret; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches"); + + if (!of_property_read_bool(dev->of_node, prop)) + return 0; + + strings = devm_kcalloc(dev, nb_controls_max, + sizeof(*strings), GFP_KERNEL); + if (!strings) + return -ENOMEM; + + ret = of_property_read_string_array(dev->of_node, prop, + strings, nb_controls_max); + if (ret < 0) + return ret; + + nb_controls = (unsigned int)ret; + + controls = devm_kcalloc(dev, nb_controls, + sizeof(*controls), GFP_KERNEL); + if (!controls) + return -ENOMEM; + + for (i = 0; i < nb_controls; i++) { + control_name = devm_kasprintf(dev, GFP_KERNEL, + "%s Switch", strings[i]); + if (!control_name) + return -ENOMEM; + + controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + controls[i].name = control_name; + controls[i].info = snd_soc_dapm_info_pin_switch; + controls[i].get = snd_soc_dapm_get_pin_switch; + controls[i].put = snd_soc_dapm_put_pin_switch; + controls[i].private_value = (unsigned long)strings[i]; + } + + card->controls = controls; + card->num_controls = nb_controls; + + return 0; +} + +int asoc_simple_parse_daifmt(struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt) +{ + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + unsigned int daifmt; + + daifmt = snd_soc_daifmt_parse_format(node, prefix); + + snd_soc_daifmt_parse_clock_provider_as_phandle(node, prefix, &bitclkmaster, &framemaster); + if (!bitclkmaster && !framemaster) { + /* + * No dai-link level and master setting was not found from + * sound node level, revert back to legacy DT parsing and + * take the settings from codec node. + */ + SND_LOG_DEBUG(HLOG, "Revert to legacy daifmt parsing\n"); + + daifmt |= snd_soc_daifmt_parse_clock_provider_as_flag(codec, NULL); + } else { + daifmt |= snd_soc_daifmt_clock_provider_from_bitmap( + ((codec == bitclkmaster) << 4) | (codec == framemaster)); + } + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + *retfmt = daifmt; + + return 0; +} + +int asoc_simple_parse_daistream(struct device_node *node, char *prefix, + struct snd_soc_dai_link *dai_link) +{ + char prop[128]; + + if (!prefix) + prefix = ""; + + /* check "[prefix]playback-only" */ + snprintf(prop, sizeof(prop), "%splayback-only", prefix); + if (of_property_read_bool(node, prop)) + dai_link->playback_only = 1; + + /* check "[prefix]capture-only" */ + snprintf(prop, sizeof(prop), "%scapture-only", prefix); + if (of_property_read_bool(node, prop)) + dai_link->capture_only = 1; + + return 0; +} + +int asoc_simple_parse_tdm_slot(struct device_node *node, char *prefix, + struct asoc_simple_dai *dais) +{ + int ret; + char prop[128]; + unsigned int val; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%sslot-num", prefix); + ret = of_property_read_u32(node, prop, &val); + if (!ret) + dais->slots = val; + + snprintf(prop, sizeof(prop), "%sslot-width", prefix); + ret = of_property_read_u32(node, prop, &val); + if (!ret) + dais->slot_width = val; + + return 0; +} + +int asoc_simple_parse_tdm_clk(struct device_node *cpu, + struct device_node *codec, + char *prefix, + struct simple_dai_props *dai_props) +{ + int ret; + char prop[128]; + unsigned int val; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%spll-fs", prefix); + ret = of_property_read_u32(cpu, prop, &val); + if (ret) + dai_props->cpu_pll_fs = 1; /* default sysclk 24.576 or 22.5792MHz * 1 */ + else + dai_props->cpu_pll_fs = val; + + ret = of_property_read_u32(codec, prop, &val); + if (ret) + dai_props->codec_pll_fs = 1; /* default sysclk 24.576 or 22.5792MHz * 1 */ + else + dai_props->codec_pll_fs = val; + + snprintf(prop, sizeof(prop), "%smclk-fp", prefix); + dai_props->mclk_fp = of_property_read_bool(cpu, prop); + + snprintf(prop, sizeof(prop), "%smclk-fs", prefix); + ret = of_property_read_u32(cpu, prop, &val); + if (ret) + dai_props->mclk_fs = 0; /* default mclk 0Hz(un output) */ + else + dai_props->mclk_fs = val; + + return 0; +} + +int asoc_simple_parse_card_name(struct snd_soc_card *card, + char *prefix) +{ + int ret; + + if (!prefix) + prefix = ""; + + /* Parse the card name from DT */ + ret = snd_soc_of_parse_card_name(card, "label"); + if (ret < 0 || !card->name) { + char prop[128]; + + snprintf(prop, sizeof(prop), "%sname", prefix); + ret = snd_soc_of_parse_card_name(card, prop); + if (ret < 0) + return ret; + } + + if (!card->name && card->dai_link) + card->name = card->dai_link->name; + + return 0; +} + +int asoc_simple_parse_dai(struct device_node *node, + struct snd_soc_dai_link_component *dlc, + const char *list_name, const char *cells_name, + int *is_single_link) +{ + struct of_phandle_args args; + int ret; + + if (!node) + return 0; + + /* + * Get node via "sound-dai = <&phandle port>" + * it will be used as xxx_of_node on soc_bind_dai_link() + */ + ret = of_parse_phandle_with_args(node, list_name, cells_name, 0, &args); + if (ret) + return ret; + + /* + * FIXME + * + * Here, dlc->dai_name is pointer to CPU/Codec DAI name. + * If user unbinded CPU or Codec driver, but not for Sound Card, + * dlc->dai_name is keeping unbinded CPU or Codec + * driver's pointer. + * + * If user re-bind CPU or Codec driver again, ALSA SoC will try + * to rebind Card via snd_soc_try_rebind_card(), but because of + * above reason, it might can't bind Sound Card. + * Because Sound Card is pointing to released dai_name pointer. + * + * To avoid this rebind Card issue, + * 1) It needs to alloc memory to keep dai_name eventhough + * CPU or Codec driver was unbinded, or + * 2) user need to rebind Sound Card everytime + * if he unbinded CPU or Codec. + */ + ret = snd_soc_of_get_dai_name(node, &dlc->dai_name, 0); + if (ret < 0) + return ret; + + dlc->of_node = args.np; + + if (is_single_link) + *is_single_link = !args.args_count; + + return 0; +} + +int asoc_simple_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...) +{ + va_list ap; + char *name = NULL; + int ret = -ENOMEM; + + va_start(ap, fmt); + name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap); + va_end(ap); + + if (name) { + ret = 0; + + dai_link->name = name; + dai_link->stream_name = name; + } + + return ret; +} + +void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link) +{ + /* Assumes platform == cpu */ + if (!dai_link->platforms->of_node) + dai_link->platforms->of_node = dai_link->cpus->of_node; + + /* + * DPCM BE can be no platform. + * Alloced memory will be waste, but not leak. + */ + if (!dai_link->platforms->of_node) + dai_link->num_platforms = 0; +} + +void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link, + int is_single_links) +{ + /* + * In soc_bind_dai_link() will check cpu name after + * of_node matching if dai_link has cpu_dai_name. + * but, it will never match if name was created by + * fmt_single_name() remove cpu_dai_name if cpu_args + * was 0. See: + * fmt_single_name() + * fmt_multiple_name() + */ + if (is_single_links) + dai_link->cpus->dai_name = NULL; +} + +MODULE_AUTHOR("Dby@allwinnertech.com"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("sunxi soundcard machine utils"); diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach_utils.h b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.h new file mode 100644 index 000000000000..a9cffa0d859b --- /dev/null +++ b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.h @@ -0,0 +1,116 @@ +/* sound\soc\sunxi\snd_sunxi_mach_utils.h + * (C) Copyright 2021-2025 + * Allwinner Technology Co., Ltd. + * Dby + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef __SND_SUNXI_MACH_UTILS_H +#define __SND_SUNXI_MACH_UTILS_H + +#define simple_priv_to_card(priv) (&(priv)->snd_card) +#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) + +#define asoc_simple_parse_cpu(node, dai_link, \ + list_name, cells_name, is_single_link) \ + asoc_simple_parse_dai(node, dai_link->cpus, \ + list_name, cells_name, is_single_link) + +#define asoc_simple_parse_codec(node, dai_link, \ + list_name, cells_name) \ + asoc_simple_parse_dai(node, dai_link->codecs, \ + list_name, cells_name, NULL) + +#define asoc_simple_parse_platform(node, dai_link, \ + list_name, cells_name) \ + asoc_simple_parse_dai(node, dai_link->platforms, \ + list_name, cells_name, NULL) + +struct asoc_simple_dai { + const char *name; + unsigned int sysclk; + int clk_direction; + int slots; + int slot_width; + unsigned int tx_slot_mask; + unsigned int rx_slot_mask; + struct clk *clk; +}; + +struct asoc_simple_data { + u32 convert_rate; + u32 convert_channels; +}; + +struct asoc_simple_jack { + struct snd_soc_jack jack; + struct snd_soc_jack_pin pin; + struct snd_soc_jack_gpio gpio; +}; + +struct asoc_simple_priv { + struct snd_soc_card snd_card; + struct simple_dai_props { + struct asoc_simple_dai *cpu_dai; + struct asoc_simple_dai *codec_dai; + struct snd_soc_dai_link_component cpus; /* single cpu */ + struct snd_soc_dai_link_component codecs; /* single codec */ + struct snd_soc_dai_link_component platforms; + struct asoc_simple_data adata; + struct snd_soc_codec_conf *codec_conf; + bool mclk_fp; + unsigned int mclk_fs; + unsigned int cpu_pll_fs; + unsigned int codec_pll_fs; + } *dai_props; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dais; + struct snd_soc_codec_conf *codec_conf; + struct gpio_desc *pa_gpio; +}; + +int asoc_simple_clean_reference(struct snd_soc_card *card); +int asoc_simple_init_priv(struct asoc_simple_priv *priv); + +int asoc_simple_parse_widgets(struct snd_soc_card *card, char *prefix); +int asoc_simple_parse_routing(struct snd_soc_card *card, char *prefix); +int asoc_simple_parse_pin_switches(struct snd_soc_card *card, char *prefix); + +int asoc_simple_parse_daistream(struct device_node *node, + char *prefix, + struct snd_soc_dai_link *dai_link); +int asoc_simple_parse_daifmt(struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt); +int asoc_simple_parse_tdm_slot(struct device_node *node, + char *prefix, + struct asoc_simple_dai *dais); +int asoc_simple_parse_tdm_clk(struct device_node *cpu, + struct device_node *codec, + char *prefix, + struct simple_dai_props *dai_props); + +int asoc_simple_parse_card_name(struct snd_soc_card *card, char *prefix); +int asoc_simple_parse_dai(struct device_node *node, + struct snd_soc_dai_link_component *dlc, + const char *list_name, + const char *cells_name, + int *is_single_link); + +int asoc_simple_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...); +void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link); +void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link, + int is_single_links); + +#endif /* __SND_SUNXI_MACH_UTILS_H */ -- 2.35.3