From: Xing Wang Date: Mon, 27 Mar 2017 11:53:59 +0000 (+0800) Subject: audio: add sound card X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=b029aa66bff84d763b09980fea80d25d46f52e99;p=GitHub%2FLineageOS%2FG12%2Fandroid_kernel_amlogic_linux-4.9.git audio: add sound card PD#138714: add sound card driver Change-Id: I8c5cea4c62507976ab28ad76f6a3e42a9472cea4 Signed-off-by: Xing Wang --- diff --git a/MAINTAINERS b/MAINTAINERS index b1d60d51aeac..7dfb5df5005e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13663,6 +13663,7 @@ F: drivers/amlogic/mmc/emmc_key.c F: drivers/amlogic/mmc/emmc_key.h F: include/linux/amlogic/key_manage.h +<<<<<<< HEAD AMLOGIC P400/P401 BSP M: Frank Chen F: arch/arm64/boot/dts/amlogic/gxl_p400_2g.dts @@ -13675,10 +13676,6 @@ F: drivers/staging/android/logger.h AMLOGIC AMLVIDEO2 DRIVER M: Guosong Zhou -F: arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts -F: arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts -F: arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts -F: arch/arm64/boot/dts/amlogic/gxm_skt.dts F: arch/arm64/configs/meson64_defconfig F: drivers/amlogic/media/Kconfig F: drivers/amlogic/media/Makefile @@ -13692,3 +13689,27 @@ F: include/linux/amlogic/media/v4l_util/* AMLOGIC M8b M: Jianxin Pan F: arch/arm/boot/dts/amlogic> + +ANLOGIC AUDIO +M: Xing Wang +F: arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts +F: arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts +F: arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts +F: arch/arm64/boot/dts/amlogic/gxm_skt.dts +F: arch/arm64/boot/dts/amlogic/mesongxl.dtsi +F: arch/arm64/boot/dts/amlogic/mesongxm.dtsi +F: arch/arm64/configs/meson64_defconfig +F: drivers/amlogic/clk/clk-mpll.c +F: drivers/amlogic/clk/clk_misc.c +F: drivers/amlogic/clk/clkc.h +F: drivers/amlogic/clk/gxl.c +F: drivers/amlogic/media/vout/hdmitx/hdmi_tx_20/hdmi_tx_main.c +F: drivers/amlogic/pinctrl/pinctrl_gxl.c +F: include/dt-bindings/clock/amlogic,gxl-clkc.h +F: include/linux/amlogic/media/sound/audin_regs.h +F: sound/soc/Kconfig +F: sound/soc/Makefile +F: sound/soc/amlogic/* +F: sound/soc/codecs/Kconfig +F: sound/soc/codecs/Makefile +F: sound/soc/codecs/amlogic/* \ No newline at end of file diff --git a/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts b/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts index 15da8a70d9e9..995efd2e6384 100644 --- a/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts +++ b/arch/arm64/boot/dts/amlogic/gxl_p212_1g.dts @@ -701,6 +701,160 @@ "clk_ge2d_gate"; }; + + /* AUDIO MESON DEVICES */ + i2s_dai: I2S { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-i2s-dai"; + clocks = + <&clkc CLKID_MPLL2>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_AIU_GLUE>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_I2S_OUT>, + <&clkc CLKID_AMCLK>, + <&clkc CLKID_AIFIFO2>, + <&clkc CLKID_MIXER>, + <&clkc CLKID_MIXER_IFACE>, + <&clkc CLKID_ADC>, + <&clkc CLKID_AIU_TOP>, + <&clkc CLKID_AOCLK_GATE>, + <&clkc CLKID_I2S_SPDIF>; + clock-names = + "mpll2", + "mclk", + "top_glue", + "aud_buf", + "i2s_out", + "amclk_measure", + "aififo2", + "aud_mixer", + "mixer_reg", + "adc", + "top_level", + "aoclk", + "aud_in"; + /*DMIC;*/ /* I2s Mic or Dmic, default for I2S mic */ + }; + dmic:snd_dmic { + #sound-dai-cells = <0>; + compatible = "aml, aml_snd_dmic"; + reg = <0x0 0xd0042000 0x0 0x2000>; + status = "okay"; + resets = < + &clkc CLKID_PDM_GATE + >; + reset-names = "pdm"; + pinctrl-names = "aml_dmic_pins"; + pinctrl-0 = <&aml_dmic_pins>; + clocks = <&clkc CLKID_PDM_COMP>, + <&clkc CLKID_AMCLK_COMP>; + clock-names = "pdm", "mclk"; + }; + spdif_dai: SPDIF { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-dai"; + clocks = + <&clkc CLKID_MPLL1>, + <&clkc CLKID_I958_COMP>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_I958_COMP_SPDIF>, + <&clkc CLKID_CLK81>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_IEC958_GATE>; + clock-names = + "mpll1", + "i958", + "mclk", + "spdif", + "clk_81", + "iec958", + "iec958_amclk"; + }; + pcm_dai: PCM { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-pcm-dai"; + pinctrl-names = "aml_audio_btpcm"; + pinctrl-0 = <&audio_pcm_pins>; + clocks = + <&clkc CLKID_MPLL0>, + <&clkc CLKID_PCM_MCLK_COMP>, + <&clkc CLKID_PCM_SCLK_GATE>; + clock-names = + "mpll0", + "pcm_mclk", + "pcm_sclk"; + pcm_mode = <1>; /* 0=slave mode, 1=master mode */ + }; + i2s_plat: i2s_platform { + compatible = "amlogic, aml-i2s"; + interrupts = <0 29 1>; + }; + pcm_plat: pcm_platform { + compatible = "amlogic, aml-pcm"; + }; + spdif_codec: spdif_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-codec"; + pinctrl-names = "aml_audio_spdif"; + pinctrl-0 = <&audio_spdif_pins>; + }; + pcm_codec: pcm_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, pcm2BT-codec"; + }; + /* endof AUDIO MESON DEVICES */ + + /* AUDIO board specific */ + dummy_codec:dummy{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_dummy_codec"; + status = "disable"; + }; + amlogic_codec:t9015{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_codec_T9015"; + reg = <0x0 0xc8832000 0x0 0x14>; + status = "okay"; + }; + aml_sound_meson { + compatible = "aml, meson-snd-card"; + status = "okay"; + aml-sound-card,format = "i2s"; + aml_sound_card,name = "AML-MESONAUDIO"; + aml,audio-routing = + "Ext Spk","LOUTL", + "Ext Spk","LOUTR"; + + mute_gpio-gpios = <&gpio GPIOH_5 0>; + mute_inv; + hp_disable; + hp_paraments = <800 300 0 5 1>; + pinctrl-names = "audio_i2s_pins"; + pinctrl-0 = <&audio_i2s_pins>; + cpu_list = <&cpudai0 &cpudai1 &cpudai2>; + codec_list = <&codec0 &codec1 &codec2>; + plat_list = <&i2s_plat &i2s_plat &pcm_plat>; + cpudai0: cpudai0 { + sound-dai = <&i2s_dai>; + }; + cpudai1: cpudai1 { + sound-dai = <&spdif_dai>; + }; + cpudai2: cpudai2 { + sound-dai = <&pcm_dai>; + }; + codec0: codec0 { + sound-dai = <&amlogic_codec>; + }; + codec1: codec1 { + sound-dai = <&spdif_codec>; + }; + codec2: codec2 { + sound-dai = <&pcm_codec>; + }; + }; + /* END OF AUDIO board specific */ rdma{ compatible = "amlogic, meson, rdma"; dev_name = "amlogic-rdma"; diff --git a/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts b/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts index b90d14a3694a..f416abdd42a3 100644 --- a/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts +++ b/arch/arm64/boot/dts/amlogic/gxl_p212_2g.dts @@ -698,6 +698,160 @@ "clk_ge2d_gate"; }; + + /* AUDIO MESON DEVICES */ + i2s_dai: I2S { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-i2s-dai"; + clocks = + <&clkc CLKID_MPLL2>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_AIU_GLUE>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_I2S_OUT>, + <&clkc CLKID_AMCLK>, + <&clkc CLKID_AIFIFO2>, + <&clkc CLKID_MIXER>, + <&clkc CLKID_MIXER_IFACE>, + <&clkc CLKID_ADC>, + <&clkc CLKID_AIU_TOP>, + <&clkc CLKID_AOCLK_GATE>, + <&clkc CLKID_I2S_SPDIF>; + clock-names = + "mpll2", + "mclk", + "top_glue", + "aud_buf", + "i2s_out", + "amclk_measure", + "aififo2", + "aud_mixer", + "mixer_reg", + "adc", + "top_level", + "aoclk", + "aud_in"; + /*DMIC;*/ /* I2s Mic or Dmic, default for I2S mic */ + }; + dmic:snd_dmic { + #sound-dai-cells = <0>; + compatible = "aml, aml_snd_dmic"; + reg = <0x0 0xd0042000 0x0 0x2000>; + status = "okay"; + resets = < + &clkc CLKID_PDM_GATE + >; + reset-names = "pdm"; + pinctrl-names = "aml_dmic_pins"; + pinctrl-0 = <&aml_dmic_pins>; + clocks = <&clkc CLKID_PDM_COMP>, + <&clkc CLKID_AMCLK_COMP>; + clock-names = "pdm", "mclk"; + }; + spdif_dai: SPDIF { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-dai"; + clocks = + <&clkc CLKID_MPLL1>, + <&clkc CLKID_I958_COMP>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_I958_COMP_SPDIF>, + <&clkc CLKID_CLK81>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_IEC958_GATE>; + clock-names = + "mpll1", + "i958", + "mclk", + "spdif", + "clk_81", + "iec958", + "iec958_amclk"; + }; + pcm_dai: PCM { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-pcm-dai"; + pinctrl-names = "aml_audio_btpcm"; + pinctrl-0 = <&audio_pcm_pins>; + clocks = + <&clkc CLKID_MPLL0>, + <&clkc CLKID_PCM_MCLK_COMP>, + <&clkc CLKID_PCM_SCLK_GATE>; + clock-names = + "mpll0", + "pcm_mclk", + "pcm_sclk"; + pcm_mode = <1>; /* 0=slave mode, 1=master mode */ + }; + i2s_plat: i2s_platform { + compatible = "amlogic, aml-i2s"; + interrupts = <0 29 1>; + }; + pcm_plat: pcm_platform { + compatible = "amlogic, aml-pcm"; + }; + spdif_codec: spdif_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-codec"; + pinctrl-names = "aml_audio_spdif"; + pinctrl-0 = <&audio_spdif_pins>; + }; + pcm_codec: pcm_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, pcm2BT-codec"; + }; + /* endof AUDIO MESON DEVICES */ + + /* AUDIO board specific */ + dummy_codec:dummy{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_dummy_codec"; + status = "disable"; + }; + amlogic_codec:t9015{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_codec_T9015"; + reg = <0x0 0xc8832000 0x0 0x14>; + status = "okay"; + }; + aml_sound_meson { + compatible = "aml, meson-snd-card"; + status = "okay"; + aml-sound-card,format = "i2s"; + aml_sound_card,name = "AML-MESONAUDIO"; + aml,audio-routing = + "Ext Spk","LOUTL", + "Ext Spk","LOUTR"; + + mute_gpio-gpios = <&gpio GPIOH_5 0>; + mute_inv; + hp_disable; + hp_paraments = <800 300 0 5 1>; + pinctrl-names = "audio_i2s_pins"; + pinctrl-0 = <&audio_i2s_pins>; + cpu_list = <&cpudai0 &cpudai1 &cpudai2>; + codec_list = <&codec0 &codec1 &codec2>; + plat_list = <&i2s_plat &i2s_plat &pcm_plat>; + cpudai0: cpudai0 { + sound-dai = <&i2s_dai>; + }; + cpudai1: cpudai1 { + sound-dai = <&spdif_dai>; + }; + cpudai2: cpudai2 { + sound-dai = <&pcm_dai>; + }; + codec0: codec0 { + sound-dai = <&amlogic_codec>; + }; + codec1: codec1 { + sound-dai = <&spdif_codec>; + }; + codec2: codec2 { + sound-dai = <&pcm_codec>; + }; + }; + /* END OF AUDIO board specific */ rdma{ compatible = "amlogic, meson, rdma"; dev_name = "amlogic-rdma"; diff --git a/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts b/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts index a4c90eec0064..ba8d3e097d9e 100644 --- a/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts +++ b/arch/arm64/boot/dts/amlogic/gxm_q200_2g.dts @@ -728,6 +728,161 @@ "clk_ge2d_gate"; }; + + /* AUDIO MESON DEVICES */ + i2s_dai: I2S { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-i2s-dai"; + clocks = + <&clkc CLKID_MPLL2>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_AIU_GLUE>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_I2S_OUT>, + <&clkc CLKID_AMCLK>, + <&clkc CLKID_AIFIFO2>, + <&clkc CLKID_MIXER>, + <&clkc CLKID_MIXER_IFACE>, + <&clkc CLKID_ADC>, + <&clkc CLKID_AIU_TOP>, + <&clkc CLKID_AOCLK_GATE>, + <&clkc CLKID_I2S_SPDIF>; + clock-names = + "mpll2", + "mclk", + "top_glue", + "aud_buf", + "i2s_out", + "amclk_measure", + "aififo2", + "aud_mixer", + "mixer_reg", + "adc", + "top_level", + "aoclk", + "aud_in"; + /*DMIC;*/ /* I2s Mic or Dmic, default for I2S mic */ + }; + dmic:snd_dmic { + #sound-dai-cells = <0>; + compatible = "aml, aml_snd_dmic"; + reg = <0x0 0xd0042000 0x0 0x2000>; + status = "okay"; + resets = < + &clkc CLKID_PDM_GATE + >; + reset-names = "pdm"; + pinctrl-names = "aml_dmic_pins"; + pinctrl-0 = <&aml_dmic_pins>; + clocks = <&clkc CLKID_PDM_COMP>, + <&clkc CLKID_AMCLK_COMP>; + clock-names = "pdm", "mclk"; + }; + spdif_dai: SPDIF { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-dai"; + clocks = + <&clkc CLKID_MPLL1>, + <&clkc CLKID_I958_COMP>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_I958_COMP_SPDIF>, + <&clkc CLKID_CLK81>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_IEC958_GATE>; + clock-names = + "mpll1", + "i958", + "mclk", + "spdif", + "clk_81", + "iec958", + "iec958_amclk"; + }; + pcm_dai: PCM { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-pcm-dai"; + pinctrl-names = "aml_audio_btpcm"; + pinctrl-0 = <&audio_pcm_pins>; + clocks = + <&clkc CLKID_MPLL0>, + <&clkc CLKID_PCM_MCLK_COMP>, + <&clkc CLKID_PCM_SCLK_GATE>; + clock-names = + "mpll0", + "pcm_mclk", + "pcm_sclk"; + pcm_mode = <1>; /* 0=slave mode, 1=master mode */ + }; + i2s_plat: i2s_platform { + compatible = "amlogic, aml-i2s"; + interrupts = <0 29 1>; + }; + pcm_plat: pcm_platform { + compatible = "amlogic, aml-pcm"; + }; + spdif_codec: spdif_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-codec"; + pinctrl-names = "aml_audio_spdif"; + pinctrl-0 = <&audio_spdif_pins>; + }; + pcm_codec: pcm_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, pcm2BT-codec"; + }; + /* endof AUDIO MESON DEVICES */ + + /* AUDIO board specific */ + dummy_codec:dummy{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_dummy_codec"; + status = "disable"; + }; + amlogic_codec:t9015{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_codec_T9015"; + reg = <0x0 0xc8832000 0x0 0x14>; + status = "okay"; + }; + aml_sound_meson { + compatible = "aml, meson-snd-card"; + status = "okay"; + aml-sound-card,format = "i2s"; + aml_sound_card,name = "AML-MESONAUDIO"; + aml,audio-routing = + "Ext Spk","LOUTL", + "Ext Spk","LOUTR"; + + mute_gpio-gpios = <&gpio GPIOH_5 0>; + mute_inv; + hp_disable; + hp_paraments = <800 300 0 5 1>; + pinctrl-names = "audio_i2s_pins"; + pinctrl-0 = <&audio_i2s_pins>; + cpu_list = <&cpudai0 &cpudai1 &cpudai2>; + codec_list = <&codec0 &codec1 &codec2>; + plat_list = <&i2s_plat &i2s_plat &pcm_plat>; + cpudai0: cpudai0 { + sound-dai = <&i2s_dai>; + }; + cpudai1: cpudai1 { + sound-dai = <&spdif_dai>; + }; + cpudai2: cpudai2 { + sound-dai = <&pcm_dai>; + }; + codec0: codec0 { + sound-dai = <&amlogic_codec>; + }; + codec1: codec1 { + sound-dai = <&spdif_codec>; + }; + codec2: codec2 { + sound-dai = <&pcm_codec>; + }; + }; + /* END OF AUDIO board specific */ + rdma{ compatible = "amlogic, meson, rdma"; dev_name = "amlogic-rdma"; diff --git a/arch/arm64/boot/dts/amlogic/gxm_skt.dts b/arch/arm64/boot/dts/amlogic/gxm_skt.dts index 31921f2726ef..d548eb4df191 100644 --- a/arch/arm64/boot/dts/amlogic/gxm_skt.dts +++ b/arch/arm64/boot/dts/amlogic/gxm_skt.dts @@ -631,6 +631,162 @@ "clk_ge2d", "clk_ge2d_gate"; }; + + + /* AUDIO MESON DEVICES */ + i2s_dai: I2S { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-i2s-dai"; + clocks = + <&clkc CLKID_MPLL2>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_AIU_GLUE>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_I2S_OUT>, + <&clkc CLKID_AMCLK>, + <&clkc CLKID_AIFIFO2>, + <&clkc CLKID_MIXER>, + <&clkc CLKID_MIXER_IFACE>, + <&clkc CLKID_ADC>, + <&clkc CLKID_AIU_TOP>, + <&clkc CLKID_AOCLK_GATE>, + <&clkc CLKID_I2S_SPDIF>; + clock-names = + "mpll2", + "mclk", + "top_glue", + "aud_buf", + "i2s_out", + "amclk_measure", + "aififo2", + "aud_mixer", + "mixer_reg", + "adc", + "top_level", + "aoclk", + "aud_in"; + /*DMIC;*/ /* I2s Mic or Dmic, default for I2S mic */ + }; + dmic:snd_dmic { + #sound-dai-cells = <0>; + compatible = "aml, aml_snd_dmic"; + reg = <0x0 0xd0042000 0x0 0x2000>; + status = "okay"; + resets = < + &clkc CLKID_PDM_GATE + >; + reset-names = "pdm"; + pinctrl-names = "aml_dmic_pins"; + pinctrl-0 = <&aml_dmic_pins>; + clocks = <&clkc CLKID_PDM_COMP>, + <&clkc CLKID_AMCLK_COMP>; + clock-names = "pdm", "mclk"; + }; + spdif_dai: SPDIF { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-dai"; + clocks = + <&clkc CLKID_MPLL1>, + <&clkc CLKID_I958_COMP>, + <&clkc CLKID_AMCLK_COMP>, + <&clkc CLKID_I958_COMP_SPDIF>, + <&clkc CLKID_CLK81>, + <&clkc CLKID_IEC958>, + <&clkc CLKID_IEC958_GATE>; + clock-names = + "mpll1", + "i958", + "mclk", + "spdif", + "clk_81", + "iec958", + "iec958_amclk"; + }; + pcm_dai: PCM { + #sound-dai-cells = <0>; + compatible = "amlogic, aml-pcm-dai"; + pinctrl-names = "aml_audio_btpcm"; + pinctrl-0 = <&audio_pcm_pins>; + clocks = + <&clkc CLKID_MPLL0>, + <&clkc CLKID_PCM_MCLK_COMP>, + <&clkc CLKID_PCM_SCLK_GATE>; + clock-names = + "mpll0", + "pcm_mclk", + "pcm_sclk"; + pcm_mode = <1>; /* 0=slave mode, 1=master mode */ + }; + i2s_plat: i2s_platform { + compatible = "amlogic, aml-i2s"; + interrupts = <0 29 1>; + }; + pcm_plat: pcm_platform { + compatible = "amlogic, aml-pcm"; + }; + spdif_codec: spdif_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml-spdif-codec"; + pinctrl-names = "aml_audio_spdif"; + pinctrl-0 = <&audio_spdif_pins>; + }; + pcm_codec: pcm_codec{ + #sound-dai-cells = <0>; + compatible = "amlogic, pcm2BT-codec"; + }; + /* endof AUDIO MESON DEVICES */ + + /* AUDIO board specific */ + dummy_codec:dummy{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_dummy_codec"; + status = "disable"; + }; + amlogic_codec:t9015{ + #sound-dai-cells = <0>; + compatible = "amlogic, aml_codec_T9015"; + reg = <0x0 0xc8832000 0x0 0x14>; + status = "okay"; + }; + aml_sound_meson { + compatible = "aml, meson-snd-card"; + status = "okay"; + aml-sound-card,format = "i2s"; + aml_sound_card,name = "AML-MESONAUDIO"; + aml,audio-routing = + "Ext Spk","LOUTL", + "Ext Spk","LOUTR"; + + mute_gpio-gpios = <&gpio GPIOH_5 0>; + mute_inv; + hp_disable; + hp_paraments = <800 300 0 5 1>; + pinctrl-names = "audio_i2s_pins"; + pinctrl-0 = <&audio_i2s_pins>; + cpu_list = <&cpudai0 &cpudai1 &cpudai2>; + codec_list = <&codec0 &codec1 &codec2>; + plat_list = <&i2s_plat &i2s_plat &pcm_plat>; + cpudai0: cpudai0 { + sound-dai = <&i2s_dai>; + }; + cpudai1: cpudai1 { + sound-dai = <&spdif_dai>; + }; + cpudai2: cpudai2 { + sound-dai = <&pcm_dai>; + }; + codec0: codec0 { + sound-dai = <&amlogic_codec>; + }; + codec1: codec1 { + sound-dai = <&spdif_codec>; + }; + codec2: codec2 { + sound-dai = <&pcm_codec>; + }; + }; + /* END OF AUDIO board specific */ + rdma{ compatible = "amlogic, meson, rdma"; dev_name = "amlogic-rdma"; diff --git a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi index c18a9d7cc70d..14b07c23fae9 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxl.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxl.dtsi @@ -825,7 +825,7 @@ }; }; - audio_pins:audio_pin { + audio_i2s_pins:audio_i2s { mux { groups = "i2s_am_clk", "i2s_ao_clk_out", @@ -835,14 +835,14 @@ }; }; - audio_spdif_pins:audio_pin1 { + audio_spdif_pins:audio_spdif { mux { groups = "spdif_out"; function = "spdif_out"; }; }; - audio_btpcm_pins:audio_btpcm_pins { + audio_pcm_pins:audio_pcm { mux { groups = "pcm_out_a", "pcm_in_a", @@ -851,6 +851,14 @@ function = "pcm_a"; }; }; + aml_dmic_pins:audio_dmic { + mux { + groups = "pdm_in", + "pdm_clk"; + function = "pdm"; + }; + }; + }; /* end of pinctrl_periphs */ }; /* end of periphs */ diff --git a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi index 41ece67c6fee..7a3e60d4fe95 100644 --- a/arch/arm64/boot/dts/amlogic/mesongxm.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesongxm.dtsi @@ -876,7 +876,7 @@ }; }; - audio_pins:audio_pin { + audio_i2s_pins:audio_i2s { mux { groups = "i2s_am_clk", "i2s_ao_clk_out", @@ -886,14 +886,14 @@ }; }; - audio_spdif_pins:audio_pin1 { + audio_spdif_pins:audio_spdif { mux { groups = "spdif_out"; function = "spdif_out"; }; }; - audio_btpcm_pins:audio_btpcm_pins { + audio_pcm_pins:audio_pcm { mux { groups = "pcm_out_a", "pcm_in_a", @@ -902,6 +902,14 @@ function = "pcm_a"; }; }; + aml_dmic_pins:audio_dmic { + mux { + groups = "pdm_in", + "pdm_clk"; + function = "pdm"; + }; + }; + }; /* end of pinctrl_periphs */ }; /* end of periphs */ diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig index 4333bc7be4ce..e2d3cd4e211a 100644 --- a/arch/arm64/configs/meson64_defconfig +++ b/arch/arm64/configs/meson64_defconfig @@ -295,6 +295,13 @@ CONFIG_FB=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_AMLOGIC_SND_SOC_CODECS=y +CONFIG_AMLOGIC_SND_CODEC_DUMMY_CODEC=y +CONFIG_AMLOGIC_SND_CODEC_PCM2BT=y +CONFIG_AMLOGIC_SND_CODEC_AMLT9015=y +CONFIG_AMLOGIC_SND_SOC=y +CONFIG_AMLOGIC_SND_SPLIT_MODE=y CONFIG_UHID=y CONFIG_USB_HIDDEV=y CONFIG_USB_XHCI_HCD=y diff --git a/drivers/amlogic/clk/clk-mpll.c b/drivers/amlogic/clk/clk-mpll.c index 46c441e4ff1f..744c1bf93907 100644 --- a/drivers/amlogic/clk/clk-mpll.c +++ b/drivers/amlogic/clk/clk-mpll.c @@ -29,6 +29,8 @@ /* #undef pr_debug */ /* #define pr_debug pr_info */ #define SDM_MAX 16384 +#define MAX_RATE 500000000 +#define MIN_RATE 5000000 #define to_meson_clk_mpll(_hw) container_of(_hw, struct meson_clk_mpll, hw) @@ -56,7 +58,14 @@ static unsigned long mpll_recalc_rate(struct clk_hw *hw, static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { - return rate; + unsigned long rate_val = rate; + + if (rate_val < MIN_RATE) + rate = MIN_RATE; + if (rate_val > MAX_RATE) + rate = MAX_RATE; + + return rate_val; } @@ -69,9 +78,9 @@ static int mpll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long reg, old_sdm, old_n2, sdm, n2; unsigned long flags = 0; - if ((rate > 500000000) || (rate < 250000000)) { + if ((rate > MAX_RATE) || (rate < MIN_RATE)) { pr_err("Err: can not set rate to %lu!\n", rate); - pr_err("Range[250000000 - 500000000]\n"); + pr_err("Range[5000000 - 500000000]\n"); return -1; } @@ -105,7 +114,7 @@ static int mpll_set_rate(struct clk_hw *hw, unsigned long rate, p = &mpll->n2; reg = PARM_SET(p->width, p->shift, reg, n2); reg = PARM_SET(1, mpll->sdm_en, reg, 1); - reg = PARM_SET(1, mpll->en_dss, reg, 1); + reg = PARM_SET(1, mpll->en_dds, reg, 1); if (!strcmp(clk_hw_get_name(hw), "mpll3")) /* MPLL_CNTL10 bit14 should be set together * with MPLL3_CNTL0 bit0 diff --git a/drivers/amlogic/clk/clk_misc.c b/drivers/amlogic/clk/clk_misc.c index c7ea3e03a55c..2fcd7e34ed49 100644 --- a/drivers/amlogic/clk/clk_misc.c +++ b/drivers/amlogic/clk/clk_misc.c @@ -181,6 +181,7 @@ static struct clk_hw *pdm_hws[] = { /* cts_clk_i958 */ const char *i958_parent_names[] = { "NULL", "mpll0", "mpll1", "mpll2"}; +const char *i958_ext_parent_names[] = {"amclk_composite", "i958_composite"}; static struct clk_mux i958_mux = { .reg = (void *)HHI_AUD_CLK_CNTL2, @@ -223,6 +224,20 @@ static struct clk_gate i958_gate = { }, }; +static struct clk_mux i958_comp_spdif = { + .reg = (void *)HHI_AUD_CLK_CNTL2, + .mask = 0x1, + .shift = 27, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "i958_comp_spdif", + .ops = &clk_mux_ops, + .parent_names = i958_ext_parent_names, + .num_parents = 2, + .flags = (CLK_GET_RATE_NOCACHE | CLK_IGNORE_UNUSED), + }, +}; + static struct clk_hw *i958_hws[] = { [CLKID_I958_MUX - CLKID_I958_MUX] = &i958_mux.hw, [CLKID_I958_DIV - CLKID_I958_MUX] = &i958_div.hw, @@ -330,6 +345,9 @@ void amlogic_init_misc(void) i958_div.reg = clk_base + (u64)(i958_div.reg); i958_gate.reg = clk_base + (u64)(i958_gate.reg); + /*clk_i958 spdif*/ + i958_comp_spdif.reg = clk_base + (u64)(i958_comp_spdif.reg); + /* cts_pclk_mclk */ pcm_mclk_mux.reg = clk_base + (u64)(pcm_mclk_mux.reg); pcm_mclk_div.reg = clk_base + (u64)(pcm_mclk_div.reg); @@ -410,6 +428,9 @@ void amlogic_init_misc(void) clks[CLKID_PCM_SCLK_GATE] = clk_register(NULL, &pcm_sclk_gate.hw); WARN_ON(IS_ERR(clks[CLKID_PCM_SCLK_GATE])); + clks[CLKID_I958_COMP_SPDIF] = clk_register(NULL, &i958_comp_spdif.hw); + WARN_ON(IS_ERR(clks[CLKID_I958_COMP_SPDIF])); + pr_info("%s: register meson misc clk\n", __func__); }; diff --git a/drivers/amlogic/clk/clkc.h b/drivers/amlogic/clk/clkc.h index c0b38c463ebe..53d7114558ed 100644 --- a/drivers/amlogic/clk/clkc.h +++ b/drivers/amlogic/clk/clkc.h @@ -101,7 +101,7 @@ struct meson_clk_mpll { struct parm n2; /* FIXME ssen gate control? */ u8 sdm_en; - u8 en_dss; + u8 en_dds; spinlock_t *lock; }; diff --git a/drivers/amlogic/clk/gxl.c b/drivers/amlogic/clk/gxl.c index c9f85b7110fd..c94f58ee7632 100644 --- a/drivers/amlogic/clk/gxl.c +++ b/drivers/amlogic/clk/gxl.c @@ -419,10 +419,12 @@ static struct meson_clk_mpll gxl_mpll0 = { .shift = 16, .width = 9, }, + .sdm_en = 15, + .en_dds = 14, .lock = &clk_lock, .hw.init = &(struct clk_init_data){ .name = "mpll0", - .ops = &meson_clk_mpll_ro_ops, + .ops = &meson_clk_mpll_ops, .parent_names = (const char *[]){ "fixed_pll" }, .num_parents = 1, }, @@ -439,10 +441,12 @@ static struct meson_clk_mpll gxl_mpll1 = { .shift = 16, .width = 9, }, + .sdm_en = 15, + .en_dds = 14, .lock = &clk_lock, .hw.init = &(struct clk_init_data){ .name = "mpll1", - .ops = &meson_clk_mpll_ro_ops, + .ops = &meson_clk_mpll_ops, .parent_names = (const char *[]){ "fixed_pll" }, .num_parents = 1, }, @@ -459,10 +463,12 @@ static struct meson_clk_mpll gxl_mpll2 = { .shift = 16, .width = 9, }, + .sdm_en = 15, + .en_dds = 14, .lock = &clk_lock, .hw.init = &(struct clk_init_data){ .name = "mpll2", - .ops = &meson_clk_mpll_ro_ops, + .ops = &meson_clk_mpll_ops, .parent_names = (const char *[]){ "fixed_pll" }, .num_parents = 1, }, @@ -480,7 +486,7 @@ static struct meson_clk_mpll gxl_mpll3 = { .width = 9, }, .sdm_en = 11, - .en_dss = 0, + .en_dds = 0, .lock = &clk_lock, .hw.init = &(struct clk_init_data){ .name = "mpll3", diff --git a/drivers/amlogic/media/vout/hdmitx/hdmi_tx_20/hdmi_tx_main.c b/drivers/amlogic/media/vout/hdmitx/hdmi_tx_20/hdmi_tx_main.c index 5aaa3a67342a..eb9c10ed461a 100644 --- a/drivers/amlogic/media/vout/hdmitx/hdmi_tx_20/hdmi_tx_main.c +++ b/drivers/amlogic/media/vout/hdmitx/hdmi_tx_20/hdmi_tx_main.c @@ -53,7 +53,9 @@ #include #include #include -/* #include */ +#ifdef CONFIG_AMLOGIC_SND_SOC +#include +#endif #include #include #include @@ -2684,7 +2686,7 @@ static int amhdmitx_probe(struct platform_device *pdev) ret = device_create_file(dev, &dev_attr_valid_mode); vout_register_server(&hdmitx_server); -#ifdef CONFIG_SND_SOC +#ifdef CONFIG_AMLOGIC_SND_SOC aout_register_client(&hdmitx_notifier_nb_a); #else r = r ? (long int)&hdmitx_notifier_nb_a : @@ -2803,7 +2805,7 @@ static int amhdmitx_remove(struct platform_device *pdev) hdmitx_device.hpd_event = 0xff; kthread_stop(hdmitx_device.task); vout_unregister_server(&hdmitx_server); -#ifdef CONFIG_SND_SOC +#ifdef CONFIG_AMLOGIC_SND_SOC aout_unregister_client(&hdmitx_notifier_nb_a); #endif diff --git a/drivers/amlogic/pinctrl/pinctrl_gxl.c b/drivers/amlogic/pinctrl/pinctrl_gxl.c index 2fc681244558..8284843762ac 100644 --- a/drivers/amlogic/pinctrl/pinctrl_gxl.c +++ b/drivers/amlogic/pinctrl/pinctrl_gxl.c @@ -240,6 +240,10 @@ static const unsigned int uart_rts_b_pins[] = { PIN(GPIODV_27, EE_OFF) }; static const unsigned int i2c_sda_a_pins[] = { PIN(GPIODV_24, EE_OFF) }; static const unsigned int i2c_scl_a_pins[] = { PIN(GPIODV_25, EE_OFF) }; +/*for dmic*/ +static const unsigned int pdm_in_pins[] = { PIN(GPIOZ_8, EE_OFF) }; +static const unsigned int pdm_clk_pins[] = { PIN(GPIOZ_9, EE_OFF)}; + static const unsigned int i2c_sda_b_pins[] = { PIN(GPIODV_26, EE_OFF) }; static const unsigned int i2c_scl_b_pins[] = { PIN(GPIODV_27, EE_OFF) }; @@ -572,6 +576,9 @@ static struct meson_pmx_group meson_gxl_periphs_groups[] = { GROUP(i2c_sda_a, 1, 15), /*dv24*/ GROUP(i2c_scl_a, 1, 14), /*dv25*/ + GROUP(pdm_in, 2, 7), /* dv24 */ + GROUP(pdm_clk, 2, 6), /* dv25 */ + GROUP(i2c_sda_b, 1, 13), /*dv26*/ GROUP(i2c_scl_b, 1, 12), /*dv27*/ diff --git a/include/dt-bindings/clock/amlogic,gxl-clkc.h b/include/dt-bindings/clock/amlogic,gxl-clkc.h index 2f6741ff8d41..d0904a21557a 100644 --- a/include/dt-bindings/clock/amlogic,gxl-clkc.h +++ b/include/dt-bindings/clock/amlogic,gxl-clkc.h @@ -261,7 +261,8 @@ #define CLKID_PCM_MCLK_COMP (CLKID_MISC_BASE + 19) #define CLKID_PCM_SCLK_DIV (CLKID_MISC_BASE + 20) #define CLKID_PCM_SCLK_GATE (CLKID_MISC_BASE + 21) +#define CLKID_I958_COMP_SPDIF (CLKID_MISC_BASE + 22) -#define NR_CLKS (OTHER_BASE + 101) +#define NR_CLKS (OTHER_BASE + 102) #endif /* __GX_CLKC_H */ diff --git a/include/linux/amlogic/media/sound/audin_regs.h b/include/linux/amlogic/media/sound/audin_regs.h index 78c668c749bf..c392390f2cab 100644 --- a/include/linux/amlogic/media/sound/audin_regs.h +++ b/include/linux/amlogic/media/sound/audin_regs.h @@ -155,17 +155,26 @@ /* write 1 to load address to AUDIN_FIFO0. */ #define AUDIN_FIFO0_LOAD 2 -#define AUDIN_FIFO0_DIN_SEL 3 - -/* 0 spdifIN */ +enum data_source { + SPDIF_IN, + I2S_IN, + PCM_IN, + HDMI_IN, + PAO_IN +}; -/* 1 i2Sin */ - -/* 2 PCMIN */ - -/* 3 HDMI in */ - -/* 4 DEMODULATOR IN */ +#define AUDIN_FIFO0_DIN_SEL 3 + /* MBOX platform*/ + /* 0 spdifIN */ + /* 1 i2Sin */ + /* 2 PCMIN */ + /* 3 Dmic in */ + /* TV platform*/ + /* 0 spdifIN */ + /* 1 i2Sin */ + /* 2 PCMIN */ + /* 3 HDMI in */ + /* 4 DEMODULATOR IN */ /* 10:8 data endian control. */ #define AUDIN_FIFO0_ENDIAN 8 diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 182d92efc7c8..bab841fa58ae 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -76,5 +76,8 @@ source "sound/soc/codecs/Kconfig" # generic frame-work source "sound/soc/generic/Kconfig" +# for Amlogic Soc +source "sound/soc/amlogic/Kconfig" + endif # SND_SOC diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9a30f21d16ee..fa8269a6e265 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -49,3 +49,4 @@ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ obj-$(CONFIG_SND_SOC) += xtensa/ obj-$(CONFIG_SND_SOC) += zte/ +obj-$(CONFIG_SND_SOC) += amlogic/ \ No newline at end of file diff --git a/sound/soc/amlogic/Kconfig b/sound/soc/amlogic/Kconfig new file mode 100644 index 000000000000..58d5799b51f6 --- /dev/null +++ b/sound/soc/amlogic/Kconfig @@ -0,0 +1,27 @@ +menuconfig AMLOGIC_SND_SOC + bool "Amlogic Meson ASoC" + default n + help + Say Y or M if you want to add support for codecs attached to + the Amlogic Asoc interface. You will also need + to select the audio interfaces to support below. + +if AMLOGIC_SND_SOC + +config AMLOGIC_SND_SPLIT_MODE + bool "AIU split mode, otherwise normal mode" + depends on AMLOGIC_SND_SOC + default n + help + Say 'Y' to enable AIU split mode. If not, it's normal mode. + +config AMLOGIC_SND_SPLIT_MODE_MMAP + bool "AIU split mode, mmap" + depends on AMLOGIC_SND_SPLIT_MODE + depends on AMLOGIC_SND_SOC + default n + help + Say 'Y' or 'N' to enable/disable AIU split mmap + +endif # AMLOGIC_SND_SOC + diff --git a/sound/soc/amlogic/Makefile b/sound/soc/amlogic/Makefile new file mode 100644 index 000000000000..a430b00d0895 --- /dev/null +++ b/sound/soc/amlogic/Makefile @@ -0,0 +1,31 @@ +# AML Platform Support +snd-soc-aml-pcm-objs := aml_pcm.o +snd-soc-aml-i2s-objs := aml_i2s.o +snd-soc-aml-i2s-dai-objs := aml_i2s_dai.o +snd-soc-aml-pcm-dai-objs := aml_pcm_dai.o +snd-soc-aml-spdif-dai-objs := aml_spdif_dai.o +snd-soc-aml-hw-objs := aml_audio_hw.o +snd-soc-aml-hw-pcm2bt-objs := aml_audio_hw_pcm.o +snd-soc-aml-dmic-objs := aml_dmic.o + +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-pcm.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-i2s.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-i2s-dai.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-pcm-dai.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-hw.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += aml_notify.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-hw-pcm2bt.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-spdif-dai.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-dmic.o + +# AML spdif codec support +snd-soc-aml-spdif-codec-objs := aml_spdif_codec.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-spdif-codec.o + +#AML M8 Machine support +snd-soc-aml-meson-objs := aml_meson.o +obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-meson.o + +#AML G9TV Machine support +snd-soc-aml-tv-objs := aml_tv.o +#obj-$(CONFIG_AMLOGIC_SND_SOC) += snd-soc-aml-tv.o diff --git a/sound/soc/amlogic/aml_audio_hw.c b/sound/soc/amlogic/aml_audio_hw.c new file mode 100644 index 000000000000..17e289ebb58c --- /dev/null +++ b/sound/soc/amlogic/aml_audio_hw.c @@ -0,0 +1,1013 @@ +/* + * sound/soc/amlogic/aml_audio_hw.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#define pr_fmt(fmt) "aml_audio_hw: " fmt + +#include +#include +#include +#include +#include +#include + +/* Amlogic headers */ +#include +#include +#include +#include +#include "aml_audio_hw.h" + +/* i2s mode 0: master 1: slave */ +/* source: 0: linein; 1: ATV; 2: HDMI-in */ +unsigned int IEC958_MODE = AIU_958_MODE_PCM16; +unsigned int I2S_MODE = AIU_I2S_MODE_PCM16; +unsigned int audio_in_source; +void set_i2s_source(unsigned int source) +{ + audio_in_source = source; +} + +int audio_in_buf_ready; +int audio_out_buf_ready; + +unsigned int IEC958_bpf = 0x7dd; +EXPORT_SYMBOL(IEC958_bpf); +unsigned int IEC958_brst = 0xc; +EXPORT_SYMBOL(IEC958_brst); +unsigned int IEC958_length = 0x7dd * 8; +EXPORT_SYMBOL(IEC958_length); +unsigned int IEC958_padsize = 0x8000; +EXPORT_SYMBOL(IEC958_padsize); +unsigned int IEC958_mode = 1; +EXPORT_SYMBOL(IEC958_mode); +unsigned int IEC958_syncword1 = 0x7ffe; +EXPORT_SYMBOL(IEC958_syncword1); +unsigned int IEC958_syncword2 = 0x8001; +EXPORT_SYMBOL(IEC958_syncword2); +unsigned int IEC958_syncword3; +EXPORT_SYMBOL(IEC958_syncword3); +unsigned int IEC958_syncword1_mask; +EXPORT_SYMBOL(IEC958_syncword1_mask); +unsigned int IEC958_syncword2_mask; +EXPORT_SYMBOL(IEC958_syncword2_mask); +unsigned int IEC958_syncword3_mask = 0xffff; +EXPORT_SYMBOL(IEC958_syncword3_mask); +unsigned int IEC958_chstat0_l = 0x1902; +EXPORT_SYMBOL(IEC958_chstat0_l); +unsigned int IEC958_chstat0_r = 0x1902; +EXPORT_SYMBOL(IEC958_chstat0_r); +unsigned int IEC958_chstat1_l = 0x200; +EXPORT_SYMBOL(IEC958_chstat1_l); +unsigned int IEC958_chstat1_r = 0x200; +EXPORT_SYMBOL(IEC958_chstat1_r); +unsigned int IEC958_mode_raw; +EXPORT_SYMBOL(IEC958_mode_raw); + +/* + * bit 0:soc in slave mode for adc; + * bit 1:audio in data source from spdif in; + * bit 2:adc & spdif in work at the same time; + */ +unsigned int audioin_mode = I2SIN_MASTER_MODE; + +/* Bit 3: mute constant + * 0 => 'h0000000 + * 1 => 'h800000 + */ +unsigned int dac_mute_const = 0x800000; + +void audio_set_aiubuf(u32 addr, u32 size, unsigned int channel) +{ +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_MEM_I2S_START_PTR, addr & 0xffffff00); + aml_write_cbus(AIU_MEM_I2S_RD_PTR, addr & 0xffffff00); +#else + aml_write_cbus(AIU_MEM_I2S_START_PTR, addr & 0xffffffc0); + aml_write_cbus(AIU_MEM_I2S_RD_PTR, addr & 0xffffffc0); +#endif + + if (channel == 8) { + /*select cts_aoclkx2_int as AIU clk to hdmi_tx_audio_mster_clk*/ + aml_cbus_update_bits(AIU_CLK_CTRL_MORE, 1 << 6, 1 << 6); + /*unmute all channels*/ + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0xff << 8, 0 << 8); +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_MEM_I2S_END_PTR, + (addr & 0xffffff00) + (size & 0xffffff00) - 256); +#else + aml_write_cbus(AIU_MEM_I2S_END_PTR, + (addr & 0xffffffc0) + (size & 0xffffffc0) - 256); +#endif + } else { + /*select cts_clk_i958 as AIU clk to hdmi_tx_audio_mster_clk*/ + aml_cbus_update_bits(AIU_CLK_CTRL_MORE, 1 << 6, 0 << 6); + /*unmute 0/1 channel*/ + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0xff << 8, 0xfc << 8); +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_MEM_I2S_END_PTR, + (addr & 0xffffff00) + (size & 0xffffff00) - 256); +#else + aml_write_cbus(AIU_MEM_I2S_END_PTR, + (addr & 0xffffffc0) + (size & 0xffffffc0) - 64); +#endif + } + /* Hold I2S */ + aml_write_cbus(AIU_I2S_MISC, 0x0004); + /* Release hold and force audio data to left or right */ + aml_write_cbus(AIU_I2S_MISC, 0x0010); + + if (channel == 8) { + pr_info("%s channel == 8\n", __func__); + /* [31:16] IRQ block. */ + aml_write_cbus(AIU_MEM_I2S_MASKS, (24 << 16) | + /* [15: 8] chan_mem_mask. + * Each bit indicates which channels exist in memory + */ + (0xff << 8) | + /* [ 7: 0] chan_rd_mask. + * Each bit indicates which channels are READ from memory + */ + (0xff << 0)); + } else { +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + /* [31:16] IRQ block. */ + aml_write_cbus(AIU_MEM_I2S_MASKS, (24 << 16) | + (0xff << 8) | + (0xff << 0)); +#else + /* [31:16] IRQ block. */ + aml_write_cbus(AIU_MEM_I2S_MASKS, (24 << 16) | + (0x3 << 8) | + (0x3 << 0)); +#endif + } + /* 16 bit PCM mode */ + /* aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1, 6, 1); */ + /* Set init high then low to initialize the I2S memory logic */ + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1, 1); + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1, 0); + + aml_write_cbus(AIU_MEM_I2S_BUF_CNTL, 1 | (0 << 1)); + aml_write_cbus(AIU_MEM_I2S_BUF_CNTL, 0 | (0 << 1)); + + audio_out_buf_ready = 1; +} + +void audio_set_958outbuf(u32 addr, u32 size, int flag) +{ + aml_write_cbus(AIU_MEM_IEC958_START_PTR, addr & 0xffffffc0); + if (aml_read_cbus(AIU_MEM_IEC958_START_PTR) == + aml_read_cbus(AIU_MEM_I2S_START_PTR)) { + aml_write_cbus(AIU_MEM_IEC958_RD_PTR, + aml_read_cbus(AIU_MEM_I2S_RD_PTR)); + } else + aml_write_cbus(AIU_MEM_IEC958_RD_PTR, + addr & 0xffffffc0); + if (flag == 0) { + /* this is for 16bit 2 channel */ +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_MEM_IEC958_END_PTR, + (addr & 0xffffffc0) + + (size & 0xffffffc0) - 8); +#else + aml_write_cbus(AIU_MEM_IEC958_END_PTR, + (addr & 0xffffffc0) + + (size & 0xffffffc0) - 64); +#endif + } else { + /* this is for RAW mode */ +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_MEM_IEC958_END_PTR, + (addr & 0xffffffc0) + + (size & 0xffffffc0) - 8); +#else + aml_write_cbus(AIU_MEM_IEC958_END_PTR, + (addr & 0xffffffc0) + + (size & 0xffffffc0) - 1); +#endif + } +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_cbus_update_bits(AIU_MEM_IEC958_MASKS, 0xffff, 0xffff); +#else + aml_cbus_update_bits(AIU_MEM_IEC958_MASKS, 0xffff, 0x303); +#endif + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1, 1); + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1, 0); + + aml_write_cbus(AIU_MEM_IEC958_BUF_CNTL, 1 | (0 << 1)); + aml_write_cbus(AIU_MEM_IEC958_BUF_CNTL, 0 | (0 << 1)); +} + +/* + * i2s mode 0: master 1: slave + * din_sel 0:spdif 1:i2s 2:pcm 3: dmic + */ +static void i2sin_fifo0_set_buf(u32 addr, u32 size, u32 i2s_mode, + u32 i2s_sync, u32 din_sel, u32 ch) +{ + unsigned char mode = 0; + unsigned int sync_mode = 0, din_pos = 0; + + if (i2s_sync) + sync_mode = i2s_sync; + if (i2s_mode & I2SIN_SLAVE_MODE) + mode = 1; + if (din_sel != 1) + din_pos = 1; + aml_write_cbus(AUDIN_FIFO0_START, addr & 0xffffffc0); + aml_write_cbus(AUDIN_FIFO0_PTR, (addr & 0xffffffc0)); + aml_write_cbus(AUDIN_FIFO0_END, + (addr & 0xffffffc0) + (size & 0xffffffc0) - 8); + + aml_write_cbus(AUDIN_FIFO0_CTRL, (1 << AUDIN_FIFO0_EN) /* FIFO0_EN */ + |(1 << AUDIN_FIFO0_LOAD) /* load start address */ + |(din_sel << AUDIN_FIFO0_DIN_SEL) + + /* DIN from i2sin */ + /* |(1<<6) // 32 bits data in. */ + /* |(0<<7) // put the 24bits data to low 24 bits */ + | (4 << AUDIN_FIFO0_ENDIAN) /* AUDIN_FIFO0_ENDIAN */ + |((ch == 2?2:1) << AUDIN_FIFO0_CHAN) /* ch mode ctl */ + |(0 << 16) /* to DDR */ + |(1 << AUDIN_FIFO0_UG) /* Urgent request. */ + |(0 << 17) /* Overflow Interrupt mask */ + |(0 << 18) + /* Audio in INT */ + /* |(1<<19) // hold 0 enable */ + | (0 << AUDIN_FIFO0_UG) /* hold0 to aififo */ + ); + + aml_write_cbus(AUDIN_FIFO0_CTRL1, 0 << 4 /* fifo0_dest_sel */ + | 2 << 2 /* fifo0_din_byte_num */ + | din_pos << 0); /* fifo0_din_pos */ + + if (audio_in_source == 0) { + aml_write_cbus(AUDIN_I2SIN_CTRL, + ((0xf>>(4 - ch/2)) << I2SIN_CHAN_EN) + | (3 << I2SIN_SIZE) + | (1 << I2SIN_LRCLK_INVT) + | (1 << I2SIN_LRCLK_SKEW) + | (sync_mode << I2SIN_POS_SYNC) + | (!mode << I2SIN_LRCLK_SEL) + | (!mode << I2SIN_CLK_SEL) + | (!mode << I2SIN_DIR)); + + } else if (audio_in_source == 1) { + aml_write_cbus(AUDIN_I2SIN_CTRL, (1 << I2SIN_CHAN_EN) + | (0 << I2SIN_SIZE) + | (0 << I2SIN_LRCLK_INVT) + | (0 << I2SIN_LRCLK_SKEW) + | (1 << I2SIN_POS_SYNC) + | (0 << I2SIN_LRCLK_SEL) + | (0 << I2SIN_CLK_SEL) + | (0 << I2SIN_DIR)); + } else if (audio_in_source == 2) { + aml_write_cbus(AUDIN_I2SIN_CTRL, (1 << I2SIN_CHAN_EN) + | (3 << I2SIN_SIZE) + | (1 << I2SIN_LRCLK_INVT) + | (1 << I2SIN_LRCLK_SKEW) + | (1 << I2SIN_POS_SYNC) + | (1 << I2SIN_LRCLK_SEL) + | (1 << I2SIN_CLK_SEL) + | (1 << I2SIN_DIR)); + } + +} + +static void spdifin_reg_set(void) +{ + /* get clk81 clk_rate */ + unsigned int clk_rate = clk81; + u32 spdif_clk_time = 54; /* 54us */ + u32 spdif_mode_14bit = (u32)((clk_rate / 500000 + 1) >> 1) + * spdif_clk_time; + /* sysclk/32(bit)/2(ch)/2(bmc) */ + u32 period_data = (u32)(clk_rate / 64000 + 1) >> 1; + u32 period_32k = (period_data + (1 << 4)) >> 5; /* 32k min period */ + u32 period_44k = (period_data / 22 + 1) >> 1; /* 44k min period */ + u32 period_48k = (period_data / 24 + 1) >> 1; /* 48k min period */ + u32 period_96k = (period_data / 48 + 1) >> 1; /* 96k min period */ + u32 period_192k = (period_data / 96 + 1) >> 1; /* 192k min period */ + + pr_info("spdifin_reg_set: clk_rate=%d\n", clk_rate); + + aml_write_cbus(AUDIN_SPDIF_MODE, + (aml_read_cbus(AUDIN_SPDIF_MODE) & 0x7fffc000) | + (spdif_mode_14bit << 0)); + aml_write_cbus(AUDIN_SPDIF_FS_CLK_RLTN, + (period_32k << 0) | + (period_44k << 6) | (period_48k << 12) | + /* Spdif_fs_clk_rltn */ + (period_96k << 18) | (period_192k << 24)); + +} + +static void spdifin_fifo1_set_buf(u32 addr, u32 size, u32 src) +{ + aml_write_cbus(AUDIN_SPDIF_MODE, + aml_read_cbus(AUDIN_SPDIF_MODE) & 0x7fffffff); + /*set channel invert from old spdif in mode*/ + aml_cbus_update_bits(AUDIN_SPDIF_MODE, (1 << 19), (1 << 19)); + aml_write_cbus(AUDIN_FIFO1_START, addr & 0xffffffc0); + aml_write_cbus(AUDIN_FIFO1_PTR, (addr & 0xffffffc0)); + aml_write_cbus(AUDIN_FIFO1_END, + (addr & 0xffffffc0) + (size & 0xffffffc0) - 8); + aml_write_cbus(AUDIN_FIFO1_CTRL, (1 << AUDIN_FIFO1_EN) /* FIFO0_EN */ + |(1 << AUDIN_FIFO1_LOAD) /* load start address. */ + |(src << AUDIN_FIFO1_DIN_SEL) + + /* DIN from i2sin. */ + /* |(1<<6) // 32 bits data in. */ + /* |(0<<7) // put the 24bits data to low 24 bits */ + | (4 << AUDIN_FIFO1_ENDIAN) /* AUDIN_FIFO0_ENDIAN */ + |(2 << AUDIN_FIFO1_CHAN) /* 2 channel */ + |(0 << 16) /* to DDR */ + |(1 << AUDIN_FIFO1_UG) /* Urgent request. */ + |(0 << 17) /* Overflow Interrupt mask */ + |(0 << 18) + /* Audio in INT */ + /* |(1<<19) //hold 0 enable */ + | (0 << AUDIN_FIFO1_UG) /* hold0 to aififo */ + ); + + /* + * according clk81 to set reg spdif_mode(0x2800) + * the last 14 bit and reg Spdif_fs_clk_rltn(0x2801) + */ + spdifin_reg_set(); + /*3 byte mode, (23:0)*/ + if (src == PAO_IN) { + aml_write_cbus(AUDIN_FIFO1_CTRL1, 0x08); + } else if (src == HDMI_IN) { + /* there are two inputs for HDMI_IN. New I2S:SPDIF */ + aml_write_cbus(AUDIN_FIFO1_CTRL1, 0x08); + if (1) { + /* new SPDIF in module */ + aml_write_cbus(AUDIN_DECODE_FORMAT, 1<<24); + } else { + /* new I2S in module */ + aml_write_cbus(AUDIN_DECODE_FORMAT, 0x103ad); + } + } else + aml_write_cbus(AUDIN_FIFO1_CTRL1, 0x88); +} + +void audio_in_i2s_set_buf(u32 addr, u32 size, + u32 i2s_mode, u32 i2s_sync, u32 din_sel, u32 ch) +{ + pr_info("i2sin_fifo0_set_buf din_sel:%d ch:%d\n", din_sel, ch); + i2sin_fifo0_set_buf(addr, size, i2s_mode, i2s_sync, din_sel, ch); + audio_in_buf_ready = 1; +} + +void audio_in_spdif_set_buf(u32 addr, u32 size, u32 src) +{ + pr_info("spdifin_fifo1_set_buf, src = %d\n", src); + spdifin_fifo1_set_buf(addr, size, src); +} + +/* extern void audio_in_enabled(int flag); */ + +void audio_in_i2s_enable(int flag) +{ + int rd = 0, start = 0; + + if (flag) { + /* reset only when start i2s input */ + reset_again: + /* reset FIFO 0 */ + aml_cbus_update_bits(AUDIN_FIFO0_CTRL, 0x2, 0x2); + aml_write_cbus(AUDIN_FIFO0_PTR, 0); + rd = aml_read_cbus(AUDIN_FIFO0_PTR); + start = aml_read_cbus(AUDIN_FIFO0_START); + if (rd != start) { + pr_err("error %08x, %08x !\n", + rd, start); + goto reset_again; + } + aml_cbus_update_bits(AUDIN_I2SIN_CTRL, 1 << I2SIN_EN, + 1 << I2SIN_EN); + } else { + aml_cbus_update_bits(AUDIN_I2SIN_CTRL, 1 << I2SIN_EN, + 0 << I2SIN_EN); + } +} + +void audio_in_spdif_enable(int flag) +{ + int rd = 0, start = 0; + + if (flag) { + reset_again: + /* reset FIFO 0 */ + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, 0x2, 0x2); + aml_write_cbus(AUDIN_FIFO1_PTR, 0); + rd = aml_read_cbus(AUDIN_FIFO1_PTR); + start = aml_read_cbus(AUDIN_FIFO1_START); + if (rd != start) { + pr_err("error %08x, %08x !\n", rd, start); + goto reset_again; + } + aml_write_cbus(AUDIN_SPDIF_MODE, + aml_read_cbus(AUDIN_SPDIF_MODE) | (1 << 31)); + } else { + aml_write_cbus(AUDIN_SPDIF_MODE, + aml_read_cbus(AUDIN_SPDIF_MODE) & ~(1 << 31)); + } +} + +int if_audio_in_i2s_enable(void) +{ + return aml_read_cbus(AUDIN_I2SIN_CTRL) & (1 << 15); +} + +int if_audio_in_spdif_enable(void) +{ + return aml_read_cbus(AUDIN_SPDIF_MODE) & (1 << 31); +} + +unsigned int audio_in_i2s_rd_ptr(void) +{ + unsigned int val; + + val = aml_read_cbus(AUDIN_FIFO0_RDPTR); + pr_info("audio in i2s rd ptr: %x\n", val); + return val; +} + +unsigned int audio_in_spdif_rd_ptr(void) +{ + unsigned int val; + + val = aml_read_cbus(AUDIN_FIFO1_RDPTR); + pr_info("audio in spdif rd ptr: %x\n", val); + return val; +} + +unsigned int audio_in_i2s_wr_ptr(void) +{ + unsigned int val; + + aml_write_cbus(AUDIN_FIFO0_PTR, 1); + val = aml_read_cbus(AUDIN_FIFO0_PTR); + return (val) & (~0x3F); + /* return val&(~0x7); */ +} + +unsigned int audio_in_spdif_wr_ptr(void) +{ + unsigned int val; + + aml_write_cbus(AUDIN_FIFO1_PTR, 1); + val = aml_read_cbus(AUDIN_FIFO1_PTR); + return (val) & (~0x3F); +} + +void audio_in_i2s_set_wrptr(unsigned int val) +{ + aml_write_cbus(AUDIN_FIFO0_RDPTR, val); +} + +void audio_in_spdif_set_wrptr(unsigned int val) +{ + aml_write_cbus(AUDIN_FIFO1_RDPTR, val); +} + +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE +void audio_set_i2s_mode(u32 mode, unsigned int channel) +{ + aml_write_cbus(AIU_I2S_SOURCE_DESC, 0x800); + + aml_cbus_update_bits(AIU_CLK_CTRL_MORE, 0x1f, 0); + + if (channel == 8) { + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 0, 1); + + if (mode == AIU_I2S_MODE_PCM32) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, 0); + + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 9, + 1 << 9); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 7 << 6, + 7 << 6); + } else if (mode == AIU_I2S_MODE_PCM24) { + /* todo: to verify it */ + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, 0); + + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 9, + 1 << 9); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 7 << 6, + 7 << 6); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 5, + 1 << 5); + + } else if (mode == AIU_I2S_MODE_PCM16) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, + 1 << 6); + + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 2 << 3, + 2 << 3); + + aml_cbus_update_bits(AIU_CLK_CTRL_MORE, 0x1f, 0x5); + } + } else if (channel == 2) { + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 0, 0); + + if (mode == AIU_I2S_MODE_PCM16) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, + 1 << 6); + + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 2 << 3, + 2 << 3); + } else if (mode == AIU_I2S_MODE_PCM24) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, 0); + + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 5, + 1 << 5); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 2 << 3, + 2 << 3); + } else if (mode == AIU_I2S_MODE_PCM32) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, 0); + + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 9, + 1 << 9); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 7 << 6, + 7 << 6); + } + } + + /* In split mode, there are not mask control, + * so aiu_mem_i2s_mask[15:0] must set 8'hffff_ffff. + */ + /* aml_write_cbus(AIU_MEM_I2S_MASKS, + * (16 << 16) | + * (0xff << 8) | + * (0xff << 0)); + */ +} +#else +void audio_set_i2s_mode(u32 mode) +{ + const unsigned short mask[4] = { + 0x303, /* 2x16 */ + 0x303, /* 2x24 */ + 0x303, /* 8x24 */ + 0x303, /* 2x32 */ + }; + + if (mode < sizeof(mask) / sizeof(unsigned short)) { + /* four two channels stream */ + aml_write_cbus(AIU_I2S_SOURCE_DESC, 1); + + if (mode == AIU_I2S_MODE_PCM16) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, + 1 << 6); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 5, 0); + } else if (mode == AIU_I2S_MODE_PCM32) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, 0); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 5, + 1 << 5); + } else if (mode == AIU_I2S_MODE_PCM24) { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 1 << 6, 0); + aml_cbus_update_bits(AIU_I2S_SOURCE_DESC, 1 << 5, + 1 << 5); + } + + aml_cbus_update_bits(AIU_MEM_I2S_MASKS, 0xffff, mask[mode]); + } +} +#endif + +/* + * if normal clock, i2s clock is twice of 958 clock, + * so the divisor for i2s is 8, but 4 for 958 + * if over clock, the devisor for i2s is 8, but for 958 should be 1, + * because 958 should be 4 times speed according to i2s. + * This is dolby digital plus's spec + */ + +/* iec958 and i2s clock are separated after M6TV. */ +void audio_util_set_dac_958_format(unsigned int format) +{ + /* 958 divisor more, if true, divided by 2, 4, 6, 8 */ + aml_cbus_update_bits(AIU_CLK_CTRL, 1 << 12, 0); +#if IEC958_OVERCLOCK == 1 + /* 958 divisor: 0=no div; 1=div by 2; 2=div by 3; 3=div by 4. */ +/* aml_cbus_update_bits(AIU_CLK_CTRL, 3 << 4, 1 << 4); */ +#else + /* 958 divisor: 0=no div; 1=div by 2; 2=div by 3; 3=div by 4. */ +/* aml_cbus_update_bits(AIU_CLK_CTRL, 3 << 4, 1 << 4); */ +#endif + /* enable 958 divider */ + aml_cbus_update_bits(AIU_CLK_CTRL, 1 << 1, 1 << 1); +} + +void audio_util_set_dac_i2s_format(unsigned int format) +{ + /* invert aoclk */ + aml_cbus_update_bits(AIU_CLK_CTRL, 1 << 6, 1 << 6); + /* invert lrclk */ + aml_cbus_update_bits(AIU_CLK_CTRL, 1 << 7, 1 << 7); + /* alrclk skew: 1=alrclk transitions on the cycle before msb is sent */ + aml_cbus_update_bits(AIU_CLK_CTRL, 0x3 << 8, 1 << 8); +#if MCLKFS_RATIO == 512 + /* i2s divisor: 0=no div; 1=div by 2; 2=div by 4; 3=div by 8. */ + aml_cbus_update_bits(AIU_CLK_CTRL, 0x3 << 2, 0x3 << 2); +#elif MCLKFS_RATIO == 256 + aml_cbus_update_bits(AIU_CLK_CTRL, 0x3 << 2, 0x2 << 2); +#else + aml_cbus_update_bits(AIU_CLK_CTRL, 0x3 << 2, 0x1 << 2); +#endif + /* enable I2S clock */ + aml_cbus_update_bits(AIU_CLK_CTRL, 1, 1); + + if (format == AUDIO_ALGOUT_DAC_FORMAT_LEFT_JUSTIFY) + aml_cbus_update_bits(AIU_CLK_CTRL, 0x3 << 8, 0); + + if (dac_mute_const == 0x800000) + aml_write_cbus(AIU_I2S_DAC_CFG, 0x000f); + else + /* Payload 24-bit, Msb first, alrclk = aoclk/64 */ + aml_write_cbus(AIU_I2S_DAC_CFG, 0x0007); + + /* four 2-channel */ +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_I2S_SOURCE_DESC, (1 << 11)); +#else + aml_write_cbus(AIU_I2S_SOURCE_DESC, 0x0001); +#endif +} + +/* set sclk and lrclk, mclk = 256fs. */ +void audio_set_i2s_clk_div(void) +{ + /* aiclk source */ + aml_cbus_update_bits(AIU_CLK_CTRL, 1 << 10, 1 << 10); + /* Set mclk over sclk ratio */ + aml_cbus_update_bits(AIU_CLK_CTRL_MORE, 0x3f << 8, (4 - 1) << 8); + /* set dac/adc lrclk ratio over sclk----64fs */ + aml_cbus_update_bits(AIU_CODEC_DAC_LRCLK_CTRL, 0xfff, (64 - 1)); + aml_cbus_update_bits(AIU_CODEC_ADC_LRCLK_CTRL, 0xfff, (64 - 1)); + /* Enable sclk */ + aml_cbus_update_bits(AIU_CLK_CTRL_MORE, 1 << 14, 1 << 14); +} + +void audio_set_spdif_clk_div(uint div) +{ + uint val = 0; + + if (div < 1 && div > 4) + return; + + val = div - 1; + /* 958 divisor: 0=no div; 1=div by 2; 2=div by 3; 3=div by 4. */ + aml_cbus_update_bits(AIU_CLK_CTRL, 3 << 4, val << 4); + /* enable 958 divider */ + aml_cbus_update_bits(AIU_CLK_CTRL, 1 << 1, 1 << 1); +} + +void audio_enable_output(int flag) +{ + if (flag) { + aml_write_cbus(AIU_RST_SOFT, 0x05); + aml_read_cbus(AIU_I2S_SYNC); + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 3 << 1, 3 << 1); + + /* Maybe cause POP noise */ + /* audio_i2s_unmute(); */ + } else { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 3 << 1, 0); + + /* Maybe cause POP noise */ + /* audio_i2s_mute(); */ + } + /* audio_out_enabled(flag); */ +} + +int if_audio_out_enable(void) +{ + return aml_read_cbus(AIU_MEM_I2S_CONTROL) & (0x3 << 1); +} +EXPORT_SYMBOL(if_audio_out_enable); + +int if_958_audio_out_enable(void) +{ + return aml_read_cbus(AIU_MEM_IEC958_CONTROL) & (0x3 << 1); +} +EXPORT_SYMBOL(if_958_audio_out_enable); + +unsigned int read_i2s_rd_ptr(void) +{ + unsigned int val; + + val = aml_read_cbus(AIU_MEM_I2S_RD_PTR); + return val; +} + +unsigned int read_iec958_rd_ptr(void) +{ + unsigned int val; + + val = aml_read_cbus(AIU_MEM_IEC958_RD_PTR); + return val; +} + +void aml_audio_i2s_unmute(void) +{ + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0xff << 8, 0); +} + +void aml_audio_i2s_mute(void) +{ + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0xff << 8, 0xff << 8); +} + +void audio_i2s_unmute(void) +{ + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0xff << 8, 0); + aml_cbus_update_bits(AIU_958_CTRL, 0x3 << 3, 0 << 3); +} + +void audio_i2s_mute(void) +{ + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0xff << 8, 0xff << 8); + aml_cbus_update_bits(AIU_958_CTRL, 0x3 << 3, 0x3 << 3); +} + +void audio_mute_left_right(unsigned int flag) +{ + if (flag == 0) { /* right */ + aml_cbus_update_bits(AIU_958_CTRL, 0x3 << 3, 0x1 << 3); + } else if (flag == 1) { /* left */ + aml_cbus_update_bits(AIU_958_CTRL, 0x3 << 3, 0x2 << 3); + } +} + +void audio_hw_958_reset(unsigned int slow_domain, unsigned int fast_domain) +{ + aml_write_cbus(AIU_958_DCU_FF_CTRL, 0); + aml_write_cbus(AIU_RST_SOFT, (slow_domain << 3) | (fast_domain << 2)); +} + +void audio_hw_958_raw(void) +{ + aml_write_cbus(AIU_958_MISC, 1); + /* raw */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 8, 1 << 8); + /* 8bit */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 7, 0); + /* endian */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 0x7 << 3, 1 << 3); + + aml_write_cbus(AIU_958_BPF, IEC958_bpf); + aml_write_cbus(AIU_958_BRST, IEC958_brst); + aml_write_cbus(AIU_958_LENGTH, IEC958_length); + aml_write_cbus(AIU_958_PADDSIZE, IEC958_padsize); + /* disable int */ + aml_cbus_update_bits(AIU_958_DCU_FF_CTRL, 0x3 << 2, 0); + + if (IEC958_mode == 1) { /* search in byte */ + aml_cbus_update_bits(AIU_958_DCU_FF_CTRL, 0x7 << 4, 0x7 << 4); + } else if (IEC958_mode == 2) { /* search in word */ + aml_cbus_update_bits(AIU_958_DCU_FF_CTRL, 0x7 << 4, 0x5 << 4); + } else { + aml_cbus_update_bits(AIU_958_DCU_FF_CTRL, 0x7 << 4, 0); + } + aml_write_cbus(AIU_958_CHSTAT_L0, IEC958_chstat0_l); + aml_write_cbus(AIU_958_CHSTAT_L1, IEC958_chstat1_l); + aml_write_cbus(AIU_958_CHSTAT_R0, IEC958_chstat0_r); + aml_write_cbus(AIU_958_CHSTAT_R1, IEC958_chstat1_r); + + aml_write_cbus(AIU_958_SYNWORD1, IEC958_syncword1); + aml_write_cbus(AIU_958_SYNWORD2, IEC958_syncword2); + aml_write_cbus(AIU_958_SYNWORD3, IEC958_syncword3); + aml_write_cbus(AIU_958_SYNWORD1_MASK, IEC958_syncword1_mask); + aml_write_cbus(AIU_958_SYNWORD2_MASK, IEC958_syncword2_mask); + aml_write_cbus(AIU_958_SYNWORD3_MASK, IEC958_syncword3_mask); + + pr_info("%s: %d\n", __func__, __LINE__); + pr_info("\tBPF: %x\n", IEC958_bpf); + pr_info("\tBRST: %x\n", IEC958_brst); + pr_info("\tLENGTH: %x\n", IEC958_length); + pr_info("\tPADDSIZE: %x\n", IEC958_length); + pr_info("\tsyncword: %x, %x, %x\n\n", IEC958_syncword1, + IEC958_syncword2, IEC958_syncword3); + +} + +void set_958_channel_status(struct _aiu_958_channel_status_t *set) +{ + if (set) { + aml_write_cbus(AIU_958_CHSTAT_L0, set->chstat0_l); + aml_write_cbus(AIU_958_CHSTAT_L1, set->chstat1_l); + aml_write_cbus(AIU_958_CHSTAT_R0, set->chstat0_r); + aml_write_cbus(AIU_958_CHSTAT_R1, set->chstat1_r); + } +} + +static void audio_hw_set_958_pcm24(struct _aiu_958_raw_setting_t *set) +{ + /* in pcm mode, set bpf to 128 */ + aml_write_cbus(AIU_958_BPF, 0x80); + set_958_channel_status(set->chan_stat); +} + +void audio_set_958_mode(unsigned int mode, struct _aiu_958_raw_setting_t *set) +{ + if (mode == AIU_958_MODE_PCM_RAW) { + mode = AIU_958_MODE_PCM16; /* use 958 raw pcm mode */ + aml_write_cbus(AIU_958_VALID_CTRL, 3); + } else + aml_write_cbus(AIU_958_VALID_CTRL, 0); + + if (mode == AIU_958_MODE_RAW) { + + audio_hw_958_raw(); + aml_write_cbus(AIU_958_MISC, 1); + /* raw */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, + 1 << 8, 1 << 8); + /* 8bit */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 7, 0); + /* endian */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, + 0x7 << 3, 0x1 << 3); + + pr_info("IEC958 RAW\n"); + } else if (mode == AIU_958_MODE_PCM32) { + audio_hw_set_958_pcm24(set); +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_958_MISC, 0x3480); + /* pcm */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 8, + 1 << 8); +#else + aml_write_cbus(AIU_958_MISC, 0x2020 | (1 << 7)); + /* pcm */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 8, 0); +#endif + /* 16bit */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 7, 0); + /* endian */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, + 0x7 << 3, 0); + + pr_info("IEC958 PCM32\n"); + } else if (mode == AIU_958_MODE_PCM24) { + audio_hw_set_958_pcm24(set); +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_write_cbus(AIU_958_MISC, 0x3480); + /* pcm */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 8, + 1 << 8); +#else + aml_write_cbus(AIU_958_MISC, 0x2020 | (1 << 7)); + /* pcm */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 8, 0); +#endif + /* 16bit */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 7, 0); + /* endian */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, + 0x7 << 3, 0); + + pr_info("IEC958 24bit\n"); + } else if (mode == AIU_958_MODE_PCM16) { + audio_hw_set_958_pcm24(set); + aml_write_cbus(AIU_958_MISC, 0x2042); + /* pcm */ +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 8, + 1 << 8); +#else + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 1 << 8, 0); +#endif + /* 16bit */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, + 1 << 7, 1 << 7); + /* endian */ + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, + 0x7 << 3, 0); + pr_info("IEC958 16bit\n"); + } + + audio_hw_958_reset(0, 1); + +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + if (mode == AIU_958_MODE_PCM32) + aml_cbus_update_bits(AIU_958_DCU_FF_CTRL, 1 << 8, 1 << 8); +#endif + + aml_write_cbus(AIU_958_FORCE_LEFT, 1); +} + +void audio_out_i2s_enable(unsigned int flag) +{ + if (flag) { + aml_write_cbus(AIU_RST_SOFT, 0x01); + aml_read_cbus(AIU_I2S_SYNC); + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 0x3 << 1, 0x3 << 1); + /* Maybe cause POP noise */ + /* audio_i2s_unmute(); */ + } else { + aml_cbus_update_bits(AIU_MEM_I2S_CONTROL, 0x3 << 1, 0); + + /* Maybe cause POP noise */ + /* audio_i2s_mute(); */ + } + /* audio_out_enabled(flag); */ +} + +void audio_hw_958_enable(unsigned int flag) +{ + if (flag) { + aml_write_cbus(AIU_RST_SOFT, 0x04); + aml_write_cbus(AIU_958_FORCE_LEFT, 0); + aml_cbus_update_bits(AIU_958_DCU_FF_CTRL, 1, 1); + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 0x3 << 1, + 0x3 << 1); + } else { + aml_write_cbus(AIU_RST_SOFT, 0x04); + aml_write_cbus(AIU_958_FORCE_LEFT, 0); + aml_write_cbus(AIU_958_DCU_FF_CTRL, 0); + aml_cbus_update_bits(AIU_MEM_IEC958_CONTROL, 0x3 << 1, + 0); + } +} + +unsigned int read_i2s_mute_swap_reg(void) +{ + unsigned int val; + + val = aml_read_cbus(AIU_I2S_MUTE_SWAP); + return val; +} + +void audio_i2s_swap_left_right(unsigned int flag) +{ + /*only LPCM output can set aiu hw channel swap*/ + if (IEC958_mode_codec == 0 || IEC958_mode_codec == 9) + aml_cbus_update_bits(AIU_958_CTRL, 0x3 << 1, flag << 1); + + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0x3, flag); + aml_cbus_update_bits(AIU_I2S_MUTE_SWAP, 0x3 << 2, flag << 2); +} + +void audio_i2s_958_same_source(unsigned int same) +{ + aml_cbus_update_bits(AIU_I2S_MISC, 1 << 3, (!!same) << 3); +} + +void set_hw_resample_source(int source) +{ + aml_cbus_update_bits(AUD_RESAMPLE_CTRL0, 1 << 29, source << 29); +} +EXPORT_SYMBOL(set_hw_resample_source); +#if 0 +unsigned int audio_hdmi_init_ready(void) +{ + return READ_MPEG_REG_BITS(AIU_HDMI_CLK_DATA_CTRL, 0, 2); +} + +/* power gate control for iec958 audio out */ +unsigned int audio_spdifout_pg_enable(unsigned char enable) +{ + if (enable) { + aml_cbus_update_bits(MPLL_958_CNTL, 1, 14, 1); + AUDIO_CLK_GATE_ON(AIU_IEC958); + AUDIO_CLK_GATE_ON(AIU_ICE958_AMCLK); + } else { + AUDIO_CLK_GATE_OFF(AIU_IEC958); + AUDIO_CLK_GATE_OFF(AIU_ICE958_AMCLK); + aml_cbus_update_bits(MPLL_958_CNTL, 0, 14, 1); + } + return 0; +} + +/* + * power gate control for normal aiu domain including i2s in/out + * TODO: move i2s out /adc related gate to i2s cpu dai driver + */ +unsigned int audio_aiu_pg_enable(unsigned char enable) +{ + if (enable) + switch_mod_gate_by_name("audio", 1); + else + switch_mod_gate_by_name("audio", 0); + + return 0; +} +#endif diff --git a/sound/soc/amlogic/aml_audio_hw.h b/sound/soc/amlogic/aml_audio_hw.h new file mode 100644 index 000000000000..c8ddea4116b2 --- /dev/null +++ b/sound/soc/amlogic/aml_audio_hw.h @@ -0,0 +1,192 @@ +/* + * sound/soc/amlogic/aml_audio_hw.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef __AML_AUDIO_HW_H__ +#define __AML_AUDIO_HW_H__ + +#define AUDIO_CLK_GATE_ON(a) CLK_GATE_ON(a) +#define AUDIO_CLK_GATE_OFF(a) CLK_GATE_OFF(a) + +struct _aiu_clk_setting_t { + unsigned short pll; + unsigned short mux; + unsigned short devisor; +}; + +struct _aiu_958_channel_status_t { + unsigned short chstat0_l; + unsigned short chstat1_l; + unsigned short chstat0_r; + unsigned short chstat1_r; +}; + +struct audio_output_config_t { + /* audio clock */ + unsigned short clock; + /* analog output */ + unsigned short i2s_mode; + unsigned short i2s_dac_mode; + unsigned short i2s_preemphsis; + /* digital output */ + unsigned short i958_buf_start_addr; + unsigned short i958_buf_blksize; + unsigned short i958_int_flag; + unsigned short i958_mode; + unsigned short i958_sync_mode; + unsigned short i958_preemphsis; + unsigned short i958_copyright; + unsigned short bpf; + unsigned short brst; + unsigned short length; + unsigned short paddsize; + struct _aiu_958_channel_status_t chan_status; +}; + +struct _aiu_958_raw_setting_t { + unsigned short int_flag; + unsigned short bpf; + unsigned short brst; + unsigned short length; + unsigned short paddsize; + struct _aiu_958_channel_status_t *chan_stat; +}; + +enum { + I2SIN_MASTER_MODE = 0, + I2SIN_SLAVE_MODE = 1 << 0, + SPDIFIN_MODE = 1 << 1, +}; +enum { + AML_AUDIO_NA = 0, + AML_AUDIO_SPDIFIN = 1 << 0, + AML_AUDIO_SPDIFOUT = 1 << 1, + AML_AUDIO_I2SIN = 1 << 2, + AML_AUDIO_I2SOUT = 1 << 3, + AML_AUDIO_PCMIN = 1 << 4, + AML_AUDIO_PCMOUT = 1 << 5, +}; + +#define AUDIO_CLK_256FS 0 +#define AUDIO_CLK_384FS 1 + +#define AUDIO_CLK_FREQ_192 0 +#define AUDIO_CLK_FREQ_1764 1 +#define AUDIO_CLK_FREQ_96 2 +#define AUDIO_CLK_FREQ_882 3 +#define AUDIO_CLK_FREQ_48 4 +#define AUDIO_CLK_FREQ_441 5 +#define AUDIO_CLK_FREQ_32 6 + +#define AUDIO_CLK_FREQ_8 7 +#define AUDIO_CLK_FREQ_11 8 +#define AUDIO_CLK_FREQ_12 9 +#define AUDIO_CLK_FREQ_16 10 +#define AUDIO_CLK_FREQ_22 11 +#define AUDIO_CLK_FREQ_24 12 + +#define AIU_958_MODE_RAW 0 +#define AIU_958_MODE_PCM16 1 +#define AIU_958_MODE_PCM24 2 +#define AIU_958_MODE_PCM32 3 +#define AIU_958_MODE_PCM_RAW 4 + +#define AIU_I2S_MODE_PCM16 0 +#define AIU_I2S_MODE_PCM24 2 +#define AIU_I2S_MODE_PCM32 3 + +#define AUDIO_ALGOUT_DAC_FORMAT_DSP 0 +#define AUDIO_ALGOUT_DAC_FORMAT_LEFT_JUSTIFY 1 + +extern unsigned int IEC958_MODE; +extern unsigned int I2S_MODE; +extern unsigned int audio_in_source; + +void set_i2s_source(unsigned int source); +void audio_set_aiubuf(u32 addr, u32 size, unsigned int channel); +void audio_set_958outbuf(u32 addr, u32 size, int flag); +void audio_in_i2s_set_buf(u32 addr, u32 size, + u32 i2s_mode, u32 i2s_sync, u32 din_sel, u32 ch); +void audio_in_spdif_set_buf(u32 addr, u32 size, u32 src); +void audio_in_i2s_enable(int flag); +void audio_in_spdif_enable(int flag); +unsigned int audio_in_i2s_rd_ptr(void); +unsigned int audio_in_i2s_wr_ptr(void); +unsigned int audio_in_spdif_wr_ptr(void); +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE +void audio_set_i2s_mode(u32 mode, unsigned int channel); +#else +void audio_set_i2s_mode(u32 mode); +#endif +void audio_set_i2s_clk_div(void); +void audio_set_spdif_clk_div(uint div); +void audio_enable_output(int flag); +unsigned int read_i2s_rd_ptr(void); +void audio_i2s_unmute(void); +void audio_i2s_mute(void); +void aml_audio_i2s_unmute(void); +void aml_audio_i2s_mute(void); +void audio_util_set_dac_i2s_format(unsigned int format); +void audio_util_set_dac_958_format(unsigned int format); +void audio_set_958_mode( + unsigned int mode, + struct _aiu_958_raw_setting_t *set); +unsigned int read_i2s_mute_swap_reg(void); +void audio_i2s_swap_left_right(unsigned int flag); +int if_audio_out_enable(void); +int if_audio_in_i2s_enable(void); +int if_audio_in_spdif_enable(void); +void audio_out_i2s_enable(unsigned int flag); +void audio_hw_958_enable(unsigned int flag); +void audio_out_enabled(int flag); +unsigned int audio_hdmi_init_ready(void); +unsigned int read_iec958_rd_ptr(void); +void audio_in_spdif_enable(int flag); +unsigned int audio_spdifout_pg_enable(unsigned char enable); +unsigned int audio_aiu_pg_enable(unsigned char enable); +void audio_mute_left_right(unsigned int flag); +void audio_i2s_958_same_source(unsigned int same); + +extern unsigned int IEC958_mode_codec; +extern unsigned int clk81; + +/* OVERCLOCK == 1, our SOC privide 512fs mclk; + * DOWNCLOCK == 1, 128fs; + * normal mclk : 256fs + */ +#define OVERCLOCK 0 +#define DOWNCLOCK 0 + +#define IEC958_OVERCLOCK 1 + +#if (OVERCLOCK == 1) +#define MCLKFS_RATIO 512 +#elif (DOWNCLOCK == 1) +#define MCLKFS_RATIO 128 +#else +#define MCLKFS_RATIO 256 +#endif + +#define DEFAULT_SAMPLERATE 48000 +#define DEFAULT_MCLK_RATIO_SR MCLKFS_RATIO + +#define I2S_PLL_SRC 1 /* MPLL0 */ +#define MPLL_I2S_CNTL HHI_MPLL_MP0 + +#define I958_PLL_SRC 2 /* MPLL1 */ +#define MPLL_958_CNTL HHI_MPLL_MP1 + +#endif diff --git a/sound/soc/amlogic/aml_audio_hw_pcm.c b/sound/soc/amlogic/aml_audio_hw_pcm.c new file mode 100644 index 000000000000..c65ac0c416a4 --- /dev/null +++ b/sound/soc/amlogic/aml_audio_hw_pcm.c @@ -0,0 +1,726 @@ +/* + * sound/soc/amlogic/aml_audio_hw_pcm.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#define pr_fmt(fmt) "audio_pcm" fmt + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "aml_audio_hw_pcm.h" + +#include + +static unsigned int pcmin_buffer_addr; +static unsigned int pcmin_buffer_size; + +static unsigned int pcmout_buffer_addr; +static unsigned int pcmout_buffer_size; + +int valid_channel[] = { + 0x1, /* slot number 1 */ + 0x3, /* slot number 2 */ + 0x7, /* slot number 3 */ + 0xf, /* slot number 4 */ + 0x1f, /* slot number 5 */ + 0x3f, /* slot number 6 */ + 0x7f, /* slot number 7 */ + 0xff, /* slot number 8 */ + 0x1ff, /* slot number 9 */ + 0x3ff, /* slot number 10 */ + 0x7ff, /* slot number 11 */ + 0xfff, /* slot number 12 */ + 0x1fff, /* slot number 13 */ + 0x2fff, /* slot number 14 */ + 0x3fff, /* slot number 15 */ + 0x7fff /* slot number 16 */ +}; + +static uint32_t aml_read_cbus_bits(uint32_t reg, const uint32_t start, + const uint32_t len) +{ + return (aml_read_cbus(reg) >> start) & ((1L << len) - 1); +} + +static void pcm_in_register_show(void) +{ + pr_debug("PCMIN registers show:\n"); + pr_debug("\tAUDIN_FIFO1_START(0x%04x): 0x%08x\n", AUDIN_FIFO1_START, + aml_read_cbus(AUDIN_FIFO1_START)); + pr_debug("\tAUDIN_FIFO1_END(0x%04x): 0x%08x\n", AUDIN_FIFO1_END, + aml_read_cbus(AUDIN_FIFO1_END)); + pr_debug("\tAUDIN_FIFO1_PTR(0x%04x): 0x%08x\n", AUDIN_FIFO1_PTR, + aml_read_cbus(AUDIN_FIFO1_PTR)); + pr_debug("\tAUDIN_FIFO1_RDPTR(0x%04x): 0x%08x\n", AUDIN_FIFO1_RDPTR, + aml_read_cbus(AUDIN_FIFO1_RDPTR)); + pr_debug("\tAUDIN_FIFO1_CTRL(0x%04x): 0x%08x\n", AUDIN_FIFO1_CTRL, + aml_read_cbus(AUDIN_FIFO1_CTRL)); + pr_debug("\tAUDIN_FIFO1_CTRL1(0x%04x): 0x%08x\n", AUDIN_FIFO1_CTRL1, + aml_read_cbus(AUDIN_FIFO1_CTRL1)); + pr_debug("\tPCMIN_CTRL0(0x%04x): 0x%08x\n", PCMIN_CTRL0, + aml_read_cbus(PCMIN_CTRL0)); + pr_debug("\tPCMIN_CTRL1(0x%04x): 0x%08x\n", PCMIN_CTRL1, + aml_read_cbus(PCMIN_CTRL1)); +} + +void pcm_master_in_enable(struct snd_pcm_substream *substream, int flag) +{ + unsigned int dsp_mode = SND_SOC_DAIFMT_DSP_B; + unsigned int fs_offset; + + if (dsp_mode == SND_SOC_DAIFMT_DSP_A) + fs_offset = 1; + else { + fs_offset = 0; + + if (dsp_mode != SND_SOC_DAIFMT_DSP_B) + pr_err("Unsupport DSP mode\n"); + } + + /* reset fifo */ +RESET_FIFO: + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, 1 << 1, 1 << 1); + aml_write_cbus(AUDIN_FIFO1_PTR, 0); + if (aml_read_cbus(AUDIN_FIFO1_PTR) != aml_read_cbus(AUDIN_FIFO1_START)) + goto RESET_FIFO; + + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, 1 << 1, 0 << 1); + + /* reset pcmin */ + aml_cbus_update_bits(PCMIN_CTRL0, 1 << 30, 1 << 30); + aml_cbus_update_bits(PCMIN_CTRL0, 1 << 30, 0 << 30); + + /* disable fifo */ + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, 1, 0); + + /* disable pcmin */ + aml_cbus_update_bits(PCMIN_CTRL0, 1 << 31, 0 << 31); + + if (flag) { + unsigned int pcm_mode = 1; + unsigned int valid_slot = + valid_channel[substream->runtime->channels - 1]; + + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S32_LE: + pcm_mode = 3; + break; + case SNDRV_PCM_FORMAT_S24_LE: + pcm_mode = 2; + break; + case SNDRV_PCM_FORMAT_S16_LE: + pcm_mode = 1; + break; + case SNDRV_PCM_FORMAT_S8: + pcm_mode = 0; + break; + } + + /* set buffer start ptr end */ + aml_write_cbus(AUDIN_FIFO1_START, pcmin_buffer_addr); + aml_write_cbus(AUDIN_FIFO1_PTR, pcmin_buffer_addr); + aml_write_cbus(AUDIN_FIFO1_END, + pcmin_buffer_addr + pcmin_buffer_size - 8); + + /* fifo control */ + aml_write_cbus(AUDIN_FIFO1_CTRL, + (1 << 15) | /* urgent request */ + (1 << 11) | /* channel */ + (6 << 8) | /* endian */ + (2 << 3) | /* PCMIN input selection */ + (1 << 2) | /* load address */ + (0 << 1) | /* reset fifo */ + (1 << 0) /* fifo enable */ + ); + + /* fifo control1 */ + aml_write_cbus(AUDIN_FIFO1_CTRL1, + /* data destination DDR */ + (0 << 4) | + /* fifo1 din byte num. 00 : 1 byte. 01: 2 bytes. + * 10: 3 bytes. 11: 4 bytes + */ + (pcm_mode << 2) | + /* data position */ + (0 << 0) + ); + + /* pcmin control1 */ + aml_write_cbus(PCMIN_CTRL1, + /* pcmin SRC sel */ + (0 << 29) | + /* pcmin clock sel */ + (1 << 28) | + /* using negedge of PCM clock to latch the input data */ + (1 << 27) | + /* max slot number in one frame */ + (0xF << 21) | + /* data msb 16bits data */ + (0xF << 16) | + /* slot valid */ + (valid_slot << 0) + ); + + /* pcmin control0 */ + aml_write_cbus(PCMIN_CTRL0, + /* pcmin enable */ + (1 << 31) | + /* sync on clock posedge */ + (1 << 29) | + /* FS SKEW */ + (fs_offset << 16) | + /* waithing 1 system clock cycles + * then sample the PCMIN singals + */ + (0 << 4) | + /* use clock counter to do the sample */ + (0 << 3) | + /* fs inverted. */ + (0 << 2) | + /* msb first */ + (1 << 1) | + /* left justified */ + (1 << 0) + ); + + if (!pcm_out_is_enable()) { + aml_write_cbus(PCMOUT_CTRL2, + aml_read_cbus(PCMOUT_CTRL2) | + /* pcmo max slot number in one frame*/ + (0xF << 22) | + /* pcmo max bit number in one slot*/ + (0xF << 16) | + (valid_slot << 0) + ); + aml_write_cbus(PCMOUT_CTRL1, + aml_read_cbus(PCMOUT_CTRL1) | + /* use posedge of PCM clock to output data*/ + (0 << 28) | + /* invert fs phase */ + (1 << 26) | + /* invert the fs_o for master mode */ + (1 << 25) | + /* fs_o start postion frame + * slot counter number + */ + (0 << 18) | + /*fs_o start postion slot bit counter number*/ + (0 << 12) | + /*fs_o end postion frame slot counter number.*/ + (0 << 6) | + /* fs_o end postion slot bit counter number.*/ + (1 << 0) + ); + aml_write_cbus(PCMOUT_CTRL0, + aml_read_cbus(PCMOUT_CTRL0) | + (1 << 31) | /* enable */ + (1 << 29) /* master */ + ); + } + } else { + if (!pcm_out_is_enable()) { + /* disable pcmout */ + aml_cbus_update_bits(PCMOUT_CTRL0, 1 << 31, 0 << 31); + } + } + + pr_debug("PCMIN %s\n", flag ? "enable" : "disable"); + pcm_in_register_show(); +} + + +void pcm_in_enable(int flag) +{ + /* reset fifo */ + RESET_FIFO: + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, 1 << 1, 1 << 1); + aml_write_cbus(AUDIN_FIFO1_PTR, 0); + if (aml_read_cbus(AUDIN_FIFO1_PTR) != aml_read_cbus(AUDIN_FIFO1_START)) + goto RESET_FIFO; + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, 1 << 1, 0 << 1); + + /* reset pcmin */ + aml_cbus_update_bits(PCMIN_CTRL0, 1 << 30, 1 << 30); + aml_cbus_update_bits(PCMIN_CTRL0, 1 << 30, 0 << 30); + + /* disable fifo */ + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, 1, 0); + + /* disable pcmin */ + aml_cbus_update_bits(PCMIN_CTRL0, 1 << 31, 0 << 31); + + if (flag) { + /* set buffer start ptr end */ + aml_write_cbus(AUDIN_FIFO1_START, pcmin_buffer_addr); + aml_write_cbus(AUDIN_FIFO1_PTR, pcmin_buffer_addr); + aml_write_cbus(AUDIN_FIFO1_END, + pcmin_buffer_addr + pcmin_buffer_size - 8); + + /* fifo control */ + /* urgent request */ + aml_write_cbus(AUDIN_FIFO1_CTRL, (1 << 15) | + (1 << 11) | /* channel */ + (6 << 8) | /* endian */ + /* (0 << 8) | // endian */ + (2 << 3) | /* PCMIN input selection */ + (1 << 2) | /* load address */ + (0 << 1) | /* reset fifo */ + (1 << 0) /* fifo enable */ + ); + + /* fifo control1 */ + /* data destination DDR */ + aml_write_cbus(AUDIN_FIFO1_CTRL1, (0 << 4) | + (1 << 2) | /* 16bits */ + (0 << 0) /* data position */ + ); + + /* pcmin control1 */ + aml_write_cbus(PCMIN_CTRL1, (0 << 29) | /* external chip */ + (0 << 28) | /* external chip */ + /* using negedge of PCM clock to latch the input data */ + (1 << 27) | + (15 << 21) | /* slot bit msb 16 clocks per slot */ + (15 << 16) | /* data msb 16bits data */ + (1 << 0) /* slot valid */ + ); + + /* pcmin control0 */ + aml_write_cbus(PCMIN_CTRL0, (1 << 31) | /* pcmin enable */ + (1 << 29) | /* sync on clock posedge */ + (0 << 16) | /* FS SKEW */ + /* waithing 1 system clock cycles + * then sample the PCMIN singals + */ + (0 << 4) | + (0 << 3) | /* use clock counter to do the sample */ + (0 << 2) | /* fs not inverted. H = left, L = right */ + (1 << 1) | /* msb first */ + (1 << 0)); /* left justified */ + } + + pr_debug("PCMIN %s\n", flag ? "enable" : "disable"); + pcm_in_register_show(); +} + +void pcm_in_set_buf(unsigned int addr, unsigned int size) +{ + pcmin_buffer_addr = addr; + pcmin_buffer_size = size; + + pr_debug("PCMIN buffer start: 0x%08x size: 0x%08x\n", + pcmin_buffer_addr, pcmin_buffer_size); +} + +int pcm_in_is_enable(void) +{ + int value = aml_read_cbus_bits(PCMIN_CTRL0, 31, 1); + + return value; +} + +unsigned int pcm_in_rd_ptr(void) +{ + unsigned int value = aml_read_cbus(AUDIN_FIFO1_RDPTR); + + pr_debug("PCMIN AUDIN_FIFO1_RDPTR: 0x%08x\n", value); + + return value; +} + +unsigned int pcm_in_set_rd_ptr(unsigned int value) +{ + unsigned int old = aml_read_cbus(AUDIN_FIFO1_RDPTR); + + aml_write_cbus(AUDIN_FIFO1_RDPTR, value); + pr_debug("PCMIN AUDIN_FIFO1_RDPTR: 0x%08x -> 0x%08x\n", old, value); + + return old; +} + +unsigned int pcm_in_wr_ptr(void) +{ + unsigned int writing = 0; + unsigned int written = 0; + unsigned int value = 0; + + writing = aml_read_cbus(AUDIN_FIFO1_PTR); + + aml_write_cbus(AUDIN_FIFO1_PTR, 1); + written = aml_read_cbus(AUDIN_FIFO1_PTR); + pr_debug("PCMIN AUDIN_FIFO1_PTR: 0x%08x (0x%08x)\n", written, writing); + + /* value = written; */ + value = written & (~0x07); + return value; +} + +unsigned int pcm_in_fifo_int(void) +{ + unsigned int value = 0; + + value = aml_read_cbus(AUDIN_FIFO_INT); + pr_debug("PCMIN AUDIN_FIFO_INT: 0x%08x\n", value); + + return value; +} + +static void pcm_out_register_show(void) +{ + pr_debug("PCMOUT registers show:\n"); + pr_debug("\tAUDOUT_BUF0_STA(0x%04x): 0x%08x\n", AUDOUT_BUF0_STA, + aml_read_cbus(AUDOUT_BUF0_STA)); + pr_debug("\tAUDOUT_BUF0_EDA(0x%04x): 0x%08x\n", AUDOUT_BUF0_EDA, + aml_read_cbus(AUDOUT_BUF0_EDA)); + pr_debug("\tAUDOUT_BUF0_WPTR(0x%04x): 0x%08x\n", AUDOUT_BUF0_WPTR, + aml_read_cbus(AUDOUT_BUF0_WPTR)); + pr_debug("\tAUDOUT_FIFO_RPTR(0x%04x): 0x%08x\n", AUDOUT_FIFO_RPTR, + aml_read_cbus(AUDOUT_FIFO_RPTR)); + pr_debug("\tAUDOUT_CTRL(0x%04x): 0x%08x\n", AUDOUT_CTRL, + aml_read_cbus(AUDOUT_CTRL)); + pr_debug("\tAUDOUT_CTRL1(0x%04x): 0x%08x\n", AUDOUT_CTRL1, + aml_read_cbus(AUDOUT_CTRL1)); + pr_debug("\tPCMOUT_CTRL0(0x%04x): 0x%08x\n", PCMOUT_CTRL0, + aml_read_cbus(PCMOUT_CTRL0)); + pr_debug("\tPCMOUT_CTRL1(0x%04x): 0x%08x\n", PCMOUT_CTRL1, + aml_read_cbus(PCMOUT_CTRL1)); + pr_debug("\tPCMOUT_CTRL2(0x%04x): 0x%08x\n", PCMOUT_CTRL2, + aml_read_cbus(PCMOUT_CTRL2)); + pr_debug("\tPCMOUT_CTRL3(0x%04x): 0x%08x\n", PCMOUT_CTRL3, + aml_read_cbus(PCMOUT_CTRL3)); +} + +void pcm_master_out_enable(struct snd_pcm_substream *substream, int flag) +{ + unsigned int pcm_mode = 1; + unsigned int valid_slot = + valid_channel[substream->runtime->channels - 1]; + unsigned int dsp_mode = SND_SOC_DAIFMT_DSP_B; + unsigned int bit_offset_s, slot_offset_s, bit_offset_e, slot_offset_e; + + if (dsp_mode == SND_SOC_DAIFMT_DSP_A) { + bit_offset_s = 0xF; + slot_offset_s = 0xF; + bit_offset_e = 0; + slot_offset_e = 0; + } else { + if (dsp_mode != SND_SOC_DAIFMT_DSP_B) + pr_err("Unsupport DSP mode\n"); + + bit_offset_s = 0; + slot_offset_s = 0; + bit_offset_e = 0; + slot_offset_e = 1; + } + + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S32_LE: + pcm_mode = 3; + break; + case SNDRV_PCM_FORMAT_S24_LE: + pcm_mode = 2; + break; + case SNDRV_PCM_FORMAT_S16_LE: + pcm_mode = 1; + break; + case SNDRV_PCM_FORMAT_S8: + pcm_mode = 0; + break; + } + + /* reset fifo */ + aml_cbus_update_bits(AUDOUT_CTRL, 1 << 30, 1 << 30); + aml_cbus_update_bits(AUDOUT_CTRL, 1 << 30, 1 << 30); + /* disable fifo */ + aml_cbus_update_bits(AUDOUT_CTRL, 1 << 31, 0 << 31); + + if (!pcm_in_is_enable()) { + /* reset pcmout */ + aml_cbus_update_bits(PCMOUT_CTRL0, 1 << 30, 1 << 30); + aml_cbus_update_bits(PCMOUT_CTRL0, 1 << 30, 0 << 30); + /* disable pcmout */ + aml_cbus_update_bits(PCMOUT_CTRL0, 1 << 31, 0 << 31); + } + + if (flag) { + /* set buffer start ptr end */ + aml_write_cbus(AUDOUT_BUF0_STA, pcmout_buffer_addr); + aml_write_cbus(AUDOUT_BUF0_WPTR, pcmout_buffer_addr); + aml_write_cbus(AUDOUT_BUF0_EDA, + pcmout_buffer_addr + pcmout_buffer_size - 8); + + /* fifo control */ + aml_write_cbus(AUDOUT_CTRL, (0 << 31) | /* fifo enable */ + (0 << 30) | /* soft reset */ + (1 << 29) | /* load address */ + /* use cbus AUDOUT BUFFER0 write pointer + * as the AUDOUT FIFO write pointer + */ + (0 << 22) | + (52 << 15) | /* data request size */ + (64 << 8) | /* buffer level to keep */ + (0 << 7) | /* buffer level control */ + (1 << 6) | /* DMA mode */ + (1 << 5) | /* circular buffer */ + (0 << 4) | /* use register set 0 always */ + (1 << 3) | /* urgent request */ + (6 << 0) /* endian */ + ); + + aml_write_cbus(AUDOUT_CTRL, (1 << 31) |/* fifo enable */ + (0 << 30) | /* soft reset */ + (1 << 29) | /* load address */ + /* use cbus AUDOUT BUFFER0 write pointer + * as the AUDOUT FIFO write pointer + */ + (1 << 22) | + (56 << 15) | /* data request size */ + (64 << 8) | /* buffer level to keep */ + (1 << 7) | /* buffer level control */ + (1 << 6) | /* DMA mode */ + (1 << 5) | /* circular buffer */ + (0 << 4) | /* use register set 0 always */ + (1 << 3) | /* urgent request */ + (6 << 0) /* endian */ + ); + + /* pcmout control3 */ + aml_write_cbus(PCMOUT_CTRL3, 0); /* mute constant */ + + /* pcmout control2 */ + /* FS * 16 * 16 = BCLK */ + aml_write_cbus(PCMOUT_CTRL2, + /* underrun use mute constant */ + (0 << 29) | + /* pcmo max slot number in one frame */ + (0xF << 22) | + /* pcmo max bit number in one slot */ + (0xF << 16) | + /* pcmo valid slot. each bit for one slot */ + (valid_slot << 0) + ); + + /* pcmout control1 */ + aml_write_cbus(PCMOUT_CTRL1, + /* pcmo output data byte number. 00 : 8bits. + * 01: 16bits. 10: 24bits. 11: 32bits + */ + (pcm_mode << 30) | + /* use posedge of PCM clock to output data */ + (0 << 28) | + /* pcmo slave parts clock invert */ + (0 << 27) | + /* invert fs phase */ + (1 << 26) | + /* invert the fs_o for master mode */ + (1 << 25) | + /* fs_o start postion frame slot counter number */ + (bit_offset_s << 18) | + /* fs_o start postion slot bit counter number.*/ + (slot_offset_s << 12) | + /* fs_o end postion frame slot counter number. */ + (bit_offset_e << 6) | + /* fs_o end postion slot bit counter number. */ + (slot_offset_e << 0) + ); + + /* pcmout control0 */ + aml_write_cbus(PCMOUT_CTRL0, + (1 << 31) | /* enable */ + (1 << 29) | /* master */ + (1 << 28) | /* sync on clock rising edge */ + /* system clock sync at clock edge of pcmout clock. + * 0 = sync on clock counter. + */ + (0 << 27) | + /* system clock sync at counter number + * if sync on clock counter + */ + (0 << 15) | + (1 << 14) | /* msb first */ + (1 << 13) | /* left justified */ + (0 << 12) | /* data position */ + /*slave mode, sync fs with the slot bit counter.*/ + (0 << 6) | + /*slave mode, sync fs with frame slot counter.*/ + (0 << 0) + ); + } + + pr_debug("PCMOUT %s\n", flag ? "enable" : "disable"); + pcm_out_register_show(); +} + +void pcm_out_enable(int flag) +{ + /* reset fifo */ + aml_cbus_update_bits(AUDOUT_CTRL, 1 << 30, 1 << 30); + aml_cbus_update_bits(AUDOUT_CTRL, 1 << 30, 1 << 30); + + /* reset pcmout */ + aml_cbus_update_bits(PCMOUT_CTRL0, 1 << 30, 1 << 30); + aml_cbus_update_bits(PCMOUT_CTRL0, 1 << 30, 0 << 30); + + /* disable fifo */ + aml_cbus_update_bits(AUDOUT_CTRL, 1 << 31, 0 << 31); + + /* disable pcmout */ + aml_cbus_update_bits(PCMOUT_CTRL0, 1 << 31, 0 << 31); + + if (flag) { + /* set buffer start ptr end */ + aml_write_cbus(AUDOUT_BUF0_STA, pcmout_buffer_addr); + aml_write_cbus(AUDOUT_BUF0_WPTR, pcmout_buffer_addr); + aml_write_cbus(AUDOUT_BUF0_EDA, + pcmout_buffer_addr + pcmout_buffer_size - 8); + + /* fifo control */ + aml_write_cbus(AUDOUT_CTRL, (0 << 31) | /* fifo enable */ + (0 << 30) | /* soft reset */ + (1 << 29) | /* load address */ + /* use cbus AUDOUT BUFFER0 write pointer + * as the AUDOUT FIFO write pointer + */ + (0 << 22) | + (52 << 15) | /* data request size */ + (64 << 8) | /* buffer level to keep */ + (0 << 7) | /* buffer level control */ + (1 << 6) | /* DMA mode */ + (1 << 5) | /* circular buffer */ + (0 << 4) | /* use register set 0 always */ + (1 << 3) | /* urgent request */ + (6 << 0)); /* endian */ + + aml_write_cbus(AUDOUT_CTRL, (1 << 31) | /* fifo enable */ + (0 << 30) | /* soft reset */ + (0 << 29) | /* load address */ + /* use cbus AUDOUT BUFFER0 write pointer + * as the AUDOUT FIFO write pointer + */ + (0 << 22) | + (52 << 15) | /* data request size */ + (64 << 8) | /* buffer level to keep */ + (0 << 7) | /* buffer level control */ + (1 << 6) | /* DMA mode */ + (1 << 5) | /* circular buffer */ + (0 << 4) | /* use register set 0 always */ + (1 << 3) | /* urgent request */ + (6 << 0)); /* endian */ + + /* pcmout control3 */ + aml_write_cbus(PCMOUT_CTRL3, 0); /* mute constant */ + + /* pcmout control2 */ + /* 1 channel per frame */ + aml_write_cbus(PCMOUT_CTRL2, (0 << 29) | (0 << 22) | + (15 << 16) | /* 16 bits per slot */ + (1 << 0) /* enable 1 slot */ + ); + + /* pcmout control1 */ + /* use posedge of PCM clock to output data */ + aml_write_cbus(PCMOUT_CTRL1, (1 << 30) | (0 << 28) | + /* use negedge of pcm clock to check the fs */ + (1 << 27)); + + /* pcmout control0 */ + /* slave */ + aml_write_cbus(PCMOUT_CTRL0, (1 << 31) | (0 << 29) | + /* sync on clock rising edge */ + (1 << 28) | + /* data sample mode */ + (0 << 27) | + /* sync on 4 system clock later ? */ + (1 << 15) | + /* msb first */ + (1 << 14) | + /* left justified */ + (1 << 13) | + /* data position */ + (0 << 12) | + /* sync fs with the slot bit counter. */ + (3 << 6) | + /* sync fs with frame slot counter. */ + (0 << 0)); + } + + pr_debug("PCMOUT %s\n", flag ? "enable" : "disable"); + pcm_out_register_show(); +} + +void pcm_out_mute(int flag) +{ + int value = flag ? 1 : 0; + + aml_cbus_update_bits(PCMOUT_CTRL2, 1 << 31, value << 31); +} + +void pcm_out_set_buf(unsigned int addr, unsigned int size) +{ + pcmout_buffer_addr = addr; + pcmout_buffer_size = size; + + pr_debug("PCMOUT buffer addr: 0x%08x end: 0x%08x\n", + pcmout_buffer_addr, pcmout_buffer_size); +} + +int pcm_out_is_enable(void) +{ + int value = aml_read_cbus_bits(PCMOUT_CTRL0, 31, 1); + + return value; +} + +int pcm_out_is_mute(void) +{ + int value = aml_read_cbus_bits(PCMOUT_CTRL2, 31, 1); + + return value; +} + +unsigned int pcm_out_rd_ptr(void) +{ + unsigned int value = aml_read_cbus(AUDOUT_FIFO_RPTR); + + pr_debug("PCMOUT read pointer: 0x%08x\n", value); + + return value; +} + +unsigned int pcm_out_wr_ptr(void) +{ + unsigned int value = 0; + + value = aml_read_cbus(AUDOUT_BUF0_WPTR); + pr_debug("PCMOUT write pointer: 0x%08x\n", value); + return value; +} + +unsigned int pcm_out_set_wr_ptr(unsigned int value) +{ + unsigned int old = aml_read_cbus(AUDOUT_BUF0_WPTR); + + aml_write_cbus(AUDOUT_BUF0_WPTR, value); + pr_debug("PCMOUT write pointer: 0x%08x -> 0x%08x\n", old, value); + + return old; +} diff --git a/sound/soc/amlogic/aml_audio_hw_pcm.h b/sound/soc/amlogic/aml_audio_hw_pcm.h new file mode 100644 index 000000000000..a7dbc30a4c61 --- /dev/null +++ b/sound/soc/amlogic/aml_audio_hw_pcm.h @@ -0,0 +1,43 @@ +/* + * sound/soc/amlogic/aml_audio_hw_pcm.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef __AML_PCM_HW_H__ +#define __AML_PCM_HW_H__ + +#include "sound/asound.h" +#include + +void pcm_in_enable(int flag); +void pcm_in_set_buf(unsigned int addr, unsigned int size); +int pcm_in_is_enable(void); +unsigned int pcm_in_rd_ptr(void); +unsigned int pcm_in_wr_ptr(void); +unsigned int pcm_in_set_rd_ptr(unsigned int value); +unsigned int pcm_in_fifo_int(void); + +void pcm_out_enable(int flag); +void pcm_out_mute(int flag); +void pcm_out_set_buf(unsigned int addr, unsigned int size); +int pcm_out_is_enable(void); +int pcm_out_is_mute(void); +unsigned int pcm_out_rd_ptr(void); +unsigned int pcm_out_wr_ptr(void); +unsigned int pcm_out_set_wr_ptr(unsigned int value); + +void pcm_master_in_enable(struct snd_pcm_substream *substream, int flag); +void pcm_master_out_enable(struct snd_pcm_substream *substream, int flag); +#endif diff --git a/sound/soc/amlogic/aml_dmic.c b/sound/soc/amlogic/aml_dmic.c new file mode 100644 index 000000000000..dfb2f63a98dd --- /dev/null +++ b/sound/soc/amlogic/aml_dmic.c @@ -0,0 +1,210 @@ +/* + * sound/soc/amlogic/aml_dmic.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "aml_snd_dmic" + +#define PDM_CTRL 0x40 +/* process_header_copy_only_on */ +#define CIC_DEC8OR16_SEL 2 +#define PDM_HPF_BYPASS 1 +#define PDM_ENABLE 0 +/* process_header_copy_only_off */ +#define PDM_IN_CTRL 0x42 +/* process_header_copy_only_on */ +#define PDMIN_REV 18 +#define PDMCLK_REV 17 +#define GET_STA_EN 16 +#define SAMPLE_CNT 8 +/* process_header_copy_only_off */ +#define PDM_VOL_CTRL 0x43 +/* process_header_copy_only_on */ +#define PDML_INV 1 +#define PDMR_INV 0 +/* process_header_copy_only_off */ +#define PDM_VOL_GAIN_L 0x44 +#define PDM_VOL_GAIN_R 0x45 +#define PDM_STATUS 0x50 + +struct aml_dmic_priv { + void __iomem *pdm_base; + struct pinctrl *dmic_pins; + struct clk *clk_pdm; + struct clk *clk_mclk; +}; + +static int aml_dmic_codec_probe(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct snd_soc_dai_driver aml_dmic_dai = { + .name = "dmic-hifi", + .capture = { + .stream_name = "dmic Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S32_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static const struct snd_soc_dapm_widget aml_dmic_dapm_widgets[] = { + SND_SOC_DAPM_AIF_OUT("DMIC AIFIN", "dmic Capture", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_INPUT("DMIC IN"), +}; + +static const struct snd_soc_dapm_route dmic_intercon[] = { + {"DMIC AIFIN", NULL, "DMIC IN"}, +}; + +static const struct snd_soc_codec_driver aml_dmic = { + .probe = aml_dmic_codec_probe, + .component_driver = { + .dapm_widgets = aml_dmic_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aml_dmic_dapm_widgets), + .dapm_routes = dmic_intercon, + .num_dapm_routes = ARRAY_SIZE(dmic_intercon), + } +}; + +static const char *const gate_names[] = { + "pdm", +}; + +static int aml_dmic_platform_probe(struct platform_device *pdev) +{ + struct resource *res; + struct clk *clk_gate; + struct aml_dmic_priv *dmic_priv; + unsigned int val; + int ret; + + dev_info(&pdev->dev, "Dmic probe!\n"); + + clk_gate = devm_clk_get(&pdev->dev, gate_names[0]); + if (IS_ERR(clk_gate)) { + dev_err(&pdev->dev, "Can't get aml dmic gate\n"); + return PTR_ERR(clk_gate); + } + clk_prepare_enable(clk_gate); + + dmic_priv = devm_kzalloc(&pdev->dev, + sizeof(struct aml_dmic_priv), GFP_KERNEL); + if (dmic_priv == NULL) + return -ENOMEM; + + dmic_priv->dmic_pins = + devm_pinctrl_get_select(&pdev->dev, "aml_dmic_pins"); + if (IS_ERR(dmic_priv->dmic_pins)) { + dev_err(&pdev->dev, "pinctrls error!\n"); + return -EINVAL; + } + + dev_set_drvdata(&pdev->dev, dmic_priv); + dmic_priv->clk_mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(dmic_priv->clk_mclk)) { + dev_err(&pdev->dev, "Can't retrieve clk_mclk clock\n"); + ret = PTR_ERR(dmic_priv->clk_mclk); + goto err; + } + + dmic_priv->clk_pdm = devm_clk_get(&pdev->dev, "pdm"); + if (IS_ERR(dmic_priv->clk_pdm)) { + dev_err(&pdev->dev, "Can't retrieve clk_pdm clock\n"); + ret = PTR_ERR(dmic_priv->clk_pdm); + goto err; + } + + ret = clk_set_parent(dmic_priv->clk_pdm, dmic_priv->clk_mclk); + if (ret) { + pr_err("Can't set dmic pdm clk parent err: %d\n", ret); + goto err; + } + + ret = clk_set_rate(dmic_priv->clk_pdm, + clk_get_rate(dmic_priv->clk_mclk)/4); + if (ret) { + pr_err("Can't set dmic pdm clock rate, err: %d\n", ret); + goto err; + } + + ret = clk_prepare_enable(dmic_priv->clk_pdm); + if (ret) { + pr_err("Can't enable dmic pdm clock: %d\n", ret); + goto err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dmic_priv->pdm_base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (IS_ERR(dmic_priv->pdm_base)) { + dev_err(&pdev->dev, "Unable to map pdm_base\n"); + return PTR_ERR(dmic_priv->pdm_base); + } + + writel(0x100000, dmic_priv->pdm_base + (PDM_VOL_GAIN_L<<2)); + writel(0x100000, dmic_priv->pdm_base + (PDM_VOL_GAIN_R<<2)); + + val = readl(dmic_priv->pdm_base + (PDM_CTRL<<2)); + writel(1, dmic_priv->pdm_base + (PDM_CTRL<<2)); + val = readl(dmic_priv->pdm_base + (PDM_CTRL<<2)); + + return snd_soc_register_codec(&pdev->dev, + &aml_dmic, &aml_dmic_dai, 1); +err: + return ret; +} + +static int aml_dmic_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static const struct of_device_id amlogic_dmic_of_match[] = { + {.compatible = "aml, aml_snd_dmic"}, + {} +}; + +static struct platform_driver aml_dmic_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = amlogic_dmic_of_match, + }, + .probe = aml_dmic_platform_probe, + .remove = aml_dmic_platform_remove, +}; + +module_platform_driver(aml_dmic_driver); + +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_DESCRIPTION("amlogic digital mic driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amlogic/aml_i2s.c b/sound/soc/amlogic/aml_i2s.c new file mode 100644 index 000000000000..33f2cb2b4041 --- /dev/null +++ b/sound/soc/amlogic/aml_i2s.c @@ -0,0 +1,1281 @@ +/* + * sound/soc/amlogic/aml_i2s.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Amlogic headers */ +#include +#include "aml_i2s.h" +#include "aml_spdif_dai.h" +#include "aml_audio_hw.h" +#include +#include + +/* + * timer for i2s data update, hw timer, hrtimer, timer + * once select only one way to update + */ +/*#define USE_HW_TIMER*/ +/*#define USE_HRTIMER*/ +#ifdef USE_HW_TIMER +#define XRUN_NUM 100 /*1ms*100=100ms timeout*/ +#else +#define XRUN_NUM 10 /*10ms*10=100ms timeout*/ +#endif + +unsigned long aml_i2s_playback_start_addr; +EXPORT_SYMBOL(aml_i2s_playback_start_addr); + +unsigned long aml_i2s_playback_phy_start_addr; +EXPORT_SYMBOL(aml_i2s_playback_phy_start_addr); + +unsigned long aml_i2s_alsa_write_addr; +EXPORT_SYMBOL(aml_i2s_alsa_write_addr); + +unsigned int aml_i2s_playback_channel = 2; +EXPORT_SYMBOL(aml_i2s_playback_channel); + +unsigned int aml_i2s_playback_format = 16; +EXPORT_SYMBOL(aml_i2s_playback_format); + +static int trigger_underrun; +void aml_audio_hw_trigger(void) +{ + trigger_underrun = 1; +} +EXPORT_SYMBOL(aml_audio_hw_trigger); + +#ifndef USE_HRTIMER +static void aml_i2s_timer_callback(unsigned long data); +#endif + +/*--------------------------------------------------------------------------* + * Hardware definition + *--------------------------------------------------------------------------* + * TODO: These values were taken from the AML platform driver, check + * them against real values for AML + */ +static const struct snd_pcm_hardware aml_i2s_hardware = { + .info = +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | +#endif + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE, + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024 * 2, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 128 * 1024 * 2 * 2, + + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 8, +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP + .fifo_size = 4, +#else + .fifo_size = 0, +#endif +}; + +static const struct snd_pcm_hardware aml_i2s_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE, + + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 64 * 1024, + + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 8, + .fifo_size = 0, +}; + +static unsigned int period_sizes[] = { + 64, 128, 256, 512, 1024, 2048, 4096, 8192, + 16384, 32768, 65536, 65536 * 2, 65536 * 4 +}; + +static struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = { + .count = ARRAY_SIZE(period_sizes), + .list = period_sizes, + .mask = 0 +}; + +/*-------------------------------------------------------------------------- + *-------------------------------------------------------------------------- + * Helper functions + *-------------------------------------------------------------------------- + */ +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP +static int aml_i2s_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = aml_i2s_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + buf->area = dmam_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + dev_info(pcm->card->dev, "aml-pcm %d: playback preallocate_dma_buffer: area=%p, addr=%p, size=%ld\n", + stream, (void *) buf->area, (void *) buf->addr, size); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} +#else +static int aml_i2s_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct aml_audio_buffer *tmp_buf = NULL; + size_t size = 0; + + tmp_buf = kzalloc(sizeof(struct aml_audio_buffer), GFP_KERNEL); + if (tmp_buf == NULL) { + /*dev_err(&pcm->card->dev, "allocate tmp buffer error\n");*/ + return -ENOMEM; + } + buf->private_data = tmp_buf; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* malloc DMA buffer */ + size = aml_i2s_hardware.buffer_bytes_max; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + /* one size for i2s output, another for 958, + *and 128 for alignment + */ + buf->area = dma_alloc_coherent(pcm->card->dev, size + 4096, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + dev_err(pcm->card->dev, "alloc playback DMA buffer error\n"); + kfree(tmp_buf); + buf->private_data = NULL; + return -ENOMEM; + } + buf->bytes = size; + /* malloc tmp buffer */ + size = aml_i2s_hardware.buffer_bytes_max; + tmp_buf->buffer_start = kzalloc(size, GFP_KERNEL); + if (tmp_buf->buffer_start == NULL) { + dev_err(pcm->card->dev, "alloc playback tmp buffer error\n"); + kfree(tmp_buf); + buf->private_data = NULL; + return -ENOMEM; + } + tmp_buf->buffer_size = size; + + } else { + /* malloc DMA buffer */ + size = aml_i2s_capture.buffer_bytes_max; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->area = dma_alloc_coherent(pcm->card->dev, size * 2, + &buf->addr, GFP_KERNEL); + + if (!buf->area) { + dev_err(pcm->card->dev, "alloc capture DMA buffer error\n"); + kfree(tmp_buf); + buf->private_data = NULL; + return -ENOMEM; + } + buf->bytes = size; + /* malloc tmp buffer */ + size = aml_i2s_capture.period_bytes_max; + tmp_buf->buffer_start = kzalloc(size, GFP_KERNEL); + if (tmp_buf->buffer_start == NULL) { + dev_err(pcm->card->dev, "alloc capture tmp buffer error\n"); + kfree(tmp_buf); + buf->private_data = NULL; + return -ENOMEM; + } + tmp_buf->buffer_size = size; + } + + return 0; + +} +#endif +/*-------------------------------------------------------------------------- + * ISR + *-------------------------------------------------------------------------- + *-------------------------------------------------------------------------- + * i2s operations + *-------------------------------------------------------------------------- + */ +static int aml_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = runtime->private_data; + struct audio_stream *s = &prtd->s; + + /* + * this may get called several times by oss emulation + * with different params + */ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + s->I2S_addr = runtime->dma_addr; + + /* + * Both capture and playback need to reset the last ptr + * to the start address, playback and capture use + * different address calculate, so we reset the different + * start address to the last ptr + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* s->last_ptr must initialized as dma buffer's start addr */ + s->last_ptr = runtime->dma_addr; + } else { + + s->last_ptr = 0; + } + + return 0; +} + +static int aml_i2s_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int aml_i2s_prepare(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = runtime->private_data; + struct audio_stream *s = &prtd->s; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct aml_audio_buffer *tmp_buf = buf->private_data; + + if (s && s->device_type == AML_AUDIO_I2SOUT && trigger_underrun) { + dev_info(substream->pcm->card->dev, "clear i2s out trigger underrun\n"); + trigger_underrun = 0; + } + if (s && s->device_type == AML_AUDIO_I2SOUT) { + aml_i2s_playback_channel = runtime->channels; + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) + aml_i2s_playback_format = 16; + else if (runtime->format == SNDRV_PCM_FORMAT_S32_LE) + aml_i2s_playback_format = 32; + else if (runtime->format == SNDRV_PCM_FORMAT_S24_LE) + aml_i2s_playback_format = 24; + } + tmp_buf->cached_len = 0; + return 0; +} + +#ifdef USE_HW_TIMER +int hw_timer_init; +static irqreturn_t audio_isr_handler(int irq, void *data) +{ + struct aml_runtime_data *prtd = data; + struct snd_pcm_substream *substream = prtd->substream; + + aml_i2s_timer_callback((unsigned long)substream); + return IRQ_HANDLED; +} + +static int snd_free_hw_timer_irq(void *data) +{ + free_irq(INT_TIMER_D, data); + return 0; +} + +static int snd_request_hw_timer(void *data) +{ + int ret = 0; + + if (hw_timer_init == 0) { + aml_write_cbus(ISA_TIMERD, TIMER_COUNT); + aml_cbus_update_bits(ISA_TIMER_MUX, 3 << 6, + TIMERD_RESOLUTION << 6); + aml_cbus_update_bits(ISA_TIMER_MUX, 1 << 15, TIMERD_MODE << 15); + aml_cbus_update_bits(ISA_TIMER_MUX, 1 << 19, 1 << 19); + hw_timer_init = 1; + } + ret = request_irq(INT_TIMER_D, audio_isr_handler, + IRQF_SHARED, "timerd_irq", data); + if (ret < 0) { + pr_err("audio hw interrupt register fail\n"); + return -1; + } + return 0; +} +#endif + +#ifdef USE_HRTIMER +static void aml_i2s_hrtimer_set_rate(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = runtime->private_data; + + prtd->wakeups_per_second = ktime_set(0, 1000000000 / (runtime->rate)); +} + +static enum hrtimer_restart aml_i2s_hrtimer_callback(struct hrtimer *timer) +{ + struct aml_runtime_data *prtd = container_of(timer, + struct aml_runtime_data, hrtimer); + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream *s = &prtd->s; + unsigned int last_ptr, size; + unsigned long flags = 0; + + if (prtd->active == 0) { + hrtimer_forward_now(timer, prtd->wakeups_per_second); + return HRTIMER_RESTART; + } + + spin_lock_irqsave(&prtd->timer_lock, flags); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + last_ptr = read_i2s_rd_ptr(); + if (last_ptr < s->last_ptr) + size = runtime->dma_bytes + last_ptr - s->last_ptr; + else + size = last_ptr - s->last_ptr; + s->last_ptr = last_ptr; + s->size += bytes_to_frames(substream->runtime, size); + if (s->size >= runtime->period_size) { + s->size %= runtime->period_size; + snd_pcm_period_elapsed(substream); + } + } else { + last_ptr = (audio_in_i2s_wr_ptr() - s->I2S_addr) / 2; + if (last_ptr < s->last_ptr) + size = runtime->dma_bytes + last_ptr - s->last_ptr; + else + size = last_ptr - s->last_ptr; + + s->last_ptr = last_ptr; + s->size += bytes_to_frames(runtime, size); + if (s->size >= runtime->period_size) { + s->size %= runtime->period_size; + snd_pcm_period_elapsed(substream); + } + } + spin_unlock_irqrestore(&prtd->timer_lock, flags); + hrtimer_forward_now(timer, prtd->wakeups_per_second); + + return HRTIMER_RESTART; +} + +#endif + +static void start_timer(struct aml_runtime_data *prtd) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&prtd->timer_lock, flags); + if (!prtd->active) { +#ifndef USE_HW_TIMER +#ifdef USE_HRTIMER + hrtimer_start(&prtd->hrtimer, prtd->wakeups_per_second, + HRTIMER_MODE_REL); +#else + prtd->timer.expires = jiffies + 1; + add_timer(&prtd->timer); +#endif +#endif + prtd->active = 1; + prtd->xrun_num = 0; + } + spin_unlock_irqrestore(&prtd->timer_lock, flags); + +} + +static void stop_timer(struct aml_runtime_data *prtd) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&prtd->timer_lock, flags); + if (prtd->active) { +#ifndef USE_HW_TIMER +#ifdef USE_HRTIMER + hrtimer_cancel(&prtd->hrtimer); +#else + del_timer(&prtd->timer); +#endif +#endif + prtd->active = 0; + prtd->xrun_num = 0; + } + spin_unlock_irqrestore(&prtd->timer_lock, flags); +} + + +static int aml_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *rtd = substream->runtime; + struct aml_runtime_data *prtd = rtd->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +#ifdef USE_HRTIMER + aml_i2s_hrtimer_set_rate(substream); +#endif + start_timer(prtd); + break; /* SNDRV_PCM_TRIGGER_START */ + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + stop_timer(prtd); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t aml_i2s_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = runtime->private_data; + struct audio_stream *s = &prtd->s; + + unsigned int addr, ptr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (s->device_type == AML_AUDIO_I2SOUT) + ptr = read_i2s_rd_ptr(); + else + ptr = read_iec958_rd_ptr(); + addr = ptr - s->I2S_addr; + return bytes_to_frames(runtime, addr); + } + + { + if (s->device_type == AML_AUDIO_I2SIN) + ptr = audio_in_i2s_wr_ptr(); + else + ptr = audio_in_spdif_wr_ptr(); + addr = ptr - s->I2S_addr; + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) + return bytes_to_frames(runtime, addr) >> 1; + else + return bytes_to_frames(runtime, addr); + } + + return 0; +} + +#ifndef USE_HRTIMER +static void aml_i2s_timer_callback(unsigned long data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = NULL; + struct audio_stream *s = NULL; + int elapsed = 0; + unsigned int last_ptr, size = 0; + unsigned long flags = 0; + + if (runtime == NULL || runtime->private_data == NULL) + return; + + prtd = runtime->private_data; + s = &prtd->s; + + if (prtd->active == 0) + return; + + spin_lock_irqsave(&prtd->timer_lock, flags); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (s->device_type == AML_AUDIO_I2SOUT) + last_ptr = read_i2s_rd_ptr(); + else + last_ptr = read_iec958_rd_ptr(); + if (last_ptr < s->last_ptr) { + size = + runtime->dma_bytes + last_ptr - + (s->last_ptr); + } else { + size = last_ptr - (s->last_ptr); + } + s->last_ptr = last_ptr; + s->size += bytes_to_frames(substream->runtime, size); + if (s->size >= runtime->period_size) { + s->size %= runtime->period_size; + elapsed = 1; + } + } else { + if (s->device_type == AML_AUDIO_I2SIN) + last_ptr = audio_in_i2s_wr_ptr(); + else + last_ptr = audio_in_spdif_wr_ptr(); + + if (last_ptr < s->last_ptr) { + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) + size = runtime->dma_bytes + + (last_ptr - (s->last_ptr)) / 2; + else + size = runtime->dma_bytes + + (last_ptr - (s->last_ptr)); + prtd->xrun_num = 0; + } else if (last_ptr == s->last_ptr) { + if (prtd->xrun_num++ > XRUN_NUM) { + prtd->xrun_num = 0; + s->size = runtime->period_size; + } + } else { + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) + size = (last_ptr - (s->last_ptr)) / 2; + else + size = last_ptr - (s->last_ptr); + prtd->xrun_num = 0; + } + + s->last_ptr = last_ptr; + s->size += bytes_to_frames(substream->runtime, size); + if (s->size >= runtime->period_size) { + s->size %= runtime->period_size; + elapsed = 1; + } + } + +#ifndef USE_HW_TIMER + mod_timer(&prtd->timer, jiffies + 1); +#endif + + spin_unlock_irqrestore(&prtd->timer_lock, flags); + if (elapsed) + snd_pcm_period_elapsed(substream); +} + +#endif + +static int aml_i2s_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = runtime->private_data; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct audio_stream *s = &prtd->s; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + snd_soc_set_runtime_hwparams(substream, &aml_i2s_hardware); + if (s->device_type == AML_AUDIO_I2SOUT) { + aml_i2s_playback_start_addr = (unsigned long)buf->area; + aml_i2s_playback_phy_start_addr = buf->addr; + } + } else { + snd_soc_set_runtime_hwparams(substream, &aml_i2s_capture); + } + + /* ensure that period size is a multiple of 32bytes */ + ret = + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &hw_constraints_period_sizes); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "set period bytes constraint error\n"); + goto out; + } + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(substream->pcm->card->dev, "set period error\n"); + goto out; + } + if (!prtd) { + prtd = kzalloc(sizeof(struct aml_runtime_data), GFP_KERNEL); + if (prtd == NULL) { + dev_err(substream->pcm->card->dev, "alloc aml_runtime_data error\n"); + ret = -ENOMEM; + goto out; + } + prtd->substream = substream; + runtime->private_data = prtd; + } + + spin_lock_init(&prtd->timer_lock); + +#if defined(USE_HW_TIMER) + ret = snd_request_hw_timer(prtd); + if (ret < 0) { + dev_err(substream->pcm->card->dev, "request audio hw timer failed\n"); + goto out; + } +#elif defined(USE_HRTIMER) + hrtimer_init(&prtd->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + prtd->hrtimer.function = aml_i2s_hrtimer_callback; + pr_info("hrtimer inited..\n"); +#else + init_timer(&prtd->timer); + prtd->timer.function = &aml_i2s_timer_callback; + prtd->timer.data = (unsigned long)substream; +#endif + + out: + return ret; +} + +static int aml_i2s_close(struct snd_pcm_substream *substream) +{ + struct aml_runtime_data *prtd = substream->runtime->private_data; + +#ifdef USE_HW_TIMER + snd_free_hw_timer_irq(prtd); +#endif + kfree(prtd); + prtd = NULL; + + return 0; +} + +#ifndef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP +static int aml_i2s_copy_playback(struct snd_pcm_runtime *runtime, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count, + struct snd_pcm_substream *substream) +{ + int res = 0; + int n; + unsigned long offset = frames_to_bytes(runtime, pos); + char *hwbuf = runtime->dma_area + offset; + struct aml_runtime_data *prtd = runtime->private_data; + struct snd_dma_buffer *buffer = &substream->dma_buffer; + struct aml_audio_buffer *tmp_buf = buffer->private_data; + void *ubuf = tmp_buf->buffer_start; + struct audio_stream *s = &prtd->s; + struct device *dev = substream->pcm->card->dev; +#ifndef CONFIG_AMLOGIC_SND_SPLIT_MODE + int i = 0, j = 0; + int align = runtime->channels * 32; + int cached_len = tmp_buf->cached_len; + char *cache_buffer_bytes = tmp_buf->cache_buffer_bytes; +#endif + n = frames_to_bytes(runtime, count); + if (n > tmp_buf->buffer_size) { + dev_err(dev, "FATAL_ERR:UserData/%d > buffer_size/%d\n", + n, tmp_buf->buffer_size); + return -EFAULT; + } + res = copy_from_user(ubuf, buf, n); + if (res) + return -EFAULT; + +#ifndef CONFIG_AMLOGIC_SND_SPLIT_MODE + /*mask align byte(64 or 256)*/ + if ((cached_len != 0 || (n % align) != 0)) { + int byte_size = n; + int total_len; + int output_len; + int next_cached_len; + char cache_buffer_bytes_tmp[256]; + + offset -= cached_len; + hwbuf = runtime->dma_area + offset; + + total_len = byte_size + cached_len; + output_len = total_len & (~(align - 1)); + next_cached_len = total_len - output_len; + + if (next_cached_len) + memcpy((void *)cache_buffer_bytes_tmp, + (void *)((char *)ubuf + + byte_size - next_cached_len), + next_cached_len); + memmove((void *)((char *)ubuf + cached_len), + (void *)ubuf, output_len - cached_len); + if (cached_len) + memcpy((void *)ubuf, + (void *)cache_buffer_bytes, cached_len); + if (next_cached_len) + memcpy((void *)cache_buffer_bytes, + (void *)cache_buffer_bytes_tmp, + next_cached_len); + + tmp_buf->cached_len = next_cached_len; + n = output_len; + } + /*end of mask*/ +#endif + + if (s->device_type == AML_AUDIO_I2SOUT) + aml_i2s_alsa_write_addr = offset; + + if (access_ok(VERIFY_READ, buf, frames_to_bytes(runtime, count))) { +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + memcpy(hwbuf, ubuf, n); +#else + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) { + int16_t *tfrom, *to; + + tfrom = (int16_t *) ubuf; + to = (int16_t *) hwbuf; + + if (runtime->channels == 8) { + int16_t *lf, *cf, *rf, *ls, + *rs, *lef, *sbl, *sbr; + + lf = to; + cf = to + 16 * 1; + rf = to + 16 * 2; + ls = to + 16 * 3; + rs = to + 16 * 4; + lef = to + 16 * 5; + sbl = to + 16 * 6; + sbr = to + 16 * 7; + for (j = 0; j < n; j += 256) { + for (i = 0; i < 16; i++) { + *lf++ = (*tfrom++); + *cf++ = (*tfrom++); + *rf++ = (*tfrom++); + *ls++ = (*tfrom++); + *rs++ = (*tfrom++); + *lef++ = (*tfrom++); + *sbl++ = (*tfrom++); + *sbr++ = (*tfrom++); + } + lf += 7 * 16; + cf += 7 * 16; + rf += 7 * 16; + ls += 7 * 16; + rs += 7 * 16; + lef += 7 * 16; + sbl += 7 * 16; + sbr += 7 * 16; + } + } else if (runtime->channels == 2) { + int16_t *left, *right; + + left = to; + right = to + 16; + + for (j = 0; j < n; j += 64) { + for (i = 0; i < 16; i++) { + *left++ = (*tfrom++); + *right++ = (*tfrom++); + } + left += 16; + right += 16; + } + } + } else if (runtime->format == SNDRV_PCM_FORMAT_S24_LE + && I2S_MODE == AIU_I2S_MODE_PCM24) { + int32_t *tfrom, *to; + + tfrom = (int32_t *) ubuf; + to = (int32_t *) hwbuf; + + if (runtime->channels == 8) { + int32_t *lf, *cf, *rf, *ls, + *rs, *lef, *sbl, *sbr; + + lf = to; + cf = to + 8 * 1; + rf = to + 8 * 2; + ls = to + 8 * 3; + rs = to + 8 * 4; + lef = to + 8 * 5; + sbl = to + 8 * 6; + sbr = to + 8 * 7; + for (j = 0; j < n; j += 256) { + for (i = 0; i < 8; i++) { + *lf++ = (*tfrom++); + *cf++ = (*tfrom++); + *rf++ = (*tfrom++); + *ls++ = (*tfrom++); + *rs++ = (*tfrom++); + *lef++ = (*tfrom++); + *sbl++ = (*tfrom++); + *sbr++ = (*tfrom++); + } + lf += 7 * 8; + cf += 7 * 8; + rf += 7 * 8; + ls += 7 * 8; + rs += 7 * 8; + lef += 7 * 8; + sbl += 7 * 8; + sbr += 7 * 8; + } + } else if (runtime->channels == 2) { + int32_t *left, *right; + + left = to; + right = to + 8; + + for (j = 0; j < n; j += 64) { + for (i = 0; i < 8; i++) { + *left++ = (*tfrom++); + *right++ = (*tfrom++); + } + left += 8; + right += 8; + } + } + + } else if (runtime->format == SNDRV_PCM_FORMAT_S32_LE) { + int32_t *tfrom, *to; + + tfrom = (int32_t *) ubuf; + to = (int32_t *) hwbuf; + + if (runtime->channels == 8) { + int32_t *lf, *cf, *rf, *ls, + *rs, *lef, *sbl, *sbr; + + lf = to; + cf = to + 8 * 1; + rf = to + 8 * 2; + ls = to + 8 * 3; + rs = to + 8 * 4; + lef = to + 8 * 5; + sbl = to + 8 * 6; + sbr = to + 8 * 7; + for (j = 0; j < n; j += 256) { + for (i = 0; i < 8; i++) { + *lf++ = (*tfrom++) >> 8; + *cf++ = (*tfrom++) >> 8; + *rf++ = (*tfrom++) >> 8; + *ls++ = (*tfrom++) >> 8; + *rs++ = (*tfrom++) >> 8; + *lef++ = (*tfrom++) >> 8; + *sbl++ = (*tfrom++) >> 8; + *sbr++ = (*tfrom++) >> 8; + } + lf += 7 * 8; + cf += 7 * 8; + rf += 7 * 8; + ls += 7 * 8; + rs += 7 * 8; + lef += 7 * 8; + sbl += 7 * 8; + sbr += 7 * 8; + } + } else if (runtime->channels == 2) { + int32_t *left, *right; + + left = to; + right = to + 8; + + for (j = 0; j < n; j += 64) { + for (i = 0; i < 8; i++) { + *left++ = (*tfrom++) >> 8; + *right++ = (*tfrom++) >> 8; + } + left += 8; + right += 8; + } + } + } +#endif + } else { + res = -EFAULT; + } + return res; +} + +static int aml_i2s_copy_capture(struct snd_pcm_runtime *runtime, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count, + struct snd_pcm_substream *substream) +{ + struct device *dev = substream->pcm->card->dev; + struct snd_dma_buffer *buffer = &substream->dma_buffer; + struct aml_audio_buffer *tmp_buf = buffer->private_data; + void *ubuf = tmp_buf->buffer_start; + unsigned long offset = frames_to_bytes(runtime, pos); + int res = 0, n = 0, i = 0, j = 0; + unsigned char r_shift = 8; + + n = frames_to_bytes(runtime, count); + + /*amlogic HW only supports 32bit mode by capture*/ + if (access_ok(VERIFY_WRITE, buf, frames_to_bytes(runtime, count))) { + if (runtime->channels == 2) { + if (pos % 8) + dev_err(dev, "audio data unligned\n"); + + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) { + char *hwbuf = runtime->dma_area + offset * 2; + int32_t *tfrom = (int32_t *)hwbuf; + int16_t *to = (int16_t *)ubuf; + int32_t *left = tfrom; + int32_t *right = tfrom + 8; + + if ((n * 2) % 64) + dev_err(dev, "audio data unaligned 64 bytes\n"); + + for (j = 0; j < n * 2; j += 64) { + for (i = 0; i < 8; i++) { + *to++ = (int16_t) + ((*left++) >> r_shift); + *to++ = (int16_t) + ((*right++) >> r_shift); + } + left += 8; + right += 8; + } + /* clean hw buffer */ + memset(hwbuf, 0, n * 2); + } else { + char *hwbuf = runtime->dma_area + offset; + int32_t *tfrom = (int32_t *)hwbuf; + int32_t *to = (int32_t *)ubuf; + int32_t *left = tfrom; + int32_t *right = tfrom + 8; + + if (n % 64) + dev_err(dev, "audio data unaligned 64 bytes\n"); + + if (runtime->format == SNDRV_PCM_FORMAT_S24_LE) + r_shift = 0; + + for (j = 0; j < n; j += 64) { + for (i = 0; i < 8; i++) { + *to++ = (int32_t) + ((*left++) << r_shift); + *to++ = (int32_t) + ((*right++) << r_shift); + } + left += 8; + right += 8; + } + memset(hwbuf, 0, n); + } + } + } + res = copy_to_user(buf, ubuf, n); + return res; +} + +static int aml_i2s_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = + aml_i2s_copy_playback(runtime, channel, pos, buf, count, + substream); + } else { + ret = + aml_i2s_copy_capture(runtime, channel, pos, buf, count, + substream); + } + return ret; +} +#endif + +int aml_i2s_silence(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, snd_pcm_uframes_t count) +{ + char *ppos; + int n; + struct snd_pcm_runtime *runtime = substream->runtime; + + n = frames_to_bytes(runtime, count); + ppos = runtime->dma_area + frames_to_bytes(runtime, pos); + memset(ppos, 0, n); + return 0; +} + +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP +static int aml_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + dev_info(substream->pcm->card->dev, + "\narea=%p,addr=%ld,bytes=%ld,rate:%d, channels:%d, subformat:%d\n", + runtime->dma_area, (long)runtime->dma_addr, runtime->dma_bytes, + runtime->rate, runtime->channels, runtime->subformat); + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); +} +#endif + +static struct snd_pcm_ops aml_i2s_ops = { + .open = aml_i2s_open, + .close = aml_i2s_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = aml_i2s_hw_params, + .hw_free = aml_i2s_hw_free, + .prepare = aml_i2s_prepare, + .trigger = aml_i2s_trigger, + .pointer = aml_i2s_pointer, +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE_MMAP + .mmap = aml_pcm_mmap, +#else + .copy = aml_i2s_copy, +#endif + .silence = aml_i2s_silence, +}; + +/*-------------------------------------------------------------------------- + * ASoC platform driver + *-------------------------------------------------------------------------- + */ +static u64 aml_i2s_dmamask = 0xffffffff; + +static int aml_i2s_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_soc_card *card = rtd->card; + struct snd_pcm *pcm = rtd->pcm; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &aml_i2s_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = aml_i2s_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = aml_i2s_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + + out: + return ret; +} + +static void aml_i2s_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + struct aml_audio_buffer *tmp_buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + + tmp_buf = buf->private_data; + if (tmp_buf->buffer_start != NULL && tmp_buf != NULL) + kfree(tmp_buf->buffer_start); + if (tmp_buf != NULL) + kfree(tmp_buf); + buf->private_data = NULL; + } +} + +static const char *const output_swap_texts[] = { "L/R", "L/L", "R/R", "R/L" }; + +static const struct soc_enum output_swap_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(output_swap_texts), + output_swap_texts); + +static int aml_output_swap_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = read_i2s_mute_swap_reg(); + return 0; +} + +static int aml_output_swap_set_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + audio_i2s_swap_left_right(ucontrol->value.enumerated.item[0]); + return 0; +} + +bool aml_audio_i2s_mute_flag; +static int aml_audio_set_i2s_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + aml_audio_i2s_mute_flag = ucontrol->value.integer.value[0]; + pr_info("aml_audio_i2s_mute_flag: flag=%d\n", aml_audio_i2s_mute_flag); + if (aml_audio_i2s_mute_flag) + aml_audio_i2s_mute(); + else + aml_audio_i2s_unmute(); + return 0; +} + +static int aml_audio_get_i2s_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aml_audio_i2s_mute_flag; + return 0; +} + +static const struct snd_kcontrol_new aml_i2s_controls[] = { + SOC_SINGLE_BOOL_EXT("Audio i2s mute", + 0, aml_audio_get_i2s_mute, + aml_audio_set_i2s_mute), + + SOC_ENUM_EXT("Output Swap", + output_swap_enum, + aml_output_swap_get_enum, + aml_output_swap_set_enum), +}; + +static int aml_i2s_probe(struct snd_soc_platform *platform) +{ + return snd_soc_add_platform_controls(platform, + aml_i2s_controls, ARRAY_SIZE(aml_i2s_controls)); +} + +struct snd_soc_platform_driver aml_soc_platform = { + .probe = aml_i2s_probe, + .ops = &aml_i2s_ops, + .pcm_new = aml_i2s_new, + .pcm_free = aml_i2s_free_dma_buffers, +}; + +static int aml_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &aml_soc_platform); +} + +static int aml_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_audio_dt_match[] = { + {.compatible = "amlogic, aml-i2s", + }, + {}, +}; +#else +#define amlogic_audio_dt_match NULL +#endif + +#ifdef CONFIG_HIBERNATION +static unsigned long isa_timerd_saved; +static unsigned long isa_timerd_mux_saved; +static int aml_i2s_freeze(struct device *dev) +{ + isa_timerd_saved = aml_read_cbus(ISA_TIMERD); + isa_timerd_mux_saved = aml_read_cbus(ISA_TIMER_MUX); + return 0; +} + +static int aml_i2s_thaw(struct device *dev) +{ + return 0; +} + +static int aml_i2s_restore(struct device *dev) +{ + aml_write_cbus(ISA_TIMERD, isa_timerd_saved); + aml_write_cbus(ISA_TIMER_MUX, isa_timerd_mux_saved); + return 0; +} + +const struct dev_pm_ops aml_i2s_pm = { + .freeze = aml_i2s_freeze, + .thaw = aml_i2s_thaw, + .restore = aml_i2s_restore, +}; +#endif + +static struct platform_driver aml_i2s_driver = { + .driver = { + .name = "aml-i2s", + .owner = THIS_MODULE, + .of_match_table = amlogic_audio_dt_match, +#ifdef CONFIG_HIBERNATION + .pm = &aml_i2s_pm, +#endif + }, + + .probe = aml_soc_platform_probe, + .remove = aml_soc_platform_remove, +}; + +static int __init aml_alsa_audio_init(void) +{ + return platform_driver_register(&aml_i2s_driver); +} + +static void __exit aml_alsa_audio_exit(void) +{ + platform_driver_unregister(&aml_i2s_driver); +} + +module_init(aml_alsa_audio_init); +module_exit(aml_alsa_audio_exit); + +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AML audio driver for ALSA"); diff --git a/sound/soc/amlogic/aml_i2s.h b/sound/soc/amlogic/aml_i2s.h new file mode 100644 index 000000000000..32e2ded00cbc --- /dev/null +++ b/sound/soc/amlogic/aml_i2s.h @@ -0,0 +1,90 @@ +/* + * sound/soc/amlogic/aml_i2s.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef __AML_I2S_H__ +#define __AML_I2S_H__ + +/* #define debug_printk */ +#ifdef debug_printk +#define dug_printk(fmt, args...) printk(fmt, ## args) +#else +#define dug_printk(fmt, args...) +#endif + +/*hw timer, timer D*/ +#define BASE_IRQ (32) +#define AM_IRQ(reg) (reg + BASE_IRQ) +#define INT_TIMER_D AM_IRQ(29) +/* note: we use TIEMRD. MODE: 1: periodic, 0: one-shot*/ +#define TIMERD_MODE 1 +/* timerbase resolution: 00: 1us; 01: 10us; 10: 100us; 11: 1ms*/ +#define TIMERD_RESOLUTION 0x1 +/* timer count: 16bits*/ +#define TIMER_COUNT 100 + +struct audio_stream { + int stream_id; + unsigned int last_ptr; + unsigned int size; + unsigned int sample_rate; + unsigned int I2S_addr; + spinlock_t lock; + struct snd_pcm_substream *stream; + unsigned int i2s_mode; /* 0:master, 1:slave, */ + unsigned int device_type; +}; +struct aml_audio_buffer { + void *buffer_start; + unsigned int buffer_size; + char cache_buffer_bytes[256]; + int cached_len; +}; + +struct aml_i2s_dma_params { + char *name; /* stream identifier */ + struct snd_pcm_substream *substream; + void (*dma_intr_handler)(u32, struct snd_pcm_substream *); +}; +struct aml_dai_info { + unsigned int i2s_mode; /* 0:master, 1:slave, */ +}; +enum { + I2S_MASTER_MODE = 0, + I2S_SLAVE_MODE, +}; +/*-------------------------------------------------------------------------- + * Data types + *-------------------------------------------------------------------------- + */ +struct aml_runtime_data { + struct aml_i2s_dma_params *params; + dma_addr_t dma_buffer; /* physical address of dma buffer */ + dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ + + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + struct audio_stream s; + struct timer_list timer; /* timeer for playback and capture */ + struct hrtimer hrtimer; + ktime_t wakeups_per_second; + spinlock_t timer_lock; + void *buf; /* tmp buffer for playback or capture */ + int active; + unsigned int xrun_num; +}; + +#endif diff --git a/sound/soc/amlogic/aml_i2s_dai.c b/sound/soc/amlogic/aml_i2s_dai.c new file mode 100644 index 000000000000..9568526e81ed --- /dev/null +++ b/sound/soc/amlogic/aml_i2s_dai.c @@ -0,0 +1,469 @@ +/* + * sound/soc/amlogic/aml_i2s_dai.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "aml_i2s_dai.h" +#include "aml_pcm.h" +#include "aml_i2s.h" +#include "aml_audio_hw.h" +#include +#include "aml_spdif_dai.h" + +struct aml_dai_info dai_info[3] = { {0} }; + +static int i2s_pos_sync; + +/* extern int set_i2s_iec958_samesource(int enable); + * + * the I2S hw and IEC958 PCM output initiation,958 initiation here, + * for the case that only use our ALSA driver for PCM s/pdif output. + */ +static void aml_hw_i2s_init(struct snd_pcm_runtime *runtime) +{ + unsigned int i2s_mode = AIU_I2S_MODE_PCM16; + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S32_LE: + i2s_mode = AIU_I2S_MODE_PCM32; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s_mode = AIU_I2S_MODE_PCM24; + break; + case SNDRV_PCM_FORMAT_S16_LE: + i2s_mode = AIU_I2S_MODE_PCM16; + break; + } +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + audio_set_i2s_mode(i2s_mode, runtime->channels); +#else + audio_set_i2s_mode(i2s_mode); +#endif + audio_set_aiubuf(runtime->dma_addr, runtime->dma_bytes, + runtime->channels); +} + +static int aml_dai_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = + (struct aml_runtime_data *)runtime->private_data; + struct audio_stream *s; + + if (prtd == NULL) { + prtd = + (struct aml_runtime_data *) + kzalloc(sizeof(struct aml_runtime_data), GFP_KERNEL); + if (prtd == NULL) { + dev_err(substream->pcm->card->dev, "alloc aml_runtime_data error\n"); + ret = -ENOMEM; + goto out; + } + prtd->substream = substream; + runtime->private_data = prtd; + } + s = &prtd->s; + if (substream->stream + == SNDRV_PCM_STREAM_PLAYBACK) { + s->device_type = AML_AUDIO_I2SOUT; + } else { + s->device_type = AML_AUDIO_I2SIN; + } + return 0; + out: + return ret; +} + +static void aml_dai_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (IEC958_mode_codec == 0) + aml_spdif_play(1); +} + +#define AOUT_EVENT_IEC_60958_PCM 0x1 +static int aml_i2s_set_amclk(struct aml_i2s *i2s, unsigned long rate) +{ + int ret = 0; + + ret = clk_set_rate(i2s->clk_mpll, rate * 10); + if (ret) + return ret; + ret = clk_set_parent(i2s->clk_mclk, i2s->clk_mpll); + if (ret) + return ret; + + ret = clk_set_rate(i2s->clk_mclk, rate); + if (ret) + return ret; + + audio_set_i2s_clk_div(); + + return 0; +} + +static int aml_dai_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = runtime->private_data; + struct audio_stream *s = &prtd->s; + struct aml_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + s->i2s_mode = dai_info[dai->id].i2s_mode; + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) { + audio_in_i2s_set_buf(runtime->dma_addr, + runtime->dma_bytes * 2, + 0, i2s_pos_sync, i2s->audin_fifo_src, + runtime->channels); + memset((void *)runtime->dma_area, 0, + runtime->dma_bytes * 2); + } else { + audio_in_i2s_set_buf(runtime->dma_addr, + runtime->dma_bytes, + 0, i2s_pos_sync, i2s->audin_fifo_src, + runtime->channels); + memset((void *)runtime->dma_area, 0, + runtime->dma_bytes); + } + s->device_type = AML_AUDIO_I2SIN; + } else { + s->device_type = AML_AUDIO_I2SOUT; + audio_out_i2s_enable(0); + audio_util_set_dac_i2s_format(AUDIO_ALGOUT_DAC_FORMAT_DSP); + aml_hw_i2s_init(runtime); + /* i2s/958 share the same audio hw buffer when PCM mode */ + if (IEC958_mode_codec == 0) { + aml_hw_iec958_init(substream, 1); + /* use the hw same sync for i2s/958 */ + dev_info(substream->pcm->card->dev, "i2s/958 same source\n"); + } + if (runtime->channels == 8) { + dev_info(substream->pcm->card->dev, + "8ch PCM output->notify HDMI\n"); + aout_notifier_call_chain(AOUT_EVENT_IEC_60958_PCM, + substream); + } + } + return 0; +} + +static int aml_dai_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* TODO */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev_info(substream->pcm->card->dev, "I2S playback enable\n"); + audio_out_i2s_enable(1); + if (IEC958_mode_codec == 0) { + dev_info(substream->pcm->card->dev, "IEC958 playback enable\n"); + audio_hw_958_enable(1); + } + } else { + audio_in_i2s_enable(1); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev_info(substream->pcm->card->dev, "I2S playback disable\n"); + audio_out_i2s_enable(0); + if (IEC958_mode_codec == 0) { + dev_info(substream->pcm->card->dev, "IEC958 playback disable\n"); + audio_hw_958_enable(0); + } + } else { + audio_in_i2s_enable(0); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int aml_dai_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct aml_i2s *i2s = snd_soc_dai_get_drvdata(dai); + int srate, mclk_rate; + + srate = params_rate(params); + if (i2s->old_samplerate != srate) { + i2s->old_samplerate = srate; + mclk_rate = srate * DEFAULT_MCLK_RATIO_SR; + aml_i2s_set_amclk(i2s, mclk_rate); + } + + return 0; +} + +static int aml_dai_set_i2s_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + if (fmt & SND_SOC_DAIFMT_CBS_CFS) /* slave mode */ + dai_info[dai->id].i2s_mode = I2S_SLAVE_MODE; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + i2s_pos_sync = 0; + break; + case SND_SOC_DAIFMT_IB_NF: + i2s_pos_sync = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +static int aml_dai_set_i2s_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int aml_dai_i2s_suspend(struct snd_soc_dai *dai) +{ + struct aml_i2s *i2s = dev_get_drvdata(dai->dev); + + if (i2s && i2s->clk_mclk && !i2s->disable_clk_suspend) + clk_disable_unprepare(i2s->clk_mclk); + + return 0; +} + +static int aml_dai_i2s_resume(struct snd_soc_dai *dai) +{ + struct aml_i2s *i2s = dev_get_drvdata(dai->dev); + + if (i2s && i2s->clk_mclk && !i2s->disable_clk_suspend) + clk_prepare_enable(i2s->clk_mclk); + + return 0; +} + +#define AML_DAI_I2S_RATES (SNDRV_PCM_RATE_8000_192000) +#define AML_DAI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops aml_dai_i2s_ops = { + .startup = aml_dai_i2s_startup, + .shutdown = aml_dai_i2s_shutdown, + .prepare = aml_dai_i2s_prepare, + .trigger = aml_dai_i2s_trigger, + .hw_params = aml_dai_i2s_hw_params, + .set_fmt = aml_dai_set_i2s_fmt, + .set_sysclk = aml_dai_set_i2s_sysclk, +}; + +struct snd_soc_dai_driver aml_i2s_dai[] = { + { + .id = 0, + .suspend = aml_dai_i2s_suspend, + .resume = aml_dai_i2s_resume, + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = AML_DAI_I2S_RATES, + .formats = AML_DAI_I2S_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 8, + .rates = AML_DAI_I2S_RATES, + .formats = AML_DAI_I2S_FORMATS,}, + .ops = &aml_dai_i2s_ops, + }, +}; +EXPORT_SYMBOL_GPL(aml_i2s_dai); + +static const struct snd_soc_component_driver aml_component = { + .name = "aml-i2s-dai", +}; + +static const char *const gate_names[] = { + "top_glue", "aud_buf", "i2s_out", "amclk_measure", + "aififo2", "aud_mixer", "mixer_reg", "adc", + "top_level", "aoclk", "aud_in" +}; + +static int aml_i2s_dai_probe(struct platform_device *pdev) +{ + struct aml_i2s *i2s = NULL; + struct device_node *pnode = pdev->dev.of_node; + int ret = 0, i; + struct clk *clk_gate; + + /* enable AIU module power gate first */ + for (i = 0; i < ARRAY_SIZE(gate_names); i++) { + clk_gate = devm_clk_get(&pdev->dev, gate_names[i]); + if (IS_ERR(clk_gate)) { + dev_err(&pdev->dev, "Can't get aml audio gate\n"); + return PTR_ERR(clk_gate); + } + pr_info("clock source gate %s : %p\n", gate_names[i], clk_gate); + clk_prepare_enable(clk_gate); + } + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct aml_i2s), GFP_KERNEL); + if (!i2s) { + /*dev_err(&pdev->dev, "Can't allocate aml_i2s\n");*/ + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, i2s); + + i2s->disable_clk_suspend = + of_property_read_bool(pnode, "disable_clk_suspend"); + + i2s->clk_mpll = devm_clk_get(&pdev->dev, "mpll2"); + if (IS_ERR(i2s->clk_mpll)) { + dev_err(&pdev->dev, "Can't retrieve mpll2 clock\n"); + ret = PTR_ERR(i2s->clk_mpll); + goto err; + } + + i2s->clk_mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(i2s->clk_mclk)) { + dev_err(&pdev->dev, "Can't retrieve clk_mclk clock\n"); + ret = PTR_ERR(i2s->clk_mclk); + goto err; + } + + /* now only 256fs is supported */ + ret = aml_i2s_set_amclk(i2s, + DEFAULT_SAMPLERATE * DEFAULT_MCLK_RATIO_SR); + if (ret < 0) { + dev_err(&pdev->dev, "Can't set aml_i2s :%d\n", ret); + goto err; + } + + ret = clk_prepare_enable(i2s->clk_mclk); + if (ret) { + dev_err(&pdev->dev, "Can't enable I2S mclk clock: %d\n", ret); + goto err; + } + + if (of_property_read_bool(pdev->dev.of_node, "DMIC")) { + i2s->audin_fifo_src = 3; + dev_info(&pdev->dev, "DMIC is in platform!\n"); + } else { + i2s->audin_fifo_src = 1; + dev_info(&pdev->dev, "I2S Mic is in platform!\n"); + } + + ret = snd_soc_register_component(&pdev->dev, &aml_component, + aml_i2s_dai, ARRAY_SIZE(aml_i2s_dai)); + if (ret) { + dev_err(&pdev->dev, "Can't register i2s dai: %d\n", ret); + goto err_clk_dis; + } + return 0; + +err_clk_dis: + clk_disable_unprepare(i2s->clk_mclk); +err: + return ret; +} + +static int aml_i2s_dai_remove(struct platform_device *pdev) +{ + struct aml_i2s *i2s = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + clk_disable_unprepare(i2s->clk_mclk); + + return 0; +} + +static void aml_i2s_dai_shutdown(struct platform_device *pdev) +{ + struct aml_i2s *i2s = dev_get_drvdata(&pdev->dev); + + if (i2s && i2s->clk_mclk) + clk_disable_unprepare(i2s->clk_mclk); +} + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_dai_dt_match[] = { + {.compatible = "amlogic, aml-i2s-dai",}, + {}, +}; +#else +#define amlogic_dai_dt_match NULL +#endif + +static struct platform_driver aml_i2s_dai_driver = { + .driver = { + .name = "aml-i2s-dai", + .owner = THIS_MODULE, + .of_match_table = amlogic_dai_dt_match, + }, + + .probe = aml_i2s_dai_probe, + .remove = aml_i2s_dai_remove, + .shutdown = aml_i2s_dai_shutdown, +}; + +static int __init aml_i2s_dai_modinit(void) +{ + return platform_driver_register(&aml_i2s_dai_driver); +} + +module_init(aml_i2s_dai_modinit); + +static void __exit aml_i2s_dai_modexit(void) +{ + platform_driver_unregister(&aml_i2s_dai_driver); +} + +module_exit(aml_i2s_dai_modexit); + +/* Module information */ +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_DESCRIPTION("AML DAI driver for ALSA"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/amlogic/aml_i2s_dai.h b/sound/soc/amlogic/aml_i2s_dai.h new file mode 100644 index 000000000000..eee31d2fe748 --- /dev/null +++ b/sound/soc/amlogic/aml_i2s_dai.h @@ -0,0 +1,30 @@ +/* + * sound/soc/amlogic/aml_i2s_dai.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef __AML_I2S_DAI_H__ +#define __AML_I2S_DAI_H__ + +extern struct snd_soc_dai_driver aml_dai[]; +struct aml_i2s { + struct clk *clk_mpll; + struct clk *clk_mclk; + int old_samplerate; + bool disable_clk_suspend; + int audin_fifo_src; +}; + +#endif diff --git a/sound/soc/amlogic/aml_meson.c b/sound/soc/amlogic/aml_meson.c new file mode 100644 index 000000000000..20be66f894de --- /dev/null +++ b/sound/soc/amlogic/aml_meson.c @@ -0,0 +1,801 @@ +/* + * sound/soc/amlogic/aml_meson.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#define pr_fmt(fmt) "aml_snd_card: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +/* #include */ +#include + +#if 0 /*tmp_mask_for_kernel_4_4*/ +#include +#include +#endif +/* #include */ +#include + +#include "aml_i2s.h" +#include "aml_meson.h" +#include "aml_audio_hw.h" +#include +#include +#include +#include +#include +#include + + +#define DRV_NAME "aml_meson_snd_card" + +static int i2sbuf[32 + 16]; +static void aml_i2s_play(void) +{ + audio_util_set_dac_i2s_format(AUDIO_ALGOUT_DAC_FORMAT_DSP); +#ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE + audio_set_i2s_mode(AIU_I2S_MODE_PCM16, 2); +#else + audio_set_i2s_mode(AIU_I2S_MODE_PCM16); +#endif + memset(i2sbuf, 0, sizeof(i2sbuf)); + audio_set_aiubuf((virt_to_phys(i2sbuf) + 63) & (~63), 128, 2); + audio_out_i2s_enable(1); + +} + +static void aml_audio_start_timer(struct aml_audio_private_data *p_aml_audio, + unsigned long delay) +{ + p_aml_audio->timer.expires = jiffies + delay; + p_aml_audio->timer.data = (unsigned long)p_aml_audio; + p_aml_audio->detect_flag = -1; + add_timer(&p_aml_audio->timer); + p_aml_audio->timer_en = 1; +} + +static void aml_audio_stop_timer(struct aml_audio_private_data *p_aml_audio) +{ + del_timer_sync(&p_aml_audio->timer); + cancel_work_sync(&p_aml_audio->work); + p_aml_audio->timer_en = 0; + p_aml_audio->detect_flag = -1; +} + +static int hp_det_adc_value(struct aml_audio_private_data *p_aml_audio) +{ + int ret, hp_value; + int hp_val_sum = 0; + int loop_num = 0; + unsigned int mic_ret = 0; + + while (loop_num < 8) { + /* hp_value = get_adc_sample(p_aml_audio->hp_adc_ch); */ + if (hp_value < 0) { + pr_info("hp detect get error adc value!\n"); + return -1; /* continue; */ + } + hp_val_sum += hp_value; + loop_num++; + msleep_interruptible(15); + } + hp_val_sum = hp_val_sum >> 3; + /* pr_info("00000000000hp_val_sum = %hx\n",hp_val_sum); */ + if (hp_val_sum >= p_aml_audio->hp_val_h) { + ret = 0; + } else if ((hp_val_sum < (p_aml_audio->hp_val_l)) && hp_val_sum >= 0) { + ret = 1; + if (p_aml_audio->mic_det) { + if (hp_val_sum <= p_aml_audio->mic_val) { + mic_ret = 8; + ret |= mic_ret; + } + } + } else { + ret = 2; + if (p_aml_audio->mic_det) { + ret = 0; + mic_ret = 8; + ret |= mic_ret; + } + + } + + return ret; +} + +static int aml_audio_hp_detect(struct aml_audio_private_data *p_aml_audio) +{ + int loop_num = 0; + int ret; + + p_aml_audio->hp_det_status = false; + /* mutex_lock(&p_aml_audio->lock); */ + + while (loop_num < 3) { + ret = hp_det_adc_value(p_aml_audio); + if (p_aml_audio->hp_last_state != ret) { + msleep_interruptible(50); + if (ret < 0) + ret = p_aml_audio->hp_last_state; + else + p_aml_audio->hp_last_state = ret; + } else + msleep_interruptible(50); + + loop_num = loop_num + 1; + } + + /* mutex_unlock(&p_aml_audio->lock); */ + + return ret; +} + +static void aml_asoc_work_func(struct work_struct *work) +{ + struct aml_audio_private_data *p_aml_audio = NULL; + struct snd_soc_card *card = NULL; + int flag = -1; + int status = SND_JACK_HEADPHONE; + + p_aml_audio = container_of(work, struct aml_audio_private_data, work); + card = (struct snd_soc_card *)p_aml_audio->data; + + flag = aml_audio_hp_detect(p_aml_audio); + + if (p_aml_audio->detect_flag != flag) { + + p_aml_audio->detect_flag = flag; + + if (flag & 0x1) { + /* 1 :have mic ; 2 no mic */ +#if 0 /*tmp_mask_for_kernel_4_4*/ + switch_set_state(&p_aml_audio->sdev, 2); +#endif + pr_info("aml aduio hp pluged 3 jack_type: %d\n", + SND_JACK_HEADPHONE); + snd_soc_jack_report(&p_aml_audio->jack, status, + SND_JACK_HEADPHONE); + + /* mic port detect */ + if (p_aml_audio->mic_det) { + if (flag & 0x8) { +#if 0 /*tmp_mask_for_kernel_4_4*/ + switch_set_state(&p_aml_audio->mic_sdev, + 1); +#endif + pr_info("aml aduio mic pluged jack_type: %d\n", + SND_JACK_MICROPHONE); + snd_soc_jack_report(&p_aml_audio->jack, + status, SND_JACK_HEADPHONE); + } + } + + } else if (flag & 0x2) { + /* 1 :have mic ; 2 no mic */ +#if 0 /*tmp_mask_for_kernel_4_4*/ + switch_set_state(&p_aml_audio->sdev, 1); +#endif + pr_info("aml aduio hp pluged 4 jack_type: %d\n", + SND_JACK_HEADSET); + snd_soc_jack_report(&p_aml_audio->jack, status, + SND_JACK_HEADPHONE); + } else { + pr_info("aml audio hp unpluged\n"); +#if 0 /*tmp_mask_for_kernel_4_4*/ + switch_set_state(&p_aml_audio->sdev, 0); +#endif + snd_soc_jack_report(&p_aml_audio->jack, 0, + SND_JACK_HEADPHONE); + + /* mic port detect */ + if (p_aml_audio->mic_det) { + if (flag & 0x8) { +#if 0 /*tmp_mask_for_kernel_4_4*/ + switch_set_state(&p_aml_audio->mic_sdev, + 1); +#endif + pr_info("aml aduio mic pluged jack_type: %d\n", + SND_JACK_MICROPHONE); + snd_soc_jack_report(&p_aml_audio->jack, + status, SND_JACK_HEADPHONE); + } + } + } + + } + p_aml_audio->hp_det_status = true; +} + +static void aml_asoc_timer_func(unsigned long data) +{ + struct aml_audio_private_data *p_aml_audio = + (struct aml_audio_private_data *)data; + unsigned long delay = msecs_to_jiffies(150); + + if (p_aml_audio->hp_det_status && + !p_aml_audio->suspended) { + schedule_work(&p_aml_audio->work); + } + mod_timer(&p_aml_audio->timer, jiffies + delay); +} + +static struct aml_audio_private_data *p_audio; +static int aml_spk_enabled; + +static int aml_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + aml_spk_enabled = ucontrol->value.integer.value[0]; + pr_info("aml_set_spk: aml_spk_enabled=%d\n", + aml_spk_enabled); + + msleep_interruptible(10); + if (!IS_ERR(p_audio->mute_desc)) + gpiod_direction_output(p_audio->mute_desc, aml_spk_enabled); + + if (aml_spk_enabled == 1) + msleep_interruptible(100); + + return 0; +} + +static int aml_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aml_spk_enabled; + return 0; +} + +static int aml_suspend_pre(struct snd_soc_card *card) +{ + struct aml_audio_private_data *p_aml_audio; + struct pinctrl_state *state; + int val = 0; + + pr_info("enter %s\n", __func__); + p_aml_audio = snd_soc_card_get_drvdata(card); + if (!p_aml_audio->hp_disable) { + /* stop timer */ + mutex_lock(&p_aml_audio->lock); + p_aml_audio->suspended = true; + if (p_aml_audio->timer_en) + aml_audio_stop_timer(p_aml_audio); + + mutex_unlock(&p_aml_audio->lock); + } + + if (IS_ERR_OR_NULL(p_aml_audio->pin_ctl)) { + pr_info("no audio pin_ctrl to suspend\n"); + return 0; + } + + state = pinctrl_lookup_state(p_aml_audio->pin_ctl, "aml_snd_suspend"); + if (!IS_ERR(state)) { + pr_info("enter %s set pin_ctl suspend state\n", __func__); + pinctrl_select_state(p_aml_audio->pin_ctl, state); + } + + if (!IS_ERR(p_aml_audio->mute_desc)) { + val = p_aml_audio->mute_inv ? + GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH; + gpiod_direction_output(p_aml_audio->mute_desc, val); + }; + + return 0; +} + +static int aml_suspend_post(struct snd_soc_card *card) +{ + pr_info("enter %s\n", __func__); + return 0; +} + +static int aml_resume_pre(struct snd_soc_card *card) +{ + pr_info("enter %s\n", __func__); + + return 0; +} + +static int aml_resume_post(struct snd_soc_card *card) +{ + struct aml_audio_private_data *p_aml_audio; + struct pinctrl_state *state; + int val = 0; + + pr_info("enter %s\n", __func__); + p_aml_audio = snd_soc_card_get_drvdata(card); + if (!p_aml_audio->hp_disable) { + mutex_lock(&p_aml_audio->lock); + p_aml_audio->suspended = false; + if (!p_aml_audio->timer_en) { + aml_audio_start_timer(p_aml_audio, + msecs_to_jiffies(100)); + } + mutex_unlock(&p_aml_audio->lock); + } + + if (IS_ERR_OR_NULL(p_aml_audio->pin_ctl)) { + pr_info("no audio pin_ctrl to resume\n"); + return 0; + } + + state = pinctrl_lookup_state(p_aml_audio->pin_ctl, "audio_i2s_pins"); + if (!IS_ERR(state)) { + pr_info("enter %s set pin_ctl working state\n", __func__); + pinctrl_select_state(p_aml_audio->pin_ctl, state); + } + + if (!IS_ERR(p_aml_audio->mute_desc)) { + if (p_aml_audio->sleep_time) + msleep(p_aml_audio->sleep_time); + val = p_aml_audio->mute_inv ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + gpiod_direction_output(p_aml_audio->mute_desc, val); + } + return 0; +} + +static int speaker_events(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int val = 0; + + if (IS_ERR(p_audio->mute_desc)) { + pr_info("no mute_gpio setting"); + return 0; + } + switch (event) { + case SND_SOC_DAPM_POST_PMU: + pr_info("audio speaker on\n"); + val = p_audio->mute_inv ? + GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH; + gpiod_direction_output(p_audio->mute_desc, 1); + aml_spk_enabled = 1; + msleep(p_audio->sleep_time); + break; + case SND_SOC_DAPM_PRE_PMD: + pr_info("audio speaker off\n"); + val = p_audio->mute_inv ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + gpiod_direction_output(p_audio->mute_desc, val); + aml_spk_enabled = 0; + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget aml_asoc_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", speaker_events), + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_MIC("MAIN MIC", NULL), + SND_SOC_DAPM_MIC("HEADSET MIC", NULL), +}; + +static const struct snd_kcontrol_new aml_asoc_controls[] = { + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "HP", + .mask = SND_JACK_HEADPHONE, + } +}; + + +static const struct snd_kcontrol_new aml_controls[] = { + SOC_SINGLE_BOOL_EXT("Ext Spk Switch", 0, + aml_get_spk, + aml_set_spk), +}; + +static int aml_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->component.dapm; + struct aml_audio_private_data *p_aml_audio; + int ret = 0; + int hp_paraments[5]; + + p_aml_audio = snd_soc_card_get_drvdata(card); + ret = snd_soc_add_card_controls(card, aml_controls, + ARRAY_SIZE(aml_controls)); + if (ret) + return ret; + /* Add specific widgets */ + snd_soc_dapm_new_controls(dapm, aml_asoc_dapm_widgets, + ARRAY_SIZE(aml_asoc_dapm_widgets)); + p_aml_audio->hp_disable = + of_property_read_bool(card->dev->of_node, "hp_disable"); + + pr_info("headphone detection disable=%d\n", p_aml_audio->hp_disable); + + if (!p_aml_audio->hp_disable) { + /* for report headphone to android */ + +#if 0 /*tmp_mask_for_kernel_4_4*/ + p_aml_audio->sdev.name = "h2w"; + ret = switch_dev_register(&p_aml_audio->sdev); + if (ret < 0) { + pr_err("ASoC: register hp switch dev failed\n"); + return ret; + } + + + /* for micphone detect */ + p_aml_audio->mic_sdev.name = "mic_dev"; + ret = switch_dev_register(&p_aml_audio->mic_sdev); + if (ret < 0) { + pr_err("ASoC: register mic switch dev failed\n"); + return ret; + } +#endif + ret = snd_soc_card_jack_new(card, + "hp switch", SND_JACK_HEADPHONE, + &p_aml_audio->jack, jack_pins, ARRAY_SIZE(jack_pins)); + if (ret) { + pr_info("Failed to alloc resource for hp switch\n"); + return ret; + } + + p_aml_audio->hp_det_status = true; + p_aml_audio->mic_det = + of_property_read_bool(card->dev->of_node, "mic_det"); + + pr_info("entern %s : mic_det=%d\n", __func__, + p_aml_audio->mic_det); + ret = + of_property_read_u32_array(card->dev->of_node, + "hp_paraments", &hp_paraments[0], + 5); + if (ret) + pr_info("falied to get hp detect paraments\n"); + else { + /* hp adc value higher base, hp unplugged */ + p_aml_audio->hp_val_h = hp_paraments[0]; + /* hp adc value low base, 3 section hp plugged. */ + p_aml_audio->hp_val_l = hp_paraments[1]; + /* hp adc value mic detect value. */ + p_aml_audio->mic_val = hp_paraments[2]; + /* hp adc value test toerance */ + p_aml_audio->hp_detal = hp_paraments[3]; + /* get adc value from which adc port for hp detect */ + p_aml_audio->hp_adc_ch = hp_paraments[4]; + + pr_info("hp detect paraments: h=%d, l=%d,mic=%d,det=%d,ch=%d\n", + p_aml_audio->hp_val_h, p_aml_audio->hp_val_l, + p_aml_audio->mic_val, p_aml_audio->hp_detal, + p_aml_audio->hp_adc_ch); + } + + init_timer(&p_aml_audio->timer); + p_aml_audio->timer.function = aml_asoc_timer_func; + p_aml_audio->timer.data = (unsigned long)p_aml_audio; + p_aml_audio->data = (void *)card; + p_aml_audio->suspended = false; + + INIT_WORK(&p_aml_audio->work, aml_asoc_work_func); + mutex_init(&p_aml_audio->lock); + + mutex_lock(&p_aml_audio->lock); + if (!p_aml_audio->timer_en) { + aml_audio_start_timer(p_aml_audio, + msecs_to_jiffies(100)); + } + mutex_unlock(&p_aml_audio->lock); + } + + ret = + of_property_read_u32(card->dev->of_node, "sleep_time", + &p_aml_audio->sleep_time); + if (ret) + pr_info("no spk event delay time set\n"); + + return 0; +} + +static void aml_pinmux_init(struct snd_soc_card *card) +{ + struct aml_audio_private_data *p_aml_audio; + int val; + + p_aml_audio = snd_soc_card_get_drvdata(card); + p_aml_audio->mute_desc = gpiod_get( + card->dev, "mute_gpio", GPIOD_OUT_LOW); + p_aml_audio->mute_inv = + of_property_read_bool(card->dev->of_node, "mute_inv"); + if (!IS_ERR(p_aml_audio->mute_desc)) { + if (p_aml_audio->sleep_time) + msleep(p_aml_audio->sleep_time); + val = p_aml_audio->mute_inv ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + gpiod_direction_output(p_aml_audio->mute_desc, val); + } + +#if 0 /*tmp_mask_for_kernel_4_4*/ + if (is_jtag_apao()) + return; + + val = aml_read_sec_reg(0xda004004); + pr_info("audio use jtag pinmux as i2s output, read val =%x\n", + aml_read_sec_reg(0xda004004)); + val = val & (~((1<<8) | (1<<1))); + aml_write_sec_reg(0xda004004, val); +#endif + + p_aml_audio->pin_ctl = devm_pinctrl_get_select( + card->dev, "audio_i2s_pins"); + if (IS_ERR(p_aml_audio->pin_ctl)) { + pr_info("%s,aml_pinmux_init error!\n", __func__); + return; + } +} + +static int aml_card_dai_parse_of(struct device *dev, + struct snd_soc_dai_link *dai_link, + int (*init)(struct snd_soc_pcm_runtime *rtd), + struct device_node *cpu_node, + struct device_node *codec_node, + struct device_node *plat_node) +{ + int ret; + + /* get cpu dai->name */ + ret = snd_soc_of_get_dai_name(cpu_node, &dai_link->cpu_dai_name); + if (ret < 0) + goto parse_error; + + /* get codec dai->name */ + ret = snd_soc_of_get_dai_name(codec_node, &dai_link->codec_dai_name); + if (ret < 0) + goto parse_error; + + dai_link->name = dai_link->stream_name = dai_link->cpu_dai_name; + dai_link->codec_of_node = of_parse_phandle(codec_node, "sound-dai", 0); + dai_link->platform_of_node = plat_node; + dai_link->init = init; + + return 0; + + parse_error: + return ret; +} + +static int aml_card_dais_parse_of(struct snd_soc_card *card) +{ + struct device_node *np = card->dev->of_node; + struct device_node *cpu_node, *codec_node, *plat_node; + struct device *dev = card->dev; + struct snd_soc_dai_link *dai_links; + int num_dai_links, cpu_num, codec_num, plat_num; + int i, ret; + int (*init)(struct snd_soc_pcm_runtime *rtd); + + ret = of_count_phandle_with_args(np, "cpu_list", NULL); + if (ret < 0) { + dev_err(dev, "AML sound card no cpu_list errno: %d\n", ret); + goto err; + } else { + cpu_num = ret; + } + + ret = of_count_phandle_with_args(np, "codec_list", NULL); + if (ret < 0) { + dev_err(dev, "AML sound card no codec_list errno: %d\n", ret); + goto err; + } else { + codec_num = ret; + } + + ret = of_count_phandle_with_args(np, "plat_list", NULL); + if (ret < 0) { + dev_err(dev, "AML sound card no plat_list errno: %d\n", ret); + goto err; + } else { + plat_num = ret; + } + + if ((cpu_num == codec_num) && (cpu_num == plat_num)) { + num_dai_links = cpu_num; + } else { + dev_err(dev, + "AML sound card cpu_dai num, codec_dai num, platform num don't match: %d\n", + ret); + ret = -EINVAL; + goto err; + } + + dai_links = + devm_kzalloc(dev, num_dai_links * sizeof(struct snd_soc_dai_link), + GFP_KERNEL); + if (!dai_links) { + dev_err(dev, "Can't allocate snd_soc_dai_links\n"); + ret = -ENOMEM; + goto err; + } + card->dai_link = dai_links; + card->num_links = num_dai_links; + for (i = 0; i < num_dai_links; i++) { + init = NULL; + /* CPU sub-node */ + cpu_node = of_parse_phandle(np, "cpu_list", i); + if (cpu_node < 0) { + dev_err(dev, "parse aml sound card cpu list error\n"); + return -EINVAL; + } + /* CODEC sub-node */ + codec_node = of_parse_phandle(np, "codec_list", i); + if (codec_node < 0) { + dev_err(dev, "parse aml sound card codec list error\n"); + return ret; + } + /* Platform sub-node */ + plat_node = of_parse_phandle(np, "plat_list", i); + if (plat_node < 0) { + dev_err(dev, + "parse aml sound card platform list error\n"); + return ret; + } + if (i == 0) + init = aml_asoc_init; + + ret = + aml_card_dai_parse_of(dev, &dai_links[i], init, cpu_node, + codec_node, plat_node); + } + + err: + return ret; +} + +static void aml_pinmux_work_func(struct work_struct *pinmux_work) +{ + struct aml_audio_private_data *p_aml_audio = NULL; + struct snd_soc_card *card = NULL; + + p_aml_audio = container_of(pinmux_work, + struct aml_audio_private_data, pinmux_work); + card = (struct snd_soc_card *)p_aml_audio->data; + + aml_pinmux_init(card); +} + +static int aml_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct snd_soc_card *card = NULL; + struct aml_audio_private_data *p_aml_audio; + int ret; + + p_aml_audio = + devm_kzalloc(dev, sizeof(struct aml_audio_private_data), + GFP_KERNEL); + if (!p_aml_audio) { + dev_err(&pdev->dev, "Can't allocate aml_audio_private_data\n"); + ret = -ENOMEM; + goto err; + } + p_audio = p_aml_audio; + + card = devm_kzalloc(dev, sizeof(struct snd_soc_card), GFP_KERNEL); + if (!card) { + /*dev_err(dev, "Can't allocate snd_soc_card\n");*/ + ret = -ENOMEM; + goto err; + } + + snd_soc_card_set_drvdata(card, p_aml_audio); + card->dev = dev; + platform_set_drvdata(pdev, card); + ret = snd_soc_of_parse_card_name(card, "aml_sound_card,name"); + if (ret < 0) { + dev_err(dev, "no specific snd_soc_card name\n"); + goto err; + } + /* DAPM routes */ + if (of_property_read_bool(np, "aml,audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, "aml,audio-routing"); + if (ret < 0) { + dev_err(dev, "parse aml sound card routing error %d\n", + ret); + return ret; + } + } + ret = aml_card_dais_parse_of(card); + if (ret < 0) { + dev_err(dev, "parse aml sound card routing error %d\n", + ret); + goto err; + } + + card->suspend_pre = aml_suspend_pre, + card->suspend_post = aml_suspend_post, + card->resume_pre = aml_resume_pre, + card->resume_post = aml_resume_post, + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret < 0) { + dev_err(dev, "register aml sound card error %d\n", ret); + goto err; + } + + aml_i2s_play(); + + p_aml_audio->data = (void *)card; + INIT_WORK(&p_aml_audio->pinmux_work, aml_pinmux_work_func); + schedule_work(&p_aml_audio->pinmux_work); + + /*aml_pinmux_init(card);*/ + return 0; + err: + dev_err(dev, "Can't probe snd_soc_card\n"); + return ret; +} + +static void aml_audio_shutdown(struct platform_device *pdev) +{ + struct pinctrl_state *state; + + if (IS_ERR_OR_NULL(p_audio->pin_ctl)) { + pr_info("no audio pin_ctrl to shutdown\n"); + return; + } + + state = pinctrl_lookup_state(p_audio->pin_ctl, "aml_snd_suspend"); + if (!IS_ERR(state)) + pinctrl_select_state(p_audio->pin_ctl, state); +} + +static const struct of_device_id amlogic_audio_of_match[] = { + {.compatible = "aml, meson-snd-card",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, amlogic_audio_dt_match); + +static struct platform_driver aml_audio_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = amlogic_audio_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = aml_audio_probe, + .shutdown = aml_audio_shutdown, +}; + +module_platform_driver(aml_audio_driver); + +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_DESCRIPTION("AML_MESON audio machine Asoc driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amlogic/aml_meson.h b/sound/soc/amlogic/aml_meson.h new file mode 100644 index 000000000000..11beaee56524 --- /dev/null +++ b/sound/soc/amlogic/aml_meson.h @@ -0,0 +1,76 @@ +/* + * sound/soc/amlogic/aml_meson.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef AML_MESON_H +#define AML_MESON_H + +#include +#include +struct aml_audio_private_data { +#if 0 + + const char *name; + const char *card; + const char *codec; + const char *platform; + + unsigned int daifmt; + struct aml_audio_dai *cpu_dai; + struct aml_audio_dai *codec_dai; + + struct snd_soc_dai_link *snd_link; + int num_links; + struct snd_soc_card snd_card; +#endif + int bias_level; + int clock_en; + int gpio_hp_det; + bool det_pol_inv; + int sleep_time; + int gpio_mute; + int gpio_i2s_m; + int gpio_i2s_s; + int gpio_i2s_r; + int gpio_i2s_o; + bool mute_inv; + struct pinctrl *pin_ctl; + int hp_last_state; + bool hp_det_status; + unsigned int hp_val_h; + unsigned int hp_val_l; + unsigned int mic_val; + unsigned int hp_detal; + unsigned int hp_adc_ch; + bool suspended; + bool mic_det; + bool hp_disable; + int timer_en; + int detect_flag; + struct timer_list timer; + struct work_struct work; + struct mutex lock; + struct snd_soc_jack jack; + void *data; + struct gpio_desc *mute_desc; +#if 0 /*tmp_mask_for_kernel_4_4*/ + struct switch_dev sdev; /* for android */ + struct switch_dev mic_sdev; /* for android */ +#endif + struct work_struct pinmux_work; +}; + +#endif diff --git a/sound/soc/amlogic/aml_notify.c b/sound/soc/amlogic/aml_notify.c new file mode 100644 index 000000000000..4a87930993d4 --- /dev/null +++ b/sound/soc/amlogic/aml_notify.c @@ -0,0 +1,51 @@ +/* + * sound/soc/amlogic/aml_notify.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include + +static BLOCKING_NOTIFIER_HEAD(aout_notifier_list); +/** + * aout_register_client - register a client notifier + * @nb: notifier block to callback on events + */ +int aout_register_client(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&aout_notifier_list, nb); +} +EXPORT_SYMBOL(aout_register_client); + +/** + * aout_unregister_client - unregister a client notifier + * @nb: notifier block to callback on events + */ +int aout_unregister_client(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&aout_notifier_list, nb); +} +EXPORT_SYMBOL(aout_unregister_client); + +/** + * aout_notifier_call_chain - notify clients of fb_events + * + */ +int aout_notifier_call_chain(unsigned long val, void *v) +{ + return blocking_notifier_call_chain(&aout_notifier_list, val, v); +} +EXPORT_SYMBOL_GPL(aout_notifier_call_chain); + + diff --git a/sound/soc/amlogic/aml_pcm.c b/sound/soc/amlogic/aml_pcm.c new file mode 100644 index 000000000000..62e9ed436994 --- /dev/null +++ b/sound/soc/amlogic/aml_pcm.c @@ -0,0 +1,625 @@ +/* + * sound/soc/amlogic/aml_pcm.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#define pr_fmt(fmt) "aml_pcm: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "aml_pcm.h" +#include "aml_audio_hw_pcm.h" + +/*-------------------------------------------------------------------------- + * Hardware definition + *-------------------------------------------------------------------------- + * TODO: These values were taken from the AML platform driver, check + * them against real values for AML + */ +static const struct snd_pcm_hardware aml_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE, + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 32, + .period_bytes_max = 8 * 1024, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 64 * 1024, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 8, +}; + +static const struct snd_pcm_hardware aml_pcm_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_PAUSE, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 64 * 1024, + + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 8, + .fifo_size = 0, +}; + + +static unsigned int period_sizes[] = { 64, 128, 256, 512, + 1024, 2048, 4096, 8192 +}; + +static struct snd_pcm_hw_constraint_list hw_constraints_period_sizes = { + .count = ARRAY_SIZE(period_sizes), + .list = period_sizes, + .mask = 0 +}; + +static unsigned int aml_pcm_offset_tx(struct aml_pcm_runtime_data *prtd) +{ + unsigned int value = 0; + signed int diff = 0; + + value = pcm_out_rd_ptr(); + diff = value - prtd->buffer_start; + if (diff < 0) + diff = 0; + else if (diff >= prtd->buffer_size) + diff = prtd->buffer_size; + + pr_debug("%s value: 0x%08x offset: 0x%08x\n", __func__, + value, diff); + return (unsigned int)diff; +} + +static unsigned int aml_pcm_offset_rx(struct aml_pcm_runtime_data *prtd) +{ + unsigned int value = 0; + signed int diff = 0; + + value = pcm_in_wr_ptr(); + diff = value - prtd->buffer_start; + if (diff < 0) + diff = 0; + else if (diff >= prtd->buffer_size) + diff = prtd->buffer_size; + + pr_debug("%s value: 0x%08x offset: 0x%08x\n", __func__, + value, diff); + return (unsigned int)diff; +} + +static void aml_pcm_timer_update(struct aml_pcm_runtime_data *prtd) +{ + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int offset = 0; + unsigned int size = 0; + + if (prtd->running && snd_pcm_running(substream)) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + offset = aml_pcm_offset_tx(prtd); + if (offset < prtd->buffer_offset) { + size = + prtd->buffer_size + offset - + prtd->buffer_offset; + } else { + size = offset - prtd->buffer_offset; + } + } else { + int rx_overflow = 0; + + offset = aml_pcm_offset_rx(prtd); + if (offset < prtd->buffer_offset) { + size = + prtd->buffer_size + offset - + prtd->buffer_offset; + } else + size = offset - prtd->buffer_offset; + + rx_overflow = pcm_in_fifo_int() & (1 << 2); + if (rx_overflow) { + pr_info("%s AUDIN_FIFO overflow !!\n", + __func__); + } + } + } + + prtd->buffer_offset = offset; + prtd->data_size += size; + if (prtd->data_size >= frames_to_bytes(runtime, runtime->period_size)) + prtd->period_elapsed++; + +} + +static void aml_pcm_timer_rearm(struct aml_pcm_runtime_data *prtd) +{ + prtd->timer.expires = jiffies + prtd->timer_period; + add_timer(&prtd->timer); +} + +static int aml_pcm_timer_start(struct aml_pcm_runtime_data *prtd) +{ + pr_info("%s\n", __func__); + spin_lock(&prtd->lock); + aml_pcm_timer_rearm(prtd); + prtd->running = 1; + spin_unlock(&prtd->lock); + return 0; +} + +static int aml_pcm_timer_stop(struct aml_pcm_runtime_data *prtd) +{ + pr_info("%s\n", __func__); + spin_lock(&prtd->lock); + prtd->running = 0; + del_timer(&prtd->timer); + spin_unlock(&prtd->lock); + return 0; +} + +static void aml_pcm_timer_callback(unsigned long data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = runtime->private_data; + unsigned long flags; + unsigned int elapsed = 0; + unsigned int datasize = 0; + + spin_lock_irqsave(&prtd->lock, flags); + aml_pcm_timer_update(prtd); + aml_pcm_timer_rearm(prtd); + elapsed = prtd->period_elapsed; + datasize = prtd->data_size; + if (elapsed) { + prtd->period_elapsed--; + prtd->data_size -= + frames_to_bytes(runtime, runtime->period_size); + } + spin_unlock_irqrestore(&prtd->lock, flags); + if (elapsed) { + if (elapsed > 1) + pr_info("PCM timer callback not fast enough!\n"); + + snd_pcm_period_elapsed(prtd->substream); + } +} + +static int aml_pcm_timer_create(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = runtime->private_data; + + pr_info("%s\n", __func__); + init_timer(&prtd->timer); + prtd->timer_period = 1; + prtd->timer.data = (unsigned long)substream; + prtd->timer.function = aml_pcm_timer_callback; + prtd->running = 0; + return 0; +} + +static int +aml_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = runtime->private_data; + size_t size = params_buffer_bytes(params); + int ret = 0; + + pr_info("enter %s\n", __func__); + + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) + pr_err("%s malloc_pages return: %d\n", __func__, ret); + else { + prtd->buffer_start = runtime->dma_addr; + prtd->buffer_size = runtime->dma_bytes; + } + + return ret; +} + +static int aml_pcm_hw_free(struct snd_pcm_substream *substream) +{ + pr_info("enter %s\n", __func__); + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static int aml_pcm_prepare(struct snd_pcm_substream *substream) +{ + pr_info("enter %s\n", __func__); + + return 0; +} + +static int aml_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = runtime->private_data; + int ret = 0; + + pr_info("enter %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + aml_pcm_timer_start(prtd); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + aml_pcm_timer_stop(prtd); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t aml_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = runtime->private_data; + snd_pcm_uframes_t frames; + + /* pr_info("enter %s\n", __func__); */ + frames = bytes_to_frames(runtime, (ssize_t) prtd->buffer_offset); + + return frames; +} + +static int aml_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = NULL; + int ret; + + pr_info("enter %s, stream:%d\n", __func__, substream->stream); + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_set_runtime_hwparams(substream, &aml_pcm_hardware); + else + snd_soc_set_runtime_hwparams(substream, &aml_pcm_capture); + + /* Ensure that period size is a multiple of 32bytes */ + ret = + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &hw_constraints_period_sizes); + if (ret < 0) { + pr_err("set period bytes constraint error\n"); + goto out; + } + + /* Ensure that buffer size is a multiple of period size */ + ret = + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_err("set periods constraint error\n"); + goto out; + } + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) { + /*pr_err("out of memory\n");*/ + ret = -ENOMEM; + goto out; + } + + runtime->private_data = prtd; + aml_pcm_timer_create(substream); + prtd->substream = substream; + spin_lock_init(&prtd->lock); + + return 0; + out: + return ret; +} + +static int aml_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = runtime->private_data; + + pr_info("enter %s type: %d\n", __func__, substream->stream); + if (prtd) + kfree(runtime->private_data); + + return 0; +} + +static int +aml_pcm_copy_playback(struct snd_pcm_runtime *runtime, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct aml_pcm_runtime_data *prtd = runtime->private_data; + unsigned char *hwbuf = + runtime->dma_area + frames_to_bytes(runtime, pos); + unsigned int wrptr = 0; + int ret = 0; + +/* pr_info("enter %s channel: %d pos: %ld count: %ld\n", + * __func__, channel, pos, count); + */ + if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, count))) { + pr_err("%s copy from user failed!\n", __func__); + return -EFAULT; + } + + { + wrptr = prtd->buffer_start + frames_to_bytes(runtime, pos) + + frames_to_bytes(runtime, count); + if (wrptr >= (prtd->buffer_start + prtd->buffer_size)) + wrptr = prtd->buffer_start + prtd->buffer_size; + + pcm_out_set_wr_ptr(wrptr); + } + return ret; +} + +static int +aml_pcm_copy_capture(struct snd_pcm_runtime *runtime, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct aml_pcm_runtime_data *prtd = runtime->private_data; + signed short *hwbuf = + (signed short *)(runtime->dma_area + frames_to_bytes(runtime, pos)); + unsigned int rdptr = 0; + int ret = 0; + +/* pr_info("enter %s channel: %d pos: %ld count: %ld\n", + * __func__, channel, pos, count); + */ + if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, count))) { + pr_err("%s copy to user failed!\n", __func__); + return -EFAULT; + } + + { + /* memset(hwbuf, 0xff, frames_to_bytes(runtime, count)); */ + rdptr = + prtd->buffer_start + frames_to_bytes(runtime, + pos) + + frames_to_bytes(runtime, count); + if (rdptr >= (prtd->buffer_start + prtd->buffer_size)) + rdptr = prtd->buffer_start + prtd->buffer_size; + + pcm_in_set_rd_ptr(rdptr); + } + return ret; +} + +static int aml_pcm_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = + aml_pcm_copy_playback(runtime, channel, pos, buf, count); + } else { + ret = + aml_pcm_copy_capture(runtime, channel, pos, buf, count); + } + + return ret; +} + +static int aml_pcm_silence(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned char *ppos = NULL; + ssize_t n; + + pr_info("enter %s\n", __func__); + n = frames_to_bytes(runtime, count); + ppos = runtime->dma_area + frames_to_bytes(runtime, pos); + memset(ppos, 0, n); + + return 0; +} + +static struct snd_pcm_ops aml_pcm_ops = { + .open = aml_pcm_open, + .close = aml_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = aml_pcm_hw_params, + .hw_free = aml_pcm_hw_free, + .prepare = aml_pcm_prepare, + .trigger = aml_pcm_trigger, + .pointer = aml_pcm_pointer, + .copy = aml_pcm_copy, + .silence = aml_pcm_silence, +}; + +/* -------------------------------------------------------------------------- + * ASoC platform driver + * -------------------------------------------------------------------------- + */ + +static u64 aml_pcm_dmamask = DMA_BIT_MASK(32); + +static int aml_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = aml_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + dev_err(pcm->card->dev, "aml_pcm alloc failed!\n"); + return -ENOMEM; + } + + buf->bytes = size; + + return 0; +} + +static int aml_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_soc_card *card = rtd->card; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_dai *dai; + + dai = rtd->cpu_dai; + pr_info("enter %s dai->name: %s dai->id: %d\n", __func__, + dai->name, dai->id); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &aml_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = aml_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = aml_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + + out: + return ret; +} + +static void aml_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + pr_info("enter %s\n", __func__); + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, buf->area, + buf->addr); + buf->area = NULL; + } +} + +struct snd_soc_platform_driver aml_soc_platform_pcm = { + .ops = &aml_pcm_ops, + .pcm_new = aml_pcm_new, + .pcm_free = aml_pcm_free, +}; +EXPORT_SYMBOL_GPL(aml_soc_platform_pcm); + +static int aml_soc_platform_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &aml_soc_platform_pcm); +} + +static int aml_soc_platform_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_audio_dt_match[] = { + { + .compatible = "amlogic, aml-pcm", + }, + {}, +}; +#else +#define amlogic_audio_dt_match NULL +#endif + +static struct platform_driver aml_platform_pcm_driver = { + .driver = { + .name = "aml-pcm", + .owner = THIS_MODULE, + .of_match_table = amlogic_audio_dt_match, + }, + + .probe = aml_soc_platform_pcm_probe, + .remove = aml_soc_platform_pcm_remove, +}; + +static int __init aml_alsa_pcm_init(void) +{ + return platform_driver_register(&aml_platform_pcm_driver); +} + +static void __exit aml_alsa_pcm_exit(void) +{ + platform_driver_unregister(&aml_platform_pcm_driver); +} + +module_init(aml_alsa_pcm_init); +module_exit(aml_alsa_pcm_exit); + +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AML audio driver for ALSA"); diff --git a/sound/soc/amlogic/aml_pcm.h b/sound/soc/amlogic/aml_pcm.h new file mode 100644 index 000000000000..8eb9dabd2e4f --- /dev/null +++ b/sound/soc/amlogic/aml_pcm.h @@ -0,0 +1,35 @@ +/* + * sound/soc/amlogic/aml_pcm.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef __AML_PCM_H__ +#define __AML_PCM_H__ + +struct aml_pcm_runtime_data { + spinlock_t lock; + dma_addr_t buffer_start; + unsigned int buffer_size; + unsigned int buffer_offset; + unsigned int data_size; + unsigned int running; + unsigned int timer_period; + unsigned int period_elapsed; + + struct timer_list timer; + struct snd_pcm_substream *substream; +}; + +#endif diff --git a/sound/soc/amlogic/aml_pcm_dai.c b/sound/soc/amlogic/aml_pcm_dai.c new file mode 100644 index 000000000000..88960bb49abd --- /dev/null +++ b/sound/soc/amlogic/aml_pcm_dai.c @@ -0,0 +1,383 @@ +/* + * sound/soc/amlogic/aml_pcm_dai.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#define pr_fmt(fmt) "aml_pcm_dai: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "aml_pcm_dai.h" +#include "aml_pcm.h" +#include "aml_i2s.h" +#include "aml_audio_hw_pcm.h" + +#include + +#define DEV_NAME "aml-pcm-dai" + +#define AML_DAI_PCM_RATES (SNDRV_PCM_RATE_8000_192000) +#define AML_DAI_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +#define PCM_DEFAULT_SAMPLERATE 8000 +#define PCM_DEFAULT_MCLK_RATIO_SR 256 + +static int aml_pcm_set_clk(struct aml_pcm *pcm, unsigned long rate) +{ + int ret = 0; + + ret = clk_set_rate(pcm->clk_mpll, rate * 10); + if (ret) { + pr_debug("%s, line:%d, error:%d\n", __func__, __LINE__, ret); + return ret; + } + ret = clk_set_parent(pcm->clk_pcm_mclk, pcm->clk_mpll); + if (ret) { + pr_debug("%s line:%d, error:%d\n", __func__, __LINE__, ret); + return ret; + } + + ret = clk_set_rate(pcm->clk_pcm_mclk, rate); + if (ret) { + pr_debug("%s, line:%d, error:%d\n", __func__, __LINE__, ret); + return ret; + } + + return 0; +} + +static int aml_pcm_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pr_debug("***Entered %s\n", __func__); + return 0; +} + +static void aml_pcm_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pr_debug("***Entered %s\n", __func__); +} + +static int aml_pcm_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_pcm_runtime_data *prtd = runtime->private_data; + + pr_debug("***Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_info( + "%s playback stream buffer start: %ld size: 0x%x\n", + __func__, (long)prtd->buffer_start, prtd->buffer_size); + pcm_out_set_buf(prtd->buffer_start, prtd->buffer_size); + } else { + pr_info( + "%s capture stream buffer start: %ld size: 0x%x\n", + __func__, (long)prtd->buffer_start, prtd->buffer_size); + pcm_in_set_buf(prtd->buffer_start, prtd->buffer_size); + } + + memset(runtime->dma_area, 0, runtime->dma_bytes); + prtd->buffer_offset = 0; + prtd->data_size = 0; + prtd->period_elapsed = 0; + + return 0; +} + +static int aml_pcm_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct aml_pcm *pcm_p = dev_get_drvdata(dai->dev); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* TODO */ + if (pcm_p && pcm_p->pcm_mode) { + pr_info("aiu pcm master stream %d enable\n\n", + substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pcm_master_out_enable(substream, 1); + else + pcm_master_in_enable(substream, 1); + } else { + pr_info("aiu slave pcm stream %d enable\n\n", + substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pcm_out_enable(1); + else + pcm_in_enable(1); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (pcm_p && pcm_p->pcm_mode) { + pr_info("aiu master pcm stream %d disable\n\n", + substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pcm_master_out_enable(substream, 0); + else + pcm_master_in_enable(substream, 0); + } else { + pr_info("aiu slave pcm stream %d disable\n\n", + substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pcm_out_enable(0); + else + pcm_in_enable(0); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int aml_pcm_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct aml_pcm *pcm = snd_soc_dai_get_drvdata(dai); + int srate, mclk_rate; + + srate = params_rate(params); + if (pcm->old_samplerate != srate) { + pcm->old_samplerate = srate; + mclk_rate = srate * PCM_DEFAULT_MCLK_RATIO_SR; + aml_pcm_set_clk(pcm, mclk_rate); + } + + pr_debug("***Entered %s:%s\n", __FILE__, __func__); + return 0; +} + +static int aml_pcm_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + pr_debug("***Entered %s\n", __func__); + if (fmt & SND_SOC_DAIFMT_CBS_CFS) + snd_soc_dai_get_drvdata(dai); + return 0; +} + +static int aml_pcm_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + pr_debug("***Entered %s\n", __func__); + return 0; +} + +#ifdef CONFIG_PM +static int aml_pcm_dai_suspend(struct snd_soc_dai *dai) +{ + + pr_debug("***Entered %s\n", __func__); + return 0; +} + +static int aml_pcm_dai_resume(struct snd_soc_dai *dai) +{ + pr_debug("***Entered %s\n", __func__); + return 0; +} + +#else /* CONFIG_PM */ +#define aml_pcm_dai_suspend NULL +#define aml_pcm_dai_resume NULL +#endif /* CONFIG_PM */ + + +static struct snd_soc_dai_ops aml_pcm_dai_ops = { + .startup = aml_pcm_dai_startup, + .shutdown = aml_pcm_dai_shutdown, + .prepare = aml_pcm_dai_prepare, + .trigger = aml_pcm_dai_trigger, + .hw_params = aml_pcm_dai_hw_params, + .set_fmt = aml_pcm_dai_set_fmt, + .set_sysclk = aml_pcm_dai_set_sysclk, +}; + +struct snd_soc_dai_driver aml_pcm_dai[] = { + { + .suspend = aml_pcm_dai_suspend, + .resume = aml_pcm_dai_resume, + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = AML_DAI_PCM_RATES, + .formats = AML_DAI_PCM_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 8, + .rates = AML_DAI_PCM_RATES, + .formats = AML_DAI_PCM_FORMATS,}, + .ops = &aml_pcm_dai_ops, + }, + +}; +EXPORT_SYMBOL_GPL(aml_pcm_dai); + +static const struct snd_soc_component_driver aml_component = { + .name = DEV_NAME, +}; + +static int aml_pcm_dai_probe(struct platform_device *pdev) +{ + struct pinctrl *pin_ctl; + struct aml_pcm *pcm_p = NULL; + int ret; + + pr_debug("enter %s\n", __func__); + + pin_ctl = devm_pinctrl_get_select(&pdev->dev, "aml_audio_btpcm"); + if (IS_ERR(pin_ctl)) { + pin_ctl = NULL; + pr_err("aml audio pcm dai pinmux set error!\n"); + } + + pcm_p = devm_kzalloc(&pdev->dev, sizeof(struct aml_pcm), GFP_KERNEL); + if (!pcm_p) { + /*dev_err(&pdev->dev, "Can't allocate pcm_p\n");*/ + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, pcm_p); + + /* is PCM master? */ + ret = + of_property_read_u32((&pdev->dev)->of_node, "pcm_mode", + &pcm_p->pcm_mode); + + pr_info("pcm mode detection =%d\n", pcm_p->pcm_mode); + + if (pcm_p->pcm_mode) { + pcm_p->clk_mpll = devm_clk_get(&pdev->dev, "mpll0"); + if (IS_ERR(pcm_p->clk_mpll)) { + dev_err(&pdev->dev, "Can't retrieve mpll0 clock\n"); + ret = PTR_ERR(pcm_p->clk_mpll); + goto err; + } + + pcm_p->clk_pcm_mclk = devm_clk_get(&pdev->dev, "pcm_mclk"); + if (IS_ERR(pcm_p->clk_pcm_mclk)) { + dev_err(&pdev->dev, "Can't retrieve clk_pcm_mclk clock\n"); + ret = PTR_ERR(pcm_p->clk_pcm_mclk); + goto err; + } + pcm_p->clk_pcm_sync = devm_clk_get(&pdev->dev, "pcm_sclk"); + if (IS_ERR(pcm_p->clk_pcm_sync)) { + dev_err(&pdev->dev, "Can't retrieve clk_pcm_sync clock\n"); + ret = PTR_ERR(pcm_p->clk_pcm_sync); + goto err; + } + + /* now only 256fs is supported */ + ret = aml_pcm_set_clk(pcm_p, + PCM_DEFAULT_SAMPLERATE * PCM_DEFAULT_MCLK_RATIO_SR); + if (ret < 0) { + dev_err(&pdev->dev, "Can't set aml_pcm clk :%d\n", ret); + goto err; + } + + ret = clk_prepare_enable(pcm_p->clk_pcm_mclk); + if (ret) { + dev_err(&pdev->dev, + "Can't enable pcm clk_pcm_mclk clock: %d\n", ret); + goto err; + } + ret = clk_prepare_enable(pcm_p->clk_pcm_sync); + if (ret) { + dev_err(&pdev->dev, + "Can't enable pcm clk_pcm_sync clock: %d\n", ret); + goto err; + } + } + + ret = snd_soc_register_component(&pdev->dev, &aml_component, + aml_pcm_dai, ARRAY_SIZE(aml_pcm_dai)); + +err: + return ret; + +} + +static int aml_pcm_dai_remove(struct platform_device *pdev) +{ + struct aml_pcm *pcm_priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(pcm_priv->clk_pcm_mclk); + clk_disable_unprepare(pcm_priv->clk_pcm_sync); + + snd_soc_unregister_component(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_pcm_dai_match[] = { + {.compatible = "amlogic, aml-pcm-dai", + }, + {}, +}; +#else +#define amlogic_pcm_dai_match NULL +#endif + +static struct platform_driver aml_pcm_dai_driver = { + .driver = { + .name = DEV_NAME, + .owner = THIS_MODULE, + .of_match_table = amlogic_pcm_dai_match, + }, + + .probe = aml_pcm_dai_probe, + .remove = aml_pcm_dai_remove, +}; + +static int __init aml_pcm_dai_modinit(void) +{ + return platform_driver_register(&aml_pcm_dai_driver); +} + +module_init(aml_pcm_dai_modinit); + +static void __exit aml_pcm_dai_modexit(void) +{ + platform_driver_unregister(&aml_pcm_dai_driver); +} + +module_exit(aml_pcm_dai_modexit); + +/* Module information */ +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_DESCRIPTION("AML DAI driver for ALSA"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/amlogic/aml_pcm_dai.h b/sound/soc/amlogic/aml_pcm_dai.h new file mode 100644 index 000000000000..0a64f3bdc2fa --- /dev/null +++ b/sound/soc/amlogic/aml_pcm_dai.h @@ -0,0 +1,32 @@ +/* + * sound/soc/amlogic/aml_pcm_dai.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef AML_DAI_H +#define AML_DAI_H + +struct aml_pcm { + struct clk *clk_mpll; + struct clk *clk_pcm_mclk; + struct clk *clk_pcm_sync; + int old_samplerate; + int pcm_mode; +}; + +void aml_hw_iec958_init(struct snd_pcm_substream *substream); +extern struct snd_soc_dai_driver aml_dai[]; + +#endif diff --git a/sound/soc/amlogic/aml_spdif_codec.c b/sound/soc/amlogic/aml_spdif_codec.c new file mode 100644 index 000000000000..15f06c2b53e4 --- /dev/null +++ b/sound/soc/amlogic/aml_spdif_codec.c @@ -0,0 +1,197 @@ +/* + * sound/soc/amlogic/aml_spdif_codec.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "spdif-dit" + +#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct pinctrl *pin_spdif_ctl; +struct device *spdif_dev; +static struct snd_soc_dai_driver dit_stub_dai = { + .name = "dit-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static unsigned int spdif_pinmux; +void aml_spdif_pinmux_init(struct device *dev) +{ + if (!spdif_pinmux) { + spdif_pinmux = 1; + pin_spdif_ctl = devm_pinctrl_get_select(dev, "aml_audio_spdif"); + if (IS_ERR(pin_spdif_ctl)) { + pin_spdif_ctl = NULL; + dev_err(dev, "aml_spdif_pinmux_init can't get pinctrl\n"); + } + } +} + +void aml_spdif_pinmux_deinit(struct device *dev) +{ + dev_dbg(dev, "aml_spdif_mute\n"); + if (spdif_pinmux) { + spdif_pinmux = 0; + if (pin_spdif_ctl) + devm_pinctrl_put(pin_spdif_ctl); + } +} +bool aml_audio_spdif_mute_flag; +static int aml_audio_set_spdif_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + aml_audio_spdif_mute_flag = ucontrol->value.integer.value[0]; + pr_info("aml_audio_set_spdif_mute: flag=%d\n", + aml_audio_spdif_mute_flag); + if (aml_audio_spdif_mute_flag) + aml_spdif_pinmux_deinit(spdif_dev); + else + aml_spdif_pinmux_init(spdif_dev); + return 0; +} + +static int aml_audio_get_spdif_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aml_audio_spdif_mute_flag; + return 0; +} + +static const struct snd_kcontrol_new spdif_controls[] = { + SOC_SINGLE_BOOL_EXT("Audio spdif mute", + 0, aml_audio_get_spdif_mute, + aml_audio_set_spdif_mute), +}; + +static int spdif_probe(struct snd_soc_codec *codec) +{ + return snd_soc_add_codec_controls(codec, + spdif_controls, ARRAY_SIZE(spdif_controls)); +} +static struct snd_soc_codec_driver soc_codec_spdif_dit = { + .probe = spdif_probe, +}; + +static ssize_t spdif_mute_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (spdif_pinmux) + return sprintf(buf, "spdif_unmute\n"); + else + return sprintf(buf, "spdif_mute\n"); + +} + +static ssize_t spdif_mute_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + if (strncmp(buf, "spdif_mute", 10)) + aml_spdif_pinmux_init(dev); + else if (strncmp(buf, "spdif_unmute", 12)) + aml_spdif_pinmux_deinit(dev); + else + dev_err(dev, "spdif set the wrong value\n"); + + return count; +} + +static DEVICE_ATTR(spdif_mute, 0660, spdif_mute_show, spdif_mute_set); + +static int spdif_dit_probe(struct platform_device *pdev) +{ + int ret = device_create_file(&pdev->dev, &dev_attr_spdif_mute); + + spdif_dev = &pdev->dev; + + aml_spdif_pinmux_init(&pdev->dev); + if (ret < 0) + dev_err(&pdev->dev, + "spdif: failed to add spdif_mute sysfs: %d\n", ret); + + return snd_soc_register_codec(&pdev->dev, &soc_codec_spdif_dit, + &dit_stub_dai, 1); +} + +static int spdif_dit_remove(struct platform_device *pdev) +{ + aml_spdif_pinmux_deinit(&pdev->dev); + device_remove_file(&pdev->dev, &dev_attr_spdif_mute); + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_spdif_codec_dt_match[] = { + {.compatible = "amlogic, aml-spdif-codec", + }, + {}, +}; +#else +#define amlogic_spdif_codec_dt_match NULL +#endif + +static struct platform_driver spdif_dit_driver = { + .probe = spdif_dit_probe, + .remove = spdif_dit_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = amlogic_spdif_codec_dt_match, + }, +}; + +static int __init spdif_codec_init(void) +{ + return platform_driver_register(&spdif_dit_driver); +} + +static void __exit spdif_codec_exit(void) +{ + platform_driver_unregister(&spdif_dit_driver); +} + +module_init(spdif_codec_init); +module_exit(spdif_codec_exit); + +MODULE_AUTHOR("Steve Chen "); +MODULE_DESCRIPTION("SPDIF dummy codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amlogic/aml_spdif_dai.c b/sound/soc/amlogic/aml_spdif_dai.c new file mode 100644 index 000000000000..22b6a88073a2 --- /dev/null +++ b/sound/soc/amlogic/aml_spdif_dai.c @@ -0,0 +1,794 @@ +/* + * sound/soc/amlogic/aml_spdif_dai.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#define pr_fmt(fmt) "aml_spdif_dai: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "aml_audio_hw.h" +#include "aml_spdif_dai.h" +#include "aml_i2s.h" +#include +#include +#include +#include + +/* + * 0 -- other formats except(DD,DD+,DTS) + * 1 -- DTS + * 2 -- DD + * 3 -- DTS with 958 PCM RAW package mode + * 4 -- DD+ + */ +unsigned int IEC958_mode_codec; +EXPORT_SYMBOL(IEC958_mode_codec); +struct aml_spdif { + struct clk *clk_mpl1; + struct clk *clk_i958; + struct clk *clk_mclk; + struct clk *clk_spdif; + struct clk *clk_81; + int old_samplerate; + uint clk_div; + /* spdif dai data in source select. + * !Check this with chip spec. + */ + uint src; +}; +struct aml_spdif *spdif_p; +unsigned int clk81; +EXPORT_SYMBOL(clk81); + +static int old_samplerate = -1; +static int flag_samesrc = -1; + +static void set_IEC958_clock_div(uint div) +{ + if (div == spdif_p->clk_div) + return; + + if (div > 0 && div <= 4) { + pr_info("set 958 audio clk div %d\n", div); + audio_set_spdif_clk_div(div); + spdif_p->clk_div = div; + } +} + +static inline bool is_meson_tv_chipset(void) +{ + return is_meson_gxtvbb_cpu() || is_meson_txl_cpu(); +} + +void aml_spdif_play(int samesrc) +{ + if (!is_meson_tv_chipset()) { + uint div = 0; + static int iec958buf[32 + 16]; + struct _aiu_958_raw_setting_t set; + struct _aiu_958_channel_status_t chstat; + struct snd_pcm_substream substream; + struct snd_pcm_runtime runtime; + + substream.runtime = &runtime; + runtime.rate = 48000; + runtime.format = SNDRV_PCM_FORMAT_S16_LE; + runtime.channels = 2; + runtime.sample_bits = 16; + memset((void *)(&set), 0, sizeof(set)); + memset((void *)(&chstat), 0, sizeof(chstat)); + set.chan_stat = &chstat; + set.chan_stat->chstat0_l = 0x0100; + set.chan_stat->chstat0_r = 0x0100; + set.chan_stat->chstat1_l = 0X200; + set.chan_stat->chstat1_r = 0X200; + audio_hw_958_enable(0); + if (old_samplerate != AUDIO_CLK_FREQ_48 + || samesrc != flag_samesrc) { + pr_info("enterd %s,set_clock:%d,sample_rate=%d\n", + __func__, old_samplerate, AUDIO_CLK_FREQ_48); + old_samplerate = AUDIO_CLK_FREQ_48; + flag_samesrc = samesrc; + aml_set_spdif_clk(48000 * 512, samesrc); + } + if (IEC958_mode_codec == 4 || IEC958_mode_codec == 5 || + IEC958_mode_codec == 7 || IEC958_mode_codec == 8) { + pr_info("set 4x audio clk for 958\n"); + div = 1; + } else if (samesrc) { + pr_info("share the same clock\n"); + div = 2; + } else { + pr_info("set normal 512 fs /4 fs\n"); + div = 4; + } + + set_IEC958_clock_div(div); + audio_util_set_dac_958_format(AUDIO_ALGOUT_DAC_FORMAT_DSP); + /*clear the same source function as new raw data output */ + audio_i2s_958_same_source(samesrc); + memset(iec958buf, 0, sizeof(iec958buf)); + audio_set_958outbuf((virt_to_phys(iec958buf) + 63) & (~63), + 128, 0); + audio_set_958_mode(AIU_958_MODE_PCM16, &set); + aout_notifier_call_chain(AOUT_EVENT_IEC_60958_PCM, &substream); + audio_hw_958_enable(1); + } +} + +static void aml_spdif_play_stop(void) +{ + audio_hw_958_enable(0); +} + +static int aml_dai_spdif_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int aml_dai_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + + struct snd_soc_pcm_runtime *rtd = NULL; + + rtd = (struct snd_soc_pcm_runtime *)substream->private_data; + 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) { + pr_info("aiu 958 playback enable\n"); + audio_hw_958_enable(1); + } else { + pr_info("spdif in capture enable\n"); + audio_in_spdif_enable(1); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_info("aiu 958 playback disable\n"); + audio_hw_958_enable(0); + } else { + pr_info("spdif in capture disable\n"); + audio_in_spdif_enable(0); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +void aml_hw_iec958_init(struct snd_pcm_substream *substream, int samesrc) +{ + struct _aiu_958_raw_setting_t set; + struct _aiu_958_channel_status_t chstat; + unsigned int i2s_mode, iec958_mode; + unsigned int start, size; + int sample_rate; + int div; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (buf == NULL && runtime == NULL) { + pr_info("buf/%p runtime/%p\n", buf, runtime); + return; + } + + i2s_mode = AIU_I2S_MODE_PCM16; + sample_rate = AUDIO_CLK_FREQ_48; + memset((void *)(&set), 0, sizeof(set)); + memset((void *)(&chstat), 0, sizeof(chstat)); + set.chan_stat = &chstat; + switch (runtime->rate) { + case 192000: + sample_rate = AUDIO_CLK_FREQ_192; + break; + case 176400: + sample_rate = AUDIO_CLK_FREQ_1764; + break; + case 96000: + sample_rate = AUDIO_CLK_FREQ_96; + break; + case 88200: + sample_rate = AUDIO_CLK_FREQ_882; + break; + case 48000: + sample_rate = AUDIO_CLK_FREQ_48; + break; + case 44100: + sample_rate = AUDIO_CLK_FREQ_441; + break; + case 32000: + sample_rate = AUDIO_CLK_FREQ_32; + break; + case 8000: + sample_rate = AUDIO_CLK_FREQ_8; + break; + case 11025: + sample_rate = AUDIO_CLK_FREQ_11; + break; + case 16000: + sample_rate = AUDIO_CLK_FREQ_16; + break; + case 22050: + sample_rate = AUDIO_CLK_FREQ_22; + break; + case 12000: + sample_rate = AUDIO_CLK_FREQ_12; + break; + case 24000: + sample_rate = AUDIO_CLK_FREQ_22; + break; + default: + sample_rate = AUDIO_CLK_FREQ_441; + break; + }; + audio_hw_958_enable(0); + pr_info("aml_hw_iec958_init,runtime->rate=%d, same source mode(%d)\n", + runtime->rate, samesrc); + + if (old_samplerate != sample_rate || samesrc != flag_samesrc) { + old_samplerate = sample_rate; + flag_samesrc = samesrc; + aml_set_spdif_clk(runtime->rate * 512, samesrc); + } + + if (IEC958_mode_codec == 4 || IEC958_mode_codec == 5 || + IEC958_mode_codec == 7 || IEC958_mode_codec == 8) { + pr_info("set 4x audio clk for 958\n"); + div = 1; + } else if (samesrc) { + pr_info("share the same clock\n"); + div = 2; + } else { + pr_info("set normal 512 fs /4 fs\n"); + div = 4; + } + set_IEC958_clock_div(div); + audio_util_set_dac_958_format(AUDIO_ALGOUT_DAC_FORMAT_DSP); + /*clear the same source function as new raw data output */ + audio_i2s_958_same_source(samesrc); + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S32_LE: + i2s_mode = AIU_I2S_MODE_PCM32; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s_mode = AIU_I2S_MODE_PCM24; + break; + case SNDRV_PCM_FORMAT_S16_LE: + i2s_mode = AIU_I2S_MODE_PCM16; + break; + } + + /* audio_set_i2s_mode(i2s_mode); */ + /* case 1,raw mode enabled */ + if (IEC958_mode_codec && IEC958_mode_codec != 9) { + if (IEC958_mode_codec == 1) { + /* dts, use raw sync-word mode */ + iec958_mode = AIU_958_MODE_RAW; + pr_info("iec958 mode RAW\n"); + } else { + /* ac3,use the same pcm mode as i2s configuration */ + iec958_mode = AIU_958_MODE_PCM_RAW; + pr_info("iec958 mode %s\n", + (i2s_mode == AIU_I2S_MODE_PCM32) ? "PCM32_RAW" + : ((I2S_MODE == AIU_I2S_MODE_PCM24) ? + "PCM24_RAW" : "PCM16_RAW")); + } + } else { + if (i2s_mode == AIU_I2S_MODE_PCM32) + iec958_mode = AIU_958_MODE_PCM32; + else if (i2s_mode == AIU_I2S_MODE_PCM24) + iec958_mode = AIU_958_MODE_PCM24; + else + iec958_mode = AIU_958_MODE_PCM16; + pr_info("iec958 mode %s\n", + (i2s_mode == + AIU_I2S_MODE_PCM32) ? "PCM32" : ((i2s_mode == + AIU_I2S_MODE_PCM24) ? + "PCM24" : "PCM16")); + } + if (iec958_mode == AIU_958_MODE_PCM16 + || iec958_mode == AIU_958_MODE_PCM24 + || iec958_mode == AIU_958_MODE_PCM32 + || IEC958_mode_codec == 9) { + set.chan_stat->chstat0_l = 0x0100; + set.chan_stat->chstat0_r = 0x0100; + set.chan_stat->chstat1_l = 0x200; + set.chan_stat->chstat1_r = 0x200; + if (sample_rate == AUDIO_CLK_FREQ_882) { + pr_info("sample_rate==AUDIO_CLK_FREQ_882\n"); + set.chan_stat->chstat1_l = 0x800; + set.chan_stat->chstat1_r = 0x800; + } else if (sample_rate == AUDIO_CLK_FREQ_96) { + pr_info("sample_rate==AUDIO_CLK_FREQ_96\n"); + set.chan_stat->chstat1_l = 0xa00; + set.chan_stat->chstat1_r = 0xa00; + } else if (sample_rate == AUDIO_CLK_FREQ_1764) { + pr_info("sample_rate==AUDIO_CLK_FREQ_1764\n"); + set.chan_stat->chstat1_l = 0xc00; + set.chan_stat->chstat1_r = 0xc00; + } else if (sample_rate == AUDIO_CLK_FREQ_192) { + pr_info("sample_rate==AUDIO_CLK_FREQ_192\n"); + set.chan_stat->chstat1_l = 0xe00; + set.chan_stat->chstat1_r = 0xe00; + } + start = buf->addr; + size = snd_pcm_lib_buffer_bytes(substream); + audio_set_958outbuf(start, size, 0); + /* audio_set_i2s_mode(AIU_I2S_MODE_PCM16); */ + /* audio_set_aiubuf(start, size); */ + } else { + + set.chan_stat->chstat0_l = 0x1902; + set.chan_stat->chstat0_r = 0x1902; + if (IEC958_mode_codec == 4 || IEC958_mode_codec == 5) { + /* DD+ */ + if (runtime->rate == 32000) { + set.chan_stat->chstat1_l = 0x300; + set.chan_stat->chstat1_r = 0x300; + } else if (runtime->rate == 44100) { + set.chan_stat->chstat1_l = 0xc00; + set.chan_stat->chstat1_r = 0xc00; + } else { + set.chan_stat->chstat1_l = 0Xe00; + set.chan_stat->chstat1_r = 0Xe00; + } + } else { + /* DTS,DD */ + if (runtime->rate == 32000) { + set.chan_stat->chstat1_l = 0x300; + set.chan_stat->chstat1_r = 0x300; + } else if (runtime->rate == 44100) { + set.chan_stat->chstat1_l = 0; + set.chan_stat->chstat1_r = 0; + } else { + set.chan_stat->chstat1_l = 0x200; + set.chan_stat->chstat1_r = 0x200; + } + } + start = buf->addr; + size = snd_pcm_lib_buffer_bytes(substream); + audio_set_958outbuf(start, size, + (iec958_mode == AIU_958_MODE_RAW) ? 1 : 0); + memset((void *)buf->area, 0, size); + } + audio_set_958_mode(iec958_mode, &set); + + if (IEC958_mode_codec == 2) { + aout_notifier_call_chain(AOUT_EVENT_RAWDATA_AC_3, substream); + } else if (IEC958_mode_codec == 3) { + aout_notifier_call_chain(AOUT_EVENT_RAWDATA_DTS, substream); + } else if (IEC958_mode_codec == 4) { + aout_notifier_call_chain(AOUT_EVENT_RAWDATA_DOBLY_DIGITAL_PLUS, + substream); + } else if (IEC958_mode_codec == 5) { + aout_notifier_call_chain(AOUT_EVENT_RAWDATA_DTS_HD, substream); + } else if (IEC958_mode_codec == 7 || IEC958_mode_codec == 8) { + aml_write_cbus(AIU_958_CHSTAT_L0, 0x1902); + aml_write_cbus(AIU_958_CHSTAT_L1, 0x900); + aml_write_cbus(AIU_958_CHSTAT_R0, 0x1902); + aml_write_cbus(AIU_958_CHSTAT_R1, 0x900); + if (IEC958_mode_codec == 8) + aout_notifier_call_chain(AOUT_EVENT_RAWDATA_DTS_HD_MA, + substream); + else + aout_notifier_call_chain(AOUT_EVENT_RAWDATA_MAT_MLP, + substream); + } else { + aout_notifier_call_chain(AOUT_EVENT_IEC_60958_PCM, substream); + } +} + +/* + * special call by the audiodsp,add these code, + * as there are three cases for 958 s/pdif output + * 1)NONE-PCM raw output ,only available when ac3/dts audio, + * when raw output mode is selected by user. + * 2)PCM output for all audio, when pcm mode is selected by user . + * 3)PCM output for audios except ac3/dts, + * when raw output mode is selected by user + */ + +void aml_alsa_hw_reprepare(void) +{ + /* M8 disable it */ +#if 0 + /* disable 958 module before call initiation */ + + audio_hw_958_enable(0); + if (playback_substream_handle != 0) + aml_hw_iec958_init((struct snd_pcm_substream *) + playback_substream_handle); +#endif +} + +static int aml_dai_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct aml_runtime_data *prtd = runtime->private_data; + struct audio_stream *s; + + if (!prtd) { + prtd = + (struct aml_runtime_data *) + kzalloc(sizeof(struct aml_runtime_data), GFP_KERNEL); + if (prtd == NULL) { + pr_err("alloc aml_runtime_data error\n"); + ret = -ENOMEM; + goto out; + } + prtd->substream = substream; + runtime->private_data = prtd; + } + s = &prtd->s; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + s->device_type = AML_AUDIO_SPDIFOUT; + /* audio_spdifout_pg_enable(1); */ + /*aml_spdif_play_stop(); */ + } else { + s->device_type = AML_AUDIO_SPDIFIN; + } + + return 0; + out: + return ret; +} + +static void aml_dai_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + /* struct snd_dma_buffer *buf = &substream->dma_buffer; */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + memset((void *)runtime->dma_area, 0, + snd_pcm_lib_buffer_bytes(substream)); + if (IEC958_mode_codec == 6) + pr_info("8chPCM output:disable aml_spdif_play\n"); + } + +} + +static int aml_dai_spdif_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + + /* struct snd_soc_pcm_runtime *rtd = substream->private_data; */ + struct snd_pcm_runtime *runtime = substream->runtime; + /* struct aml_runtime_data *prtd = runtime->private_data; */ + /* audio_stream_t *s = &prtd->s; */ + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aml_hw_iec958_init(substream, 0); + } else { + audio_in_spdif_set_buf(runtime->dma_addr, + runtime->dma_bytes * 2, spdif_p->src); + memset((void *)runtime->dma_area, 0, runtime->dma_bytes * 2); + } + + return 0; +} + +/* if src_i2s, then spdif parent clk is mclk, otherwise i958clk */ +int aml_set_spdif_clk(unsigned long rate, bool src_i2s) +{ + int ret = 0; + + if (src_i2s) { + ret = clk_set_parent(spdif_p->clk_spdif, spdif_p->clk_mclk); + if (ret) { + pr_err("Can't set spdif clk parent: %d\n", ret); + return ret; + } + } else { + ret = clk_set_rate(spdif_p->clk_mpl1, rate * 4); + if (ret) { + pr_err("Can't set spdif mpl1 clock rate: %d\n", ret); + return ret; + } + ret = clk_set_parent(spdif_p->clk_i958, spdif_p->clk_mpl1); + if (ret) { + pr_err("Can't set spdif clk parent: %d\n", ret); + return ret; + } + ret = clk_set_rate(spdif_p->clk_i958, rate); + if (ret) { + pr_err("Can't set spdif mpl1 clock rate: %d\n", ret); + return ret; + } + + ret = clk_set_parent(spdif_p->clk_spdif, spdif_p->clk_i958); + if (ret) { + pr_err("Can't set spdif clk parent: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int aml_dai_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ +#if 0 + int srate; + + srate = params_rate(params); + aml_set_spdif_clk(srate * 512, 0); +#endif + return 0; +} + +static int aml_dai_spdif_suspend(struct snd_soc_dai *cpu_dai) +{ + struct aml_spdif *spdif_priv = dev_get_drvdata(cpu_dai->dev); + + aml_spdif_play_stop(); + if (spdif_priv && spdif_priv->clk_spdif) + clk_disable_unprepare(spdif_priv->clk_spdif); + + return 0; +} + +static int aml_dai_spdif_resume(struct snd_soc_dai *cpu_dai) +{ + struct aml_spdif *spdif_priv = dev_get_drvdata(cpu_dai->dev); + + /*aml_spdif_play();*/ + if (spdif_priv && spdif_priv->clk_spdif) + clk_prepare_enable(spdif_priv->clk_spdif); + + return 0; +} + +static struct snd_soc_dai_ops spdif_dai_ops = { + .set_sysclk = aml_dai_spdif_set_sysclk, + .trigger = aml_dai_spdif_trigger, + .prepare = aml_dai_spdif_prepare, + .hw_params = aml_dai_spdif_hw_params, + .shutdown = aml_dai_spdif_shutdown, + .startup = aml_dai_spdif_startup, +}; + +static struct snd_soc_dai_driver aml_spdif_dai[] = { + { + .playback = { + .stream_name = "S/PDIF Playback", + .channels_min = 1, + .channels_max = 8, + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .capture = { + .stream_name = "S/PDIF Capture", + .channels_min = 1, + .channels_max = 8, + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &spdif_dai_ops, + .suspend = aml_dai_spdif_suspend, + .resume = aml_dai_spdif_resume, + } +}; + +static const struct snd_soc_component_driver aml_component = { + .name = "aml-spdif-dai", +}; + +static const char *const gate_names[] = { + "iec958", "iec958_amclk" +}; + +static int aml_dai_spdif_probe(struct platform_device *pdev) +{ + int i, ret; + struct aml_spdif *spdif_priv = NULL; + struct clk *clk_gate; + + /* enable spdif power gate first */ + for (i = 0; i < ARRAY_SIZE(gate_names); i++) { + clk_gate = devm_clk_get(&pdev->dev, gate_names[i]); + if (IS_ERR(clk_gate)) { + dev_err(&pdev->dev, "Can't get aml audio gate\n"); + return PTR_ERR(clk_gate); + } + clk_prepare_enable(clk_gate); + } + + spdif_priv = devm_kzalloc(&pdev->dev, + sizeof(struct aml_spdif), GFP_KERNEL); + if (!spdif_priv) { + /*dev_err(&pdev->dev, "Can't allocate spdif_priv\n");*/ + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, spdif_priv); + spdif_p = spdif_priv; + + spdif_priv->clk_mpl1 = devm_clk_get(&pdev->dev, "mpll1"); + if (IS_ERR(spdif_priv->clk_mpl1)) { + dev_err(&pdev->dev, "Can't retrieve mpll1 clock\n"); + ret = PTR_ERR(spdif_priv->clk_mpl1); + goto err; + } + + spdif_priv->clk_i958 = devm_clk_get(&pdev->dev, "i958"); + if (IS_ERR(spdif_priv->clk_i958)) { + dev_err(&pdev->dev, "Can't retrieve spdif clk_i958 clock\n"); + ret = PTR_ERR(spdif_priv->clk_i958); + goto err; + } + + spdif_priv->clk_mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(spdif_priv->clk_mclk)) { + dev_err(&pdev->dev, "Can't retrieve spdif clk_mclk clock\n"); + ret = PTR_ERR(spdif_priv->clk_mclk); + goto err; + } + + spdif_priv->clk_spdif = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif_priv->clk_spdif)) { + dev_err(&pdev->dev, "Can't retrieve spdif clock\n"); + ret = PTR_ERR(spdif_priv->clk_spdif); + goto err; + } + ret = clk_set_parent(spdif_priv->clk_i958, spdif_priv->clk_mpl1); + if (ret) { + pr_err("Can't set i958 clk parent: %d\n", ret); + return ret; + } + + ret = clk_set_parent(spdif_priv->clk_spdif, spdif_priv->clk_i958); + if (ret) { + pr_err("Can't set spdif clk parent: %d\n", ret); + return ret; + } + ret = clk_prepare_enable(spdif_priv->clk_spdif); + if (ret) { + pr_err("Can't enable spdif clock: %d\n", ret); + goto err; + } + + spdif_priv->clk_81 = devm_clk_get(&pdev->dev, "clk_81"); + if (IS_ERR(spdif_priv->clk_81)) { + dev_err(&pdev->dev, "Can't get clk81\n"); + ret = PTR_ERR(spdif_priv->clk_81); + goto err; + } + clk81 = clk_get_rate(spdif_priv->clk_81); + + /* In PAO mode, audio data from hdmi-rx has no channel order, + * set default mode from spdif-in + */ + spdif_priv->src = SPDIF_IN; + aml_spdif_play(1); + ret = snd_soc_register_component(&pdev->dev, &aml_component, + aml_spdif_dai, + ARRAY_SIZE(aml_spdif_dai)); + if (ret) { + pr_err("Can't register spdif dai: %d\n", ret); + goto err_clk_dis; + } + return 0; + +err_clk_dis: + clk_disable_unprepare(spdif_priv->clk_spdif); +err: + return ret; +} + +static int aml_dai_spdif_remove(struct platform_device *pdev) +{ + struct aml_spdif *spdif_priv = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + clk_disable_unprepare(spdif_priv->clk_spdif); + return 0; +} + +static void aml_spdif_dai_shutdown(struct platform_device *pdev) +{ + struct aml_spdif *spdif_priv = dev_get_drvdata(&pdev->dev); + + if (spdif_priv && spdif_priv->clk_spdif) + clk_disable_unprepare(spdif_priv->clk_spdif); +} + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_spdif_dai_dt_match[] = { + {.compatible = "amlogic, aml-spdif-dai", + }, + {}, +}; +#else +#define amlogic_spdif_dai_dt_match NULL +#endif + +static struct platform_driver aml_spdif_dai_driver = { + .probe = aml_dai_spdif_probe, + .remove = aml_dai_spdif_remove, + .shutdown = aml_spdif_dai_shutdown, + .driver = { + .name = "aml-spdif-dai", + .owner = THIS_MODULE, + .of_match_table = amlogic_spdif_dai_dt_match, + }, +}; + +static int __init aml_dai_spdif_init(void) +{ + return platform_driver_register(&aml_spdif_dai_driver); +} + +module_init(aml_dai_spdif_init); + +static void __exit aml_dai_spdif_exit(void) +{ + platform_driver_unregister(&aml_spdif_dai_driver); +} + +module_exit(aml_dai_spdif_exit); + +MODULE_AUTHOR("jian.xu, "); +MODULE_DESCRIPTION("Amlogic S/PDIF Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:aml-spdif"); diff --git a/sound/soc/amlogic/aml_spdif_dai.h b/sound/soc/amlogic/aml_spdif_dai.h new file mode 100644 index 000000000000..98b5b06978d6 --- /dev/null +++ b/sound/soc/amlogic/aml_spdif_dai.h @@ -0,0 +1,51 @@ +/* + * sound/soc/amlogic/aml_spdif_dai.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef _AML_SPDIF_DAI_H +#define _AML_SPDIF_DAI_H + +/* HDMI audio stream type ID */ +#define AOUT_EVENT_IEC_60958_PCM 0x1 +#define AOUT_EVENT_RAWDATA_AC_3 0x2 +#define AOUT_EVENT_RAWDATA_MPEG1 0x3 +#define AOUT_EVENT_RAWDATA_MP3 0x4 +#define AOUT_EVENT_RAWDATA_MPEG2 0x5 +#define AOUT_EVENT_RAWDATA_AAC 0x6 +#define AOUT_EVENT_RAWDATA_DTS 0x7 +#define AOUT_EVENT_RAWDATA_ATRAC 0x8 +#define AOUT_EVENT_RAWDATA_ONE_BIT_AUDIO 0x9 +#define AOUT_EVENT_RAWDATA_DOBLY_DIGITAL_PLUS 0xA +#define AOUT_EVENT_RAWDATA_DTS_HD 0xB +#define AOUT_EVENT_RAWDATA_MAT_MLP 0xC +#define AOUT_EVENT_RAWDATA_DST 0xD +#define AOUT_EVENT_RAWDATA_WMA_PRO 0xE +#define AOUT_EVENT_RAWDATA_DTS_HD_MA (AOUT_EVENT_RAWDATA_DTS_HD|(1<<8)) +extern unsigned int IEC958_mode_codec; + +/* + * special call by the audiodsp,add these code, + * as there are three cases for 958 s/pdif output + * 1)NONE-PCM raw output ,only available when ac3/dts audio, + * when raw output mode is selected by user. + * 2)PCM output for all audio, when pcm mode is selected by user . + * 3)PCM output for audios except ac3/dts, + * when raw output mode is selected by user + */ +void aml_hw_iec958_init(struct snd_pcm_substream *substream, int samesrc); +int aml_set_spdif_clk(unsigned long rate, bool src_i2s); +void aml_spdif_play(int samesrc); +#endif /* _AML_SPDIF_DAI_H */ diff --git a/sound/soc/amlogic/aml_tv.c b/sound/soc/amlogic/aml_tv.c new file mode 100644 index 000000000000..d5ec348b8ba4 --- /dev/null +++ b/sound/soc/amlogic/aml_tv.c @@ -0,0 +1,1722 @@ +/* + * sound/soc/amlogic/aml_tv.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#define pr_fmt(fmt) "aml_tv: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SND_SOC_TAS5707 +#include +#endif + +#include "aml_i2s.h" +#include "aml_audio_hw.h" +#include "aml_tv.h" + +#define DRV_NAME "aml_snd_card_tv" + +static int aml_audio_Hardware_resample; +static int hardware_resample_locked_flag; +unsigned int clk_rate; + +static u32 aml_EQ_param[20][5] = { + /*channel 1 param*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef0*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef1*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef2*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef3*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef4*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef5*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef6*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef7*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef8*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch1_coef9*/ + /*channel 2 param*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef0*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef1*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef2*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef3*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef4*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef5*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef6*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef7*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef8*/ + {0x800000, 0x00, 0x00, 0x00, 0x00}, /*eq_ch2_coef9*/ +}; + +static u32 drc_table[3][2] = { + {0x800000, 0x00}, /*drc_ae && drc_ae_1m*/ + {0x800000, 0x00}, /*drc_aa && drc_aa_1m*/ + {0x800000, 0x00}, /*drc_ad && drc_ad_1m*/ +}; + +static u32 drc_tko_table[2][3] = { + {0x0, 0xbf000000, 0x40000}, /*offset, thd, k*/ + {0x0, 0x0, 0x40000}, /*offset, thd, k*/ +}; + +static int DRC0_enable(int enable) +{ + if ((aml_read_cbus(AED_DRC_EN) & 1) == 1) { + if (enable == 1) { + aml_write_cbus(AED_DRC_THD0, drc_tko_table[0][1]); + aml_write_cbus(AED_DRC_K0, drc_tko_table[0][2]); + } else { + aml_write_cbus(AED_DRC_THD0, 0xbf000000); + aml_write_cbus(AED_DRC_K0, 0x40000); + } + } + return 0; +} + +static const char *const audio_in_source_texts[] = { "LINEIN", "ATV", "HDMI"}; + +static const struct soc_enum audio_in_source_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(audio_in_source_texts), + audio_in_source_texts); + +static int aml_audio_get_in_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = audio_in_source; + + ucontrol->value.enumerated.item[0] = value; + + return 0; +} + +static int aml_audio_set_in_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (ucontrol->value.enumerated.item[0] == 0) { + if (is_meson_txl_cpu()) { + /* select internal acodec output in TXL as I2S source */ + aml_write_cbus(AUDIN_SOURCE_SEL, 3); + } else + /* select external codec output as I2S source */ + aml_write_cbus(AUDIN_SOURCE_SEL, 0); + audio_in_source = 0; + if (is_meson_txl_cpu()) + DRC0_enable(1); + } else if (ucontrol->value.enumerated.item[0] == 1) { + /* select ATV output as I2S source */ + aml_write_cbus(AUDIN_SOURCE_SEL, 1); + audio_in_source = 1; + if (is_meson_txl_cpu()) + DRC0_enable(1); + } else if (ucontrol->value.enumerated.item[0] == 2) { + /* select HDMI-rx as I2S source */ + /* [14:12]cntl_hdmirx_chsts_sel: */ + /* 0=Report chan1 status; 1=Report chan2 status */ + /* [11:8] cntl_hdmirx_chsts_en */ + /* [5:4] spdif_src_sel:*/ + /* 1=Select HDMIRX SPDIF output as AUDIN source */ + /* [1:0] i2sin_src_sel: */ + /*2=Select HDMIRX I2S output as AUDIN source */ + aml_write_cbus(AUDIN_SOURCE_SEL, (0 << 12) | + (0xf << 8) | (1 << 4) | (2 << 0)); + audio_in_source = 2; + if (is_meson_txl_cpu()) + DRC0_enable(0); + } + set_i2s_source(audio_in_source); + return 0; +} + +/* i2s audio format detect: LPCM or NONE-LPCM */ +static const char *const i2s_audio_type_texts[] = { + "LPCM", "NONE-LPCM", "UN-KNOWN" +}; +static const struct soc_enum i2s_audio_type_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(i2s_audio_type_texts), + i2s_audio_type_texts); + +static int aml_i2s_audio_type_get_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ch_status = 0; + + if ((aml_read_cbus(AUDIN_DECODE_CONTROL_STATUS) >> 24) & 0x1) { + ch_status = aml_read_cbus(AUDIN_DECODE_CHANNEL_STATUS_A_0); + if (ch_status & 2) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + } else { + ucontrol->value.enumerated.item[0] = 2; + } + return 0; +} + +/* spdif in audio format detect: LPCM or NONE-LPCM */ +struct sppdif_audio_info { + unsigned char aud_type; + /*IEC61937 package presamble Pc value*/ + short pc; + char *aud_type_str; +}; +static const char *const spdif_audio_type_texts[] = { + "LPCM", + "AC3", + "EAC3", + "DTS", + "DTS-HD", + "TRUEHD", +}; +static const struct sppdif_audio_info type_texts[] = { + {0, 0, "LPCM"}, + {1, 0x1, "AC3"}, + {2, 0x15, "EAC3"}, + {3, 0xb, "DTS-I"}, + {3, 0x0c, "DTS-II"}, + {3, 0x0d, "DTS-III"}, + {3, 0x11, "DTS-IV"}, + {4, 0, "DTS-HD"}, + {5, 0x16, "TRUEHD"}, +}; +static const struct soc_enum spdif_audio_type_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(spdif_audio_type_texts), + spdif_audio_type_texts); + +static int last_audio_type = -1; +static int aml_spdif_audio_type_get_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int audio_type = 0; + int i; + int total_num = sizeof(type_texts)/sizeof(struct sppdif_audio_info); + int pc = aml_read_cbus(AUDIN_SPDIF_NPCM_PCPD)>>16; + + pc = pc&0xff; + for (i = 0; i < total_num; i++) { + if (pc == type_texts[i].pc) { + audio_type = type_texts[i].aud_type; + break; + } + } + ucontrol->value.enumerated.item[0] = audio_type; + if (last_audio_type != audio_type) { + if (audio_type == 0) { + /*In LPCM, use old spdif mode*/ + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, + (0x7 << AUDIN_FIFO1_DIN_SEL), + (SPDIF_IN << AUDIN_FIFO1_DIN_SEL)); + /*spdif-in data fromat:(27:4)*/ + aml_write_cbus(AUDIN_FIFO1_CTRL1, 0x88); + hardware_resample_locked_flag = 0; + } else { + /*In RAW data, use PAO mode*/ + aml_cbus_update_bits(AUDIN_FIFO1_CTRL, + (0x7 << AUDIN_FIFO1_DIN_SEL), + (PAO_IN << AUDIN_FIFO1_DIN_SEL)); + /*spdif-in data fromat:(23:0)*/ + aml_write_cbus(AUDIN_FIFO1_CTRL1, 0x8); + hardware_resample_locked_flag = 1; + } + last_audio_type = audio_type; + } + return 0; +} + +static int aml_spdif_audio_type_set_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +#define RESAMPLE_BUFFER_SOURCE 1 +/*Cnt_ctrl = mclk/fs_out-1 ; fest 256fs */ +#define RESAMPLE_CNT_CONTROL 255 + +static int hardware_resample_enable(int input_sr) +{ + u16 Avg_cnt_init = 0; + unsigned int clk_rate = clk81; + + if (hardware_resample_locked_flag == 1) { + pr_info("HW resample is locked in RAW data.\n"); + return 0; + } + + if (input_sr < 8000 || input_sr > 48000) { + pr_err("Error input sample rate,input_sr = %d!\n", input_sr); + return -1; + } + + Avg_cnt_init = (u16)(clk_rate * 4 / input_sr); + pr_info("clk_rate = %u, input_sr = %d, Avg_cnt_init = %u\n", + clk_rate, input_sr, Avg_cnt_init); + + aml_write_cbus(AUD_RESAMPLE_CTRL0, (1 << 31)); + aml_write_cbus(AUD_RESAMPLE_CTRL0, 0); + aml_write_cbus(AUD_RESAMPLE_CTRL0, + (1 << 29) + | (1 << 28) + | (0 << 26) + | (RESAMPLE_CNT_CONTROL << 16) + | Avg_cnt_init); + + return 0; +} + +static int hardware_resample_disable(void) +{ + aml_write_cbus(AUD_RESAMPLE_CTRL0, 0); + return 0; +} + +static const char *const hardware_resample_texts[] = { + "Disable", + "Enable:48K", + "Enable:44K", + "Enable:32K", + "Lock Resample", + "Unlock Resample" +}; + +static const struct soc_enum hardware_resample_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(hardware_resample_texts), + hardware_resample_texts); + +static int aml_hardware_resample_get_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = aml_audio_Hardware_resample; + return 0; +} + +static int aml_hardware_resample_set_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (ucontrol->value.enumerated.item[0] == 0) { + hardware_resample_disable(); + aml_audio_Hardware_resample = 0; + } else if (ucontrol->value.enumerated.item[0] == 1) { + hardware_resample_enable(48000); + aml_audio_Hardware_resample = 1; + } else if (ucontrol->value.enumerated.item[0] == 2) { + hardware_resample_enable(44100); + aml_audio_Hardware_resample = 2; + } else if (ucontrol->value.enumerated.item[0] == 3) { + hardware_resample_enable(32000); + aml_audio_Hardware_resample = 3; + } else if (ucontrol->value.enumerated.item[0] == 4) { + hardware_resample_disable(); + aml_audio_Hardware_resample = 4; + hardware_resample_locked_flag = 1; + } else if (ucontrol->value.enumerated.item[0] == 5) { + hardware_resample_locked_flag = 0; + hardware_resample_enable(48000); + aml_audio_Hardware_resample = 5; + } + return 0; +} + +static const struct snd_soc_dapm_widget aml_asoc_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LINEIN"), + SND_SOC_DAPM_OUTPUT("LINEOUT"), +}; + +int audio_in_GPIO; +struct gpio_desc *av_source; +static const char * const audio_in_switch_texts[] = { "AV", "Karaok"}; + +static const struct soc_enum audio_in_switch_enum = SOC_ENUM_SINGLE( + SND_SOC_NOPM, 0, ARRAY_SIZE(audio_in_switch_texts), + audio_in_switch_texts); + +static int aml_get_audio_in_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + + if (audio_in_GPIO == 0) { + ucontrol->value.enumerated.item[0] = 0; + pr_info("audio in source: AV\n"); + } else if (audio_in_GPIO == 1) { + ucontrol->value.enumerated.item[0] = 1; + pr_info("audio in source: Karaok\n"); + } + return 0; +} + +static int aml_set_audio_in_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + if (ucontrol->value.enumerated.item[0] == 0) { + gpiod_direction_output(av_source, + GPIOF_OUT_INIT_LOW); + audio_in_GPIO = 0; + pr_info("Set audio in source: AV\n"); + } else if (ucontrol->value.enumerated.item[0] == 1) { + gpiod_direction_output(av_source, + GPIOF_OUT_INIT_HIGH); + audio_in_GPIO = 1; + pr_info("Set audio in source: Karaok\n"); + } + return 0; +} + +static int init_EQ_DRC_module(void) +{ + aml_write_cbus(AED_TOP_CTL, (1 << 31)); /* fifo init */ + aml_write_cbus(AED_ED_CTL, 1); /* soft reset*/ + msleep(20); + aml_write_cbus(AED_ED_CTL, 0); /* soft reset*/ + aml_write_cbus(AED_TOP_CTL, (0 << 1) /*i2s in sel*/ + | (1 << 0)); /*module enable*/ + aml_write_cbus(AED_NG_CTL, (3 << 30)); /* disable noise gate*/ + return 0; +} + +static int set_internal_EQ_volume( + unsigned int master_volume, + unsigned int channel_1_volume, + unsigned int channel_2_volume) +{ + aml_write_cbus(AED_EQ_VOLUME, (2 << 30) /* volume step: 0.5dB*/ + | (master_volume << 16) /* master volume: 0dB*/ + | (channel_1_volume << 8) /* channel 1 volume: 0dB*/ + | (channel_2_volume << 0) /* channel 2 volume: 0dB*/ + ); + aml_write_cbus(AED_EQ_VOLUME_SLEW_CNT, 0x40); + aml_write_cbus(AED_MUTE, 0); + return 0; +} + +static int Speaker_Channel_Mask; +static const char *const Speaker_Channel_Mask_texts[] = { + "Channel0/1", "Channel2/3", "Channe4/5", "Channe6/7" }; + +static const struct soc_enum Speaker_Channel_Mask_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(Speaker_Channel_Mask_texts), + Speaker_Channel_Mask_texts); + +static int aml_Speaker_Channel_Mask_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = Speaker_Channel_Mask; + return 0; +} + +static int aml_Speaker_Channel_Mask_set_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + Speaker_Channel_Mask = ucontrol->value.enumerated.item[0]; + return 0; +} + +static const struct snd_kcontrol_new av_controls[] = { + SOC_ENUM_EXT("AudioIn Switch", + audio_in_switch_enum, + aml_get_audio_in_switch, + aml_set_audio_in_switch), +}; + +static const struct snd_kcontrol_new aml_tv_controls[] = { + SOC_ENUM_EXT("Audio In Source", + audio_in_source_enum, + aml_audio_get_in_source, + aml_audio_set_in_source), + + SOC_ENUM_EXT("I2SIN Audio Type", + i2s_audio_type_enum, + aml_i2s_audio_type_get_enum, + NULL), + + SOC_ENUM_EXT("SPDIFIN Audio Type", + spdif_audio_type_enum, + aml_spdif_audio_type_get_enum, + aml_spdif_audio_type_set_enum), + + SOC_ENUM_EXT("Hardware resample enable", + hardware_resample_enum, + aml_hardware_resample_get_enum, + aml_hardware_resample_set_enum), + + SOC_ENUM_EXT("Speaker Channel Mask", + Speaker_Channel_Mask_enum, + aml_Speaker_Channel_Mask_get_enum, + aml_Speaker_Channel_Mask_set_enum), +}; + +static int set_HW_resample_pause_thd(unsigned int thd) +{ + aml_write_cbus(AUD_RESAMPLE_CTRL2, + (1 << 24) /* enable HW_resample_pause*/ + | (thd << 11) /* set HW resample pause thd (sample)*/ + ); + return 0; +} + +static int aml_get_cbus_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int max = mc->max; + unsigned int invert = mc->invert; + unsigned int value = + (((unsigned int)aml_read_cbus(reg)) >> shift) & max; + + if (invert) + value = (~value) & max; + ucontrol->value.integer.value[0] = value; + + return 0; +} + +static int aml_set_cbus_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int max = mc->max; + unsigned int invert = mc->invert; + unsigned int value = ucontrol->value.integer.value[0]; + unsigned int reg_value = (unsigned int)aml_read_cbus(reg); + + if (invert) + value = (~value) & max; + max = ~(max << shift); + reg_value &= max; + reg_value |= (value << shift); + aml_write_cbus(reg, reg_value); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(mvol_tlv, -12276, 12, 1); +static const DECLARE_TLV_DB_SCALE(chvol_tlv, -12750, 50, 1); + +static const struct snd_kcontrol_new aml_EQ_DRC_controls[] = { + SOC_SINGLE_EXT_TLV("EQ master volume", + AED_EQ_VOLUME, 16, 0x3FF, 1, + aml_get_cbus_reg, aml_set_cbus_reg, + mvol_tlv), + + SOC_SINGLE_EXT_TLV("EQ ch1 volume", + AED_EQ_VOLUME, 8, 0xFF, 1, + aml_get_cbus_reg, aml_set_cbus_reg, + chvol_tlv), + + SOC_SINGLE_EXT_TLV("EQ ch2 volume", + AED_EQ_VOLUME, 0, 0xFF, 1, + aml_get_cbus_reg, aml_set_cbus_reg, + chvol_tlv), + + SOC_SINGLE_EXT_TLV("EQ master volume mute", + AED_MUTE, 31, 0x1, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("EQ enable", + AED_EQ_EN, 0, 0x1, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("DRC enable", + AED_DRC_EN, 0, 0x1, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("NG enable", + AED_NG_CTL, 0, 0x1, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("NG noise thd", + AED_NG_THD0, 8, 0x7FFF, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("NG signal thd", + AED_NG_THD1, 8, 0x7FFF, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("NG counter thd", + AED_NG_CNT_THD, 0, 0xFFFF, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("Hw resample pause enable", + AUD_RESAMPLE_CTRL2, 24, 0x1, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), + + SOC_SINGLE_EXT_TLV("Hw resample pause thd", + AUD_RESAMPLE_CTRL2, 11, 0x1FFF, 0, + aml_get_cbus_reg, aml_set_cbus_reg, + NULL), +}; + +static void aml_audio_start_timer(struct aml_audio_private_data *p_aml_audio, + unsigned long delay) +{ + p_aml_audio->timer.expires = jiffies + delay; + p_aml_audio->timer.data = (unsigned long)p_aml_audio; + p_aml_audio->detect_flag = -1; + add_timer(&p_aml_audio->timer); + p_aml_audio->timer_en = 1; +} + +static void aml_audio_stop_timer(struct aml_audio_private_data *p_aml_audio) +{ + del_timer_sync(&p_aml_audio->timer); + cancel_work_sync(&p_aml_audio->work); + p_aml_audio->timer_en = 0; + p_aml_audio->detect_flag = -1; +} + +static int audio_hp_status; +static int aml_get_audio_hp_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = audio_hp_status; + return 0; +} + +static const char * const audio_hp_status_texts[] = {"Unpluged", "Pluged"}; + +static const struct soc_enum audio_hp_status_enum = SOC_ENUM_SINGLE( + SND_SOC_NOPM, 0, ARRAY_SIZE(audio_hp_status_texts), + audio_hp_status_texts); + +static const struct snd_kcontrol_new hp_controls[] = { + SOC_ENUM_EXT("Hp Status", + audio_hp_status_enum, + aml_get_audio_hp_status, + NULL), +}; + +static int hp_det_adc_value(struct aml_audio_private_data *p_aml_audio) +{ + int ret, hp_value; + int hp_val_sum = 0; + int loop_num = 0; + + while (loop_num < 8) { + hp_value = gpiod_get_value(p_aml_audio->hp_det_desc); + if (hp_value < 0) { + pr_info("hp detect get error adc value!\n"); + return -1; /* continue; */ + } + hp_val_sum += hp_value; + loop_num++; + msleep_interruptible(15); + } + hp_val_sum = hp_val_sum >> 3; + + if (p_aml_audio->hp_det_inv) { + if (hp_val_sum > 0) { + /* plug in */ + ret = 1; + } else { + /* unplug */ + ret = 0; + } + } else { + if (hp_val_sum > 0) { + /* unplug */ + ret = 0; + } else { + /* plug in */ + ret = 1; + } + } + + return ret; +} + +static int aml_audio_hp_detect(struct aml_audio_private_data *p_aml_audio) +{ + int loop_num = 0; + int ret; + + p_aml_audio->hp_det_status = false; + + while (loop_num < 3) { + ret = hp_det_adc_value(p_aml_audio); + if (p_aml_audio->hp_last_state != ret) { + msleep_interruptible(50); + if (ret < 0) + ret = p_aml_audio->hp_last_state; + else + p_aml_audio->hp_last_state = ret; + } else + msleep_interruptible(50); + + loop_num = loop_num + 1; + } + + return ret; +} + +/*mute: 1, ummute: 0*/ +static int aml_mute_unmute(struct snd_soc_card *card, int av_mute, int amp_mute) +{ + struct aml_audio_private_data *p_aml_audio; + + p_aml_audio = snd_soc_card_get_drvdata(card); + + if (!IS_ERR(p_aml_audio->av_mute_desc)) { + if (p_aml_audio->av_mute_inv ^ av_mute) { + gpiod_direction_output( + p_aml_audio->av_mute_desc, GPIOF_OUT_INIT_LOW); + pr_info("set av out GPIOF_OUT_INIT_LOW!\n"); + } else { + gpiod_direction_output( + p_aml_audio->av_mute_desc, GPIOF_OUT_INIT_HIGH); + pr_info("set av out GPIOF_OUT_INIT_HIGH!\n"); + } + } + + if (!IS_ERR(p_aml_audio->amp_mute_desc)) { + if (p_aml_audio->amp_mute_inv ^ amp_mute) { + gpiod_direction_output( + p_aml_audio->amp_mute_desc, GPIOF_OUT_INIT_LOW); + pr_info("set amp out GPIOF_OUT_INIT_LOW!\n"); + } else { + gpiod_direction_output( + p_aml_audio->amp_mute_desc, + GPIOF_OUT_INIT_HIGH); + pr_info("set amp out GPIOF_OUT_INIT_HIGH!\n"); + } + } + return 0; +} + +static void aml_asoc_work_func(struct work_struct *work) +{ + struct aml_audio_private_data *p_aml_audio = NULL; + struct snd_soc_card *card = NULL; + int flag = -1; + + p_aml_audio = container_of(work, struct aml_audio_private_data, work); + card = (struct snd_soc_card *)p_aml_audio->data; + + flag = aml_audio_hp_detect(p_aml_audio); + + if (p_aml_audio->detect_flag != flag) { + p_aml_audio->detect_flag = flag; + + if (flag & 0x1) { + pr_info("aml aduio hp pluged\n"); + audio_hp_status = 1; + aml_mute_unmute(card, 0, 1); + } else { + pr_info("aml audio hp unpluged\n"); + audio_hp_status = 0; + aml_mute_unmute(card, 1, 0); + } + + } + + p_aml_audio->hp_det_status = true; +} + +static void aml_asoc_timer_func(unsigned long data) +{ + struct aml_audio_private_data *p_aml_audio = + (struct aml_audio_private_data *)data; + unsigned long delay = msecs_to_jiffies(150); + + if (p_aml_audio->hp_det_status && + !p_aml_audio->suspended) { + schedule_work(&p_aml_audio->work); + } + mod_timer(&p_aml_audio->timer, jiffies + delay); +} + +static int aml_suspend_pre(struct snd_soc_card *card) +{ + struct aml_audio_private_data *p_aml_audio; + + pr_info("enter %s\n", __func__); + p_aml_audio = snd_soc_card_get_drvdata(card); + + if (p_aml_audio->av_hs_switch) { + /* stop timer */ + mutex_lock(&p_aml_audio->lock); + p_aml_audio->suspended = true; + if (p_aml_audio->timer_en) + aml_audio_stop_timer(p_aml_audio); + + mutex_unlock(&p_aml_audio->lock); + } + + aml_mute_unmute(card, 1, 1); + return 0; +} + +static int aml_suspend_post(struct snd_soc_card *card) +{ + pr_info("enter %s\n", __func__); + return 0; +} + +static int aml_resume_pre(struct snd_soc_card *card) +{ + pr_info("enter %s\n", __func__); + return 0; +} + +static int aml_resume_post(struct snd_soc_card *card) +{ + struct aml_audio_private_data *p_aml_audio; + + pr_info("enter %s\n", __func__); + p_aml_audio = snd_soc_card_get_drvdata(card); + + schedule_work(&p_aml_audio->pinmux_work); + + return 0; +} + +static int aml_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + /* set cpu DAI configuration */ + if (is_meson_txl_cpu()) + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + else + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBM_CFM); + + if (ret < 0) { + pr_err("%s: set cpu dai fmt failed!\n", __func__); + return ret; + } + + return 0; +} + +static struct snd_soc_ops aml_asoc_ops = { + .hw_params = aml_asoc_hw_params, +}; + +static int aml_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->component.dapm; + struct aml_audio_private_data *p_aml_audio; + int ret = 0; + + pr_info("enter %s\n", __func__); + p_aml_audio = snd_soc_card_get_drvdata(card); + + ret = snd_soc_add_card_controls(codec->component.card, + aml_tv_controls, + ARRAY_SIZE(aml_tv_controls)); + + /* Add specific widgets */ + snd_soc_dapm_new_controls(dapm, aml_asoc_dapm_widgets, + ARRAY_SIZE(aml_asoc_dapm_widgets)); + + p_aml_audio->pin_ctl = + devm_pinctrl_get_select(card->dev, "aml_snd_tv"); + if (IS_ERR(p_aml_audio->pin_ctl)) { + pr_info("%s, aml_tv_pinmux_init error!\n", __func__); + return 0; + } + + /*read avmute pinmux from dts*/ + p_aml_audio->av_mute_desc = + gpiod_get(card->dev, + "mute_gpio", + GPIOD_OUT_HIGH); + of_property_read_u32(card->dev->of_node, "av_mute_inv", + &p_aml_audio->av_mute_inv); + of_property_read_u32(card->dev->of_node, "sleep_time", + &p_aml_audio->sleep_time); + + /*read amp mute pinmux from dts*/ + p_aml_audio->amp_mute_desc = + gpiod_get(card->dev, + "amp_mute_gpio", + GPIOD_OUT_HIGH); + of_property_read_u32(card->dev->of_node, "amp_mute_inv", + &p_aml_audio->amp_mute_inv); + + /*read headset pinmux from dts*/ + of_property_read_u32(card->dev->of_node, "av_hs_switch", + &p_aml_audio->av_hs_switch); + + if (p_aml_audio->av_hs_switch) { + /* headset dection gipo */ + p_aml_audio->hp_det_desc = gpiod_get(card->dev, + "hp_det", GPIOD_IN); + if (!IS_ERR(p_aml_audio->hp_det_desc)) + gpiod_direction_input(p_aml_audio->hp_det_desc); + else + pr_err("ASoC: hp_det-gpio failed\n"); + + of_property_read_u32(card->dev->of_node, + "hp_det_inv", + &p_aml_audio->hp_det_inv); + pr_info("hp_det_inv:%d, %s\n", + p_aml_audio->hp_det_inv, + p_aml_audio->hp_det_inv ? + "hs pluged, HP_DET:1; hs unpluged, HS_DET:0" + : + "hs pluged, HP_DET:0; hs unpluged, HS_DET:1"); + + p_aml_audio->hp_det_status = true; + + init_timer(&p_aml_audio->timer); + p_aml_audio->timer.function = aml_asoc_timer_func; + p_aml_audio->timer.data = (unsigned long)p_aml_audio; + p_aml_audio->data = (void *)card; + + INIT_WORK(&p_aml_audio->work, aml_asoc_work_func); + mutex_init(&p_aml_audio->lock); + + ret = snd_soc_add_card_controls(codec->component.card, + hp_controls, ARRAY_SIZE(hp_controls)); + } + + /*It is used for KaraOK, */ + av_source = gpiod_get(card->dev, "av_source", GPIOD_OUT_LOW); + if (!IS_ERR(av_source)) { + pr_info("%s, make av_source gpio low!\n", __func__); + gpiod_direction_output(av_source, GPIOF_OUT_INIT_LOW); + snd_soc_add_card_controls(card, av_controls, + ARRAY_SIZE(av_controls)); + } + + return 0; +} + +static void aml_tv_pinmux_init(struct snd_soc_card *card) +{ + struct aml_audio_private_data *p_aml_audio; + + p_aml_audio = snd_soc_card_get_drvdata(card); + + if (!p_aml_audio->av_hs_switch) { + if (p_aml_audio->sleep_time && + (!IS_ERR(p_aml_audio->av_mute_desc))) + msleep(p_aml_audio->sleep_time); + aml_mute_unmute(card, 0, 0); + pr_info("av_mute_inv:%d, amp_mute_inv:%d, sleep %d ms\n", + p_aml_audio->av_mute_inv, p_aml_audio->amp_mute_inv, + p_aml_audio->sleep_time); + } else { + if (p_aml_audio->sleep_time && + (!IS_ERR(p_aml_audio->av_mute_desc))) + msleep(p_aml_audio->sleep_time); + pr_info("aml audio hs detect enable!\n"); + p_aml_audio->suspended = false; + mutex_lock(&p_aml_audio->lock); + if (!p_aml_audio->timer_en) { + aml_audio_start_timer(p_aml_audio, + msecs_to_jiffies(100)); + } + mutex_unlock(&p_aml_audio->lock); + } +} + +static int aml_card_dai_parse_of(struct device *dev, + struct snd_soc_dai_link *dai_link, + int (*init)( + struct snd_soc_pcm_runtime *rtd), + struct device_node *cpu_node, + struct device_node *codec_node, + struct device_node *plat_node) +{ + int ret; + + /* get cpu dai->name */ + ret = snd_soc_of_get_dai_name(cpu_node, &dai_link->cpu_dai_name); + if (ret < 0) + goto parse_error; + + /* get codec dai->name */ + ret = snd_soc_of_get_dai_name(codec_node, &dai_link->codec_dai_name); + if (ret < 0) + goto parse_error; + + dai_link->name = dai_link->stream_name = dai_link->cpu_dai_name; + dai_link->codec_of_node = of_parse_phandle(codec_node, "sound-dai", 0); + dai_link->platform_of_node = plat_node; + dai_link->init = init; + + return 0; + +parse_error: + return ret; +} + +struct snd_soc_aux_dev tv_audio_aux_dev; +static struct snd_soc_codec_conf tv_audio_codec_conf[] = { + { + .name_prefix = "AMP", + }, +}; +static struct codec_probe_priv prob_priv; +static struct codec_info codec_info_aux; + +static int get_audio_codec_i2c_info(struct device_node *p_node, + struct aml_audio_codec_info *audio_codec_dev) +{ + const char *str; + int ret = 0; + unsigned int i2c_addr; + + ret = of_property_read_string(p_node, "codec_name", + &audio_codec_dev->name); + if (ret) { + pr_info("get audio codec name failed!\n"); + goto err_out; + } + + ret = of_property_match_string(p_node, "status", "okay"); + if (ret) { + pr_info("%s:this audio codec is disabled!\n", + audio_codec_dev->name); + goto err_out; + } + + pr_debug("use audio aux codec %s\n", audio_codec_dev->name); + + ret = of_property_read_string(p_node, "i2c_bus", &str); + if (ret) { + pr_err("%s: failed to get i2c_bus str,use default i2c bus!\n", + audio_codec_dev->name); + audio_codec_dev->i2c_bus_type = AML_I2C_BUS_D; + } else { + if (!strncmp(str, "i2c_bus_a", 9)) + audio_codec_dev->i2c_bus_type = AML_I2C_BUS_A; + else if (!strncmp(str, "i2c_bus_b", 9)) + audio_codec_dev->i2c_bus_type = AML_I2C_BUS_B; + else if (!strncmp(str, "i2c_bus_c", 9)) + audio_codec_dev->i2c_bus_type = AML_I2C_BUS_C; + else if (!strncmp(str, "i2c_bus_d", 9)) + audio_codec_dev->i2c_bus_type = AML_I2C_BUS_D; + else if (!strncmp(str, "i2c_bus_ao", 10)) + audio_codec_dev->i2c_bus_type = AML_I2C_BUS_AO; + else + audio_codec_dev->i2c_bus_type = AML_I2C_BUS_D; + } + + ret = of_property_read_u32(p_node, "i2c_addr", &i2c_addr); + if (!ret) + audio_codec_dev->i2c_addr = i2c_addr; + /* pr_info("audio aux codec addr: 0x%x, audio codec i2c bus: %d\n", + * audio_codec_dev->i2c_addr, audio_codec_dev->i2c_bus_type); + */ +err_out: + return ret; +} + +static char drc1_table[15] = "drc1_table_0"; +static char drc1_tko_table[20] = "drc1_tko_table_0"; +static char drc2_table[15] = "drc2_table_0"; +static char drc2_tko_table[20] = "drc2_tko_table_0"; +static int aml_drc_type_select(char *s) +{ + char *sel = s; + + if (s != NULL) { + sprintf(drc1_table, "%s%s", "drc1_table_", sel); + sprintf(drc1_tko_table, "%s%s", "drc1_tko_table_", sel); + sprintf(drc2_table, "%s%s", "drc2_table_", sel); + sprintf(drc2_tko_table, "%s%s", "drc2_tko_table_", sel); + pr_info("select drc type: %s\n", sel); + } + return 0; +} +__setup("amp_drc_type=", aml_drc_type_select); + +static char table[10] = "table_0"; +static char wall[10] = "wall_0"; +static char sub_bq_table[20] = "sub_bq_table_0"; +static int aml_eq_type_select(char *s) +{ + char *sel = s; + + if (s != NULL) { + sprintf(table, "%s%s", "table_", sel); + sprintf(wall, "%s%s", "wall_", sel); + sprintf(sub_bq_table, "%s%s", "sub_bq_table_", sel); + pr_info("select eq type: %s\n", sel); + } + return 0; +} +__setup("amp_eq_type=", aml_eq_type_select); + +static void *alloc_and_get_data_array(struct device_node *p_node, char *str, + int *lenp) +{ + int ret = 0, length = 0; + char *p = NULL; + + if (of_find_property(p_node, str, &length) == NULL) { + pr_err("DTD of %s not found!\n", str); + goto exit; + } + pr_debug("prop name=%s,length=%d\n", str, length); + p = kzalloc((length * sizeof(char *)), GFP_KERNEL); + if (p == NULL) { + pr_err("ERROR, NO enough mem for %s!\n", str); + length = 0; + goto exit; + } + + ret = of_property_read_u8_array(p_node, str, p, length); + if (ret) { + pr_err("no of property %s!\n", str); + kfree(p); + p = NULL; + goto exit; + } + + *lenp = length; + +exit: return p; +} +#ifdef CONFIG_SND_SOC_TAS5707 +static int of_get_eq_pdata(struct tas57xx_platform_data *pdata, + struct device_node *p_node) +{ + int length = 0; + char *regs = NULL; + int ret = 0; + + ret = of_property_read_u32(p_node, "eq_enable", &pdata->eq_enable); + if (pdata->eq_enable == 0 || ret != 0) { + pr_err("Fail to get eq_enable node or EQ disable!\n"); + return -2; + } + + prob_priv.num_eq = 2; + pdata->num_eq_cfgs = prob_priv.num_eq; + + prob_priv.eq_configs = kzalloc( + prob_priv.num_eq * sizeof(struct tas57xx_eq_cfg), GFP_KERNEL); + + regs = alloc_and_get_data_array(p_node, table, &length); + if (regs == NULL) { + kfree(prob_priv.eq_configs); + return -2; + } + strncpy(prob_priv.eq_configs[0].name, table, NAME_SIZE); + prob_priv.eq_configs[0].regs = regs; + prob_priv.eq_configs[0].reg_bytes = length; + + regs = alloc_and_get_data_array(p_node, wall, &length); + if (regs == NULL) { + kfree(prob_priv.eq_configs); + return -2; + } + strncpy(prob_priv.eq_configs[1].name, wall, NAME_SIZE); + prob_priv.eq_configs[1].regs = regs; + prob_priv.eq_configs[1].reg_bytes = length; + + pdata->eq_cfgs = prob_priv.eq_configs; + return 0; +} + +static int of_get_drc_pdata(struct tas57xx_platform_data *pdata, + struct device_node *p_node) +{ + int length = 0; + char *pd = NULL; + int ret = 0; + + ret = of_property_read_u32(p_node, "drc_enable", &pdata->drc_enable); + if (pdata->drc_enable == 0 || ret != 0) { + pr_err("Fail to get drc_enable node or DRC disable!\n"); + return -2; + } + + /* get drc1 table */ + pd = alloc_and_get_data_array(p_node, drc1_table, &length); + if (pd == NULL) + return -2; + pdata->custom_drc1_table_len = length; + pdata->custom_drc1_table = pd; + + /* get drc1 tko table */ + length = 0; + pd = NULL; + + pd = alloc_and_get_data_array(p_node, drc1_tko_table, &length); + if (pd == NULL) + return -2; + pdata->custom_drc1_tko_table_len = length; + pdata->custom_drc1_tko_table = pd; + pdata->enable_ch1_drc = 1; + + /* get drc2 table */ + length = 0; + pd = NULL; + pd = alloc_and_get_data_array(p_node, drc2_table, &length); + if (pd == NULL) + return -1; + pdata->custom_drc2_table_len = length; + pdata->custom_drc2_table = pd; + + /* get drc2 tko table */ + length = 0; + pd = NULL; + pd = alloc_and_get_data_array(p_node, drc2_tko_table, &length); + if (pd == NULL) + return -1; + pdata->custom_drc2_tko_table_len = length; + pdata->custom_drc2_tko_table = pd; + pdata->enable_ch2_drc = 1; + + return 0; +} + +static int of_get_init_pdata(struct tas57xx_platform_data *pdata, + struct device_node *p_node) +{ + int length = 0; + char *pd = NULL; + + pd = alloc_and_get_data_array(p_node, "input_mux_reg_buf", &length); + if (pd == NULL) { + pr_err("%s : can't get input_mux_reg_buf\n", __func__); + return -1; + } + + /*Now only support 0x20 input mux init*/ + pdata->num_init_regs = length; + pdata->init_regs = pd; + + if (of_property_read_u32(p_node, "master_vol", + &pdata->custom_master_vol)) { + pr_err("%s fail to get master volume\n", __func__); + return -1; + } + + return 0; +} + +static int of_get_resetpin_pdata(struct tas57xx_platform_data *pdata, + struct device_node *p_node) +{ + enum of_gpio_flags flags; + int reset_pin; + + reset_pin = of_get_named_gpio_flags(p_node, "reset_pin", 0, &flags); + if (reset_pin < 0) { + pr_err("%s fail to get reset pin from dts!\n", __func__); + return reset_pin; + } + gpio_request(reset_pin, NULL); + gpio_direction_output(reset_pin, GPIOF_OUT_INIT_LOW); + pdata->reset_pin = reset_pin; + pr_info("%s pdata->reset_pin = %d!\n", __func__, + pdata->reset_pin); + return 0; +} + +static int of_get_phonepin_pdata(struct tas57xx_platform_data *pdata, + struct device_node *p_node) +{ + enum of_gpio_flags flags; + int phone_pin; + + phone_pin = of_get_named_gpio_flags(p_node, "phone_pin", 0, &flags); + if (phone_pin < 0) { + pr_err("%s fail to get phone pin from dts!\n", __func__); + return phone_pin; + } + + gpio_request(phone_pin, NULL); + gpio_direction_output(phone_pin, GPIOF_OUT_INIT_LOW); + pdata->phone_pin = phone_pin; + pr_info("%s pdata->phone_pin = %d!\n", __func__, + pdata->phone_pin); + return 0; +} +static int of_get_scanpin_pdata(struct tas57xx_platform_data *pdata, + struct device_node *p_node) +{ + enum of_gpio_flags flags; + int scan_pin; + + scan_pin = of_get_named_gpio_flags(p_node, "scan_pin", 0, &flags); + if (scan_pin < 0) { + pr_err("%s fail to get scan pin from dts!\n", __func__); + return scan_pin; + } + gpio_request(scan_pin, NULL); + gpio_direction_input(scan_pin); + pdata->scan_pin = scan_pin; + pr_info("%s pdata->scan_pin = %d!\n", __func__, + pdata->scan_pin); + return 0; +} + +static int codec_get_of_pdata(struct tas57xx_platform_data *pdata, + struct device_node *p_node) +{ + int ret = 0; + + ret = of_get_resetpin_pdata(pdata, p_node); + if (ret) + pr_info("codec reset pin is not found in dts\n"); + ret = of_get_phonepin_pdata(pdata, p_node); + if (ret) + pr_info("codec phone pin is not found in dtd\n"); + + ret = of_get_scanpin_pdata(pdata, p_node); + if (ret) + pr_info("codec scanp pin is not found in dtd\n"); + + ret = of_get_drc_pdata(pdata, p_node); + if (ret == -2) + pr_info("codec DRC configs are not found in dts\n"); + + ret = of_get_eq_pdata(pdata, p_node); + if (ret) + pr_info("codec EQ configs are not found in dts\n"); + + ret = of_get_init_pdata(pdata, p_node); + if (ret) + pr_info("codec init configs are not found in dts\n"); + return ret; +} +#endif + +static int aml_aux_dev_parse_of(struct snd_soc_card *card) +{ + struct device_node *audio_codec_node = card->dev->of_node; + struct device_node *child; + struct i2c_board_info board_info; + struct i2c_adapter *adapter; + struct i2c_client *client; + struct aml_audio_codec_info temp_audio_codec; +#ifdef CONFIG_SND_SOC_TAS5707 + struct tas57xx_platform_data *pdata; +#endif + char tmp[I2C_NAME_SIZE]; + const char *aux_dev; + + if (of_property_read_string(audio_codec_node, "aux_dev", &aux_dev)) { + pr_info("no aux dev!\n"); + return -ENODEV; + } + pr_info("aux name = %s\n", aux_dev); + child = of_get_child_by_name(audio_codec_node, aux_dev); + if (child == NULL) { + pr_info("error: failed to find aux dev node %s\n", aux_dev); + return -1; + } + + memset(&temp_audio_codec, 0, sizeof(struct aml_audio_codec_info)); + /*pr_info("%s, child name:%s\n", __func__, child->name);*/ + + if (get_audio_codec_i2c_info(child, &temp_audio_codec) == 0) { + memset(&board_info, 0, sizeof(board_info)); + strncpy(board_info.type, temp_audio_codec.name, I2C_NAME_SIZE); + adapter = i2c_get_adapter(temp_audio_codec.i2c_bus_type); + board_info.addr = temp_audio_codec.i2c_addr; + board_info.platform_data = &temp_audio_codec; + client = i2c_new_device(adapter, &board_info); + snprintf(tmp, I2C_NAME_SIZE, "%s", temp_audio_codec.name); + strlcpy(codec_info_aux.name, tmp, I2C_NAME_SIZE); + snprintf(tmp, I2C_NAME_SIZE, "%s.%s", temp_audio_codec.name, + dev_name(&client->dev)); + strlcpy(codec_info_aux.name_bus, tmp, I2C_NAME_SIZE); + + tv_audio_aux_dev.name = codec_info_aux.name; + tv_audio_aux_dev.codec_name = codec_info_aux.name_bus; + tv_audio_codec_conf[0].dev_name = codec_info_aux.name_bus; + + card->aux_dev = &tv_audio_aux_dev, + card->num_aux_devs = 1, + card->codec_conf = tv_audio_codec_conf, + card->num_configs = ARRAY_SIZE(tv_audio_codec_conf), +#ifdef CONFIG_SND_SOC_TAS5707 + pdata = + kzalloc(sizeof(struct tas57xx_platform_data), + GFP_KERNEL); + if (!pdata) { + pr_err("error: malloc tas57xx_platform_data!\n"); + return -ENOMEM; + } + codec_get_of_pdata(pdata, child); + client->dev.platform_data = pdata; +#endif + Speaker_Channel_Mask = 1; + } + return 0; +} +static int aml_card_dais_parse_of(struct snd_soc_card *card) +{ + struct device_node *np = card->dev->of_node; + struct device_node *cpu_node, *codec_node, *plat_node; + struct device *dev = card->dev; + struct snd_soc_dai_link *dai_links; + int num_dai_links, cpu_num, codec_num, plat_num; + int i, ret; + + int (*init)(struct snd_soc_pcm_runtime *rtd); + + ret = of_count_phandle_with_args(np, "cpu_list", NULL); + if (ret < 0) { + dev_err(dev, "AML sound card no cpu_list errno: %d\n", ret); + goto err; + } else { + cpu_num = ret; + } + ret = of_count_phandle_with_args(np, "codec_list", NULL); + if (ret < 0) { + dev_err(dev, "AML sound card no codec_list errno: %d\n", ret); + goto err; + } else { + codec_num = ret; + } + ret = of_count_phandle_with_args(np, "plat_list", NULL); + if (ret < 0) { + dev_err(dev, "AML sound card no plat_list errno: %d\n", ret); + goto err; + } else { + plat_num = ret; + } + if ((cpu_num == codec_num) && (cpu_num == plat_num)) { + num_dai_links = cpu_num; + } else { + dev_err(dev, + "AML sound card cpu_dai num, codec_dai num, platform num don't match: %d\n", + ret); + ret = -EINVAL; + goto err; + } + + dai_links = + devm_kzalloc(dev, + num_dai_links * sizeof(struct snd_soc_dai_link), + GFP_KERNEL); + if (!dai_links) { + dev_err(dev, "Can't allocate snd_soc_dai_links\n"); + ret = -ENOMEM; + goto err; + } + card->dai_link = dai_links; + card->num_links = num_dai_links; + for (i = 0; i < num_dai_links; i++) { + init = NULL; + /* CPU sub-node */ + cpu_node = of_parse_phandle(np, "cpu_list", i); + if (cpu_node < 0) { + dev_err(dev, "parse aml sound card cpu list error\n"); + return -EINVAL; + } + /* CODEC sub-node */ + codec_node = of_parse_phandle(np, "codec_list", i); + if (codec_node < 0) { + dev_err(dev, "parse aml sound card codec list error\n"); + return ret; + } + /* Platform sub-node */ + plat_node = of_parse_phandle(np, "plat_list", i); + if (plat_node < 0) { + dev_err(dev, + "parse aml sound card platform list error\n"); + return ret; + } + if (i == 0) + init = aml_asoc_init; + + ret = + aml_card_dai_parse_of(dev, &dai_links[i], init, + cpu_node, + codec_node, plat_node); + + dai_links[0].ops = &aml_asoc_ops; + } + +err: + return ret; +} + +static int aml_EQ_DRC_parse_of(struct snd_soc_card *card) +{ + struct device_node *audio_codec_node = card->dev->of_node; + struct device_node *child; + struct aml_audio_private_data *p_aml_audio; + int length = 0; + int ret = 0; + int i = 0; + u32 *reg_ptr = &aml_EQ_param[0][0]; + + p_aml_audio = snd_soc_card_get_drvdata(card); + + child = of_get_child_by_name(audio_codec_node, "aml_EQ_DRC"); + if (child == NULL) { + pr_err("Error: failed to find node %s\n", "aml_EQ_DRC"); + return -1; + } + + if (of_find_property(child, "eq_table", &length) == NULL) { + pr_err("[%s] node not found!\n", "eq_table"); + } else { + of_property_read_u32(child, "EQ_enable", + &p_aml_audio->aml_EQ_enable); + /*read EQ value from dts*/ + if (p_aml_audio->aml_EQ_enable) { + ret = of_property_read_u32_array(child, "eq_table", + reg_ptr, 100); + if (ret) { + pr_err("Can't get EQ param [%s]!\n", + "eq_table"); + } else { + for (i = 0; i < 100; i++) { + aml_write_cbus(AED_EQ_CH1_COEF00 + i, + *reg_ptr); + /* pr_info("EQ value[%d]: 0x%x\n", + * i, *reg_ptr); + */ + reg_ptr++; + } + /*enable aml EQ*/ + aml_cbus_update_bits(AED_EQ_EN, 0x1, 0x1); + pr_info("aml EQ enable!\n"); + } + } + } + + if (of_find_property(child, "drc_table", &length) == NULL || + of_find_property(child, "drc_tko_table", &length) + == NULL) { + pr_err("[%s or %s] not found!\n", "drc_table", "drc_tko_table"); + } else { + /*read DRC value from dts*/ + of_property_read_u32(child, "DRC_enable", + &p_aml_audio->aml_DRC_enable); + if (p_aml_audio->aml_DRC_enable) { + reg_ptr = &drc_table[0][0]; + ret = of_property_read_u32_array(child, "drc_table", + reg_ptr, 6); + if (ret) { + pr_err("Can't get drc param [%s]!\n", + "drc_table"); + } else { + aml_write_cbus(AED_DRC_AE, + drc_table[0][0]); + aml_write_cbus(AED_DRC_AA, + drc_table[1][0]); + aml_write_cbus(AED_DRC_AD, + drc_table[2][0]); + aml_write_cbus(AED_DRC_AE_1M, + drc_table[0][1]); + aml_write_cbus(AED_DRC_AA_1M, + drc_table[1][1]); + aml_write_cbus(AED_DRC_AD_1M, + drc_table[2][1]); + /* pr_info("DRC table: 0x%x, 0x%x," + * "0x%x, 0x%x, 0x%x, 0x%x,\n", + * drc_table[0][0], drc_table[0][1], + * drc_table[1][0], drc_table[1][1], + * drc_table[2][0], drc_table[2][1]); + */ + } + + reg_ptr = &drc_tko_table[0][0]; + ret = of_property_read_u32_array(child, "drc_tko_table", + reg_ptr, 6); + if (ret) { + pr_err("Can't get drc param [%s]!\n", + "drc_tko_table"); + } else { + aml_write_cbus(AED_DRC_OFFSET0, + drc_tko_table[0][0]); + aml_write_cbus(AED_DRC_OFFSET1, + drc_tko_table[1][0]); + aml_write_cbus(AED_DRC_THD0, + drc_tko_table[0][1]); + aml_write_cbus(AED_DRC_THD1, + drc_tko_table[1][1]); + aml_write_cbus(AED_DRC_K0, + drc_tko_table[0][2]); + aml_write_cbus(AED_DRC_K1, + drc_tko_table[1][2]); + /* pr_info("DRC tko: 0x%x, 0x%x," + * "0x%x, 0x%x, 0x%x, 0x%x,\n", + * drc_tko_table[0][0], drc_tko_table[1][0], + * drc_tko_table[0][1], drc_tko_table[1][1], + * drc_tko_table[0][2], drc_tko_table[1][2]); + */ + + /*enable aml DRC*/ + aml_cbus_update_bits(AED_DRC_EN, 0x1, 0x1); + pr_info("aml DRC enable!\n"); + } + } + } + return 0; +} + +static void aml_pinmux_work_func(struct work_struct *pinmux_work) +{ + struct aml_audio_private_data *p_aml_audio = NULL; + struct snd_soc_card *card = NULL; + + p_aml_audio = container_of(pinmux_work, + struct aml_audio_private_data, pinmux_work); + card = (struct snd_soc_card *)p_aml_audio->data; + + aml_tv_pinmux_init(card); +} + +static int aml_tv_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + struct aml_audio_private_data *p_aml_audio; + int ret; + + p_aml_audio = + devm_kzalloc(dev, sizeof(struct aml_audio_private_data), + GFP_KERNEL); + if (!p_aml_audio) { + dev_err(&pdev->dev, "Can't allocate aml_audio_private_data\n"); + ret = -ENOMEM; + goto err; + } + + card = devm_kzalloc(dev, sizeof(struct snd_soc_card), GFP_KERNEL); + if (!card) { + /*dev_err(&pdev->dev, "Can't allocate snd_soc_card\n");*/ + ret = -ENOMEM; + goto err; + } + + snd_soc_card_set_drvdata(card, p_aml_audio); + + card->dev = dev; + platform_set_drvdata(pdev, card); + ret = snd_soc_of_parse_card_name(card, "aml_sound_card,name"); + if (ret < 0) { + dev_err(dev, "no specific snd_soc_card name\n"); + goto err; + } + + ret = aml_card_dais_parse_of(card); + if (ret < 0) { + dev_err(dev, "parse aml sound card dais error %d\n", ret); + goto err; + } + aml_aux_dev_parse_of(card); + + card->suspend_pre = aml_suspend_pre, + card->suspend_post = aml_suspend_post, + card->resume_pre = aml_resume_pre, + card->resume_post = aml_resume_post, + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret < 0) { + dev_err(dev, "register aml sound card error %d\n", ret); + goto err; + } + + if (is_meson_txl_cpu()) { + set_internal_EQ_volume(0xc0, 0x30, 0x30); + init_EQ_DRC_module(); + snd_soc_add_card_controls(card, aml_EQ_DRC_controls, + ARRAY_SIZE(aml_EQ_DRC_controls)); + aml_EQ_DRC_parse_of(card); + set_HW_resample_pause_thd(128); + } + + p_aml_audio->data = (void *)card; + INIT_WORK(&p_aml_audio->pinmux_work, aml_pinmux_work_func); + schedule_work(&p_aml_audio->pinmux_work); + + return 0; +err: + dev_err(dev, "Can't probe snd_soc_card\n"); + return ret; +} + +static void aml_tv_audio_shutdown(struct platform_device *pdev) +{ + struct snd_soc_card *card; + + card = platform_get_drvdata(pdev); + aml_suspend_pre(card); +} + + +static const struct of_device_id amlogic_audio_of_match[] = { + { .compatible = "aml, aml_snd_tv", }, + {}, +}; + +static struct platform_driver aml_tv_audio_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = amlogic_audio_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = aml_tv_audio_probe, + .shutdown = aml_tv_audio_shutdown, +}; + +module_platform_driver(aml_tv_audio_driver); + +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_DESCRIPTION("AML_TV audio machine Asoc driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amlogic/aml_tv.h b/sound/soc/amlogic/aml_tv.h new file mode 100644 index 000000000000..260c76665537 --- /dev/null +++ b/sound/soc/amlogic/aml_tv.h @@ -0,0 +1,79 @@ +/* + * sound/soc/amlogic/aml_tv.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef AML_TV_H +#define AML_TV_H + +#include +#include + +#define AML_I2C_BUS_AO 0 +#define AML_I2C_BUS_A 1 +#define AML_I2C_BUS_B 2 +#define AML_I2C_BUS_C 3 +#define AML_I2C_BUS_D 4 + +struct aml_audio_private_data { + int clock_en; + bool suspended; + void *data; + + int hp_last_state; + bool hp_det_status; + int av_hs_switch; + int hp_det_inv; + int timer_en; + int detect_flag; + struct work_struct work; + struct mutex lock; + struct gpio_desc *hp_det_desc; + + struct pinctrl *pin_ctl; + struct timer_list timer; + struct gpio_desc *av_mute_desc; + int av_mute_inv; + struct gpio_desc *amp_mute_desc; + int amp_mute_inv; + struct clk *clk; + int sleep_time; + struct work_struct pinmux_work; + int aml_EQ_enable; + int aml_DRC_enable; +}; + +struct aml_audio_codec_info { + const char *name; + const char *status; + struct device_node *p_node; + unsigned int i2c_bus_type; + unsigned int i2c_addr; + unsigned int id_reg; + unsigned int id_val; + unsigned int capless; +}; + +struct codec_info { + char name[I2C_NAME_SIZE]; + char name_bus[I2C_NAME_SIZE]; +}; + +struct codec_probe_priv { + int num_eq; + struct tas57xx_eq_cfg *eq_configs; +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c67667bb970f..218ec12314d6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1089,4 +1089,6 @@ config SND_SOC_TPA6130A2 tristate "Texas Instruments TPA6130A2 headphone amplifier" depends on I2C +# for Amlogic Codecs +source "sound/soc/codecs/amlogic/Kconfig" endmenu diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 958cd4912fbc..efad182254c9 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -440,3 +440,5 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o +# Amlogic +obj-$(CONFIG_AMLOGIC_SND_SOC_CODECS) += amlogic/ diff --git a/sound/soc/codecs/amlogic/Kconfig b/sound/soc/codecs/amlogic/Kconfig new file mode 100644 index 000000000000..2e38b1313278 --- /dev/null +++ b/sound/soc/codecs/amlogic/Kconfig @@ -0,0 +1,41 @@ +menuconfig AMLOGIC_SND_SOC_CODECS + bool "AMLOGIC CODEC drivers" + default n + help + Say Y or M if you want to add support for codecs attached to + the Amlogic Asoc interface. You will also need + to select the audio interfaces to support below. + +#if AMLOGIC_SND_SOC_CODECS + +config AMLOGIC_SND_CODEC_DUMMY_CODEC + bool "Amlogic Audio dummy codec" + depends on AMLOGIC_SND_SOC_CODECS + default n + help + Amlogic Audio codec, + dummy codec, + dummy codec, + this codec is internal + +config AMLOGIC_SND_CODEC_PCM2BT + bool "Amlogic Audio pcm2bt codec" + depends on AMLOGIC_SND_SOC_CODECS + default n + help + Amlogic Audio codec, + pcm2bt codec, + pcm2bt codec, + this codec is internal + +config AMLOGIC_SND_CODEC_AMLT9015 + bool "Amlogic Audio AMLT9015 codec" + depends on AMLOGIC_SND_SOC_CODECS + default n + help + Amlogic Audio codec, + AMLT9015 codec, + AMLT9015 codec, + this codec is internal + +#endif #AMLOGIC_SND_SOC_CODECS \ No newline at end of file diff --git a/sound/soc/codecs/amlogic/Makefile b/sound/soc/codecs/amlogic/Makefile new file mode 100644 index 000000000000..817b59ca70b1 --- /dev/null +++ b/sound/soc/codecs/amlogic/Makefile @@ -0,0 +1,9 @@ +#Amlogic +snd-soc-dummy_codec-objs := dummy_codec.o +snd-soc-pcm2bt-objs := pcm2bt.o +snd-soc-aml_t9015-objs := aml_codec_t9015.o + +# Amlogic +obj-$(CONFIG_AMLOGIC_SND_CODEC_DUMMY_CODEC) += snd-soc-dummy_codec.o +obj-$(CONFIG_AMLOGIC_SND_CODEC_PCM2BT) += snd-soc-pcm2bt.o +obj-$(CONFIG_AMLOGIC_SND_CODEC_AMLT9015) += snd-soc-aml_t9015.o \ No newline at end of file diff --git a/sound/soc/codecs/amlogic/aml_codec_t9015.c b/sound/soc/codecs/amlogic/aml_codec_t9015.c new file mode 100644 index 000000000000..c43ca2f3869a --- /dev/null +++ b/sound/soc/codecs/amlogic/aml_codec_t9015.c @@ -0,0 +1,552 @@ +/* + * sound/soc/codecs/amlogic/aml_codec_t9015.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "aml_codec_t9015.h" + +struct aml_T9015_audio_priv { + struct snd_soc_codec *codec; + struct snd_pcm_hw_params *params; +}; + +static const struct reg_default t9015_init_list[] = { + {AUDIO_CONFIG_BLOCK_ENABLE, 0x0000B00F}, + {ADC_VOL_CTR_PGA_IN_CONFIG, 0x00000000}, + {DAC_VOL_CTR_DAC_SOFT_MUTE, 0xFBFB0000}, + {LINE_OUT_CONFIG, 0x00001111}, + {POWER_CONFIG, 0x00010000}, +}; + +static int aml_T9015_audio_reg_init(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(t9015_init_list); i++) + snd_soc_write(codec, t9015_init_list[i].reg, + t9015_init_list[i].def); + + return 0; +} + +static int aml_DAC_Gain_get_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u32 add, val, val1, val2; + + if (codec == NULL) + return -1; + + add = ADC_VOL_CTR_PGA_IN_CONFIG; + val = snd_soc_read(codec, add); + val1 = (val & (0x1 << DAC_GAIN_SEL_L)) >> DAC_GAIN_SEL_L; + val2 = (val & (0x1 << DAC_GAIN_SEL_H)) >> (DAC_GAIN_SEL_H - 1); + + val = val1 | val2; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int aml_DAC_Gain_set_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u32 add = ADC_VOL_CTR_PGA_IN_CONFIG; + u32 val = snd_soc_read(codec, add); + + if (ucontrol->value.enumerated.item[0] == 0) { + val &= ~(0x1 << DAC_GAIN_SEL_H); + val &= ~(0x1 << DAC_GAIN_SEL_L); + } else if (ucontrol->value.enumerated.item[0] == 1) { + val &= ~(0x1 << DAC_GAIN_SEL_H); + val |= (0x1 << DAC_GAIN_SEL_L); + pr_info("It has risk of distortion!\n"); + } else if (ucontrol->value.enumerated.item[0] == 2) { + val |= (0x1 << DAC_GAIN_SEL_H); + val &= ~(0x1 << DAC_GAIN_SEL_L); + pr_info("It has risk of distortion!\n"); + } else if (ucontrol->value.enumerated.item[0] == 3) { + val |= (0x1 << DAC_GAIN_SEL_H); + val |= (0x1 << DAC_GAIN_SEL_L); + pr_info("It has risk of distortion!\n"); + } + + snd_soc_write(codec, val, add); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -95250, 375, 1); + +static const char *const DAC_Gain_texts[] = { "0dB", "6dB", "12dB", "18dB" }; + +static const struct soc_enum DAC_Gain_enum = SOC_ENUM_SINGLE( + SND_SOC_NOPM, 0, ARRAY_SIZE(DAC_Gain_texts), + DAC_Gain_texts); + +static const struct snd_kcontrol_new T9015_audio_snd_controls[] = { + + /*DAC Digital Volume control */ + SOC_DOUBLE_TLV("DAC Digital Playback Volume", + DAC_VOL_CTR_DAC_SOFT_MUTE, + DACL_VC, DACR_VC, + 0xff, 0, dac_vol_tlv), + + /*DAC extra Digital Gain control */ + SOC_ENUM_EXT("DAC Extra Digital Gain", + DAC_Gain_enum, + aml_DAC_Gain_get_enum, + aml_DAC_Gain_set_enum), + +}; + +/*line out Left Positive mux */ +static const char * const T9015_out_lp_txt[] = { + "None", "LOLP_SEL_DACL", "LOLP_SEL_DACL_INV" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015_out_lp_enum, LINE_OUT_CONFIG, + LOLP_SEL_DACL, T9015_out_lp_txt); + +static const struct snd_kcontrol_new line_out_lp_mux = +SOC_DAPM_ENUM("ROUTE_LP_OUT", T9015_out_lp_enum); + +/*line out Left Negative mux */ +static const char * const T9015_out_ln_txt[] = { + "None", "LOLN_SEL_DACL_INV", "LOLN_SEL_DACL" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015_out_ln_enum, LINE_OUT_CONFIG, + LOLN_SEL_DACL_INV, T9015_out_ln_txt); + +static const struct snd_kcontrol_new line_out_ln_mux = +SOC_DAPM_ENUM("ROUTE_LN_OUT", T9015_out_ln_enum); + +/*line out Right Positive mux */ +static const char * const T9015_out_rp_txt[] = { + "None", "LORP_SEL_DACR", "LORP_SEL_DACR_INV" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015_out_rp_enum, LINE_OUT_CONFIG, + LORP_SEL_DACR, T9015_out_rp_txt); + +static const struct snd_kcontrol_new line_out_rp_mux = +SOC_DAPM_ENUM("ROUTE_RP_OUT", T9015_out_rp_enum); + +/*line out Right Negative mux */ +static const char * const T9015_out_rn_txt[] = { + "None", "LORN_SEL_DACR_INV", "LORN_SEL_DACR" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015_out_rn_enum, LINE_OUT_CONFIG, + LORN_SEL_DACR_INV, T9015_out_rn_txt); + +static const struct snd_kcontrol_new line_out_rn_mux = +SOC_DAPM_ENUM("ROUTE_RN_OUT", T9015_out_rn_enum); + +static const struct snd_soc_dapm_widget T9015_audio_dapm_widgets[] = { + + /*Output */ + SND_SOC_DAPM_OUTPUT("Lineout left N"), + SND_SOC_DAPM_OUTPUT("Lineout left P"), + SND_SOC_DAPM_OUTPUT("Lineout right N"), + SND_SOC_DAPM_OUTPUT("Lineout right P"), + + /*DAC playback stream */ + SND_SOC_DAPM_DAC("Left DAC", "HIFI Playback", + AUDIO_CONFIG_BLOCK_ENABLE, + DACL_EN, 0), + SND_SOC_DAPM_DAC("Right DAC", "HIFI Playback", + AUDIO_CONFIG_BLOCK_ENABLE, + DACR_EN, 0), + + /*DRV output */ + SND_SOC_DAPM_OUT_DRV("LOLP_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LOLN_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LORP_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LORN_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + + /*MUX output source select */ + SND_SOC_DAPM_MUX("Lineout left P switch", SND_SOC_NOPM, + 0, 0, &line_out_lp_mux), + SND_SOC_DAPM_MUX("Lineout left N switch", SND_SOC_NOPM, + 0, 0, &line_out_ln_mux), + SND_SOC_DAPM_MUX("Lineout right P switch", SND_SOC_NOPM, + 0, 0, &line_out_rp_mux), + SND_SOC_DAPM_MUX("Lineout right N switch", SND_SOC_NOPM, + 0, 0, &line_out_rn_mux), +}; + +static const struct snd_soc_dapm_route T9015_audio_dapm_routes[] = { + /*Output path*/ + {"Lineout left P switch", "LOLP_SEL_DACL", "Left DAC"}, + {"Lineout left P switch", "LOLP_SEL_DACL_INV", "Left DAC"}, + + {"Lineout left N switch", "LOLN_SEL_DACL_INV", "Left DAC"}, + {"Lineout left N switch", "LOLN_SEL_DACL", "Left DAC"}, + + {"Lineout right P switch", "LORP_SEL_DACR", "Right DAC"}, + {"Lineout right P switch", "LORP_SEL_DACR_INV", "Right DAC"}, + + {"Lineout right N switch", "LORN_SEL_DACR_INV", "Right DAC"}, + {"Lineout right N switch", "LORN_SEL_DACR", "Right DAC"}, + + {"LOLN_OUT_EN", NULL, "Lineout left N switch"}, + {"LOLP_OUT_EN", NULL, "Lineout left P switch"}, + {"LORN_OUT_EN", NULL, "Lineout right N switch"}, + {"LORP_OUT_EN", NULL, "Lineout right P switch"}, + + {"Lineout left N", NULL, "LOLN_OUT_EN"}, + {"Lineout left P", NULL, "LOLP_OUT_EN"}, + {"Lineout right N", NULL, "LORN_OUT_EN"}, + {"Lineout right P", NULL, "LORP_OUT_EN"}, +}; + +static int aml_T9015_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_update_bits(codec, AUDIO_CONFIG_BLOCK_ENABLE, + I2S_MODE, 1); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_update_bits(codec, AUDIO_CONFIG_BLOCK_ENABLE, + I2S_MODE, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static int aml_T9015_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int aml_T9015_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct aml_T9015_audio_priv *T9015_audio = + snd_soc_codec_get_drvdata(codec); + + T9015_audio->params = params; + + return 0; +} + +static int aml_T9015_audio_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + + break; + + case SND_SOC_BIAS_PREPARE: + + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->component.dapm.bias_level == SND_SOC_BIAS_OFF) { +#if 0 /*tmp_mask_for_kernel_4_4*/ + codec->cache_only = false; + codec->cache_sync = 1; +#endif + snd_soc_cache_sync(codec); + } + break; + + case SND_SOC_BIAS_OFF: + + break; + + default: + break; + } + codec->component.dapm.bias_level = level; + + return 0; +} + +static int aml_T9015_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + /*struct snd_soc_codec *codec = dai->codec;*/ + return 0; + +} + +static int aml_T9015_audio_reset(struct snd_soc_codec *codec) +{ + aml_cbus_update_bits(RESET1_REGISTER, (1 << ACODEC_RESET), + (1 << ACODEC_RESET)); + udelay(1000); + return 0; +} + +static int aml_T9015_audio_start_up(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, AUDIO_CONFIG_BLOCK_ENABLE, 0xF000); + msleep(200); + snd_soc_write(codec, AUDIO_CONFIG_BLOCK_ENABLE, 0xB000); + return 0; +} + +static int aml_T9015_codec_mute_stream(struct snd_soc_dai *dai, int mute, + int stream) +{ + struct snd_soc_codec *codec = dai->codec; + u32 reg; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = snd_soc_read(codec, DAC_VOL_CTR_DAC_SOFT_MUTE); + if (mute) + reg |= 0x1 << DAC_SOFT_MUTE; + else + reg &= ~(0x1 << DAC_SOFT_MUTE); + + snd_soc_write(codec, DAC_VOL_CTR_DAC_SOFT_MUTE, reg); + } + return 0; +} + +static int aml_T9015_audio_probe(struct snd_soc_codec *codec) +{ + struct aml_T9015_audio_priv *T9015_audio = NULL; + + T9015_audio = kzalloc(sizeof(struct aml_T9015_audio_priv), GFP_KERNEL); + if (T9015_audio == NULL) + return -ENOMEM; + snd_soc_codec_set_drvdata(codec, T9015_audio); +#if 0 /*tmp_mask_for_kernel_4_4*/ + snd_soc_codec_set_cache_io(codec, 32, 32, SND_SOC_REGMAP); +#endif + /*reset audio codec register*/ + aml_T9015_audio_reset(codec); + aml_T9015_audio_start_up(codec); + aml_T9015_audio_reg_init(codec); + + aml_write_cbus(AIU_ACODEC_CTRL, (1 << 4) + |(1 << 6) + |(1 << 11) + |(1 << 15) + |(2 << 2) + ); + + codec->component.dapm.bias_level = SND_SOC_BIAS_STANDBY; + T9015_audio->codec = codec; + + return 0; +} + +static int aml_T9015_audio_remove(struct snd_soc_codec *codec) +{ + aml_T9015_audio_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int aml_T9015_audio_suspend(struct snd_soc_codec *codec) +{ + pr_info("aml_T9015_audio_suspend!\n"); + aml_T9015_audio_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_write(codec, AUDIO_CONFIG_BLOCK_ENABLE, 0); + return 0; +} + +static int aml_T9015_audio_resume(struct snd_soc_codec *codec) +{ + pr_info("aml_T9015_audio_resume!\n"); + aml_T9015_audio_reset(codec); + aml_T9015_audio_start_up(codec); + aml_T9015_audio_reg_init(codec); + codec->component.dapm.bias_level = SND_SOC_BIAS_STANDBY; + aml_T9015_audio_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +#define T9015_AUDIO_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define T9015_AUDIO_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \ + | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai_ops T9015_audio_aif_dai_ops = { + .hw_params = aml_T9015_hw_params, + .prepare = aml_T9015_prepare, + .set_fmt = aml_T9015_set_dai_fmt, + .set_sysclk = aml_T9015_set_dai_sysclk, + .mute_stream = aml_T9015_codec_mute_stream, +}; + +struct snd_soc_dai_driver aml_T9015_audio_dai[] = { + { + .name = "T9015-audio-hifi", + .id = 0, + .playback = { + .stream_name = "HIFI Playback", + .channels_min = 2, + .channels_max = 8, + .rates = T9015_AUDIO_STEREO_RATES, + .formats = T9015_AUDIO_FORMATS, + }, + .capture = { + .stream_name = "HIFI Capture", + .channels_min = 2, + .channels_max = 8, + .rates = T9015_AUDIO_STEREO_RATES, + .formats = T9015_AUDIO_FORMATS, + }, + .ops = &T9015_audio_aif_dai_ops, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_aml_T9015_audio = { + .probe = aml_T9015_audio_probe, + .remove = aml_T9015_audio_remove, + .suspend = aml_T9015_audio_suspend, + .resume = aml_T9015_audio_resume, + .set_bias_level = aml_T9015_audio_set_bias_level, + .component_driver = { + .controls = T9015_audio_snd_controls, + .num_controls = ARRAY_SIZE(T9015_audio_snd_controls), + .dapm_widgets = T9015_audio_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(T9015_audio_dapm_widgets), + .dapm_routes = T9015_audio_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(T9015_audio_dapm_routes), + } +}; + +static const struct regmap_config t9015_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14, + .reg_defaults = t9015_init_list, + .num_reg_defaults = ARRAY_SIZE(t9015_init_list), + .cache_type = REGCACHE_RBTREE, +}; + +static int aml_T9015_audio_codec_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res_mem; + struct device_node *np; + void __iomem *regs; + struct regmap *regmap; + + dev_info(&pdev->dev, "aml_T9015_audio_codec_probe\n"); + + np = pdev->dev.of_node; + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_mem) + return -ENODEV; + + regs = devm_ioremap_resource(&pdev->dev, res_mem); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &t9015_codec_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_aml_T9015_audio, + &aml_T9015_audio_dai[0], 1); + return ret; +} + +static int aml_T9015_audio_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static const struct of_device_id aml_T9015_codec_dt_match[] = { + {.compatible = "amlogic, aml_codec_T9015",}, + {}, +}; + +static struct platform_driver aml_T9015_codec_platform_driver = { + .driver = { + .name = "aml_codec_T9015", + .owner = THIS_MODULE, + .of_match_table = aml_T9015_codec_dt_match, + }, + .probe = aml_T9015_audio_codec_probe, + .remove = aml_T9015_audio_codec_remove, +}; + +static int __init aml_T9015_audio_modinit(void) +{ + int ret = 0; + + ret = platform_driver_register(&aml_T9015_codec_platform_driver); + if (ret != 0) { + pr_err( + "Failed to register AML T9015 codec platform driver: %d\n", + ret); + } + + return ret; +} + +module_init(aml_T9015_audio_modinit); + +static void __exit aml_T9015_audio_exit(void) +{ + platform_driver_unregister(&aml_T9015_codec_platform_driver); +} + +module_exit(aml_T9015_audio_exit); + +MODULE_DESCRIPTION("ASoC AML T9015 audio codec driver"); +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/amlogic/aml_codec_t9015.h b/sound/soc/codecs/amlogic/aml_codec_t9015.h new file mode 100644 index 000000000000..ae908e64c919 --- /dev/null +++ b/sound/soc/codecs/amlogic/aml_codec_t9015.h @@ -0,0 +1,71 @@ +/* + * sound/soc/codecs/amlogic/aml_codec_t9015.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#ifndef AML_T9015_H_ +#define AML_T9015_H_ + +#define ACODEC_BASE_ADD 0xc8832000 +#define ACODEC_TOP_ADDR(x) (x) + +#define AUDIO_CONFIG_BLOCK_ENABLE ACODEC_TOP_ADDR(0x00) +#define MCLK_FREQ 0x1F +#define I2S_MODE 0x1E +#define DAC_CLK_TO_GPIO_EN 0x18 +#define DACL_DATA_SOURCE 0x17 +#define DACR_DATA_SOURCE 0x16 +#define DACL_INV 0x15 +#define DACR_INV 0x14 +#define VMID_GEN_EN 0x0F +#define VMID_GEN_FAST 0x0E +#define BIAS_CURRENT_EN 0x0D +#define REFP_BUF_EN 0x0C +#define DACL_EN 0x05 +#define DACR_EN 0x04 +#define LOLP_EN 0x03 +#define LOLN_EN 0x02 +#define LORP_EN 0x01 +#define LORN_EN 0x00 + +#define ADC_VOL_CTR_PGA_IN_CONFIG ACODEC_TOP_ADDR(0x04) +#define DAC_GAIN_SEL_H 0x1F +#define DAC_GAIN_SEL_L 0x17 + + +#define DAC_VOL_CTR_DAC_SOFT_MUTE ACODEC_TOP_ADDR(0x08) +#define DACL_VC 0x18 +#define DACR_VC 0x10 +#define DAC_SOFT_MUTE 0x0F +#define DAC_UNMUTE_MODE 0x0E +#define DAC_MUTE_MODE 0x0D +#define DAC_VC_RAMP_MODE 0x0C +#define DAC_RAMP_RATE 0x0A +#define DAC_MONO 0x08 + +#define LINE_OUT_CONFIG ACODEC_TOP_ADDR(0x0c) +#define LOLP_SEL_DACL_INV 0x0D +#define LOLP_SEL_DACL 0x0C +#define LOLN_SEL_DACL 0x09 +#define LOLN_SEL_DACL_INV 0x08 +#define LORP_SEL_DACR_INV 0x05 +#define LORP_SEL_DACR 0x04 +#define LORN_SEL_DACR 0x01 +#define LORN_SEL_DACR_INV 0x00 + +#define POWER_CONFIG ACODEC_TOP_ADDR(0x10) +#define MUTE_DAC_PD_EN 0x1F +#define IB_CON 0x10 + +#endif diff --git a/sound/soc/codecs/amlogic/aml_codec_t9015S.c b/sound/soc/codecs/amlogic/aml_codec_t9015S.c new file mode 100644 index 000000000000..cc283609fe64 --- /dev/null +++ b/sound/soc/codecs/amlogic/aml_codec_t9015S.c @@ -0,0 +1,633 @@ +/* + * sound/soc/codecs/amlogic/aml_codec_t9015S.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "aml_codec_t9015S.h" + +struct aml_T9015S_audio_priv { + struct snd_soc_codec *codec; + struct snd_pcm_hw_params *params; +}; + +static const struct reg_default t9015s_init_list[] = { + {AUDIO_CONFIG_BLOCK_ENABLE, 0x3400BCFF}, + {ADC_VOL_CTR_PGA_IN_CONFIG, 0x50502929}, + {DAC_VOL_CTR_DAC_SOFT_MUTE, 0xFBFB0000}, + {LINE_OUT_CONFIG, 0x00004444}, + {POWER_CONFIG, 0x00010000}, +}; + +static int aml_T9015S_audio_reg_init(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(t9015s_init_list); i++) + snd_soc_write(codec, t9015s_init_list[i].reg, + t9015s_init_list[i].def); + + return 0; +} + +static int aml_DAC_Gain_get_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u32 add = ADC_VOL_CTR_PGA_IN_CONFIG; + u32 val = snd_soc_read(codec, add); + u32 val1 = (val & (0x1 << DAC_GAIN_SEL_L)) >> DAC_GAIN_SEL_L; + u32 val2 = (val & (0x1 << DAC_GAIN_SEL_H)) >> (DAC_GAIN_SEL_H - 1); + + val = val1 | val2; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int aml_DAC_Gain_set_enum( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u32 add = ADC_VOL_CTR_PGA_IN_CONFIG; + u32 val = snd_soc_read(codec, add); + + if (ucontrol->value.enumerated.item[0] == 0) { + val &= ~(0x1 << DAC_GAIN_SEL_H); + val &= ~(0x1 << DAC_GAIN_SEL_L); + } else if (ucontrol->value.enumerated.item[0] == 1) { + val &= ~(0x1 << DAC_GAIN_SEL_H); + val |= (0x1 << DAC_GAIN_SEL_L); + pr_info("It has risk of distortion!\n"); + } else if (ucontrol->value.enumerated.item[0] == 2) { + val |= (0x1 << DAC_GAIN_SEL_H); + val &= ~(0x1 << DAC_GAIN_SEL_L); + pr_info("It has risk of distortion!\n"); + } else if (ucontrol->value.enumerated.item[0] == 3) { + val |= (0x1 << DAC_GAIN_SEL_H); + val |= (0x1 << DAC_GAIN_SEL_L); + pr_info("It has risk of distortion!\n"); + } + + snd_soc_write(codec, val, add); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(pga_in_tlv, -1200, 250, 1); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -29625, 375, 1); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -95250, 375, 1); + +static const char *const DAC_Gain_texts[] = { "0dB", "6dB", "12dB", "18dB" }; + +static const struct soc_enum DAC_Gain_enum = SOC_ENUM_SINGLE( + SND_SOC_NOPM, 0, ARRAY_SIZE(DAC_Gain_texts), + DAC_Gain_texts); + +static const struct snd_kcontrol_new T9015S_audio_snd_controls[] = { + /*PGA_IN Gain */ + SOC_DOUBLE_TLV("PGA IN Gain", ADC_VOL_CTR_PGA_IN_CONFIG, + PGAL_IN_GAIN, PGAR_IN_GAIN, + 0x1f, 0, pga_in_tlv), + + /*ADC Digital Volume control */ + SOC_DOUBLE_TLV("ADC Digital Capture Volume", ADC_VOL_CTR_PGA_IN_CONFIG, + ADCL_VC, ADCR_VC, + 0x7f, 0, adc_vol_tlv), + + /*DAC Digital Volume control */ + SOC_DOUBLE_TLV("DAC Digital Playback Volume", + DAC_VOL_CTR_DAC_SOFT_MUTE, + DACL_VC, DACR_VC, + 0xff, 0, dac_vol_tlv), + + /*DAC extra Digital Gain control */ + SOC_ENUM_EXT("DAC Extra Digital Gain", + DAC_Gain_enum, + aml_DAC_Gain_get_enum, + aml_DAC_Gain_set_enum), + +}; + +/*pgain Left Channel Input */ +static const char * const T9015S_pgain_left_txt[] = { + "None", "AIL1", "AIL2", "AIL3", "AIL4" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015S_pgain_left_enum, + ADC_VOL_CTR_PGA_IN_CONFIG, + PGAL_IN_SEL, T9015S_pgain_left_txt); + +static const struct snd_kcontrol_new pgain_ln_mux = +SOC_DAPM_ENUM("ROUTE_L", T9015S_pgain_left_enum); + +/*pgain right Channel Input */ +static const char * const T9015S_pgain_right_txt[] = { + "None", "AIR1", "AIR2", "AIR3", "AIR4" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015S_pgain_right_enum, + ADC_VOL_CTR_PGA_IN_CONFIG, + PGAR_IN_SEL, T9015S_pgain_right_txt); + +static const struct snd_kcontrol_new pgain_rn_mux = +SOC_DAPM_ENUM("ROUTE_R", T9015S_pgain_right_enum); + +/*line out Left Positive mux */ +static const char * const T9015S_out_lp_txt[] = { + "None", "LOLP_SEL_AIL_INV", "LOLP_SEL_AIL", "Reserved", "LOLP_SEL_DACL" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015S_out_lp_enum, LINE_OUT_CONFIG, + LOLP_SEL_AIL_INV, T9015S_out_lp_txt); + +static const struct snd_kcontrol_new line_out_lp_mux = +SOC_DAPM_ENUM("ROUTE_LP_OUT", T9015S_out_lp_enum); + +/*line out Left Negative mux */ +static const char * const T9015S_out_ln_txt[] = { + "None", "LOLN_SEL_AIL", "LOLN_SEL_DACL", "Reserved", "LOLN_SEL_DACL_INV" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015S_out_ln_enum, LINE_OUT_CONFIG, + LOLN_SEL_AIL, T9015S_out_ln_txt); + +static const struct snd_kcontrol_new line_out_ln_mux = +SOC_DAPM_ENUM("ROUTE_LN_OUT", T9015S_out_ln_enum); + +/*line out Right Positive mux */ +static const char * const T9015S_out_rp_txt[] = { + "None", "LORP_SEL_AIR_INV", "LORP_SEL_AIR", "Reserved", "LORP_SEL_DACR" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015S_out_rp_enum, LINE_OUT_CONFIG, + LORP_SEL_AIR_INV, T9015S_out_rp_txt); + +static const struct snd_kcontrol_new line_out_rp_mux = +SOC_DAPM_ENUM("ROUTE_RP_OUT", T9015S_out_rp_enum); + +/*line out Right Negative mux */ +static const char * const T9015S_out_rn_txt[] = { + "None", "LORN_SEL_AIR", "LORN_SEL_DACR", "Reserved", "LORN_SEL_DACR_INV" +}; + +static const SOC_ENUM_SINGLE_DECL(T9015S_out_rn_enum, LINE_OUT_CONFIG, + LORN_SEL_AIR, T9015S_out_rn_txt); + +static const struct snd_kcontrol_new line_out_rn_mux = +SOC_DAPM_ENUM("ROUTE_RN_OUT", T9015S_out_rn_enum); + +static const struct snd_soc_dapm_widget T9015S_audio_dapm_widgets[] = { + + /* Input */ + SND_SOC_DAPM_INPUT("Linein left 1"), + SND_SOC_DAPM_INPUT("Linein left 2"), + SND_SOC_DAPM_INPUT("Linein left 3"), + SND_SOC_DAPM_INPUT("Linein left 4"), + + SND_SOC_DAPM_INPUT("Linein right 1"), + SND_SOC_DAPM_INPUT("Linein right 2"), + SND_SOC_DAPM_INPUT("Linein right 3"), + SND_SOC_DAPM_INPUT("Linein right 4"), + + /*PGA input */ + SND_SOC_DAPM_PGA("PGAL_IN_EN", AUDIO_CONFIG_BLOCK_ENABLE, + PGAL_IN_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGAR_IN_EN", AUDIO_CONFIG_BLOCK_ENABLE, + PGAL_IN_EN, 0, NULL, 0), + + /*PGA input source select */ + SND_SOC_DAPM_MUX("Linein left switch", SND_SOC_NOPM, + 0, 0, &pgain_ln_mux), + SND_SOC_DAPM_MUX("Linein right switch", SND_SOC_NOPM, + 0, 0, &pgain_rn_mux), + + /*ADC capture stream */ + SND_SOC_DAPM_ADC("Left ADC", "HIFI Capture", AUDIO_CONFIG_BLOCK_ENABLE, + ADCL_EN, 0), + SND_SOC_DAPM_ADC("Right ADC", "HIFI Capture", AUDIO_CONFIG_BLOCK_ENABLE, + ADCR_EN, 0), + + /*Output */ + SND_SOC_DAPM_OUTPUT("Lineout left N"), + SND_SOC_DAPM_OUTPUT("Lineout left P"), + SND_SOC_DAPM_OUTPUT("Lineout right N"), + SND_SOC_DAPM_OUTPUT("Lineout right P"), + + /*DAC playback stream */ + SND_SOC_DAPM_DAC("Left DAC", "HIFI Playback", + AUDIO_CONFIG_BLOCK_ENABLE, + DACL_EN, 0), + SND_SOC_DAPM_DAC("Right DAC", "HIFI Playback", + AUDIO_CONFIG_BLOCK_ENABLE, + DACR_EN, 0), + + /*DRV output */ + SND_SOC_DAPM_OUT_DRV("LOLP_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LOLN_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LORP_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LORN_OUT_EN", SND_SOC_NOPM, + 0, 0, NULL, 0), + + /*MUX output source select */ + SND_SOC_DAPM_MUX("Lineout left P switch", SND_SOC_NOPM, + 0, 0, &line_out_lp_mux), + SND_SOC_DAPM_MUX("Lineout left N switch", SND_SOC_NOPM, + 0, 0, &line_out_ln_mux), + SND_SOC_DAPM_MUX("Lineout right P switch", SND_SOC_NOPM, + 0, 0, &line_out_rp_mux), + SND_SOC_DAPM_MUX("Lineout right N switch", SND_SOC_NOPM, + 0, 0, &line_out_rn_mux), +}; + +static const struct snd_soc_dapm_route T9015S_audio_dapm_routes[] = { +/* Input path */ + {"Linein left switch", "AIL1", "Linein left 1"}, + {"Linein left switch", "AIL2", "Linein left 2"}, + {"Linein left switch", "AIL3", "Linein left 3"}, + {"Linein left switch", "AIL4", "Linein left 4"}, + + {"Linein right switch", "AIR1", "Linein right 1"}, + {"Linein right switch", "AIR2", "Linein right 2"}, + {"Linein right switch", "AIR3", "Linein right 3"}, + {"Linein right switch", "AIR4", "Linein right 4"}, + + {"PGAL_IN_EN", NULL, "Linein left switch"}, + {"PGAR_IN_EN", NULL, "Linein right switch"}, + + {"Left ADC", NULL, "PGAL_IN_EN"}, + {"Right ADC", NULL, "PGAR_IN_EN"}, + +/*Output path*/ + {"Lineout left P switch", "LOLP_SEL_DACL", "Left DAC"}, + {"Lineout left P switch", "LOLP_SEL_AIL", "PGAL_IN_EN"}, + {"Lineout left P switch", "LOLP_SEL_AIL_INV", "PGAL_IN_EN"}, + + {"Lineout left N switch", "LOLN_SEL_AIL", "PGAL_IN_EN"}, + {"Lineout left N switch", "LOLN_SEL_DACL", "Left DAC"}, + {"Lineout left N switch", "LOLN_SEL_DACL_INV", "Left DAC"}, + + {"Lineout right P switch", "LORP_SEL_DACR", "Right DAC"}, + {"Lineout right P switch", "LORP_SEL_AIR", "PGAR_IN_EN"}, + {"Lineout right P switch", "LORP_SEL_AIR_INV", "PGAR_IN_EN"}, + + {"Lineout right N switch", "LORN_SEL_AIR", "PGAR_IN_EN"}, + {"Lineout right N switch", "LORN_SEL_DACR", "Right DAC"}, + {"Lineout right N switch", "LORN_SEL_DACR_INV", "Right DAC"}, + + {"LOLN_OUT_EN", NULL, "Lineout left N switch"}, + {"LOLP_OUT_EN", NULL, "Lineout left P switch"}, + {"LORN_OUT_EN", NULL, "Lineout right N switch"}, + {"LORP_OUT_EN", NULL, "Lineout right P switch"}, + + {"Lineout left N", NULL, "LOLN_OUT_EN"}, + {"Lineout left P", NULL, "LOLP_OUT_EN"}, + {"Lineout right N", NULL, "LORN_OUT_EN"}, + {"Lineout right P", NULL, "LORP_OUT_EN"}, +}; + +static int aml_T9015S_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_update_bits(codec, AUDIO_CONFIG_BLOCK_ENABLE, + I2S_MODE, 1); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_update_bits(codec, AUDIO_CONFIG_BLOCK_ENABLE, + I2S_MODE, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static int aml_T9015S_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int aml_T9015S_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct aml_T9015S_audio_priv *T9015S_audio = + snd_soc_codec_get_drvdata(codec); + + T9015S_audio->params = params; + + return 0; +} + +static int aml_T9015S_audio_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + + break; + + case SND_SOC_BIAS_PREPARE: + + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + codec->cache_only = false; + codec->cache_sync = 1; + snd_soc_cache_sync(codec); + } + break; + + case SND_SOC_BIAS_OFF: + + break; + + default: + break; + } + codec->dapm.bias_level = level; + + return 0; +} + +static int aml_T9015S_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + /*struct snd_soc_codec *codec = dai->codec;*/ + return 0; + +} + +static int aml_T9015S_audio_reset(struct snd_soc_codec *codec) +{ + aml_cbus_update_bits(RESET1_REGISTER, (1 << ACODEC_RESET), + (1 << ACODEC_RESET)); + udelay(1000); + return 0; +} + +static int aml_T9015S_audio_start_up(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, AUDIO_CONFIG_BLOCK_ENABLE, 0xF000); + msleep(200); + snd_soc_write(codec, AUDIO_CONFIG_BLOCK_ENABLE, 0xB000); + return 0; +} + +static int aml_T9015S_codec_mute_stream(struct snd_soc_dai *dai, int mute, + int stream) +{ + struct snd_soc_codec *codec = dai->codec; + u32 reg; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = snd_soc_read(codec, DAC_VOL_CTR_DAC_SOFT_MUTE); + if (mute) + reg |= 0x1 << DAC_SOFT_MUTE; + else + reg &= ~(0x1 << DAC_SOFT_MUTE); + + snd_soc_write(codec, DAC_VOL_CTR_DAC_SOFT_MUTE, reg); + } + return 0; +} + +static int aml_T9015S_audio_probe(struct snd_soc_codec *codec) +{ + struct aml_T9015S_audio_priv *T9015S_audio = NULL; + + T9015S_audio = kzalloc(sizeof(struct aml_T9015S_audio_priv), + GFP_KERNEL); + if (T9015S_audio == NULL) + return -ENOMEM; + snd_soc_codec_set_drvdata(codec, T9015S_audio); +#if 0 /*tmp_mask_for_kernel_4_4*/ + snd_soc_codec_set_cache_io(codec, 32, 32, SND_SOC_REGMAP); +#endif + /*reset audio codec register*/ + aml_T9015S_audio_reset(codec); + aml_T9015S_audio_start_up(codec); + aml_T9015S_audio_reg_init(codec); + + aml_write_cbus(AIU_ACODEC_CTRL, (1 << 4) + |(1 << 6) + |(1 << 11) + |(1 << 15) + |(2 << 2) + ); + + aml_write_cbus(AUDIN_SOURCE_SEL, 3); + codec->dapm.bias_level = SND_SOC_BIAS_STANDBY; + T9015S_audio->codec = codec; + + return 0; +} + +static int aml_T9015S_audio_remove(struct snd_soc_codec *codec) +{ + aml_T9015S_audio_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int aml_T9015S_audio_suspend(struct snd_soc_codec *codec) +{ + pr_info("aml_T9015S_audio_suspend!\n"); + aml_T9015S_audio_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_write(codec, AUDIO_CONFIG_BLOCK_ENABLE, 0); + return 0; +} + +static int aml_T9015S_audio_resume(struct snd_soc_codec *codec) +{ + pr_info("aml_T9015S_audio_resume!\n"); + aml_T9015S_audio_reset(codec); + aml_T9015S_audio_start_up(codec); + aml_T9015S_audio_reg_init(codec); + codec->dapm.bias_level = SND_SOC_BIAS_STANDBY; + aml_T9015S_audio_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +#define T9015S_AUDIO_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define T9015S_AUDIO_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \ + | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai_ops T9015S_audio_aif_dai_ops = { + .hw_params = aml_T9015S_hw_params, + .prepare = aml_T9015S_prepare, + .set_fmt = aml_T9015S_set_dai_fmt, + .set_sysclk = aml_T9015S_set_dai_sysclk, + .mute_stream = aml_T9015S_codec_mute_stream, +}; + +struct snd_soc_dai_driver aml_T9015S_audio_dai[] = { + { + .name = "T9015S-audio-hifi", + .id = 0, + .playback = { + .stream_name = "HIFI Playback", + .channels_min = 2, + .channels_max = 8, + .rates = T9015S_AUDIO_STEREO_RATES, + .formats = T9015S_AUDIO_FORMATS, + }, + .capture = { + .stream_name = "HIFI Capture", + .channels_min = 1, + .channels_max = 2, + .rates = T9015S_AUDIO_STEREO_RATES, + .formats = T9015S_AUDIO_FORMATS, + }, + .ops = &T9015S_audio_aif_dai_ops, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_aml_T9015S_audio = { + .probe = aml_T9015S_audio_probe, + .remove = aml_T9015S_audio_remove, + .suspend = aml_T9015S_audio_suspend, + .resume = aml_T9015S_audio_resume, + .set_bias_level = aml_T9015S_audio_set_bias_level, + .component_driver = { + .controls = T9015S_audio_snd_controls, + .num_controls = ARRAY_SIZE(T9015S_audio_snd_controls), + .dapm_widgets = T9015S_audio_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(T9015S_audio_dapm_widgets), + .dapm_routes = T9015S_audio_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(T9015S_audio_dapm_routes), + } +}; + +static const struct regmap_config t9015s_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14, + .reg_defaults = t9015s_init_list, + .num_reg_defaults = ARRAY_SIZE(t9015s_init_list), + .cache_type = REGCACHE_RBTREE, +}; + +static int aml_T9015S_audio_codec_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res_mem; + struct device_node *np; + void __iomem *regs; + struct regmap *regmap; + + dev_info(&pdev->dev, "aml_T9015S_audio_codec_probe\n"); + + np = pdev->dev.of_node; + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_mem) + return -ENODEV; + + regs = devm_ioremap_resource(&pdev->dev, res_mem); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &t9015s_codec_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_aml_T9015S_audio, + &aml_T9015S_audio_dai[0], 1); + return ret; +} + +static int aml_T9015S_audio_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static const struct of_device_id aml_T9015S_codec_dt_match[] = { + {.compatible = "amlogic, aml_codec_T9015S",}, + {}, +}; + +static struct platform_driver aml_T9015S_codec_platform_driver = { + .driver = { + .name = "aml_codec_T9015S", + .owner = THIS_MODULE, + .of_match_table = aml_T9015S_codec_dt_match, + }, + .probe = aml_T9015S_audio_codec_probe, + .remove = aml_T9015S_audio_codec_remove, +}; + +static int __init aml_T9015S_audio_modinit(void) +{ + int ret = 0; + + ret = platform_driver_register(&aml_T9015S_codec_platform_driver); + if (ret != 0) { + pr_err( + "Failed to register AML T9015S codec platform driver: %d\n", + ret); + } + + return ret; +} + +module_init(aml_T9015S_audio_modinit); + +static void __exit aml_T9015S_audio_exit(void) +{ + platform_driver_unregister(&aml_T9015S_codec_platform_driver); +} + +module_exit(aml_T9015S_audio_exit); + +MODULE_DESCRIPTION("ASoC AML T9015S audio codec driver"); +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/amlogic/aml_codec_t9015S.h b/sound/soc/codecs/amlogic/aml_codec_t9015S.h new file mode 100644 index 000000000000..a1440321f7be --- /dev/null +++ b/sound/soc/codecs/amlogic/aml_codec_t9015S.h @@ -0,0 +1,96 @@ +/* + * sound/soc/codecs/amlogic/aml_codec_t9015S.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef AML_T9015S_H_ +#define AML_T9015S_H_ + +#define ACODEC_BASE_ADD 0xc8832000 +#define ACODEC_TOP_ADDR(x) (x) + +#define AUDIO_CONFIG_BLOCK_ENABLE ACODEC_TOP_ADDR(0x00) +#define MCLK_FREQ 0x1F +#define I2S_MODE 0x1E +#define ADC_HPF_EN 0x1D +#define ADC_HPF_MODE 0x1C +#define ADC_OVERLOAD_DET_EN 0x1B +#define ADC_DEM_EN 0x1A +#define ADC_CLK_TO_GPIO_EN 0x19 +#define DAC_CLK_TO_GPIO_EN 0x18 +#define DACL_DATA_SOURCE 0x17 +#define DACR_DATA_SOURCE 0x16 +#define DACL_INV 0x15 +#define DACR_INV 0x14 +#define ADCDATL_SOURCE 0x13 +#define ADCDATR_SOURCE 0x12 +#define ADCL_INV 0x11 +#define ADCR_INV 0x10 +#define VMID_GEN_EN 0x0F +#define VMID_GEN_FAST 0x0E +#define BIAS_CURRENT_EN 0x0D +#define REFP_BUF_EN 0x0C +#define PGAL_IN_EN 0x0B +#define PGAR_IN_EN 0x0A +#define PGAL_IN_ZC_EN 0x09 +#define PGAR_IN_ZC_EN 0x08 +#define ADCL_EN 0x07 +#define ADCR_EN 0x06 +#define DACL_EN 0x05 +#define DACR_EN 0x04 +#define LOLP_EN 0x03 +#define LOLN_EN 0x02 +#define LORP_EN 0x01 +#define LORN_EN 0x00 + +#define ADC_VOL_CTR_PGA_IN_CONFIG ACODEC_TOP_ADDR(0x04) +#define DAC_GAIN_SEL_H 0x1F +#define ADCL_VC 0x18 +#define DAC_GAIN_SEL_L 0x17 +#define ADCR_VC 0x10 +#define PGAL_IN_SEL 0x0D +#define PGAL_IN_GAIN 0x08 +#define PGAR_IN_SEL 0x05 +#define PGAR_IN_GAIN 0x00 + +#define DAC_VOL_CTR_DAC_SOFT_MUTE ACODEC_TOP_ADDR(0x08) +#define DACL_VC 0x18 +#define DACR_VC 0x10 +#define DAC_SOFT_MUTE 0x0F +#define DAC_UNMUTE_MODE 0x0E +#define DAC_MUTE_MODE 0x0D +#define DAC_VC_RAMP_MODE 0x0C +#define DAC_RAMP_RATE 0x0A +#define DAC_MONO 0x08 + +#define LINE_OUT_CONFIG ACODEC_TOP_ADDR(0x0c) +#define LOLP_SEL_DACL 0x0E +#define LOLP_SEL_AIL 0x0D +#define LOLP_SEL_AIL_INV 0x0C +#define LOLN_SEL_DACL_INV 0x0A +#define LOLN_SEL_DACL 0x09 +#define LOLN_SEL_AIL 0x08 +#define LORP_SEL_DACR 0x06 +#define LORP_SEL_AIR 0x05 +#define LORP_SEL_AIR_INV 0x04 +#define LORN_SEL_DACR_INV 0x02 +#define LORN_SEL_DACR 0x01 +#define LORN_SEL_AIR 0x00 + +#define POWER_CONFIG ACODEC_TOP_ADDR(0x10) +#define MUTE_DAC_PD_EN 0x1F +#define IB_CON 0x10 + +#endif diff --git a/sound/soc/codecs/amlogic/aml_pmu4_codec.c b/sound/soc/codecs/amlogic/aml_pmu4_codec.c new file mode 100644 index 000000000000..272988af94b3 --- /dev/null +++ b/sound/soc/codecs/amlogic/aml_pmu4_codec.c @@ -0,0 +1,587 @@ +/* + * sound/soc/codecs/amlogic/aml_pmu4_codec.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aml_pmu4_codec.h" + +#ifdef CONFIG_USE_OF +#include +#include +#include +#include +#include +#include +#endif + +struct aml_pmu4_audio_priv { + struct snd_soc_codec *codec; + struct snd_pcm_hw_params *params; +}; + +struct pmu4_audio_init_reg { + u8 reg; + u16 val; +}; + +#define AML1220_PMU_CTR_04 0x05 + +static struct pmu4_audio_init_reg init_list[] = { + {PMU4_BLOCK_ENABLE, 0xBCF6}, + {PMU4_AUDIO_CONFIG, 0x3400}, + {PMU4_PGA_IN_CONFIG, 0x2929}, + {PMU4_ADC_VOL_CTR, 0x5050}, + {PMU4_DAC_SOFT_MUTE, 0x0000}, + {PMU4_DAC_VOL_CTR, 0xFFFF}, + {PMU4_LINE_OUT_CONFIG, 0x4242}, + +}; + +#define PMU4_AUDIO_INIT_REG_LEN ARRAY_SIZE(init_list) + +static int aml_pmu4_audio_reg_init(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < PMU4_AUDIO_INIT_REG_LEN; i++) + snd_soc_write(codec, init_list[i].reg, init_list[i].val); + + return 0; +} + +static unsigned int aml_pmu4_audio_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 pmu4_audio_reg; + u16 val; + + pmu4_audio_reg = PMU4_AUDIO_BASE + reg; + aml_pmu4_read16(pmu4_audio_reg, &val); + + return val; + +} + +static int aml_pmu4_audio_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 pmu4_audio_reg; + + pmu4_audio_reg = PMU4_AUDIO_BASE + reg; + aml_pmu4_write16(pmu4_audio_reg, val); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(pga_in_tlv, -1200, 250, 1); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -29625, 375, 1); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -95250, 375, 1); + +static const struct snd_kcontrol_new pmu4_audio_snd_controls[] = { + /*PGA_IN Gain */ + SOC_DOUBLE_TLV("PGA IN Gain", PMU4_PGA_IN_CONFIG, + PMU4_PGAL_IN_GAIN, PMU4_PGAR_IN_GAIN, + 0x1f, 0, pga_in_tlv), + + /*ADC Digital Volume control */ + SOC_DOUBLE_TLV("ADC Digital Capture Volume", PMU4_ADC_VOL_CTR, + PMU4_ADCL_VOL_CTR, PMU4_ADCR_VOL_CTR, + 0x7f, 0, adc_vol_tlv), + + /*DAC Digital Volume control */ + SOC_DOUBLE_TLV("DAC Digital Playback Volume", PMU4_DAC_VOL_CTR, + PMU4_DACL_VOL_CTR, PMU4_DACR_VOL_CTR, + 0xff, 0, dac_vol_tlv), + +}; + +/*pgain Left Channel Input */ +static const char * const pmu4_pgain_left_txt[] = { + "None", "AIL1", "AIL2", "AIL3", "AIL4" +}; + +static const SOC_ENUM_SINGLE_DECL(pmu4_pgain_left_enum, PMU4_PGA_IN_CONFIG, + PMU4_PGAL_IN_SEL, pmu4_pgain_left_txt); + +static const struct snd_kcontrol_new pgain_ln_mux = +SOC_DAPM_ENUM("ROUTE_L", pmu4_pgain_left_enum); + +/*pgain right Channel Input */ +static const char * const pmu4_pgain_right_txt[] = { + "None", "AIR1", "AIR2", "AIR3", "AIR4" +}; + +static const SOC_ENUM_SINGLE_DECL(pmu4_pgain_right_enum, PMU4_PGA_IN_CONFIG, + PMU4_PGAR_IN_SEL, pmu4_pgain_right_txt); + +static const struct snd_kcontrol_new pgain_rn_mux = +SOC_DAPM_ENUM("ROUTE_R", pmu4_pgain_right_enum); + +/*line out Left Positive mux */ +static const char * const pmu4_out_lp_txt[] = { + "None", "LOLP_SEL_AIL_INV", "LOLP_SEL_AIL", "Reserved", "LOLP_SEL_DACL" +}; + +static const SOC_ENUM_SINGLE_DECL(pmu4_out_lp_enum, PMU4_LINE_OUT_CONFIG, + PMU4_LOLP_SEL_SHIFT, pmu4_out_lp_txt); + +static const struct snd_kcontrol_new line_out_lp_mux = +SOC_DAPM_ENUM("ROUTE_LP_OUT", pmu4_out_lp_enum); + +/*line out Left Negative mux */ +static const char * const pmu4_out_ln_txt[] = { + "None", "LOLN_SEL_AIL", "LOLN_SEL_DACL", "Reserved", "LOLN_SEL_DACL_INV" +}; + +static const SOC_ENUM_SINGLE_DECL(pmu4_out_ln_enum, PMU4_LINE_OUT_CONFIG, + PMU4_LOLN_SEL_SHIFT, pmu4_out_ln_txt); + +static const struct snd_kcontrol_new line_out_ln_mux = +SOC_DAPM_ENUM("ROUTE_LN_OUT", pmu4_out_ln_enum); + +/*line out Right Positive mux */ +static const char * const pmu4_out_rp_txt[] = { + "None", "LORP_SEL_AIR_INV", "LORP_SEL_AIR", "Reserved", "LORP_SEL_DACR" +}; + +static const SOC_ENUM_SINGLE_DECL(pmu4_out_rp_enum, PMU4_LINE_OUT_CONFIG, + PMU4_LORP_SEL_SHIFT, pmu4_out_rp_txt); + +static const struct snd_kcontrol_new line_out_rp_mux = +SOC_DAPM_ENUM("ROUTE_RP_OUT", pmu4_out_rp_enum); + +/*line out Right Negative mux */ +static const char * const pmu4_out_rn_txt[] = { + "None", "LORN_SEL_AIR", "LORN_SEL_DACR", "Reserved", "LORN_SEL_DACR_INV" +}; + +static const SOC_ENUM_SINGLE_DECL(pmu4_out_rn_enum, PMU4_LINE_OUT_CONFIG, + PMU4_LORN_SEL_SHIFT, pmu4_out_rn_txt); + +static const struct snd_kcontrol_new line_out_rn_mux = +SOC_DAPM_ENUM("ROUTE_RN_OUT", pmu4_out_rn_enum); + +static const struct snd_soc_dapm_widget pmu4_audio_dapm_widgets[] = { + + /* Input */ + SND_SOC_DAPM_INPUT("Linein left 1"), + SND_SOC_DAPM_INPUT("Linein left 2"), + SND_SOC_DAPM_INPUT("Linein left 3"), + SND_SOC_DAPM_INPUT("Linein left 4"), + + SND_SOC_DAPM_INPUT("Linein right 1"), + SND_SOC_DAPM_INPUT("Linein right 2"), + SND_SOC_DAPM_INPUT("Linein right 3"), + SND_SOC_DAPM_INPUT("Linein right 4"), + + /*PGA input */ + SND_SOC_DAPM_PGA("PGAL_IN_EN", PMU4_BLOCK_ENABLE, + PMU4_PGAL_IN_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGAR_IN_EN", PMU4_BLOCK_ENABLE, + PMU4_PGAR_IN_EN, 0, NULL, 0), + + /*PGA input source select */ + SND_SOC_DAPM_MUX("Linein left switch", SND_SOC_NOPM, + 0, 0, &pgain_ln_mux), + SND_SOC_DAPM_MUX("Linein right switch", SND_SOC_NOPM, + 0, 0, &pgain_rn_mux), + + /*ADC capture stream */ + SND_SOC_DAPM_ADC("Left ADC", "HIFI Capture", PMU4_BLOCK_ENABLE, + PMU4_ADCL_EN, 0), + SND_SOC_DAPM_ADC("Right ADC", "HIFI Capture", PMU4_BLOCK_ENABLE, + PMU4_ADCR_EN, 0), + + /*Output */ + SND_SOC_DAPM_OUTPUT("Lineout left N"), + SND_SOC_DAPM_OUTPUT("Lineout left P"), + SND_SOC_DAPM_OUTPUT("Lineout right N"), + SND_SOC_DAPM_OUTPUT("Lineout right P"), + + /*DAC playback stream */ + SND_SOC_DAPM_DAC("Left DAC", "HIFI Playback", PMU4_BLOCK_ENABLE, + PMU4_DACL_EN, 0), + SND_SOC_DAPM_DAC("Right DAC", "HIFI Playback", PMU4_BLOCK_ENABLE, + PMU4_DACR_EN, 0), + + /*DRV output */ + SND_SOC_DAPM_OUT_DRV("LOLP_OUT_EN", PMU4_BLOCK_ENABLE, + PMU4_LOLP_EN, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LOLN_OUT_EN", PMU4_BLOCK_ENABLE, + PMU4_LOLN_EN, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LORP_OUT_EN", PMU4_BLOCK_ENABLE, + PMU4_LORP_EN, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LORN_OUT_EN", PMU4_BLOCK_ENABLE, + PMU4_LORN_EN, 0, NULL, 0), + + /*MUX output source select */ + SND_SOC_DAPM_MUX("Lineout left P switch", SND_SOC_NOPM, + 0, 0, &line_out_lp_mux), + SND_SOC_DAPM_MUX("Lineout left N switch", SND_SOC_NOPM, + 0, 0, &line_out_ln_mux), + SND_SOC_DAPM_MUX("Lineout right P switch", SND_SOC_NOPM, + 0, 0, &line_out_rp_mux), + SND_SOC_DAPM_MUX("Lineout right N switch", SND_SOC_NOPM, + 0, 0, &line_out_rn_mux), +}; + +static const struct snd_soc_dapm_route pmu4_audio_dapm_routes[] = { +/* Input path */ + {"Linein left switch", "AIL1", "Linein left 1"}, + {"Linein left switch", "AIL2", "Linein left 2"}, + {"Linein left switch", "AIL3", "Linein left 3"}, + {"Linein left switch", "AIL4", "Linein left 4"}, + + {"Linein right switch", "AIR1", "Linein right 1"}, + {"Linein right switch", "AIR2", "Linein right 2"}, + {"Linein right switch", "AIR3", "Linein right 3"}, + {"Linein right switch", "AIR4", "Linein right 4"}, + + {"PGAL_IN_EN", NULL, "Linein left switch"}, + {"PGAR_IN_EN", NULL, "Linein right switch"}, + + {"Left ADC", NULL, "PGAL_IN_EN"}, + {"Right ADC", NULL, "PGAR_IN_EN"}, + +/*Output path*/ + {"Lineout left P switch", "LOLP_SEL_DACL", "Left DAC"}, + {"Lineout left P switch", "LOLP_SEL_AIL", "PGAL_IN_EN"}, + {"Lineout left P switch", "LOLP_SEL_AIL_INV", "PGAL_IN_EN"}, + + {"Lineout left N switch", "LOLN_SEL_AIL", "PGAL_IN_EN"}, + {"Lineout left N switch", "LOLN_SEL_DACL", "Left DAC"}, + {"Lineout left N switch", "LOLN_SEL_DACL_INV", "Left DAC"}, + + {"Lineout right P switch", "LORP_SEL_DACR", "Right DAC"}, + {"Lineout right P switch", "LORP_SEL_AIR", "PGAR_IN_EN"}, + {"Lineout right P switch", "LORP_SEL_AIR_INV", "PGAR_IN_EN"}, + + {"Lineout right N switch", "LORN_SEL_AIR", "PGAR_IN_EN"}, + {"Lineout right N switch", "LORN_SEL_DACR", "Right DAC"}, + {"Lineout right N switch", "LORN_SEL_DACR_INV", "Right DAC"}, + + {"LOLN_OUT_EN", NULL, "Lineout left N switch"}, + {"LOLP_OUT_EN", NULL, "Lineout left P switch"}, + {"LORN_OUT_EN", NULL, "Lineout right N switch"}, + {"LORP_OUT_EN", NULL, "Lineout right P switch"}, + + {"Lineout left N", NULL, "LOLN_OUT_EN"}, + {"Lineout left P", NULL, "LOLP_OUT_EN"}, + {"Lineout right N", NULL, "LORN_OUT_EN"}, + {"Lineout right P", NULL, "LORP_OUT_EN"}, +}; + +static int aml_pmu4_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_update_bits(codec, PMU4_AUDIO_CONFIG, + PMU4_I2S_MODE, PMU4_I2S_MODE); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_update_bits(codec, PMU4_AUDIO_CONFIG, PMU4_I2S_MODE, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static int aml_pmu4_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int aml_pmu4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct aml_pmu4_audio_priv *pmu4_audio = + snd_soc_codec_get_drvdata(codec); + + pmu4_audio->params = params; + + return 0; +} + +static int aml_pmu4_audio_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + + break; + + case SND_SOC_BIAS_PREPARE: + + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->component.dapm.bias_level == SND_SOC_BIAS_OFF) { +#if 0 /*xang_kernel44_tmp*/ + codec->cache_only = false; + codec->cache_sync = 1; +#endif + snd_soc_cache_sync(codec); + } + break; + + case SND_SOC_BIAS_OFF: + + break; + + default: + break; + } + codec->component.dapm.bias_level = level; + + return 0; +} + +static int aml_pmu4_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + /*struct snd_soc_codec *codec = dai->codec;*/ + return 0; + +} + +static int aml_pmu4_audio_reset(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, PMU4_SOFT_RESET, 0xF); + snd_soc_write(codec, PMU4_SOFT_RESET, 0x0); + udelay(10*1000); + return 0; +} + +static int aml_pmu4_audio_start_up(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, PMU4_BLOCK_ENABLE, 0xF000); + msleep(200); + snd_soc_write(codec, PMU4_BLOCK_ENABLE, 0xB000); + return 0; +} + +static int aml_pmu4_audio_power_init(void) +{ + uint8_t val = 0; + + /*set audio ldo supply en in,reg:0x05,bit 0*/ + aml_pmu4_read(AML1220_PMU_CTR_04, &val); + aml_pmu4_write(AML1220_PMU_CTR_04, val | 0x01); + return 0; + +} + +static int aml_pmu4_codec_mute_stream(struct snd_soc_dai *dai, int mute, + int stream) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = snd_soc_read(codec, PMU4_DAC_SOFT_MUTE); + if (mute) + reg |= 0x8000; + else + reg &= ~0x8000; + + snd_soc_write(codec, PMU4_DAC_SOFT_MUTE, reg); + } + return 0; +} + +static int aml_pmu4_audio_probe(struct snd_soc_codec *codec) +{ + struct aml_pmu4_audio_priv *pmu4_audio = NULL; + + /*pr_info("enter %s\n", __func__);*/ + pmu4_audio = kzalloc(sizeof(struct aml_pmu4_audio_priv), GFP_KERNEL); + if (pmu4_audio == NULL) + return -ENOMEM; + snd_soc_codec_set_drvdata(codec, pmu4_audio); + + /*enable LDO1V8 for audio*/ + aml_pmu4_audio_power_init(); + + /*reset audio codec register*/ + aml_pmu4_audio_reset(codec); + aml_pmu4_audio_start_up(codec); + aml_pmu4_audio_reg_init(codec); + + codec->component.dapm.bias_level = SND_SOC_BIAS_STANDBY; + pmu4_audio->codec = codec; + + return 0; +} + +static int aml_pmu4_audio_remove(struct snd_soc_codec *codec) +{ + aml_pmu4_audio_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int aml_pmu4_audio_suspend(struct snd_soc_codec *codec) +{ + aml_pmu4_audio_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int aml_pmu4_audio_resume(struct snd_soc_codec *codec) +{ + pr_info("enter %s\n", __func__); + aml_pmu4_audio_power_init(); + aml_pmu4_audio_reset(codec); + aml_pmu4_audio_start_up(codec); + aml_pmu4_audio_reg_init(codec); + codec->component.dapm.bias_level = SND_SOC_BIAS_STANDBY; + aml_pmu4_audio_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +#define PMU4_AUDIO_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define PMU4_AUDIO_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \ + | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai_ops pmu4_audio_aif_dai_ops = { + .hw_params = aml_pmu4_hw_params, + .prepare = aml_pmu4_prepare, + .set_fmt = aml_pmu4_set_dai_fmt, + .set_sysclk = aml_pmu4_set_dai_sysclk, + .mute_stream = aml_pmu4_codec_mute_stream, +}; + +struct snd_soc_dai_driver aml_pmu4_audio_dai[] = { + { + .name = "pmu4-audio-hifi", + .id = 0, + .playback = { + .stream_name = "HIFI Playback", + .channels_min = 2, + .channels_max = 8, + .rates = PMU4_AUDIO_STEREO_RATES, + .formats = PMU4_AUDIO_FORMATS, + }, + .capture = { + .stream_name = "HIFI Capture", + .channels_min = 1, + .channels_max = 2, + .rates = PMU4_AUDIO_STEREO_RATES, + .formats = PMU4_AUDIO_FORMATS, + }, + .ops = &pmu4_audio_aif_dai_ops, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_aml_pmu4_audio = { + .probe = aml_pmu4_audio_probe, + .remove = aml_pmu4_audio_remove, + .suspend = aml_pmu4_audio_suspend, + .resume = aml_pmu4_audio_resume, + .read = aml_pmu4_audio_read, + .write = aml_pmu4_audio_write, + .set_bias_level = aml_pmu4_audio_set_bias_level, + .component_driver = { + .controls = pmu4_audio_snd_controls, + .num_controls = ARRAY_SIZE(pmu4_audio_snd_controls), + .dapm_widgets = pmu4_audio_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pmu4_audio_dapm_widgets), + .dapm_routes = pmu4_audio_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pmu4_audio_dapm_routes), + }, + .reg_cache_size = 16, + .reg_word_size = sizeof(u16), + .reg_cache_step = 2, +}; + +static int aml_pmu4_audio_codec_probe(struct platform_device *pdev) +{ + int ret; + + dev_info(&pdev->dev, "aml_pmu4_audio_codec_probe\n"); + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_aml_pmu4_audio, + &aml_pmu4_audio_dai[0], 1); + return ret; +} + +static int aml_pmu4_audio_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static const struct of_device_id aml_pmu4_codec_dt_match[] = { + {.compatible = "amlogic, aml_pmu4_codec",}, + {}, +}; + +static struct platform_driver aml_pmu4_codec_platform_driver = { + .driver = { + .name = "aml_pmu4_codec", + .owner = THIS_MODULE, + .of_match_table = aml_pmu4_codec_dt_match, + }, + .probe = aml_pmu4_audio_codec_probe, + .remove = aml_pmu4_audio_codec_remove, +}; + +static int __init aml_pmu4_audio_modinit(void) +{ + int ret = 0; + + ret = platform_driver_register(&aml_pmu4_codec_platform_driver); + if (ret != 0) { + pr_info("Failed to register AML PMU4 codec platform driver: %d\n", + ret); + } + + return ret; +} + +module_init(aml_pmu4_audio_modinit); + +static void __exit aml_pmu4_audio_exit(void) +{ + platform_driver_unregister(&aml_pmu4_codec_platform_driver); +} + +module_exit(aml_pmu4_audio_exit); + +MODULE_DESCRIPTION("ASoC AML pmu4 audio codec driver"); +MODULE_AUTHOR("Chengshun Wang "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/amlogic/aml_pmu4_codec.h b/sound/soc/codecs/amlogic/aml_pmu4_codec.h new file mode 100644 index 000000000000..f557d22afdc7 --- /dev/null +++ b/sound/soc/codecs/amlogic/aml_pmu4_codec.h @@ -0,0 +1,100 @@ +/* + * sound/soc/codecs/amlogic/aml_pmu4_codec.h + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef AML_PMU4_H_ +#define AML_PMU4_H_ + +#define PMU4_AUDIO_BASE 0x40 +/*Info*/ +#define PMU4_SOFT_RESET 0x00 +#define PMU4_BLOCK_ENABLE 0x02 +#define PMU4_AUDIO_CONFIG 0x04 +#define PMU4_PGA_IN_CONFIG 0x06 +#define PMU4_ADC_VOL_CTR 0x08 +#define PMU4_DAC_SOFT_MUTE 0x0A +#define PMU4_DAC_VOL_CTR 0x0C +#define PMU4_LINE_OUT_CONFIG 0x0E + +/*Block Enable , Reg 0x02h*/ +#define PMU4_BIAS_CURRENT_EN 0xD +#define PMU4_PGAL_IN_EN 0xB +#define PMU4_PGAR_IN_EN 0xA +#define PMU4_PGAL_IN_ZC_EN 0x9 +#define PMU4_PGAR_IN_ZC_EN 0x8 +#define PMU4_ADCL_EN 0x7 +#define PMU4_ADCR_EN 0x6 +#define PMU4_DACL_EN 0x5 +#define PMU4_DACR_EN 0x4 +#define PMU4_LOLP_EN 0x3 +#define PMU4_LOLN_EN 0x2 +#define PMU4_LORP_EN 0x1 +#define PMU4_LORN_EN 0x0 + +/*Audio Config,Reg 0x04h*/ +#define PMU4_MCLK_FREQ 0xF +#define PMU4_I2S_MODE 0xE +#define PMU4_ADC_HPF_MODE 0xC +#define PMU4_ADC_DEM_EN 0xA +#define PMU4_ADC_CLK_TO_GPIO_EN 0x9 +#define PMU4_DAC_CLK_TO_GPIO_EN 0x8 +#define PMU4_DACL_DATA_SOURCE 0x7 +#define PMU4_DACR_DATA_SOURCE 0x6 +#define PMU4_DACL_INV 0x5 +#define PMU4_DACR_INV 0x4 +#define PMU4_ADCDATL_SOURCE 0x3 +#define PMU4_ADCDATR_SOURCE 0x2 +#define PMU4_ADCL_INV 0x1 +#define PMU4_ADCR_INV 0x0 + +/*PGA_IN Config, Reg 0x06h*/ +#define PMU4_PGAL_IN_SEL 0xD +#define PMU4_PGAL_IN_GAIN 0x8 +#define PMU4_PGAR_IN_SEL 0x5 +#define PMU4_PGAR_IN_GAIN 0x0 + +/*ADC_Volume_Control , Reg 0x08h*/ +#define PMU4_ADCL_VOL_CTR 0x8 +#define PMU4_ADCR_VOL_CTR 0x0 + +/*DAC Soft Mute, Reg 0xA*/ +#define PMU4_DAC_SOFT_MUTE_BIT 0xF +#define PMU4_DAC_UNMUTE_MODE 0xE +#define PMU4_DAC_MUTE_MODE 0xD +#define PMU4_DAC_VC_RAMP_MODE 0xC +#define PMU4_DAC_RAMP_RATE 0xA +#define PMU4_DAC_MONO 0x8 +#define PMU4_MUTE_DAC_PD_EN 0x7 + +/*DAC_Volume_Control, Reg 0xC*/ +#define PMU4_DACL_VOL_CTR 0x8 +#define PMU4_DACR_VOL_CTR 0x0 + +/*Line-Out Config, Reg 0xE*/ +#define PMU4_LOLP_SEL_DACL 0xE +#define PMU4_LOLP_SEL_AIL 0xD +#define PMU4_LOLP_SEL_SHIFT 0xC +#define PMU4_LOLN_SEL_DACL_INV 0xA +#define PMU4_LOLN_SEL_DACL 0x9 +#define PMU4_LOLN_SEL_SHIFT 0x8 +#define PMU4_LORP_SEL_DACR 0x6 +#define PMU4_LORP_SEL_AIR 0x5 +#define PMU4_LORP_SEL_SHIFT 0x4 +#define PMU4_LORN_SEL_DACR_INV 0x2 +#define PMU4_LORN_SEL_DACR 0x1 +#define PMU4_LORN_SEL_SHIFT 0x0 + +#endif diff --git a/sound/soc/codecs/amlogic/dummy_codec.c b/sound/soc/codecs/amlogic/dummy_codec.c new file mode 100644 index 000000000000..89b79743fc34 --- /dev/null +++ b/sound/soc/codecs/amlogic/dummy_codec.c @@ -0,0 +1,183 @@ +/* + * sound/soc/codecs/amlogic/dummy_codec.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dummy_codec_private { + struct snd_soc_codec codec; +}; + +#define DUMMY_CODEC_RATES (SNDRV_PCM_RATE_8000_192000) +#define DUMMY_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int dummy_codec_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int dummy_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + return 0; +} + +static int dummy_codec_mute(struct snd_soc_dai *dai, int mute) +{ + return 0; +} + +static const struct snd_soc_dapm_widget dummy_codec_dapm_widgets[] = { + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("Left DAC", "HIFI Playback", + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "HIFI Playback", + SND_SOC_NOPM, 7, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), + +}; + +static const struct snd_soc_dapm_route dummy_codec_dapm_routes[] = { + + {"LOUTL", NULL, "Left DAC"}, + {"LOUTR", NULL, "Right DAC"}, +}; + +static struct snd_soc_dai_ops dummy_codec_ops = { + .hw_params = dummy_codec_pcm_hw_params, + .set_fmt = dummy_codec_set_dai_fmt, + .digital_mute = dummy_codec_mute, +}; + +struct snd_soc_dai_driver dummy_codec_dai = { + .name = "dummy", + .id = 1, + .playback = { + .stream_name = "HIFI Playback", + .channels_min = 1, + .channels_max = 8, + .rates = DUMMY_CODEC_RATES, + .formats = DUMMY_CODEC_FORMATS, + }, + .capture = { + .stream_name = "HIFI Capture", + .channels_min = 1, + .channels_max = 2, + .rates = DUMMY_CODEC_RATES, + .formats = DUMMY_CODEC_FORMATS, + }, + .ops = &dummy_codec_ops, +}; + +static int dummy_codec_probe(struct snd_soc_codec *codec) +{ + return 0; +} + +static int dummy_codec_remove(struct snd_soc_codec *codec) +{ + return 0; +}; + +struct snd_soc_codec_driver soc_codec_dev_dummy_codec = { + .probe = dummy_codec_probe, + .remove = dummy_codec_remove, + .component_driver = { + .dapm_widgets = dummy_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dummy_codec_dapm_widgets), + .dapm_routes = dummy_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(dummy_codec_dapm_routes), + } +}; + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_codec_dt_match[] = { + {.compatible = "amlogic, aml_dummy_codec", + }, + {}, +}; +#else +#define amlogic_codec_dt_match NULL +#endif + +static int dummy_codec_platform_probe(struct platform_device *pdev) +{ + struct dummy_codec_private *dummy_codec; + int ret; + + dummy_codec = kzalloc(sizeof(struct dummy_codec_private), GFP_KERNEL); + if (dummy_codec == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, dummy_codec); + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_dummy_codec, + &dummy_codec_dai, 1); + + if (ret < 0) + kfree(dummy_codec); + + return ret; +} + +static int dummy_codec_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + kfree(platform_get_drvdata(pdev)); + return 0; +} + +static struct platform_driver dummy_codec_platform_driver = { + .driver = { + .name = "dummy", + .owner = THIS_MODULE, + .of_match_table = amlogic_codec_dt_match, + }, + .probe = dummy_codec_platform_probe, + .remove = dummy_codec_platform_remove, +}; + +static int __init dummy_codec_init(void) +{ + return platform_driver_register(&dummy_codec_platform_driver); +} + +static void __exit dummy_codec_exit(void) +{ + platform_driver_unregister(&dummy_codec_platform_driver); +} + +module_init(dummy_codec_init); +module_exit(dummy_codec_exit); + +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_DESCRIPTION("ASoC dummy_codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/amlogic/pcm2bt.c b/sound/soc/codecs/amlogic/pcm2bt.c new file mode 100644 index 000000000000..fb2835a02161 --- /dev/null +++ b/sound/soc/codecs/amlogic/pcm2bt.c @@ -0,0 +1,174 @@ +/* + * sound/soc/codecs/amlogic/pcm2bt.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pcm2bt_priv { + struct snd_soc_codec codec; +}; + +#define PCM2BT_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define PCM2BT_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S8) + +static int pcm2bt_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int pcm2bt_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + return 0; +} + +static int pcm2bt_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + return 0; +} + +static int pcm2bt_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + return 0; +} + +struct snd_soc_dai_ops pcm2bt_dai_ops = { + .hw_params = pcm2bt_hw_params, + .set_fmt = pcm2bt_set_fmt, + .set_sysclk = pcm2bt_set_sysclk, + .set_sysclk = pcm2bt_set_sysclk, +}; + +struct snd_soc_dai_driver pcm2bt_dai[] = { + { + .name = "pcm2bt-pcm", + .playback = { + .stream_name = "PCM2BT Playback", + .channels_min = 1, + .channels_max = 1, + .rates = PCM2BT_RATES, + .formats = PCM2BT_FORMATS, + }, + .capture = { + .stream_name = "BT2PCM Capture", + .channels_min = 1, + .channels_max = 1, + .rates = PCM2BT_RATES, + .formats = PCM2BT_FORMATS, + }, + .ops = &pcm2bt_dai_ops, + .symmetric_rates = 1, + }, +}; + +static int pcm2bt_probe(struct snd_soc_codec *codec) +{ + return 0; +} + +static int pcm2bt_suspend(struct snd_soc_codec *codec) +{ + return 0; +} + +static int pcm2bt_resume(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_pcm2bt = { + .probe = pcm2bt_probe, + .suspend = pcm2bt_suspend, + .resume = pcm2bt_resume, + .set_bias_level = pcm2bt_set_bias_level, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_pcm2bt); + +static int pcm2bt_platform_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_pcm2bt, pcm2bt_dai, + ARRAY_SIZE(pcm2bt_dai)); + + return ret; + +} + +static int pcm2bt_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id amlogic_pcm2BT_codec_dt_match[] = { + {.compatible = "amlogic, pcm2BT-codec",}, + {}, +}; +#else +#define amlogic_pcm2BT_codec_dt_match NULL +#endif + +static struct platform_driver pcm2bt_platform_driver = { + .driver = { + .name = "pcm2bt", + .owner = THIS_MODULE, + .of_match_table = amlogic_pcm2BT_codec_dt_match, + }, + .probe = pcm2bt_platform_probe, + .remove = pcm2bt_platform_remove, +}; + +static int __init pcm_bt_init(void) +{ + return platform_driver_register(&pcm2bt_platform_driver); +} + +module_init(pcm_bt_init); + +static void __exit pcm_bt_exit(void) +{ + platform_driver_unregister(&pcm2bt_platform_driver); +} + +module_exit(pcm_bt_exit); + +MODULE_DESCRIPTION("ASoC pcm2bt driver"); +MODULE_AUTHOR("AMLogic, Inc."); +MODULE_LICENSE("GPL");