From: ShinHyung Date: Fri, 18 May 2018 09:17:00 +0000 (+0900) Subject: [9610] ASoC: abox: Added abox driver for 4.14 kernel X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=18a6abd743c692e03e56b3e59d89817a83d4f283;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [9610] ASoC: abox: Added abox driver for 4.14 kernel Change-Id: I4dff3f2d9254e54f581ad6fd0d5567ed16aaa980 Signed-off-by: ShinHyung --- diff --git a/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts b/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts index a1eafa469dbc..f17850834859 100644 --- a/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts +++ b/arch/arm64/boot/dts/exynos/exynos9610-erd9610.dts @@ -1113,6 +1113,496 @@ interrupts = <0 460 0>; }; + V_SYS: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "V_SYS"; + regulator-min-microvolt = <4200000>; + regulator-max-microvolt = <4200000>; + regulator-boot-on; + regulator-always-on; + }; + + pinctrl@11850000 { + amp_irq: amp-irq { + samsung,pins ="gpa0-2"; + samsung,pin-function = <0xf>; + samsung,pin-pud = <0>; + }; + }; + + pinctrl@139B0000 { + amp_reset: amp-reset { + samsung,pins ="gpg3-3"; + samsung,pin-pud = <0>; + samsung,pin-con-pdn =<3>; + samsung,pin-pud-pdn = <0>; + }; + }; + + i2c_3: i2c@13860000 { + status = "okay"; + cs35l35_left: cs35l35@40 { + #sound-dai-cells = <1>; + compatible = "cirrus,cs35l35"; + reg = <0x40>; + + pinctrl-names = "default"; + pinctrl-0 = <&_irq &_reset>; + + interrupt-parent = <&gpa0>; + interrupts = <2 0 0>; + + reset-gpios = <&gpg3 3 0>; + + VA-supply = <&l42_reg>; + VP-supply = <&V_SYS>; + cirrus,audio-channel = <0x0>; + cirrus,boost-ctl-millivolt = <7300>; + cirrus,sp-drv-strength = <0x1>; + cirrus,sp-drv-unused = <0x0>; + cirrus,boost-ind-nanohenry = <1000>; + + cirrus,monitor-signal-format { + cirrus,imon = /bits/ 8 <0x03 0x04 0x00 0x06>; + cirrus,vmon = /bits/ 8 <0x03 0x00 0x00>; + cirrus,vpmon = /bits/ 8 <0x00 0x00 0x00>; + cirrus,vbstmon = /bits/ 8 <0x00 0x00 0x00>; + cirrus,vpbrstat = /bits/ 8 <0x00 0x00 0x00>; + cirrus,zerofill = /bits/ 8 <0x00 0x00 0x00>; + }; + }; + }; + + pinctrl@139B0000 { + codec_reset: codec-reset { + samsung,pins ="gpg3-2"; + samsung,pin-pud = <0>; + samsung,pin-con-pdn =<3>; + samsung,pin-pud-pdn = <0>; + }; + }; + + spi_9: spi@13940000 { + pinctrl-names = "default"; + pinctrl-0 = <&spi9_bus &spi9_cs_func>; + status = "okay"; + cs47l35: cs47l35@0 { + compatible = "cirrus,cs47l35"; + reg = <0x0>; + + spi-max-frequency = <11000000>; + + interrupts = <6 0 0>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&gpa0>; + gpio-controller; + #gpio-cells = <2>; + #sound-dai-cells = <1>; + + AVDD-supply = <&l42_reg>; + DBVDD1-supply = <&l42_reg>; + DBVDD2-supply = <&l42_reg>; + CPVDD1-supply = <&l42_reg>; + CPVDD2-supply = <&l44_reg>; + DCVDD-supply = <&l44_reg>; + SPKVDD-supply = <&V_SYS>; + + reset-gpios = <&gpg3 2 0>; + + cirrus,dmic-ref = <0 0 0>; + cirrus,inmode = < + 0 0 0 0 /* IN1 */ + 0 0 0 0 /* IN2 */ + >; + + cirrus,gpsw = <3 0>; + + pinctrl-names = "probe", "active"; + pinctrl-0 = <&codec_reset>; + pinctrl-1 = <&codec_reset &cs47l35_defaults>; + + madera_pinctrl: madera-pinctrl { + compatible = "cirrus,madera-pinctrl"; + cs47l35_defaults: cs47l35-gpio-defaults { + aif1 { + groups = "aif1"; + function = "aif1"; + bias-bus-hold; + }; + + aif2 { + groups = "aif2"; + function = "aif2"; + bias-bus-hold; + }; + + aif3 { + groups = "aif3"; + function = "aif3"; + bias-bus-hold; + }; + + gpio6 { /* Amp Clock */ + groups = "gpio6"; + function = "opclk"; + bias-pull-up; + output-low; + }; + + gpio5 { /* Mic Polarity Flip */ + groups = "gpio5"; + function = "io"; + }; + }; + }; + + micvdd { + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + }; + + MICBIAS1 { + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + cirrus,ext-cap = <1>; + }; + MICBIAS1A { + regulator-active-discharge = <1>; + }; + MICBIAS1B { + regulator-active-discharge = <1>; + }; + + MICBIAS2 { + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + cirrus,ext-cap = <1>; + }; + MICBIAS2B { + regulator-active-discharge = <1>; + }; + + cirrus,accdet { + #address-cells = <1>; + #size-cells = <0>; + + acc@1 { + reg = <1>; + + cirrus,micd-configs = < + 0 0 3 0 0 + >; + cirrus,micd-bias-start-time = <8>; + cirrus,micd-rate = <6>; + cirrus,micd-pol-gpios = <&cs47l35 4 0>; + cirrus,micd-detect-debounce-ms = <500>; + cirrus,jd-use-jd2; + cirrus,micd-clamp-mode = <0x8>; + }; + }; + + controller-data { + samsung,spi-feedback-delay = <1>; + samsung,spi-chip-select-mode = <0>; + }; + }; + }; + + dummy_audio_codec: audio_codec_dummy { + status = "okay"; + compatible = "snd-soc-dummy"; + }; + + dummy_audio_cpu: audio_cpu_dummy { + compatible = "samsung,dummy-cpu"; + status = "okay"; + }; + + sound { + status = "okay"; + compatible = "samsung,exynos9610-madera"; + + clock-names = "xclkout"; + clocks = <&clock OSC_AUD>; + pinctrl-names = "default"; + pinctrl-0 = <&xclkout0>; + + cirrus,sysclk = <1 4 98304000>; + cirrus,dspclk = <8 4 147456000>; + cirrus,fll1-refclk = <1 0 26000000 98304000>; + + cirrus,opclk = <3 0 12288000>; + + samsung,routing = + "HEADSETMIC", "MICBIAS2B", + "MICBIAS2A", "MICBIAS2B", + "IN1BL", "HEADSETMIC", + "DMIC1", "MICBIAS1A", + "IN1AL", "DMIC1", + "DMIC2", "MICBIAS1B", + "IN2L", "DMIC2", + "RECEIVER", "EPOUTN", + "RECEIVER", "EPOUTP", + "HEADPHONE", "HPOUTL", + "HEADPHONE", "HPOUTR", + "AIF2 Playback", "OPCLK", + "AIF2 Capture", "OPCLK", + "VOUTPUT", "ABOX UAIF0 Playback", + "SPEAKER", "Left SPK", + "VOUTPUTCALL", "ABOX UAIF2 Playback", + "ABOX UAIF2 Capture", "VINPUTCALL"; + + samsung,codec = <&abox &abox_uaif_0 &abox_uaif_1 &abox_uaif_2 + &abox_uaif_4 &abox_dsif &abox_spdy &cs35l35_left>; + samsung,prefix = "ABOX", "ABOX", "ABOX", "ABOX", + "ABOX", "ABOX", "ABOX", "Left"; + samsung,aux = <&abox_effect>; + + rdma@0 { + cpu { + sound-dai = <&abox 0>; + }; + platform { + sound-dai = <&abox_rdma_0>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + rdma@1 { + cpu { + sound-dai = <&abox 1>; + }; + platform { + sound-dai = <&abox_rdma_1>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + rdma@2 { + cpu { + sound-dai = <&abox 2>; + }; + platform { + sound-dai = <&abox_rdma_2>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + rdma@3 { + cpu { + sound-dai = <&abox 3>; + }; + platform { + sound-dai = <&abox_rdma_3>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + rdma@4 { + cpu { + sound-dai = <&abox 4>; + }; + platform { + sound-dai = <&abox_rdma_4>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + rdma@5 { + cpu { + sound-dai = <&abox 5>; + }; + platform { + sound-dai = <&abox_rdma_5>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + rdma@6 { + cpu { + sound-dai = <&abox 6>; + }; + platform { + sound-dai = <&abox_rdma_6>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + rdma@7 { + cpu { + sound-dai = <&abox 7>; + }; + platform { + sound-dai = <&abox_rdma_7>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + wdma@0 { + cpu { + sound-dai = <&abox 8>; + }; + platform { + sound-dai = <&abox_wdma_0>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + wdma@1 { + cpu { + sound-dai = <&abox 9>; + }; + platform { + sound-dai = <&abox_wdma_1>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + wdma@2 { + cpu { + sound-dai = <&abox 10>; + }; + platform { + sound-dai = <&abox_wdma_2>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + wdma@3 { + cpu { + sound-dai = <&abox 11>; + }; + platform { + sound-dai = <&abox_wdma_3>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + wdma@4 { + cpu { + sound-dai = <&abox 12>; + }; + platform { + sound-dai = <&abox_wdma_4>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; +/** ToDo: enable dp_audio link after enabling DP Audio + * dp_audio@0 { + * cpu { + * sound-dai = <&dummy_audio_cpu>; + * }; + * codec { + * sound-dai = <&dummy_audio_codec>; + * }; + * }; + */ + uaif@0 { + format = "i2s"; + cpu { + sound-dai = <&abox_uaif_0>; + }; + codec { + sound-dai = <&cs47l35 0>; + }; + }; + uaif@1 { + format = "i2s"; + cpu { + sound-dai = <&abox_uaif_1>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + uaif@2 { + format = "i2s"; + cpu { + sound-dai = <&abox_uaif_2>; + }; + codec { + sound-dai = <&cs47l35 2>; + }; + }; + uaif@4 { + format = "i2s"; + bitclock-master; + frame-master; + cpu { + sound-dai = <&abox_uaif_4>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + dsif@0 { + format = "pdm"; + cpu { + sound-dai = <&abox_dsif>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + spdy@0 { + cpu { + sound-dai = <&abox_spdy>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + sifs0@0 { + cpu { + sound-dai = <&abox 13>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + sifs1@0 { + cpu { + sound-dai = <&abox 14>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + sifs2@0 { + cpu { + sound-dai = <&abox 15>; + }; + codec { + sound-dai = <&dummy_audio_codec>; + }; + }; + codec-left-amp@0 { + format = "i2s"; + + cpu { + sound-dai = <&cs47l35 1>; + }; + codec { + sound-dai = <&cs35l35_left 0>; + }; + }; + + }; }; &pinctrl_0 { diff --git a/arch/arm64/boot/dts/exynos/exynos9610-pinctrl.dtsi b/arch/arm64/boot/dts/exynos/exynos9610-pinctrl.dtsi index 1590b913d07f..26743ebb2975 100644 --- a/arch/arm64/boot/dts/exynos/exynos9610-pinctrl.dtsi +++ b/arch/arm64/boot/dts/exynos/exynos9610-pinctrl.dtsi @@ -56,6 +56,12 @@ #interrupt-cells = <2>; }; + xclkout0: xclkout0 { + samsung,pins = "gpq0-1"; + samsung,pin-function = <2>; + samsung,pin-pud = <0>; + }; + /* USI_PERIC0_UART_DBG */ uart0_bus: uart0-bus { samsung,pins = "gpq0-3", "gpq0-4"; @@ -584,6 +590,108 @@ interrupt-controller; #interrupt-cells = <2>; }; + + aud_codec_mclk: aud-codec-mclk { + samsung,pins = "gpb0-0"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + }; + + aud_codec_mclk_idle: aud-codec-mclk-idle { + samsung,pins = "gpb0-0"; + samsung,pin-function = <0>; + samsung,pin-pud = <1>; + }; + + aud_i2s0_bus: aud-i2s0-bus { + samsung,pins = "gpb0-1", "gpb0-2", "gpb0-4"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + samsung,pin-con-pdn =<2>; + samsung,pin-pud-pdn = <1>; + }; + + aud_i2s0_sdo_bus: aud-i2s0-sdo-bus { + samsung,pins = "gpb0-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + samsung,pin-con-pdn =<2>; + samsung,pin-pud-pdn = <1>; + }; + + aud_i2s0_idle: aud-i2s0-idle { + samsung,pins = "gpb0-1", "gpb0-2", "gpb0-3", "gpb0-4"; + samsung,pin-function = <0>; + samsung,pin-pud = <1>; + }; + + aud_i2s1_bus: aud-i2s1-bus { + samsung,pins = "gpb1-0", "gpb1-1", "gpb1-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + samsung,pin-con-pdn =<2>; + samsung,pin-pud-pdn = <1>; + }; + + aud_i2s1_sdo_bus: aud-i2s1-sdo-bus { + samsung,pins = "gpb1-2"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + samsung,pin-con-pdn =<2>; + samsung,pin-pud-pdn = <3>; + }; + + aud_i2s1_idle: aud-i2s1-idle { + samsung,pins = "gpb1-0", "gpb1-1", "gpb1-2", "gpb1-3"; + samsung,pin-function = <0>; + samsung,pin-pud = <1>; + }; + + aud_i2s2_bus: aud-i2s2-bus { + samsung,pins = "gpb2-0", "gpb2-1", "gpb2-3"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + samsung,pin-con-pdn =<2>; + samsung,pin-pud-pdn = <1>; + }; + + aud_i2s2_sdo_bus: aud-i2s2-sdo-bus { + samsung,pins = "gpb2-2"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + samsung,pin-con-pdn =<2>; + samsung,pin-pud-pdn = <3>; + }; + + aud_i2s2_idle: aud-i2s2-idle { + samsung,pins = "gpb2-0", "gpb2-1", "gpb2-2", "gpb2-3"; + samsung,pin-function = <0>; + samsung,pin-pud = <1>; + }; + + aud_dsd_bus: aud-dsd-bus { + samsung,pins = "gpb2-0", "gpb2-1", "gpb2-2"; + samsung,pin-function = <3>; + samsung,pin-pud = <1>; + }; + + aud_dsd_idle: aud-dsd-idle { + samsung,pins = "gpb2-0", "gpb2-1", "gpb2-2"; + samsung,pin-function = <0>; + samsung,pin-pud = <1>; + }; + + aud_fm_bus: aud-fm-bus { + samsung,pins = "gpb2-4"; + samsung,pin-function = <2>; + samsung,pin-pud = <1>; + }; + + aud_fm_idle: aud-fm-idle { + samsung,pins = "gpb2-4"; + samsung,pin-function = <0>; + samsung,pin-pud = <1>; + }; }; /* FSYS */ diff --git a/arch/arm64/boot/dts/exynos/exynos9610.dtsi b/arch/arm64/boot/dts/exynos/exynos9610.dtsi index d6fc9c93fc21..543f9ecec4b6 100644 --- a/arch/arm64/boot/dts/exynos/exynos9610.dtsi +++ b/arch/arm64/boot/dts/exynos/exynos9610.dtsi @@ -2492,6 +2492,279 @@ }; }; + abox_gic: abox_gic@0x14AF0000 { + compatible = "samsung,abox_gic"; + status = "okay"; + reg = <0x0 0x14AF1000 0x1000>, <0x0 0x14AF2000 0x1004>; + reg-names = "gicd", "gicc"; + interrupts = <0 198 0>; + }; + + abox: abox@0x14A50000 { + compatible = "samsung,abox"; + status = "okay"; + reg = <0x0 0x14A50000 0x10000>, <0x0 0x14810000 0x3000>, <0x0 0x14B00000 0x35000>; + reg-names = "sfr", "sysreg", "sram"; + #address-cells = <2>; + #size-cells = <1>; + ranges; + quirks = "try to asrc off", "off on suspend", "scsc bt"; + #sound-dai-cells = <1>; + ipc_tx_offset = <0x22000>; + ipc_rx_offset = <0x22300>; + ipc_tx_ack_offset = <0x222FC>; + ipc_rx_ack_offset = <0x225FC>; + abox_gic = <&abox_gic>; + clocks = <&clock PLL_OUT_AUD>, <&clock GATE_ABOX_QCH_CPU>, + <&clock DOUT_CLK_AUD_AUDIF>, <&clock DOUT_CLK_AUD_ACLK>; + clock-names = "pll", "cpu", "audif", "bus"; + uaif_max_div = <512>; + iommus = <&sysmmu_abox>; + pm_qos_int = <0 0 0 0 0>; + pm_qos_aud = <1180000 800000 590000 394000 0>; + + abox_rdma_0: abox_rdma@0x14A51000 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51000 0x100>; + id = <0>; + type = "normal"; + }; + + abox_rdma_1: abox_rdma@0x14A51100 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51100 0x100>; + id = <1>; + type = "normal"; + }; + + abox_rdma_2: abox_rdma@0x14A51200 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51200 0x100>; + id = <2>; + type = "normal"; + }; + + abox_rdma_3: abox_rdma@0x14A51300 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51300 0x100>; + id = <3>; + type = "sync"; + }; + + abox_rdma_4: abox_rdma@0x14A51400 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51400 0x100>; + id = <4>; + type = "call"; + }; + + abox_rdma_5: abox_rdma@0x14A51500 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51500 0x100>, <0x0 0x14B22600 0x70>; + id = <5>; + type = "compress"; + }; + + abox_rdma_6: abox_rdma@0x14A51600 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51600 0x100>; + id = <6>; + type = "realtime"; + scsc_bt; + }; + + abox_rdma_7: abox_rdma@0x14A51700 { + compatible = "samsung,abox-rdma"; + reg = <0x0 0x14A51700 0x100>; + id = <7>; + type = "realtime"; + }; + + abox_wdma_0: abox_wdma@0x14A52000 { + compatible = "samsung,abox-wdma"; + reg = <0x0 0x14A52000 0x100>; + id = <0>; + type = "normal"; + scsc_bt; + }; + + abox_wdma_1: abox_wdma@0x14A52100 { + compatible = "samsung,abox-wdma"; + reg = <0x0 0x14A52100 0x100>; + id = <1>; + type = "normal"; + }; + + abox_wdma_2: abox_wdma@0x14A52200 { + compatible = "samsung,abox-wdma"; + reg = <0x0 0x14A52200 0x100>; + id = <2>; + type = "call"; + }; + + abox_wdma_3: abox_wdma@0x14A52300 { + compatible = "samsung,abox-wdma"; + reg = <0x0 0x14A52300 0x100>; + id = <3>; + type = "normal"; + }; + + abox_wdma_4: abox_wdma@0x14A52400 { + compatible = "samsung,abox-wdma"; + reg = <0x0 0x14A52400 0x100>; + id = <4>; + type = "realtime"; + }; + + abox_uaif_0: abox_uaif@0x14A50500 { + compatible = "samsung,abox-uaif"; + reg = <0x0 0x14A50500 0x10>; + id = <0>; + clocks = <&clock DOUT_CLK_AUD_UAIF0>, <&clock GATE_ABOX_QCH_S_BCLK0>; + clock-names = "bclk", "bclk_gate"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&aud_i2s0_bus &aud_i2s0_sdo_bus &aud_codec_mclk>; + pinctrl-1 = <&aud_i2s0_idle &aud_codec_mclk_idle>; + #sound-dai-cells = <0>; + }; + + abox_uaif_1: abox_uaif@0x14A50510 { + compatible = "samsung,abox-uaif"; + reg = <0x0 0x14A50510 0x10>; + id = <1>; + clocks = <&clock DOUT_CLK_AUD_UAIF1>, <&clock GATE_ABOX_QCH_S_BCLK1>; + clock-names = "bclk", "bclk_gate"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&aud_i2s1_bus &aud_i2s1_sdo_bus>; + pinctrl-1 = <&aud_i2s1_idle>; + #sound-dai-cells = <0>; + }; + + abox_uaif_2: abox_uaif@0x14A50520 { + compatible = "samsung,abox-uaif"; + reg = <0x0 0x14A50520 0x10>; + id = <2>; + clocks = <&clock DOUT_CLK_AUD_UAIF2>, <&clock GATE_ABOX_QCH_S_BCLK2>; + clock-names = "bclk", "bclk_gate"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&aud_i2s2_bus &aud_i2s2_sdo_bus>; + pinctrl-1 = <&aud_i2s2_idle>; + #sound-dai-cells = <0>; + }; + + abox_uaif_4: abox_uaif@0x14A50540 { + compatible = "samsung,abox-uaif"; + reg = <0x0 0x14A50540 0x10>; + id = <4>; + /* UAIF4 is connected to UAIF0 as slave */ + clocks = <&clock DOUT_CLK_AUD_UAIF0>, <&clock GATE_ABOX_QCH_S_BCLK0>; + clock-names = "bclk", "bclk_gate"; + #sound-dai-cells = <0>; + }; + + abox_dsif: abox_dsif@0x14A50550 { + compatible = "samsung,abox-dsif"; + reg = <0x0 0x14A50550 0x10>; + id = <5>; + clocks = <&clock DOUT_CLK_AUD_DSIF>, <&clock GATE_ABOX_QCH_S_BCLK_DSIF>; + clock-names = "bclk", "bclk_gate"; + /* DSIF and UAIF2 shares GPIO + * pinctrl-names = "default", "sleep"; + * pinctrl-0 = <&aud_dsd_bus>; + * pinctrl-1 = <&aud_dsd_idle>; + */ + #sound-dai-cells = <0>; + }; + + abox_spdy: abox_spdy@0x14A50560 { + compatible = "samsung,abox-spdy"; + reg = <0x0 0x14A50560 0x10>; + id = <6>; + clocks = <&clock DOUT_CLK_AUD_FM>, <&clock GATE_ABOX_QCH_FM>; + clock-names = "bclk", "bclk_gate"; + /* FM SPEEDY GPIO is controlled by FM radio driver + * pinctrl-names = "default", "sleep"; + * pinctrl-0 = <&aud_fm_bus>; + * pinctrl-1 = <&aud_fm_idle>; + */ + #sound-dai-cells = <0>; + }; + + abox_effect: abox_effect@0x14B2E000 { + compatible = "samsung,abox-effect"; + reg = <0x0 0x14B2E000 0x1000>; + reg-names = "reg"; + abox = <&abox>; + }; + + abox_debug: abox_debug@0 { + compatible = "samsung,abox-debug"; + memory-region = <&abox_rmem>; + reg = <0x0 0x0 0x0>; + }; + + abox_vss: abox_vss@0 { + compatible = "samsung,abox-vss"; + magic_offset = <0x600000>; + reg = <0x0 0x0 0x0>; + }; + /* + *abox_bt: abox_bt@0 { + *compatible = "samsung,abox-bt"; + *reg = <0x0 0x0 0x0>, <0x0 0x119D0000 0x1000>; + *reg-names = "sfr", "mailbox"; + *}; + */ + ext_bin_0: ext_bin@0 { + status = "enabled"; + samsung,name = "dsm.bin"; + samsung,area = <1>; /* 0:SRAM, 1:DRAM, 2:VSS */ + samsung,offset = <0x502000>; + }; + ext_bin_1: ext_bin@1 { + status = "disabled"; + samsung,name = "AP_AUDIO_SLSI.bin"; + samsung,area = <1>; + samsung,offset = <0x7F0000>; + }; + ext_bin_2: ext_bin@2 { + status = "disabled"; + samsung,name = "APBargeIn_AUDIO_SLSI.bin"; + samsung,area = <1>; + samsung,offset = <0x7EC000>; + }; + ext_bin_3: ext_bin@3 { + status = "disabled"; + samsung,name = "SoundBoosterParam.bin"; + samsung,area = <1>; + samsung,offset = <0x4FC000>; + }; + ext_bin_4: ext_bin@4 { + status = "disabled"; + samsung,name = "dummy.bin"; + samsung,area = <1>; + samsung,offset = <0x800000>; + }; + ext_bin_5: ext_bin@5 { + status = "disabled"; + samsung,name = "APBiBF_AUDIO_SLSI.bin"; + samsung,area = <1>; + samsung,offset = <0x7EF000>; + }; + ext_bin_6: ext_bin@6 { + status = "disabled"; + samsung,name = "dummy.bin"; + samsung,area = <1>; + samsung,offset = <0x800000>; + }; + ext_bin_7: ext_bin@7 { + status = "disabled"; + samsung,name = "dummy.bin"; + samsung,area = <1>; + samsung,offset = <0x800000>; + }; + }; + udc: usb@13200000 { compatible = "samsung,exynos-dwusb"; clocks = <&clock GATE_USB30DRD_QCH_USB30>; @@ -2663,7 +2936,7 @@ #size-cells = <1>; ranges; - domain-clients = <>; + domain-clients = <&abox>; }; iommu-domain_isp { diff --git a/arch/arm64/configs/erd9610_defconfig b/arch/arm64/configs/erd9610_defconfig index d84449ace3ce..67b6acac6916 100644 --- a/arch/arm64/configs/erd9610_defconfig +++ b/arch/arm64/configs/erd9610_defconfig @@ -180,6 +180,7 @@ CONFIG_NET_ACT_MIRRED=y CONFIG_CFG80211=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" CONFIG_DEVTMPFS=y +CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y CONFIG_DMA_CMA=y CONFIG_CMA_SIZE_MBYTES=20 CONFIG_BLK_DEV_LOOP=y @@ -321,6 +322,11 @@ CONFIG_BACKLIGHT_CLASS_DEVICE=y # CONFIG_BACKLIGHT_GENERIC is not set CONFIG_SOUND=y CONFIG_SND=y +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_SAMSUNG=y +CONFIG_SND_SOC_SAMSUNG_EXYNOS9610=y +CONFIG_SND_SOC_SAMSUNG_EXYNOS9610_MADERA=y CONFIG_HIDRAW=y CONFIG_UHID=y CONFIG_HID_A4TECH=y @@ -433,6 +439,7 @@ CONFIG_PM_DEVFREQ=y CONFIG_ARM_EXYNOS_DEVFREQ=y CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG=y CONFIG_IIO=y +CONFIG_EXTCON=y CONFIG_EXYNOS_ADC=y CONFIG_PWM=y CONFIG_PWM_SAMSUNG=y diff --git a/include/sound/compress_driver.h b/include/sound/compress_driver.h index 9924bc9cbc7c..5971bb933040 100644 --- a/include/sound/compress_driver.h +++ b/include/sound/compress_driver.h @@ -132,6 +132,8 @@ struct snd_compr_ops { struct snd_compr_caps *caps); int (*get_codec_caps) (struct snd_compr_stream *stream, struct snd_compr_codec_caps *codec); + int (*get_hw_params)(struct snd_compr_stream *stream, + struct snd_pcm_hw_params *params); }; /** diff --git a/include/sound/samsung/abox.h b/include/sound/samsung/abox.h new file mode 100644 index 000000000000..ea9509887a72 --- /dev/null +++ b/include/sound/samsung/abox.h @@ -0,0 +1,200 @@ +/* + * ALSA SoC - Samsung ABOX driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ABOX_H +#define __ABOX_H + +#include +#include +#include +#include + +/** + * abox irq handler type definition + * @param[in] ipc_id id of ipc + * @param[in] dev_id dev_id from abox_register_irq_handler + * @param[in] msg message data + * @return reference irqreturn_t + */ +typedef irqreturn_t (*abox_irq_handler_t)(int ipc_id, void *dev_id, + ABOX_IPC_MSG *msg); + +#ifdef CONFIG_SND_SOC_SAMSUNG_ABOX +/** + * Check ABOX is on + * @return true if A-Box is on, false on otherwise + */ +extern bool abox_is_on(void); + +/** + * Get INT frequency required by ABOX + * @return INT frequency in kHz + */ +extern unsigned int abox_get_requiring_int_freq_in_khz(void); + +/** + * Get AUD frequency required by ABOX + * @return AUD frequency in kHz + */ +extern unsigned int abox_get_requiring_aud_freq_in_khz(void); + +/** + * Start abox IPC + * @param[in] dev pointer to abox device + * @param[in] hw_irq hardware IRQ number + * @param[in] supplement pointer to data + * @param[in] size size of data which are pointed by supplement + * @param[in] atomic 1, if caller context is atomic. 0, if not. + * @param[in] sync 1 to wait for ack. 0 if not. + * @return error code if any + */ +extern int abox_request_ipc(struct device *dev, + int hw_irq, const void *supplement, + size_t size, int atomic, int sync); + +/** + * Start abox IPC + * @param[in] dev pointer to abox device + * @param[in] hw_irq hardware IRQ number + * @param[in] supplement pointer to data + * @param[in] size size of data which are pointed by supplement + * @param[in] atomic 1, if caller context is atomic. 0, if not. + * @param[in] sync 1 to wait for ack. 0 if not. + * @return error code if any + */ +static inline int abox_start_ipc_transaction(struct device *dev, + int hw_irq, const void *supplement, + size_t size, int atomic, int sync) +{ + return abox_request_ipc(dev, hw_irq, supplement, size, atomic, sync); +} + +/** + * Register irq handler to abox + * @param[in] dev pointer to abox device + * @param[in] ipc_id id of ipc + * @param[in] irq_handler abox irq handler to register + * @param[in] dev_id cookie which would be summitted with irq_handler + * @return error code if any + */ +extern int abox_register_irq_handler(struct device *dev, int ipc_id, + abox_irq_handler_t irq_handler, void *dev_id); + +/** + * UAIF/DSIF hw params fixup helper + * @param[in] rtd snd_soc_pcm_runtime + * @param[out] params snd_pcm_hw_params + * @param[in] stream SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE + * @return error code if any + */ +extern int abox_hw_params_fixup_helper(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params, int stream); +/** + * iommu map for abox + * @param[in] dev pointer to abox device + * @param[in] iova device virtual address + * @param[in] vaddr kernel virtual address + * @param[in] size size of the mapping area + * @return error code if any + */ +extern int abox_iommu_map(struct device *dev, unsigned long iova, + phys_addr_t paddr, size_t size); + +/** + * iommu unmap for abox + * @param[in] dev pointer to abox device + * @param[in] iova device virtual address + * @param[in] vaddr kernel virtual address + * @param[in] size size of the mapping area + * @return error code if any + */ +extern int abox_iommu_unmap(struct device *dev, unsigned long iova, + phys_addr_t paddr, size_t size); + +/** + * query physical address from device virtual address + * @param[in] dev pointer to abox device + * @param[in] iova device virtual address + * @return physical address. 0 if not mapped + */ +extern phys_addr_t abox_iova_to_phys(struct device *dev, unsigned long iova); + +/** + * query virtual address from device virtual address + * @param[in] dev pointer to abox device + * @param[in] iova device virtual address + * @return virtual address. undefined if not mapped + */ +extern void *abox_iova_to_virt(struct device *dev, unsigned long iova); + +/** + * power off abox + */ +extern void abox_poweroff(void); + +#else /* !CONFIG_SND_SOC_SAMSUNG_ABOX */ + +static inline bool abox_is_on(void) +{ + return false; +} + +static inline unsigned int abox_get_requiring_int_freq_in_khz(void) +{ + return 0; +} + +static inline int abox_request_ipc(struct device *dev, + int hw_irq, const void *supplement, + size_t size, int atomic, int sync) +{ + return -ENODEV; +} + +static inline int abox_register_irq_handler(struct device *dev, int ipc_id, + abox_irq_handler_t irq_handler, void *dev_id) +{ + return -ENODEV; +} + +static inline int abox_hw_params_fixup_helper(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + return -ENODEV; +} + +static inline int abox_iommu_map(struct device *dev, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + return -ENODEV; +} + +static inline int abox_iommu_unmap(struct device *dev, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + return -ENODEV; +} + +static inline phys_addr_t abox_iova_to_phys(struct device *dev, + unsigned long iova) +{ + return 0; +} + +static inline void *abox_iova_to_virt(struct device *dev, unsigned long iova) +{ + return ERR_PTR(-ENODEV); +} + +static inline void abox_poweroff(void) {} + +#endif /* !CONFIG_SND_SOC_SAMSUNG_ABOX */ + +#endif /* __ABOX_H */ diff --git a/include/sound/samsung/abox_ipc.h b/include/sound/samsung/abox_ipc.h new file mode 100644 index 000000000000..37c84109d9fa --- /dev/null +++ b/include/sound/samsung/abox_ipc.h @@ -0,0 +1,355 @@ +/* sound/soc/samsung/abox/abox_ipc.h + * + * ALSA SoC - Samsung Abox driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_IPC_H +#define __SND_SOC_ABOX_IPC_H + +/******** IPC signal ID *********************/ +enum SIGNAL_ID { + SIGID_SYSTEM = 1, + SIGID_PCMOUT_TASK0, + SIGID_PCMOUT_TASK1, + SIGID_PCMOUT_TASK2, + SIGID_PCMOUT_TASK3, + SIGID_PCMIN_TASK0, + SIGID_PCMIN_TASK1, + SIGID_OFFLOAD, + SIGID_ERAP, + SIGID_ASB, +}; + +/******** PCMTASK IPC ***********************/ +enum PCMMSG { + PCM_OPEN = 1, + PCM_CLOSE = 2, + PCM_CPUDAI_TRIGGER = 3, + PCM_CPUDAI_HW_PARAMS = 4, + PCM_CPUDAI_SET_FMT = 5, + PCM_CPUDAI_SET_CLKDIV = 6, + PCM_CPUDAI_SET_SYSCLK = 7, + PCM_CPUDAI_STARTUP = 8, + PCM_CPUDAI_SHUTDOWN = 9, + PCM_CPUDAI_DELAY = 10, + PCM_PLTDAI_OPEN = 11, + PCM_PLTDAI_CLOSE = 12, + PCM_PLTDAI_IOCTL = 13, + PCM_PLTDAI_HW_PARAMS = 14, + PCM_PLTDAI_HW_FREE = 15, + PCM_PLTDAI_PREPARE = 16, + PCM_PLTDAI_TRIGGER = 17, + PCM_PLTDAI_POINTER = 18, + PCM_PLTDAI_MMAP = 19, + PCM_SET_BUFFER = 20, + PCM_SYNCHRONIZE = 21, + PCM_PLTDAI_ACK = 22, + PCM_PLTDAI_REGISTER = 50, +}; + +struct PCMTASK_HW_PARAMS { + int sample_rate; + int bit_depth; + int channels; +}; + +struct PCMTASK_SET_BUFFER { + int phyaddr; + int size; + int count; +}; + +struct PCMTASK_HARDWARE { + char name[32]; /* name */ + unsigned int addr; /* buffer address */ + unsigned int width_min; /* min width */ + unsigned int width_max; /* max width */ + unsigned int rate_min; /* min rate */ + unsigned int rate_max; /* max rate */ + unsigned int channels_min; /* min channels */ + unsigned int channels_max; /* max channels */ + unsigned int buffer_bytes_max; /* max buffer size */ + unsigned int period_bytes_min; /* min period size */ + unsigned int period_bytes_max; /* max period size */ + unsigned int periods_min; /* min # of periods */ + unsigned int periods_max; /* max # of periods */ +}; + +/* Channel id of the Virtual DMA should be started from the BASE */ +#define PCMTASK_VDMA_ID_BASE 100 + +/* Parameter of the PCMTASK command */ +struct IPC_PCMTASK_MSG { + enum PCMMSG msgtype; + int channel_id; + union { + struct PCMTASK_HW_PARAMS hw_params; + struct PCMTASK_SET_BUFFER setbuff; + struct PCMTASK_HARDWARE hardware; + unsigned int pointer; + int trigger; + int synchronize; + } param; +}; + +/******** OFFLOAD IPC ***********************/ +enum OFFLOADMSG { + OFFLOAD_OPEN = 1, + OFFLOAD_CLOSE, + OFFLOAD_SETPARAM, + OFFLOAD_START, + OFFLOAD_WRITE, + OFFLOAD_PAUSE, + OFFLOAD_STOP, +}; + +/* The parameter of the set_param */ +struct OFFLOAD_SET_PARAM { + int sample_rate; + int chunk_size; +}; + +/* The parameter of the start */ +struct OFFLOAD_START { + int id; +}; + +/* The parameter of the write */ +struct OFFLOAD_WRITE { + int id; + int buff; + int size; +}; + +/* Parameter of the OFFLOADTASK command */ +struct IPC_OFFLOADTASK_MSG { + enum OFFLOADMSG msgtype; + int channel_id; + union { + struct OFFLOAD_SET_PARAM setparam; + struct OFFLOAD_START start; + struct OFFLOAD_WRITE write; + } param; +}; + +/******** ABOX_LOOPBACK IPC ***********************/ +enum ABOX_ERAP_MSG { + REALTIME_OPEN = 1, + REALTIME_CLOSE, + REALTIME_OUTPUT_SRATE, + REALTIME_INPUT_SRATE, + REALTIME_BIND_CHANNEL, + REALTIME_START, + REALTIME_STOP, + REALTIME_PREDSM_AUDIO, + REALTIME_PREDSM_VI_SENSE, + REALTIME_TONEGEN = 0x20, + REALTIME_EXTRA = 0xea, + REALTIME_USB = 0x30, +}; + +enum ABOX_ERAP_TYPE { + ERAP_ECHO_CANCEL, + ERAP_VI_SENSE, + ERAP_TYPE_COUNT, +}; + +enum ABOX_USB_MSG { + IPC_USB_PCM_OPEN, + IPC_USB_DESC, + IPC_USB_XHCI, + IPC_USB_L2, + IPC_USB_CONN, + IPC_USB_CTRL, + IPC_USB_SET_INTF, + IPC_USB_SAMPLE_RATE, + IPC_USB_PCM_BUF, +}; + +struct ERAP_ONOFF_PARAM { + enum ABOX_ERAP_TYPE type; + int channel_no; + int version; +}; + +struct ERAP_SET_SRATE_PARAM { + int channel_no; + int srate; +}; + +struct ERAP_BIND_CHANNEL_PARAM { + int input_channel; + int output_channel; +}; + +struct ERAP_RAW_PARAM { + unsigned int params[188]; +}; + +struct ERAP_USB_AUDIO_PARAM { + enum ABOX_USB_MSG type; + int param1; + int param2; + int param3; + int param4; +}; + +struct IPC_ERAP_MSG { + enum ABOX_ERAP_MSG msgtype; + union { + struct ERAP_ONOFF_PARAM onoff; + struct ERAP_BIND_CHANNEL_PARAM bind; + struct ERAP_SET_SRATE_PARAM srate; + struct ERAP_RAW_PARAM raw; + struct ERAP_USB_AUDIO_PARAM usbaudio; + } param; +}; + +/******** ABOX_CONFIG IPC ***********************/ +enum ABOX_CONFIGMSG { + SET_MIXER_SAMPLE_RATE = 1, + SET_OUT1_SAMPLE_RATE, + SET_OUT2_SAMPLE_RATE, + SET_RECP_SAMPLE_RATE, + SET_INMUX0_SAMPLE_RATE, + SET_INMUX1_SAMPLE_RATE, + SET_INMUX2_SAMPLE_RATE, + SET_INMUX3_SAMPLE_RATE, + SET_INMUX4_SAMPLE_RATE, + SET_MIXER_FORMAT = 0x10, + SET_OUT1_FORMAT, + SET_OUT2_FORMAT, + SET_RECP_FORMAT, + SET_INMUX0_FORMAT, + SET_INMUX1_FORMAT, + SET_INMUX2_FORMAT, + SET_INMUX3_FORMAT, + SET_INMUX4_FORMAT, +}; + +struct IPC_ABOX_CONFIG_MSG { + enum ABOX_CONFIGMSG msgtype; + int param1; + int param2; +}; + +/******** ABOX_ASB_TEST IPC ***********************/ +enum ABOX_TEST_MSG { + ASB_MULTITX_SINGLERX = 1, + ASB_SINGLETX_MULTIRX, + ASB_MULTITX_MULTIRX, + ASB_FFT_TEST, +}; + +struct IPC_ABOX_TEST_MSG { + enum ABOX_TEST_MSG msgtype; + int param1; + int param2; +}; + +/******** IPC_ABOX_SYSTEM_MSG IPC ***********************/ +enum ABOX_SYSTEM_MSG { + ABOX_SUSPEND = 1, + ABOX_RESUME, + ABOX_BOOT_DONE, + ABOX_CHANGE_GEAR, + ABOX_START_L2C_CONTROL, + ABOX_END_L2C_CONTROL, + ABOX_REQUEST_SYSCLK, + ABOX_REQUEST_L2C, + ABOX_CHANGED_GEAR, + ABOX_REPORT_LOG = 0x10, + ABOX_FLUSH_LOG, + ABOX_REPORT_DUMP = 0x20, + ABOX_REQUEST_DUMP, + ABOX_FLUSH_DUMP, + ABOX_REPORT_SRAM = 0x40, + ABOX_START_CLAIM_SRAM, + ABOX_END_CLAIM_SRAM, + ABOX_START_RECLAIM_SRAM, + ABOX_END_RECLAIM_SRAM, + ABOX_REPORT_DRAM, + ABOX_SET_MODE = 0x50, + ABOX_SET_TYPE = 0x60, + ABOX_START_VSS = 0xA0, + ABOX_REPORT_COMPONENT = 0xC0, + ABOX_UPDATE_COMPONENT_CONTROL, + ABOX_REQUEST_COMPONENT_CONTROL, + ABOX_REPORT_COMPONENT_CONTROL, + ABOX_BT_SCO_ENABLE = 0xD0, + ABOX_REQUEST_DEBUG = 0xDE, + ABOX_REPORT_FAULT = 0xFA, +}; + +struct IPC_SYSTEM_MSG { + enum ABOX_SYSTEM_MSG msgtype; + int param1; + int param2; + int param3; + union { + int param_s32[0]; + char param_bundle[740]; + } bundle; +}; + +struct ABOX_LOG_BUFFER { + unsigned int index_writer; + unsigned int index_reader; + unsigned int size; + char buffer[0]; +}; + +struct ABOX_COMPONENT_CONTROL { + unsigned int id; + char name[16]; + unsigned int count; + int min, max; +}; + +struct ABOX_COMPONENT_DESCRIPTIOR { + unsigned int id; + char name[16]; + unsigned int control_count; + struct ABOX_COMPONENT_CONTROL controls[]; +}; + +/************ IPC DEFINITION ***************/ +/* Categorized IPC, */ +enum IPC_ID { + IPC_RECEIVED, + IPC_SYSTEM = 1, + IPC_PCMPLAYBACK, + IPC_PCMCAPTURE, + IPC_OFFLOAD, + IPC_ERAP, + WDMA0_BUF_FULL = 0x8, + WDMA1_BUF_FULL = 0x9, + IPC_ABOX_CONFIG = 0xA, + RDMA0_BUF_EMPTY = 0xB, + RDMA1_BUF_EMPTY = 0xC, + RDMA2_BUF_EMPTY = 0xD, + RDMA3_BUF_EMPTY = 0xE, + IPC_ASB_TEST = 0xF, + IPC_ID_COUNT, +}; + +typedef struct { + enum IPC_ID ipcid; + int task_id; + union IPC_MSG { + struct IPC_SYSTEM_MSG system; + struct IPC_PCMTASK_MSG pcmtask; + struct IPC_OFFLOADTASK_MSG offload; + struct IPC_ERAP_MSG erap; + struct IPC_ABOX_CONFIG_MSG config; + struct IPC_ABOX_TEST_MSG asb; + } msg; +} ABOX_IPC_MSG; + +#endif /* __SND_SOC_ABOX_IPC_H */ diff --git a/include/sound/samsung/dp_ado.h b/include/sound/samsung/dp_ado.h new file mode 100644 index 000000000000..0fbea6c075b5 --- /dev/null +++ b/include/sound/samsung/dp_ado.h @@ -0,0 +1,20 @@ +/* + * Samsung DP Audio driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DPADO_H +#define __DPADO_H + +#ifdef CONFIG_SOC_EXYNOS9810 +void dp_ado_switch_set_state(int state); +#else +#define dp_ado_set_state(state) do { } while (0) +#endif + +#endif /* __DPADO_H */ diff --git a/include/sound/samsung/mailbox.h b/include/sound/samsung/mailbox.h new file mode 100644 index 000000000000..78f131446a38 --- /dev/null +++ b/include/sound/samsung/mailbox.h @@ -0,0 +1,42 @@ +/* + * ALSA SoC - Samsung Mailbox driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __VTS_MAILBOX_H +#define __VTS_MAILBOX_H + +/** + * Mailbox interrupt generator + * @param[in] pdev pointer to struct platform_device of target mailbox device + * @param[in] hw_irq hardware irq number which is same with bit index of the irq + * @return error code if any + */ +extern int mailbox_generate_interrupt(const struct platform_device *pdev, int hw_irq); + +/** + * Mailbox shared register writer + * @param[in] pdev pointer to struct platform_device of target mailbox device + * @param[in] values values to write + * @param[in] start start position to write. if 0, values are written from ISSR0 + * @param[in] count count of value in values. + */ +extern void mailbox_write_shared_register(const struct platform_device *pdev, + const u32 *values, int start, int count); + +/** + * Mailbox shared register reader + * @param[in] pdev pointer to struct platform_device of target mailbox device + * @param[out] values memory to write + * @param[in] start start position to read. if 1, values are read from ISSR1 + * @param[in] count count of value in values. + */ +extern void mailbox_read_shared_register(const struct platform_device *pdev, + u32 *values, int start, int count); + +#endif /* __VTS_MAILBOX_H */ \ No newline at end of file diff --git a/include/sound/samsung/vts.h b/include/sound/samsung/vts.h new file mode 100644 index 000000000000..cb879836748c --- /dev/null +++ b/include/sound/samsung/vts.h @@ -0,0 +1,54 @@ +/* + * ALSA SoC - Samsung VTS driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __VTS_H +#define __VTS_H + +#include + +#ifdef CONFIG_SND_SOC_SAMSUNG_VTS +/** + * Acquire SRAM in VTS + * @param[in] pdev pointer to struct platform_device of a VTS device + * @param[in] vts 1, if requester is vts. 0 for others. + * @return error code if any + */ +extern int vts_acquire_sram(struct platform_device *pdev, int vts); + +/** + * Release SRAM in VTS + * @param[in] pdev pointer to struct platform_device of a VTS device + * @param[in] vts 1, if requester is vts. 0 for others. + * @return error code if any + */ +extern int vts_release_sram(struct platform_device *pdev, int vts); + +/** + * Clear SRAM in VTS + * @param[in] pdev pointer to struct platform_device of a VTS device + * @return error code if any + */ +extern int vts_clear_sram(struct platform_device *pdev); + +/** + * Check VTS is on + * @return true if VTS is on, false on otherwise + */ +extern volatile bool vts_is_on(void); +#else /* !CONFIG_SND_SOC_SAMSUNG_VTS */ +static inline int vts_acquire_sram(struct platform_device *pdev, int vts) +{ return -ENODEV; } +static inline int vts_release_sram(struct platform_device *pdev, int vts) +{ return -ENODEV; } +static inline int vts_clear_sram(struct platform_device *pdev) { return -ENODEV; } +static inline bool vts_is_on(void) { return false; } +#endif /* !CONFIG_SND_SOC_SAMSUNG_VTS */ + +#endif /* __VTS_H */ diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 58acd00cae19..1e0b1698b0eb 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -292,8 +292,8 @@ struct snd_soc_dai { struct snd_soc_dai_driver *driver; /* DAI runtime info */ - unsigned int capture_active:1; /* stream is in use */ - unsigned int playback_active:1; /* stream is in use */ + unsigned int capture_active:4; /* stream is in use */ + unsigned int playback_active:4; /* stream is in use */ unsigned int symmetric_rates:1; unsigned int symmetric_channels:1; unsigned int symmetric_samplebits:1; @@ -312,6 +312,7 @@ struct snd_soc_dai { unsigned int rate; unsigned int channels; unsigned int sample_bits; + unsigned int sample_width; /* parent platform/codec */ struct snd_soc_codec *codec; diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 344b96c206a3..85a138b09e02 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -172,6 +172,12 @@ struct device; SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \ .kcontrol_news = wcontrols, .num_kcontrols = 1, \ .event = wevent, .event_flags = wflags} +#define SND_SOC_DAPM_DEMUX_E(wname, wreg, wshift, winvert, wcontrols, \ + wevent, wflags) \ +{ .id = snd_soc_dapm_demux, .name = wname, \ + SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \ + .kcontrol_news = wcontrols, .num_kcontrols = 1, \ + .event = wevent, .event_flags = wflags} /* additional sequencing control within an event type */ #define SND_SOC_DAPM_PGA_S(wname, wsubseq, wreg, wshift, winvert, \ @@ -478,6 +484,12 @@ struct snd_soc_dapm_widget *snd_soc_dapm_kcontrol_widget( int snd_soc_dapm_force_bias_level(struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level); +int snd_soc_dapm_connected_output_ep(struct snd_soc_dapm_widget *widget, + struct list_head *list); + +int snd_soc_dapm_connected_input_ep(struct snd_soc_dapm_widget *widget, + struct list_head *list); + /* dapm widget types */ enum snd_soc_dapm_type { snd_soc_dapm_input = 0, /* input pin */ diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index 806059052bfc..aeb9a4099988 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -62,6 +62,8 @@ enum snd_soc_dpcm_trigger { SND_SOC_DPCM_TRIGGER_PRE = 0, SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_BESPOKE, + SND_SOC_DPCM_TRIGGER_PRE_POST, /* PRE on start, POST on stop */ + SND_SOC_DPCM_TRIGGER_POST_PRE, /* POST on start, PRE on stop */ }; /* diff --git a/include/sound/soc.h b/include/sound/soc.h index d22de9712c45..a65a63e5eea6 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1044,7 +1044,7 @@ struct snd_soc_dai_link { /* optional hw_params re-writing for BE and FE sync */ int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params); + struct snd_pcm_hw_params *params, int stream); /* machine stream operations */ const struct snd_soc_ops *ops; diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 0520f5afd7cc..f268335b0430 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -30,6 +30,9 @@ config SND_SAMSUNG_SPDIF config SND_SAMSUNG_I2S tristate "Samsung I2S interface support" +config SND_SOC_SAMSUNG_DISPLAYPORT + tristate "Samsung display port audio interface support" + config SND_SOC_SAMSUNG_NEO1973_WM8753 tristate "Audio support for Openmoko Neo1973 Smartphones (GTA02)" depends on MACH_NEO1973_GTA02 @@ -193,6 +196,24 @@ config SND_SOC_ODROID help Say Y here to enable audio support for the Odroid XU3/XU4. +config SND_SOC_SAMSUNG_EXYNOS9610 + tristate "Audio support on exynos9610" + select SND_SOC_SAMSUNG_ABOX + #select SND_SOC_SAMSUNG_DISPLAYPORT + help + Say Y if you want to add support for audio on the Exynos9610. + +config SND_SOC_SAMSUNG_EXYNOS9610_MADERA + tristate "Madera audio codec support on exynos9610" + select SND_SOC_CS47L35 + select MFD_MADERA_SPI + select MFD_CS47L35 + select REGULATOR_ARIZONA_LDO1 + select REGULATOR_ARIZONA_MICSUPP + select EXTCON_MADERA + select EXTCON_MADERA_INPUT_EVENT + select SND_SOC_CS35L35 + config SND_SOC_ARNDALE_RT5631_ALC5631 tristate "Audio support for RT5631(ALC5631) on Arndale Board" depends on I2C @@ -209,4 +230,7 @@ config SND_SOC_SAMSUNG_TM2_WM5110 help Say Y if you want to add support for SoC audio on the TM2 board. +source "sound/soc/samsung/abox/Kconfig" +source "sound/soc/samsung/vts/Kconfig" + endif #SND_SOC_SAMSUNG diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index 030949e1e434..1ed6642cba49 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -8,6 +8,7 @@ snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-samsung-spdif-objs := spdif.o snd-soc-pcm-objs := pcm.o snd-soc-i2s-objs := i2s.o +snd-soc-dp_dma-objs := dp_dma.o dummy_cpu_dai.o obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c-dma.o obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o @@ -17,6 +18,9 @@ obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o +obj-$(CONFIG_SND_SOC_SAMSUNG_DISPLAYPORT) += snd-soc-dp_dma.o +obj-$(CONFIG_SND_SOC_SAMSUNG_VTS) += vts/ +obj-$(CONFIG_SND_SOC_SAMSUNG_ABOX) += abox/ # S3C24XX Machine Support snd-soc-jive-wm8750-objs := jive_wm8750.o @@ -67,3 +71,4 @@ obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o obj-$(CONFIG_SND_SOC_ODROID) += snd-soc-odroid.o obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o +obj-$(CONFIG_SND_SOC_SAMSUNG_EXYNOS9610_MADERA) += exynos9610_madera.o \ No newline at end of file diff --git a/sound/soc/samsung/abox/Kconfig b/sound/soc/samsung/abox/Kconfig new file mode 100644 index 000000000000..4a86343a5d83 --- /dev/null +++ b/sound/soc/samsung/abox/Kconfig @@ -0,0 +1,8 @@ +config SND_SOC_SAMSUNG_ABOX + tristate "ASoC support for Samsung ABOX Audio" + select REGMAP_MMIO + select SND_SOC_COMPRESS + help + Say Y or M if you want to add support for codecs attached to + the Samsung SoC ABOX interface. You will also need to + select the audio interfaces to support below. \ No newline at end of file diff --git a/sound/soc/samsung/abox/Makefile b/sound/soc/samsung/abox/Makefile new file mode 100644 index 000000000000..79258f8a5aa8 --- /dev/null +++ b/sound/soc/samsung/abox/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SND_SOC_SAMSUNG_ABOX) += abox_util.o abox_dbg.o abox_dump.o abox_log.o abox_gic.o abox.o abox_rdma.o abox_wdma.o abox_if.o abox_effect.o abox_vss.o abox_failsafe.o abox_vdma.o abox_bt.o diff --git a/sound/soc/samsung/abox/abox.c b/sound/soc/samsung/abox/abox.c new file mode 100644 index 000000000000..f3bd43fb5223 --- /dev/null +++ b/sound/soc/samsung/abox/abox.c @@ -0,0 +1,6303 @@ +/* sound/soc/samsung/abox/abox.c + * + * ALSA SoC Audio Layer - Samsung Abox driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#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 +//#include +#include "../../../../drivers/iommu/exynos-iommu.h" + +#include "abox_util.h" +#include "abox_dbg.h" +#include "abox_log.h" +#include "abox_dump.h" +#include "abox_gic.h" +#include "abox_failsafe.h" +#include "abox_if.h" +#include "abox_vdma.h" +#include "abox_effect.h" +#ifdef CONFIG_SCSC_BT +#include "abox_bt.h" +#endif +#include "abox.h" + +#undef EMULATOR +#ifdef EMULATOR +static void __iomem *pmu_alive; +static void update_mask_value(void __iomem *sfr, + unsigned int mask, unsigned int value) +{ + unsigned int sfr_value = readl(sfr); + + set_mask_value(sfr_value, mask, value); + writel(sfr_value, sfr); +} +#endif + +#if IS_ENABLED(CONFIG_SOC_EXYNOS8895) +#define GPIO_MODE_ABOX_SYS_PWR_REG (0x1308) +#define PAD_RETENTION_ABOX_OPTION (0x3048) +#define ABOX_MAGIC (0x0814) +#define ABOX_MAGIC_VALUE (0xAB0CAB0C) +#define ABOX_CPU_CONFIGURATION (0x2520) +#define ABOX_CPU_LOCAL_PWR_CFG (0x00000001) +#define ABOX_CPU_STATUS (0x2524) +#define ABOX_CPU_STATUS_STATUS_MASK (0x00000001) +#define ABOX_CPU_STANDBY ABOX_CPU_STATUS +#define ABOX_CPU_STANDBY_WFE_MASK (0x20000000) +#define ABOX_CPU_STANDBY_WFI_MASK (0x10000000) +#define ABOX_CPU_OPTION (0x2528) +#define ABOX_CPU_OPTION_USE_STANDBYWFE_MASK (0x00020000) +#define ABOX_CPU_OPTION_USE_STANDBYWFI_MASK (0x00010000) +#define ABOX_CPU_OPTION_ENABLE_CPU_MASK (0x00008000) +#elif IS_ENABLED(CONFIG_SOC_EXYNOS9810) +#define GPIO_MODE_ABOX_SYS_PWR_REG (0x1424) +#define PAD_RETENTION_ABOX_OPTION (0x4170) +#define ABOX_MAGIC (0x0814) +#define ABOX_MAGIC_VALUE (0xAB0CAB0C) +#define ABOX_CPU_CONFIGURATION (0x415C) +#define ABOX_CPU_LOCAL_PWR_CFG (0x00000001) +#define ABOX_CPU_STATUS (0x4160) +#define ABOX_CPU_STATUS_STATUS_MASK (0x00000001) +#define ABOX_CPU_STANDBY (0x3804) +#define ABOX_CPU_STANDBY_WFE_MASK (0x20000000) +#define ABOX_CPU_STANDBY_WFI_MASK (0x10000000) +#define ABOX_CPU_OPTION (0x4164) +#define ABOX_CPU_OPTION_ENABLE_CPU_MASK (0x10000000) +#elif IS_ENABLED(CONFIG_SOC_EXYNOS9610) +#define GPIO_MODE_ABOX_SYS_PWR_REG (0x1308) +#define PAD_RETENTION_ABOX_OPTION (0x3048) +#define ABOX_MAGIC (0x0814) +#define ABOX_MAGIC_VALUE (0xAB0CAB0C) +#define ABOX_CPU_CONFIGURATION (0x2520) +#define ABOX_CPU_LOCAL_PWR_CFG (0x00000001) +#define ABOX_CPU_STATUS (0x2524) +#define ABOX_CPU_STATUS_STATUS_MASK (0x00000001) +#define ABOX_CPU_STANDBY (0x2524) +#define ABOX_CPU_STANDBY_WFE_MASK (0x20000000) +#define ABOX_CPU_STANDBY_WFI_MASK (0x10000000) +#define ABOX_CPU_OPTION (0x2528) +#define ABOX_CPU_OPTION_ENABLE_CPU_MASK (0x00008000) +#endif + +#define DEFAULT_CPU_GEAR_ID (0xAB0CDEFA) +#define TEST_CPU_GEAR_ID (DEFAULT_CPU_GEAR_ID + 1) +#define DEFAULT_LIT_FREQ_ID DEFAULT_CPU_GEAR_ID +#define DEFAULT_BIG_FREQ_ID DEFAULT_CPU_GEAR_ID +#define DEFAULT_HMP_BOOST_ID DEFAULT_CPU_GEAR_ID +#define DEFAULT_INT_FREQ_ID DEFAULT_CPU_GEAR_ID +#define DEFAULT_MIF_FREQ_ID DEFAULT_CPU_GEAR_ID +#define AUD_PLL_RATE_KHZ (1179648) +#define AUD_PLL_RATE_HZ_BYPASS (26000000) +#define AUDIF_RATE_HZ (24576000) +#define CALLIOPE_ENABLE_TIMEOUT_MS (1000) +#define IPC_TIMEOUT_US (10000) +#define BOOT_DONE_TIMEOUT_MS (10000) +#define IPC_RETRY (10) + +#define ERAP(wname, wcontrols, event_fn, wparams) \ +{ .id = snd_soc_dapm_dai_link, .name = wname, \ + .reg = SND_SOC_NOPM, .event = event_fn, \ + .kcontrol_news = wcontrols, .num_kcontrols = 1, \ + .params = wparams, .num_params = ARRAY_SIZE(wparams), \ + .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_WILL_PMD } + +/* For only external static functions */ +static struct abox_data *p_abox_data; + +struct abox_data *abox_get_abox_data(void) +{ + return p_abox_data; +} + +static int abox_iommu_fault_handler( + struct iommu_domain *domain, struct device *dev, + unsigned long fault_addr, int fault_flags, void *token) +{ + struct abox_data *data = token; + + abox_dbg_print_gpr(&data->pdev->dev, data); + return 0; +} + +static void abox_cpu_power(bool on); +static int abox_cpu_enable(bool enable); +static int abox_cpu_pm_ipc(struct device *dev, bool resume); +static void abox_boot_done(struct device *dev, unsigned int version); +static int abox_enable(struct device *dev); + +static void exynos_abox_panic_handler(void) +{ + static bool has_run; + struct abox_data *data = p_abox_data; + struct device *dev = data ? (data->pdev ? &data->pdev->dev : NULL) : + NULL; + + dev_dbg(dev, "%s\n", __func__); + + if (abox_is_on() && dev) { + if (has_run) { + dev_info(dev, "already dumped\n"); + return; + } + has_run = true; + + abox_dbg_dump_gpr(dev, data, ABOX_DBG_DUMP_KERNEL, "panic"); + abox_cpu_pm_ipc(dev, false); + writel(0x504E4943, data->sram_base + data->sram_size - + sizeof(u32)); + abox_cpu_enable(false); + abox_cpu_power(false); + abox_cpu_power(true); + abox_cpu_enable(true); + mdelay(100); + abox_dbg_dump_mem(dev, data, ABOX_DBG_DUMP_KERNEL, "panic"); + } else { + dev_info(dev, "%s: dump is skipped due to no power\n", + __func__); + } +} + +static int abox_panic_handler(struct notifier_block *nb, + unsigned long action, void *data) +{ + exynos_abox_panic_handler(); + return NOTIFY_OK; +} + +static struct notifier_block abox_panic_notifier = { + .notifier_call = abox_panic_handler, + .next = NULL, + .priority = 0 /* priority: INT_MAX >= x >= 0 */ +}; + +static struct platform_driver samsung_abox_driver; +static bool is_abox(struct device *dev) +{ + return (&samsung_abox_driver.driver) == dev->driver; +} + +static void abox_probe_quirks(struct abox_data *data, struct device_node *np) +{ + #define QUIRKS "quirks" + #define DEC_MAP(id) {ABOX_QUIRK_STR_##id, ABOX_QUIRK_##id} + + static const struct { + const char *str; + unsigned int bit; + } map[] = { + DEC_MAP(TRY_TO_ASRC_OFF), + DEC_MAP(SHARE_VTS_SRAM), + DEC_MAP(OFF_ON_SUSPEND), + DEC_MAP(SCSC_BT), + DEC_MAP(SCSC_BT_HACK), + }; + + int i, ret; + + for (i = 0; i < ARRAY_SIZE(map); i++) { + ret = of_property_match_string(np, QUIRKS, map[i].str); + if (ret >= 0) + data->quirks |= map[i].bit; + } +} + + +int abox_disable_qchannel(struct device *dev, struct abox_data *data, + enum qchannel clk, int disable) +{ + return regmap_update_bits(data->regmap, ABOX_QCHANNEL_DISABLE, + ABOX_QCHANNEL_DISABLE_MASK(clk), + !!disable << ABOX_QCHANNEL_DISABLE_L(clk)); +} + +phys_addr_t abox_addr_to_phys_addr(struct abox_data *data, unsigned int addr) +{ + phys_addr_t ret; + + if (addr < IOVA_DRAM_FIRMWARE) + ret = data->sram_base_phys + addr; + else + ret = iommu_iova_to_phys(data->iommu_domain, addr); + + return ret; +} + +void *abox_addr_to_kernel_addr(struct abox_data *data, unsigned int addr) +{ + void *ret; + + if (addr < IOVA_DRAM_FIRMWARE) + ret = data->sram_base + addr; + else if (addr >= IOVA_DRAM_FIRMWARE && addr < IOVA_IVA_FIRMWARE) + ret = data->dram_base + (addr - IOVA_DRAM_FIRMWARE); + else if (addr >= IOVA_IVA_FIRMWARE && addr < IOVA_VSS_FIRMWARE) + ret = data->iva_base + (addr - IOVA_IVA_FIRMWARE); + else if (addr >= IOVA_VSS_FIRMWARE && addr < IOVA_DUMP_BUFFER) + ret = phys_to_virt(shm_get_vss_base() + + (addr - IOVA_VSS_FIRMWARE)); + else if (addr >= IOVA_DUMP_BUFFER) + ret = data->dump_base + (addr - IOVA_DUMP_BUFFER); + else + ret = phys_to_virt(abox_addr_to_phys_addr(data, addr)); + + return ret; +} + +phys_addr_t abox_iova_to_phys(struct device *dev, unsigned long iova) +{ + return abox_addr_to_phys_addr(dev_get_drvdata(dev), iova); +} +EXPORT_SYMBOL(abox_iova_to_phys); + +void *abox_iova_to_virt(struct device *dev, unsigned long iova) +{ + return abox_addr_to_kernel_addr(dev_get_drvdata(dev), iova); +} +EXPORT_SYMBOL(abox_iova_to_virt); + +static int abox_sif_idx(enum ABOX_CONFIGMSG configmsg) +{ + return configmsg - ((configmsg < SET_MIXER_FORMAT) ? + SET_MIXER_SAMPLE_RATE : SET_MIXER_FORMAT); +} + +static unsigned int abox_get_sif_rate_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + return data->sif_rate_min[abox_sif_idx(configmsg)]; +} + +static void abox_set_sif_rate_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, unsigned int val) +{ + data->sif_rate_min[abox_sif_idx(configmsg)] = val; +} + +static snd_pcm_format_t abox_get_sif_format_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + return data->sif_format_min[abox_sif_idx(configmsg)]; +} + +static void abox_set_sif_format_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, snd_pcm_format_t val) +{ + data->sif_format_min[abox_sif_idx(configmsg)] = val; +} + +static int abox_get_sif_width_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + return snd_pcm_format_width(abox_get_sif_format_min(data, configmsg)); +} + +static void abox_set_sif_width_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, int width) +{ + struct device *dev = &data->pdev->dev; + snd_pcm_format_t format = SNDRV_PCM_FORMAT_S16; + + switch (width) { + case 16: + format = SNDRV_PCM_FORMAT_S16; + break; + case 24: + format = SNDRV_PCM_FORMAT_S24; + break; + case 32: + format = SNDRV_PCM_FORMAT_S32; + break; + default: + dev_warn(dev, "%s(%d): invalid argument\n", __func__, width); + } + + abox_set_sif_format_min(data, configmsg, format); +} + +static unsigned int abox_get_sif_channels_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + return data->sif_channels_min[abox_sif_idx(configmsg)]; +} + +static void __maybe_unused abox_set_sif_channels_min(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, unsigned int val) +{ + data->sif_channels_min[abox_sif_idx(configmsg)] = val; +} + +static bool abox_get_sif_auto_config(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + return data->sif_auto_config[abox_sif_idx(configmsg)]; +} + +static void abox_set_sif_auto_config(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, bool val) +{ + data->sif_auto_config[abox_sif_idx(configmsg)] = val; +} + +static unsigned int abox_get_sif_rate(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + unsigned int val = data->sif_rate[abox_sif_idx(configmsg)]; + unsigned int min = abox_get_sif_rate_min(data, configmsg); + + return (abox_get_sif_auto_config(data, configmsg) && (min > val)) ? + min : val; +} + +static void abox_set_sif_rate(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, unsigned int val) +{ + data->sif_rate[abox_sif_idx(configmsg)] = val; +} + +static snd_pcm_format_t abox_get_sif_format(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + snd_pcm_format_t val = data->sif_format[abox_sif_idx(configmsg)]; + snd_pcm_format_t min = abox_get_sif_format_min(data, configmsg); + + return (abox_get_sif_auto_config(data, configmsg) && (min > val)) ? + min : val; +} + +static void abox_set_sif_format(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, snd_pcm_format_t val) +{ + data->sif_format[abox_sif_idx(configmsg)] = val; +} + +static int abox_get_sif_physical_width(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + snd_pcm_format_t format = abox_get_sif_format(data, configmsg); + + return snd_pcm_format_physical_width(format); +} + +static int abox_get_sif_width(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + return snd_pcm_format_width(abox_get_sif_format(data, configmsg)); +} + +static void abox_set_sif_width(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, int width) +{ + struct device *dev = &data->pdev->dev; + snd_pcm_format_t format = SNDRV_PCM_FORMAT_S16; + + switch (width) { + case 16: + format = SNDRV_PCM_FORMAT_S16; + break; + case 24: + format = SNDRV_PCM_FORMAT_S24; + break; + case 32: + format = SNDRV_PCM_FORMAT_S32; + break; + default: + dev_err(dev, "%s(%d): invalid argument\n", __func__, width); + } + + abox_set_sif_format(data, configmsg, format); +} + +static unsigned int abox_get_sif_channels(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg) +{ + unsigned int val = data->sif_channels[abox_sif_idx(configmsg)]; + unsigned int min = abox_get_sif_channels_min(data, configmsg); + + return (abox_get_sif_auto_config(data, configmsg) && (min > val)) ? + min : val; +} + +static void abox_set_sif_channels(struct abox_data *data, + enum ABOX_CONFIGMSG configmsg, unsigned int val) +{ + data->sif_channels[abox_sif_idx(configmsg)] = val; +} + +static bool __abox_ipc_queue_empty(struct abox_data *data) +{ + return (data->ipc_queue_end == data->ipc_queue_start); +} + +static bool __abox_ipc_queue_full(struct abox_data *data) +{ + size_t length = ARRAY_SIZE(data->ipc_queue); + + return (((data->ipc_queue_end + 1) % length) == data->ipc_queue_start); +} + +static int abox_ipc_queue_put(struct abox_data *data, struct device *dev, + int hw_irq, const void *supplement, size_t size) +{ + spinlock_t *lock = &data->ipc_queue_lock; + size_t length = ARRAY_SIZE(data->ipc_queue); + unsigned long flags; + int ret; + + spin_lock_irqsave(lock, flags); + if (!__abox_ipc_queue_full(data)) { + struct abox_ipc *ipc; + + ipc = &data->ipc_queue[data->ipc_queue_end]; + ipc->dev = dev; + ipc->hw_irq = hw_irq; + ipc->put_time = sched_clock(); + ipc->get_time = 0; + memcpy(&ipc->msg, supplement, size); + data->ipc_queue_end = (data->ipc_queue_end + 1) % length; + + ret = 0; + } else { + ret = -EBUSY; + } + spin_unlock_irqrestore(lock, flags); + + return ret; +} + +static int abox_ipc_queue_get(struct abox_data *data, struct abox_ipc *ipc) +{ + spinlock_t *lock = &data->ipc_queue_lock; + size_t length = ARRAY_SIZE(data->ipc_queue); + unsigned long flags; + int ret; + + spin_lock_irqsave(lock, flags); + if (!__abox_ipc_queue_empty(data)) { + struct abox_ipc *tmp; + + tmp = &data->ipc_queue[data->ipc_queue_start]; + tmp->get_time = sched_clock(); + *ipc = *tmp; + data->ipc_queue_start = (data->ipc_queue_start + 1) % length; + + ret = 0; + } else { + ret = -ENODATA; + } + spin_unlock_irqrestore(lock, flags); + + return ret; +} + +static bool abox_can_calliope_ipc(struct device *dev, + struct abox_data *data) +{ + bool ret = true; + + switch (data->calliope_state) { + case CALLIOPE_DISABLING: + case CALLIOPE_ENABLED: + break; + case CALLIOPE_ENABLING: + wait_event_timeout(data->ipc_wait_queue, + data->calliope_state == CALLIOPE_ENABLED, + msecs_to_jiffies(CALLIOPE_ENABLE_TIMEOUT_MS)); + if (data->calliope_state == CALLIOPE_ENABLED) + break; + /* Fallthrough */ + case CALLIOPE_DISABLED: + default: + dev_warn(dev, "Invalid calliope state: %d\n", + data->calliope_state); + ret = false; + } + + dev_dbg(dev, "%s: %d\n", __func__, ret); + + return ret; +} + +static int __abox_process_ipc(struct device *dev, struct abox_data *data, + int hw_irq, const ABOX_IPC_MSG *msg) +{ + static unsigned int tout_cnt; + static DEFINE_SPINLOCK(lock); + + void __iomem *tx_base = data->sram_base + data->ipc_tx_offset; + void __iomem *tx_ack = data->sram_base + data->ipc_tx_ack_offset; + int ret, i; + + dev_dbg(dev, "%s(%d, %d, %d)\n", __func__, hw_irq, + msg->ipcid, msg->msg.system.msgtype); + + do { + spin_lock(&lock); + + memcpy_toio(tx_base, msg, sizeof(*msg)); + writel(1, tx_ack); + abox_gic_generate_interrupt(data->dev_gic, hw_irq); + for (i = IPC_TIMEOUT_US; i && readl(tx_ack); i--) + udelay(1); + + if (readl(tx_ack)) { + tout_cnt++; + dev_warn_ratelimited(dev, "Transaction timeout(%d)\n", + tout_cnt); + + if (tout_cnt == 1) + abox_dbg_dump_simple(dev, data, + "Transaction timeout"); + + if ((tout_cnt % IPC_RETRY) == 0) { + abox_failsafe_report(dev); + writel(0, tx_ack); + } + + ret = -EIO; + } else { + tout_cnt = 0; + ret = 0; + } + spin_unlock(&lock); + } while (readl(tx_ack)); + + return ret; +} + +static void abox_process_ipc(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, ipc_work); + struct device *dev = &data->pdev->dev; + struct abox_ipc ipc; + + dev_dbg(dev, "%s: %d %d\n", __func__, data->ipc_queue_start, + data->ipc_queue_end); + + pm_runtime_get_sync(dev); + + if (abox_can_calliope_ipc(dev, data)) { + while (abox_ipc_queue_get(data, &ipc) == 0) { + struct device *dev = ipc.dev; + int hw_irq = ipc.hw_irq; + ABOX_IPC_MSG *msg = &ipc.msg; + + __abox_process_ipc(dev, data, hw_irq, msg); + + /* giving time to ABOX for processing */ + usleep_range(10, 100); + } + } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +static int abox_schedule_ipc(struct device *dev, struct abox_data *data, + int hw_irq, const void *supplement, size_t size, + bool atomic, bool sync) +{ + struct abox_ipc *ipc; + int retry = 0; + int ret; + + dev_dbg(dev, "%s(%d, %p, %zu, %d, %d)\n", __func__, hw_irq, supplement, + size, atomic, sync); + + if (unlikely(sizeof(ipc->msg) < size)) { + dev_err(dev, "%s: too large supplement\n", __func__); + return -EINVAL; + } + + do { + ret = abox_ipc_queue_put(data, dev, hw_irq, supplement, size); + queue_work(data->ipc_workqueue, &data->ipc_work); + if (!atomic && sync) + flush_work(&data->ipc_work); + if (ret >= 0) + break; + + if (!atomic) { + dev_info(dev, "%s: flush(%d)\n", __func__, retry); + flush_work(&data->ipc_work); + } else { + dev_info(dev, "%s: delay(%d)\n", __func__, retry); + mdelay(10); + } + } while (retry++ < IPC_RETRY); + + if (ret < 0) { + dev_err(dev, "%s(%d): ipc queue overflow\n", __func__, hw_irq); + abox_failsafe_report(dev); + } + + return ret; +} + +int abox_request_ipc(struct device *dev, + int hw_irq, const void *supplement, + size_t size, int atomic, int sync) +{ + struct abox_data *data = dev_get_drvdata(dev); + int ret; + + if (atomic && sync) { + ret = __abox_process_ipc(dev, data, hw_irq, supplement); + } else { + ret = abox_schedule_ipc(dev, data, hw_irq, supplement, size, + !!atomic, !!sync); + } + + return ret; +} +EXPORT_SYMBOL(abox_request_ipc); + +bool abox_is_on(void) +{ + return p_abox_data && p_abox_data->enabled; +} +EXPORT_SYMBOL(abox_is_on); + +int abox_register_bclk_usage(struct device *dev, struct abox_data *data, + enum abox_dai dai_id, unsigned int rate, unsigned int channels, + unsigned int width) +{ + unsigned long target_pll, audif_rate; + int id = dai_id - ABOX_UAIF0; + int ret = 0; + int i; + + dev_dbg(dev, "%s(%d, %d)\n", __func__, id, rate); + + if (id < 0 || id >= ABOX_DAI_COUNT) { + dev_err(dev, "invalid dai_id: %d\n", dai_id); + return -EINVAL; + } + + if (rate == 0) { + data->audif_rates[id] = 0; + return 0; + } + + target_pll = ((rate % 44100) == 0) ? AUD_PLL_RATE_HZ_FOR_44100 : + AUD_PLL_RATE_HZ_FOR_48000; + if (target_pll != clk_get_rate(data->clk_pll)) { + dev_info(dev, "Set AUD_PLL rate: %lu -> %lu\n", + clk_get_rate(data->clk_pll), target_pll); + ret = clk_set_rate(data->clk_pll, target_pll); + if (ret < 0) { + dev_err(dev, "AUD_PLL set error=%d\n", ret); + return ret; + } + } + + if (data->uaif_max_div <= 32) { + if ((rate % 44100) == 0) + audif_rate = ((rate > 176400) ? 352800 : 176400) * + width * 2; + else + audif_rate = ((rate > 192000) ? 384000 : 192000) * + width * 2; + + while (audif_rate / rate / channels / width > + data->uaif_max_div) + audif_rate /= 2; + } else { + int clk_width = 96; /* LCM of 24 and 32 */ + int clk_channels = 2; + + if ((rate % 44100) == 0) + audif_rate = 352800 * clk_width * clk_channels; + else + audif_rate = 384000 * clk_width * clk_channels; + + if (audif_rate < rate * width * channels) + audif_rate = rate * width * channels; + } + + data->audif_rates[id] = audif_rate; + + for (i = 0; i < ARRAY_SIZE(data->audif_rates); i++) { + if (data->audif_rates[i] > 0 && + data->audif_rates[i] > audif_rate) { + audif_rate = data->audif_rates[i]; + } + } + + ret = clk_set_rate(data->clk_audif, audif_rate); + if (ret < 0) + dev_err(dev, "Failed to set audif clock: %d\n", ret); + + dev_info(dev, "audif clock: %lu\n", clk_get_rate(data->clk_audif)); + + return ret; +} + +static int abox_sif_format_put_ipc(struct device *dev, snd_pcm_format_t format, + int channels, enum ABOX_CONFIGMSG configmsg) +{ + struct abox_data *data = dev_get_drvdata(dev); + ABOX_IPC_MSG msg; + struct IPC_ABOX_CONFIG_MSG *abox_config_msg = &msg.msg.config; + int width = snd_pcm_format_width(format); + int ret; + + dev_dbg(dev, "%s(%d, %d, %d)\n", __func__, width, channels, configmsg); + + abox_set_sif_format(data, configmsg, format); + abox_set_sif_channels(data, configmsg, channels); + + /* update manually for regmap cache sync */ + switch (configmsg) { + case SET_MIXER_SAMPLE_RATE: + case SET_MIXER_FORMAT: + regmap_update_bits(data->regmap, ABOX_SPUS_CTRL1, + ABOX_SPUS_MIXP_FORMAT_MASK, + abox_get_format(width, channels) << + ABOX_SPUS_MIXP_FORMAT_L); + break; + case SET_RECP_SAMPLE_RATE: + case SET_RECP_FORMAT: + regmap_update_bits(data->regmap, ABOX_SPUM_CTRL1, + ABOX_RECP_SRC_FORMAT_MASK, + abox_get_format(width, channels) << + ABOX_RECP_SRC_FORMAT_L); + break; + default: + /* Nothing to do */ + break; + } + + msg.ipcid = IPC_ABOX_CONFIG; + abox_config_msg->param1 = abox_get_sif_width(data, configmsg); + abox_config_msg->param2 = abox_get_sif_channels(data, configmsg); + abox_config_msg->msgtype = configmsg; + ret = abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); + if (ret < 0) + dev_err(dev, "%d(%d bit, %d channels) failed: %d\n", configmsg, + width, channels, ret); + + return ret; +} + +static unsigned int abox_sifsx_cnt_val(unsigned long aclk, unsigned int rate, + unsigned int physical_width, unsigned int channels) +{ + static const int correction = -2; + unsigned int n, d; + + /* k = n / d */ + d = channels; + n = 2 * (32 / physical_width); + + return ((aclk * n) / d / rate) - 5 + correction; +} + +static int abox_sifs_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *be = substream->private_data; + struct snd_soc_dpcm *dpcm; + int stream = substream->stream; + struct device *dev = dai->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + enum abox_dai id = dai->id; + unsigned int rate = params_rate(params); + unsigned int width = params_width(params); + unsigned int pwidth = params_physical_width(params); + unsigned int channels = params_channels(params); + unsigned long aclk; + unsigned int cnt_val; + bool skip = true; + int ret = 0; + + if (stream != SNDRV_PCM_STREAM_CAPTURE) + goto out; + + /* sifs count is needed only when SIFS is connected to NSRC */ + list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + if (dpcm->fe->cpu_dai->id != ABOX_WDMA0) { + skip = false; + break; + } + } + if (skip) + goto out; + + abox_request_cpu_gear_sync(dev, data, substream, ABOX_CPU_GEAR_MAX); + aclk = clk_get_rate(data->clk_bus); + cnt_val = abox_sifsx_cnt_val(aclk, rate, pwidth, channels); + + dev_info(dev, "%s[%d](%ubit %uchannel %uHz at %luHz): %u\n", + __func__, id, width, channels, rate, aclk, cnt_val); + + switch (id) { + case ABOX_SIFS0: + ret = regmap_update_bits(regmap, ABOX_SPUS_CTRL_SIFS_CNT0, + ABOX_SIFS0_CNT_VAL_MASK, + cnt_val << ABOX_SIFS0_CNT_VAL_L); + break; + case ABOX_SIFS1: + ret = regmap_update_bits(regmap, ABOX_SPUS_CTRL_SIFS_CNT0, + ABOX_SIFS1_CNT_VAL_MASK, + cnt_val << ABOX_SIFS1_CNT_VAL_L); + break; + case ABOX_SIFS2: + ret = regmap_update_bits(regmap, ABOX_SPUS_CTRL_SIFS_CNT1, + ABOX_SIFS2_CNT_VAL_MASK, + cnt_val << ABOX_SIFS2_CNT_VAL_L); + break; + default: + dev_err(dev, "%s: invalid id(%d)\n", __func__, id); + ret = -EINVAL; + break; + } +out: + return ret; +} + +static int abox_sifs_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + enum abox_dai id = dai->id; + int ret = 0; + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + goto out; + + dev_info(dev, "%s[%d]\n", __func__, id); + + switch (id) { + case ABOX_SIFS0: + ret = regmap_update_bits(regmap, ABOX_SPUS_CTRL_SIFS_CNT0, + ABOX_SIFS0_CNT_VAL_MASK, 0); + break; + case ABOX_SIFS1: + ret = regmap_update_bits(regmap, ABOX_SPUS_CTRL_SIFS_CNT0, + ABOX_SIFS1_CNT_VAL_MASK, 0); + break; + case ABOX_SIFS2: + ret = regmap_update_bits(regmap, ABOX_SPUS_CTRL_SIFS_CNT1, + ABOX_SIFS2_CNT_VAL_MASK, 0); + break; + default: + dev_err(dev, "%s: invalid id(%d)\n", __func__, id); + ret = -EINVAL; + break; + } + + abox_request_cpu_gear(dev, data, substream, ABOX_CPU_GEAR_MIN); +out: + return ret; +} + +static const struct snd_soc_dai_ops abox_sifs_dai_ops = { + .hw_params = abox_sifs_hw_params, + .hw_free = abox_sifs_hw_free, +}; + +static struct snd_soc_dai_driver abox_dais[] = { + { + .name = "RDMA0", + .id = ABOX_RDMA0, + .playback = { + .stream_name = "RDMA0 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + }, + { + .name = "RDMA1", + .id = ABOX_RDMA1, + .playback = { + .stream_name = "RDMA1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + }, + { + .name = "RDMA2", + .id = ABOX_RDMA2, + .playback = { + .stream_name = "RDMA2 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + }, + { + .name = "RDMA3", + .id = ABOX_RDMA3, + .playback = { + .stream_name = "RDMA3 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + }, + { + .name = "RDMA4", + .id = ABOX_RDMA4, + .playback = { + .stream_name = "RDMA4 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + }, + { + .name = "RDMA5", + .id = ABOX_RDMA5, + .playback = { + .stream_name = "RDMA5 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .compress_new = snd_soc_new_compress, + }, + { + .name = "RDMA6", + .id = ABOX_RDMA6, + .playback = { + .stream_name = "RDMA6 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + }, + { + .name = "RDMA7", + .id = ABOX_RDMA7, + .playback = { + .stream_name = "RDMA7 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + }, + { + .name = "WDMA0", + .id = ABOX_WDMA0, + .capture = { + .stream_name = "WDMA0 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_WDMA_SAMPLE_FORMATS, + }, + }, + { + .name = "WDMA1", + .id = ABOX_WDMA1, + .capture = { + .stream_name = "WDMA1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_WDMA_SAMPLE_FORMATS, + }, + }, + { + .name = "WDMA2", + .id = ABOX_WDMA2, + .capture = { + .stream_name = "WDMA2 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_WDMA_SAMPLE_FORMATS, + }, + }, + { + .name = "WDMA3", + .id = ABOX_WDMA3, + .capture = { + .stream_name = "WDMA3 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_WDMA_SAMPLE_FORMATS, + }, + }, + { + .name = "WDMA4", + .id = ABOX_WDMA4, + .capture = { + .stream_name = "WDMA4 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_WDMA_SAMPLE_FORMATS, + }, + }, + { + .name = "SIFS0", + .id = ABOX_SIFS0, + .playback = { + .stream_name = "SIFS0 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .capture = { + .stream_name = "SIFS0 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .ops = &abox_sifs_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, + }, + { + .name = "SIFS1", + .id = ABOX_SIFS1, + .playback = { + .stream_name = "SIFS1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .capture = { + .stream_name = "SIFS1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .ops = &abox_sifs_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, + }, + { + .name = "SIFS2", + .id = ABOX_SIFS2, + .playback = { + .stream_name = "SIFS2 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .capture = { + .stream_name = "SIFS2 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .ops = &abox_sifs_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, + }, +}; + +static int abox_cmpnt_probe(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + struct abox_data *data = dev_get_drvdata(dev); + + dev_info(dev, "%s\n", __func__); + + data->cmpnt = component; + snd_soc_component_update_bits(component, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_RSRC_RECP_MASK, + ABOX_FUNC_CHAIN_RSRC_RECP_MASK); + + return 0; +} + +static void abox_cmpnt_remove(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + + dev_info(dev, "%s\n", __func__); + + snd_soc_component_exit_regmap(component); +} + +static int abox_sample_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = abox_get_sif_rate(data, reg); + + dev_dbg(dev, "%s(0x%08x): %u\n", __func__, reg, val); + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int abox_sample_rate_put_ipc(struct device *dev, unsigned int val, + enum ABOX_CONFIGMSG configmsg) +{ + struct abox_data *data = dev_get_drvdata(dev); + ABOX_IPC_MSG msg; + struct IPC_ABOX_CONFIG_MSG *abox_config_msg = &msg.msg.config; + int ret; + + dev_dbg(dev, "%s(%u, 0x%08x)\n", __func__, val, configmsg); + + abox_set_sif_rate(data, configmsg, val); + + msg.ipcid = IPC_ABOX_CONFIG; + abox_config_msg->param1 = abox_get_sif_rate(data, configmsg); + abox_config_msg->msgtype = configmsg; + ret = abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); + if (ret < 0) { + dev_err(dev, "%s(%u, 0x%08x) failed: %d\n", __func__, val, + configmsg, ret); + } + + return ret; +} + +static int abox_sample_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = (unsigned int)ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(0x%08x, %u)\n", __func__, reg, val); + + return abox_sample_rate_put_ipc(dev, val, reg); +} + +static int abox_bit_width_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = abox_get_sif_width(data, reg); + + dev_dbg(dev, "%s(0x%08x): %u\n", __func__, reg, val); + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int abox_bit_width_put_ipc(struct device *dev, unsigned int val, + enum ABOX_CONFIGMSG configmsg) +{ + struct abox_data *data = dev_get_drvdata(dev); + snd_pcm_format_t format = SNDRV_PCM_FORMAT_S16; + int channels = data->sif_channels[abox_sif_idx(configmsg)]; + + dev_dbg(dev, "%s(%u, 0x%08x)\n", __func__, val, configmsg); + + switch (val) { + case 16: + format = SNDRV_PCM_FORMAT_S16; + break; + case 24: + format = SNDRV_PCM_FORMAT_S24; + break; + case 32: + format = SNDRV_PCM_FORMAT_S32; + break; + default: + dev_warn(dev, "%s(%u, 0x%08x) invalid argument\n", __func__, + val, configmsg); + break; + } + + return abox_sif_format_put_ipc(dev, format, channels, configmsg); +} + +static int abox_bit_width_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = (unsigned int)ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(0x%08x, %u)\n", __func__, reg, val); + + return abox_bit_width_put_ipc(dev, val, reg); +} + +static int abox_sample_rate_min_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = abox_get_sif_rate_min(data, reg); + + dev_dbg(dev, "%s(0x%08x): %u\n", __func__, reg, val); + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int abox_sample_rate_min_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = (unsigned int)ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(0x%08x, %u)\n", __func__, reg, val); + + abox_set_sif_rate_min(data, reg, val); + + return 0; +} + +static int abox_bit_width_min_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = abox_get_sif_width_min(data, reg); + + dev_dbg(dev, "%s(0x%08x): %u\n", __func__, reg, val); + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int abox_bit_width_min_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = (unsigned int)ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(0x%08x, %u)\n", __func__, reg, val); + + abox_set_sif_width_min(data, reg, val); + + return 0; +} + +static int abox_auto_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = abox_get_sif_auto_config(data, reg); + + dev_dbg(dev, "%s(0x%08x): %u\n", __func__, reg, val); + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int abox_auto_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = (unsigned int)ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(0x%08x, %u)\n", __func__, reg, val); + + abox_set_sif_auto_config(data, reg, !!val); + + return 0; +} + +static int abox_erap_handler_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + enum ABOX_ERAP_TYPE type = (enum ABOX_ERAP_TYPE)mc->reg; + + dev_dbg(dev, "%s(%d)\n", __func__, type); + + ucontrol->value.integer.value[0] = data->erap_status[type]; + + return 0; +} + +static int abox_erap_handler_put_ipc(struct device *dev, + enum ABOX_ERAP_TYPE type, unsigned int val) +{ + struct abox_data *data = dev_get_drvdata(dev); + ABOX_IPC_MSG msg; + struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap; + struct ERAP_ONOFF_PARAM *erap_param = &erap_msg->param.onoff; + int ret; + + dev_dbg(dev, "%s(%u, %d)\n", __func__, val, type); + + msg.ipcid = IPC_ERAP; + erap_msg->msgtype = val ? REALTIME_OPEN : REALTIME_CLOSE; + erap_param->type = type; + erap_param->channel_no = 0; + erap_param->version = val; + ret = abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); + if (ret < 0) + dev_err(dev, "erap control failed(type:%d, status:%d)\n", + type, val); + + data->erap_status[type] = val; + + return ret; +} + +static int abox_erap_handler_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + enum ABOX_ERAP_TYPE type = (enum ABOX_ERAP_TYPE)mc->reg; + unsigned int val = (unsigned int)ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(%u, %d)\n", __func__, val, type); + + return abox_erap_handler_put_ipc(dev, type, val); +} + +static int abox_audio_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int item; + + dev_dbg(dev, "%s: %u\n", __func__, data->audio_mode); + + item = snd_soc_enum_val_to_item(e, data->audio_mode); + ucontrol->value.enumerated.item[0] = item; + + return 0; +} + +static int abox_audio_mode_put_ipc(struct device *dev, enum audio_mode mode) +{ + struct abox_data *data = dev_get_drvdata(dev); + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + + dev_dbg(dev, "%s(%d)\n", __func__, mode); + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_SET_MODE; + system_msg->param1 = data->audio_mode = mode; + + return abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); +} + +static int abox_audio_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + enum audio_mode mode; + + if (item[0] >= e->items) + return -EINVAL; + + mode = snd_soc_enum_item_to_val(e, item[0]); + dev_info(dev, "%s(%u)\n", __func__, mode); + + return abox_audio_mode_put_ipc(dev, mode); +} + +static const char * const abox_audio_mode_enum_texts[] = { + "NORMAL", + "RINGTONE", + "IN_CALL", + "IN_COMMUNICATION", + "IN_VIDEOCALL", +}; +static const unsigned int abox_audio_mode_enum_values[] = { + MODE_NORMAL, + MODE_RINGTONE, + MODE_IN_CALL, + MODE_IN_COMMUNICATION, + MODE_IN_VIDEOCALL, +}; +SOC_VALUE_ENUM_SINGLE_DECL(abox_audio_mode_enum, SND_SOC_NOPM, 0, 0, + abox_audio_mode_enum_texts, abox_audio_mode_enum_values); + +static int abox_sound_type_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int item; + + dev_dbg(dev, "%s: %u\n", __func__, data->sound_type); + + item = snd_soc_enum_val_to_item(e, data->sound_type); + ucontrol->value.enumerated.item[0] = item; + + return 0; +} + +static int abox_sound_type_put_ipc(struct device *dev, enum sound_type type) +{ + struct abox_data *data = dev_get_drvdata(dev); + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + + dev_dbg(dev, "%s(%d)\n", __func__, type); + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_SET_TYPE; + system_msg->param1 = data->sound_type = type; + + return abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); +} + +static int abox_sound_type_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + enum sound_type type; + + if (item[0] >= e->items) + return -EINVAL; + + type = snd_soc_enum_item_to_val(e, item[0]); + dev_info(dev, "%s(%d)\n", __func__, type); + + return abox_sound_type_put_ipc(dev, type); +} +static const char * const abox_sound_type_enum_texts[] = { + "VOICE", + "SPEAKER", + "HEADSET", + "BTVOICE", + "USB", +}; +static const unsigned int abox_sound_type_enum_values[] = { + SOUND_TYPE_VOICE, + SOUND_TYPE_SPEAKER, + SOUND_TYPE_HEADSET, + SOUND_TYPE_BTVOICE, + SOUND_TYPE_USB, +}; +SOC_VALUE_ENUM_SINGLE_DECL(abox_sound_type_enum, SND_SOC_NOPM, 0, 0, + abox_sound_type_enum_texts, abox_sound_type_enum_values); + +static int abox_tickle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + ucontrol->value.integer.value[0] = data->enabled; + + return 0; +} + + +static void abox_tickle_work_func(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct abox_data *data = container_of(dwork, struct abox_data, + tickle_work); + struct device *dev = &data->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + pm_runtime_put(dev); +} +static DECLARE_DELAYED_WORK(abox_tickle_work, abox_tickle_work_func); + +static int abox_tickle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + long val = ucontrol->value.integer.value[0]; + + dev_dbg(dev, "%s(%ld)\n", __func__, val); + + if (!!val) { + pm_runtime_get(dev); + schedule_delayed_work(&data->tickle_work, 1 * HZ); + } + + return 0; +} + +static const struct snd_kcontrol_new abox_cmpnt_controls[] = { + SOC_SINGLE_EXT("Sampling Rate Mixer", SET_MIXER_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Out1", SET_OUT1_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Out2", SET_OUT2_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Recp", SET_RECP_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Inmux0", SET_INMUX0_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Inmux1", SET_INMUX1_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Inmux2", SET_INMUX2_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Inmux3", SET_INMUX3_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Sampling Rate Inmux4", SET_INMUX4_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_get, abox_sample_rate_put), + SOC_SINGLE_EXT("Bit Width Mixer", SET_MIXER_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Out1", SET_OUT1_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Out2", SET_OUT2_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Recp", SET_RECP_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Inmux0", SET_INMUX0_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Inmux1", SET_INMUX1_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Inmux2", SET_INMUX2_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Inmux3", SET_INMUX3_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Bit Width Inmux4", SET_INMUX4_FORMAT, 16, 32, 0, + abox_bit_width_get, abox_bit_width_put), + SOC_SINGLE_EXT("Sampling Rate Mixer Min", SET_MIXER_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Out1 Min", SET_OUT1_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Out2 Min", SET_OUT2_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Recp Min", SET_RECP_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Inmux0 Min", SET_INMUX0_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Inmux1 Min", SET_INMUX1_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Inmux2 Min", SET_INMUX2_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Inmux3 Min", SET_INMUX3_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Sampling Rate Inmux4 Min", SET_INMUX4_SAMPLE_RATE, + 8000, 384000, 0, + abox_sample_rate_min_get, abox_sample_rate_min_put), + SOC_SINGLE_EXT("Bit Width Mixer Min", SET_MIXER_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Out1 Min", SET_OUT1_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Out2 Min", SET_OUT2_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Recp Min", SET_RECP_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Inmux0 Min", SET_INMUX0_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Inmux1 Min", SET_INMUX1_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Inmux2 Min", SET_INMUX2_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Inmux3 Min", SET_INMUX3_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Bit Width Inmux4 Min", SET_INMUX4_FORMAT, 16, 32, 0, + abox_bit_width_min_get, abox_bit_width_min_put), + SOC_SINGLE_EXT("Auto Config Mixer", SET_MIXER_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Out1", SET_OUT1_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Out2", SET_OUT2_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Recp", SET_RECP_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Inmux0", SET_INMUX0_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Inmux1", SET_INMUX1_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Inmux2", SET_INMUX2_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Inmux3", SET_INMUX3_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Auto Config Inmux4", SET_INMUX4_SAMPLE_RATE, 0, 1, 0, + abox_auto_config_get, abox_auto_config_put), + SOC_SINGLE_EXT("Echo Cancellation", ERAP_ECHO_CANCEL, 0, 2, 0, + abox_erap_handler_get, abox_erap_handler_put), + SOC_SINGLE_EXT("VI Sensing", ERAP_VI_SENSE, 0, 2, 0, + abox_erap_handler_get, abox_erap_handler_put), + SOC_VALUE_ENUM_EXT("Audio Mode", abox_audio_mode_enum, + abox_audio_mode_get, abox_audio_mode_put), + SOC_VALUE_ENUM_EXT("Sound Type", abox_sound_type_enum, + abox_sound_type_get, abox_sound_type_put), + SOC_SINGLE_EXT("Tickle", 0, 0, 1, 0, abox_tickle_get, abox_tickle_put), +}; + +static const char * const spus_inx_texts[] = {"RDMA", "SIFSM"}; +static SOC_ENUM_SINGLE_DECL(spus_in0_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(0), spus_inx_texts); +static const struct snd_kcontrol_new spus_in0_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in0_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_in1_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(1), spus_inx_texts); +static const struct snd_kcontrol_new spus_in1_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in1_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_in2_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(2), spus_inx_texts); +static const struct snd_kcontrol_new spus_in2_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in2_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_in3_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(3), spus_inx_texts); +static const struct snd_kcontrol_new spus_in3_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in3_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_in4_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(4), spus_inx_texts); +static const struct snd_kcontrol_new spus_in4_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in4_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_in5_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(5), spus_inx_texts); +static const struct snd_kcontrol_new spus_in5_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in5_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_in6_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(6), spus_inx_texts); +static const struct snd_kcontrol_new spus_in6_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in6_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_in7_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_IN_L(7), spus_inx_texts); +static const struct snd_kcontrol_new spus_in7_controls[] = { + SOC_DAPM_ENUM("MUX", spus_in7_enum), +}; + +static const char * const spus_asrcx_texts[] = {"Off", "On"}; +static SOC_ENUM_SINGLE_DECL(spus_asrc0_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(0), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc0_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc0_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_asrc1_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(1), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc1_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc1_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_asrc2_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(2), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc2_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc2_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_asrc3_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(3), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc3_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc3_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_asrc4_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(4), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc4_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc4_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_asrc5_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(5), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc5_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc5_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_asrc6_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(6), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc6_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc6_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_asrc7_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_ASRC_L(7), spus_asrcx_texts); +static const struct snd_kcontrol_new spus_asrc7_controls[] = { + SOC_DAPM_ENUM("ASRC", spus_asrc7_enum), +}; + +static const char * const spus_outx_texts[] = {"SIFS1", "SIFS0", "SIFS2"}; +static SOC_ENUM_SINGLE_DECL(spus_out0_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(0), spus_outx_texts); +static const struct snd_kcontrol_new spus_out0_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out0_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_out1_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(1), spus_outx_texts); +static const struct snd_kcontrol_new spus_out1_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out1_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_out2_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(2), spus_outx_texts); +static const struct snd_kcontrol_new spus_out2_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out2_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_out3_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(3), spus_outx_texts); +static const struct snd_kcontrol_new spus_out3_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out3_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_out4_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(4), spus_outx_texts); +static const struct snd_kcontrol_new spus_out4_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out4_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_out5_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(5), spus_outx_texts); +static const struct snd_kcontrol_new spus_out5_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out5_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_out6_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(6), spus_outx_texts); +static const struct snd_kcontrol_new spus_out6_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out6_enum), +}; +static SOC_ENUM_SINGLE_DECL(spus_out7_enum, ABOX_SPUS_CTRL0, + ABOX_FUNC_CHAIN_SRC_OUT_L(7), spus_outx_texts); +static const struct snd_kcontrol_new spus_out7_controls[] = { + SOC_DAPM_ENUM("DEMUX", spus_out7_enum), +}; + +static const char * const spusm_texts[] = { + "RESERVED", "RESERVED", "RESERVED", "RESERVED", + "RESERVED", "RESERVED", "RESERVED", "RESERVED", + "UAIF0", "UAIF1", "UAIF2", "UAIF3", "UAIF4", + "RESERVED", "RESERVED", "SPDY", +}; +static SOC_ENUM_SINGLE_DECL(spusm_enum, ABOX_ROUTE_CTRL1, ABOX_ROUTE_SPUSM_L, + spusm_texts); +static const struct snd_kcontrol_new spusm_controls[] = { + SOC_DAPM_ENUM("MUX", spusm_enum), +}; + +static int abox_flush_mixp(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUS_CTRL2, + ABOX_SPUS_MIXP_FLUSH_MASK, + 1 << ABOX_SPUS_MIXP_FLUSH_L); + + return 0; +} + +static int abox_flush_sifm(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_input_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUS_CTRL3, + ABOX_SPUS_SIFM_FLUSH_MASK, + 1 << ABOX_SPUS_SIFM_FLUSH_L); + } + + return 0; +} + +static int abox_flush_sifs1(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_input_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUS_CTRL3, + ABOX_SPUS_SIFS1_FLUSH_MASK, + 1 << ABOX_SPUS_SIFS1_FLUSH_L); + } + + return 0; +} + +static int abox_flush_sifs2(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_input_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUS_CTRL3, + ABOX_SPUS_SIFS2_FLUSH_MASK, + 1 << ABOX_SPUS_SIFS2_FLUSH_L); + } + + return 0; +} + +static int abox_flush_recp(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_output_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUM_CTRL2, + ABOX_SPUM_RECP_FLUSH_MASK, + 1 << ABOX_SPUM_RECP_FLUSH_L); + } + + return 0; +} + +static int abox_flush_sifm0(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_output_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUM_CTRL3, + ABOX_SPUM_SIFM0_FLUSH_MASK, + 1 << ABOX_SPUM_SIFM0_FLUSH_L); + } + + return 0; +} + +static int abox_flush_sifm1(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_output_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUM_CTRL3, + ABOX_SPUM_SIFM1_FLUSH_MASK, + 1 << ABOX_SPUM_SIFM1_FLUSH_L); + } + + return 0; +} + +static int abox_flush_sifm2(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_output_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUM_CTRL3, + ABOX_SPUM_SIFM2_FLUSH_MASK, + 1 << ABOX_SPUM_SIFM2_FLUSH_L); + } + + return 0; +} + +static int abox_flush_sifm3(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct device *dev = cmpnt->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!snd_soc_dapm_connected_output_ep(w, NULL)) { + dev_info(dev, "%s: flush\n", __func__); + snd_soc_component_update_bits(cmpnt, ABOX_SPUM_CTRL3, + ABOX_SPUM_SIFM3_FLUSH_MASK, + 1 << ABOX_SPUM_SIFM3_FLUSH_L); + } + + return 0; +} + +static const char * const sifsx_texts[] = { + "SPUS OUT0", "SPUS OUT1", "SPUS OUT2", "SPUS OUT3", + "SPUS OUT4", "SPUS OUT5", "SPUS OUT6", "SPUS OUT7", +}; +static SOC_ENUM_SINGLE_DECL(sifs1_enum, ABOX_SPUS_CTRL1, ABOX_SIFS_OUT1_SEL_L, + sifsx_texts); +static const struct snd_kcontrol_new sifs1_controls[] = { + SOC_DAPM_ENUM("MUX", sifs1_enum), +}; +static SOC_ENUM_SINGLE_DECL(sifs2_enum, ABOX_SPUS_CTRL1, ABOX_SIFS_OUT2_SEL_L, + sifsx_texts); +static const struct snd_kcontrol_new sifs2_controls[] = { + SOC_DAPM_ENUM("MUX", sifs2_enum), +}; + +static const char * const sifsm_texts[] = { + "SPUS IN0", "SPUS IN1", "SPUS IN2", "SPUS IN3", + "SPUS IN4", "SPUS IN5", "SPUS IN6", "SPUS IN7", +}; +static SOC_ENUM_SINGLE_DECL(sifsm_enum, ABOX_SPUS_CTRL1, ABOX_SIFM_IN_SEL_L, + sifsm_texts); +static const struct snd_kcontrol_new sifsm_controls[] = { + SOC_DAPM_ENUM("DEMUX", sifsm_enum), +}; + +static const char * const uaif_spkx_texts[] = { + "RESERVED", "SIFS0", "SIFS1", "SIFS2", + "RESERVED", "RESERVED", "RESERVED", "RESERVED", + "RESERVED", "RESERVED", "RESERVED", "RESERVED", + "SIFMS", +}; +static SOC_ENUM_SINGLE_DECL(uaif0_spk_enum, ABOX_ROUTE_CTRL0, + ABOX_ROUTE_UAIF_SPK_L(0), uaif_spkx_texts); +static const struct snd_kcontrol_new uaif0_spk_controls[] = { + SOC_DAPM_ENUM("MUX", uaif0_spk_enum), +}; +static SOC_ENUM_SINGLE_DECL(uaif1_spk_enum, ABOX_ROUTE_CTRL0, + ABOX_ROUTE_UAIF_SPK_L(1), uaif_spkx_texts); +static const struct snd_kcontrol_new uaif1_spk_controls[] = { + SOC_DAPM_ENUM("MUX", uaif1_spk_enum), +}; +static SOC_ENUM_SINGLE_DECL(uaif2_spk_enum, ABOX_ROUTE_CTRL0, + ABOX_ROUTE_UAIF_SPK_L(2), uaif_spkx_texts); +static const struct snd_kcontrol_new uaif2_spk_controls[] = { + SOC_DAPM_ENUM("MUX", uaif2_spk_enum), +}; +static SOC_ENUM_SINGLE_DECL(uaif3_spk_enum, ABOX_ROUTE_CTRL0, + ABOX_ROUTE_UAIF_SPK_L(3), uaif_spkx_texts); +static const struct snd_kcontrol_new uaif3_spk_controls[] = { + SOC_DAPM_ENUM("MUX", uaif3_spk_enum), +}; +static SOC_ENUM_SINGLE_DECL(uaif4_spk_enum, ABOX_ROUTE_CTRL0, + ABOX_ROUTE_UAIF_SPK_L(4), uaif_spkx_texts); +static const struct snd_kcontrol_new uaif4_spk_controls[] = { + SOC_DAPM_ENUM("MUX", uaif4_spk_enum), +}; + +static const char * const dsif_spk_texts[] = { + "RESERVED", "RESERVED", "SIFS1", "SIFS2", +}; +static SOC_ENUM_SINGLE_DECL(dsif_spk_enum, ABOX_ROUTE_CTRL0, 20, + dsif_spk_texts); +static const struct snd_kcontrol_new dsif_spk_controls[] = { + SOC_DAPM_ENUM("MUX", dsif_spk_enum), +}; + +static const char * const rsrcx_texts[] = { + "RESERVED", "SIFS0", "SIFS1", "SIFS2", + "RESERVED", "RESERVED", "RESERVED", "RESERVED", + "NSRC0", "NSRC1", "NSRC2", "NSRC3", +}; +static SOC_ENUM_SINGLE_DECL(rsrc0_enum, ABOX_ROUTE_CTRL2, ABOX_ROUTE_RSRC_L(0), + rsrcx_texts); +static const struct snd_kcontrol_new rsrc0_controls[] = { + SOC_DAPM_ENUM("DEMUX", rsrc0_enum), +}; +static SOC_ENUM_SINGLE_DECL(rsrc1_enum, ABOX_ROUTE_CTRL2, ABOX_ROUTE_RSRC_L(1), + rsrcx_texts); +static const struct snd_kcontrol_new rsrc1_controls[] = { + SOC_DAPM_ENUM("DEMUX", rsrc1_enum), +}; + +static const char * const nsrcx_texts[] = { + "RESERVED", "SIFS0", "SIFS1", "SIFS2", + "RESERVED", "RESERVED", "RESERVED", "RESERVED", + "UAIF0", "UAIF1", "UAIF2", "UAIF3", "UAIF4", + "RESERVED", "RESERVED", "SPDY", +}; +static SOC_ENUM_SINGLE_DECL(nsrc0_enum, ABOX_ROUTE_CTRL1, ABOX_ROUTE_NSRC_L(0), + nsrcx_texts); +static const struct snd_kcontrol_new nsrc0_controls[] = { + SOC_DAPM_ENUM("DEMUX", nsrc0_enum), +}; +static SOC_ENUM_SINGLE_DECL(nsrc1_enum, ABOX_ROUTE_CTRL1, ABOX_ROUTE_NSRC_L(1), + nsrcx_texts); +static const struct snd_kcontrol_new nsrc1_controls[] = { + SOC_DAPM_ENUM("DEMUX", nsrc1_enum), +}; +static SOC_ENUM_SINGLE_DECL(nsrc2_enum, ABOX_ROUTE_CTRL1, ABOX_ROUTE_NSRC_L(2), + nsrcx_texts); +static const struct snd_kcontrol_new nsrc2_controls[] = { + SOC_DAPM_ENUM("DEMUX", nsrc2_enum), +}; +static SOC_ENUM_SINGLE_DECL(nsrc3_enum, ABOX_ROUTE_CTRL1, ABOX_ROUTE_NSRC_L(3), + nsrcx_texts); +static const struct snd_kcontrol_new nsrc3_controls[] = { + SOC_DAPM_ENUM("DEMUX", nsrc3_enum), +}; + +static const struct snd_kcontrol_new recp_controls[] = { + SOC_DAPM_SINGLE("PIFS0", ABOX_SPUM_CTRL1, ABOX_RECP_SRC_VALID_L, 1, 0), + SOC_DAPM_SINGLE("PIFS1", ABOX_SPUM_CTRL1, ABOX_RECP_SRC_VALID_H, 1, 0), +}; + +static const char * const spum_asrcx_texts[] = {"Off", "On"}; +static SOC_ENUM_SINGLE_DECL(spum_asrc0_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_RSRC_ASRC_L, spum_asrcx_texts); +static const struct snd_kcontrol_new spum_asrc0_controls[] = { + SOC_DAPM_ENUM("ASRC", spum_asrc0_enum), +}; +static SOC_ENUM_SINGLE_DECL(spum_asrc1_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_NSRC_ASRC_L(0), spum_asrcx_texts); +static const struct snd_kcontrol_new spum_asrc1_controls[] = { + SOC_DAPM_ENUM("ASRC", spum_asrc1_enum), +}; +static SOC_ENUM_SINGLE_DECL(spum_asrc2_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_NSRC_ASRC_L(1), spum_asrcx_texts); +static const struct snd_kcontrol_new spum_asrc2_controls[] = { + SOC_DAPM_ENUM("ASRC", spum_asrc2_enum), +}; +static SOC_ENUM_SINGLE_DECL(spum_asrc3_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_NSRC_ASRC_L(2), spum_asrcx_texts); +static const struct snd_kcontrol_new spum_asrc3_controls[] = { + SOC_DAPM_ENUM("ASRC", spum_asrc3_enum), +}; + +static const char * const sifmx_texts[] = { + "WDMA", "SIFMS", +}; +static SOC_ENUM_SINGLE_DECL(sifm0_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_NSRC_OUT_L(0), sifmx_texts); +static const struct snd_kcontrol_new sifm0_controls[] = { + SOC_DAPM_ENUM("DEMUX", sifm0_enum), +}; +static SOC_ENUM_SINGLE_DECL(sifm1_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_NSRC_OUT_L(1), sifmx_texts); +static const struct snd_kcontrol_new sifm1_controls[] = { + SOC_DAPM_ENUM("DEMUX", sifm1_enum), +}; +static SOC_ENUM_SINGLE_DECL(sifm2_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_NSRC_OUT_L(2), sifmx_texts); +static const struct snd_kcontrol_new sifm2_controls[] = { + SOC_DAPM_ENUM("DEMUX", sifm2_enum), +}; +static SOC_ENUM_SINGLE_DECL(sifm3_enum, ABOX_SPUM_CTRL0, + ABOX_FUNC_CHAIN_NSRC_OUT_L(3), sifmx_texts); +static const struct snd_kcontrol_new sifm3_controls[] = { + SOC_DAPM_ENUM("DEMUX", sifm3_enum), +}; + +static const char * const sifms_texts[] = { + "RESERVED", "SIFM0", "SIFM1", "SIFM2", "SIFM3", +}; +static SOC_ENUM_SINGLE_DECL(sifms_enum, ABOX_SPUM_CTRL1, ABOX_SIFS_OUT_SEL_L, + sifms_texts); +static const struct snd_kcontrol_new sifms_controls[] = { + SOC_DAPM_ENUM("MUX", sifms_enum), +}; + +static const struct snd_soc_dapm_widget abox_cmpnt_dapm_widgets[] = { + SND_SOC_DAPM_MUX("SPUSM", SND_SOC_NOPM, 0, 0, spusm_controls), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN5", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN6", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFSM-SPUS IN7", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_DEMUX_E("SIFSM", SND_SOC_NOPM, 0, 0, sifsm_controls, + abox_flush_sifm, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SPUS IN0", SND_SOC_NOPM, 0, 0, spus_in0_controls), + SND_SOC_DAPM_MUX("SPUS IN1", SND_SOC_NOPM, 0, 0, spus_in1_controls), + SND_SOC_DAPM_MUX("SPUS IN2", SND_SOC_NOPM, 0, 0, spus_in2_controls), + SND_SOC_DAPM_MUX("SPUS IN3", SND_SOC_NOPM, 0, 0, spus_in3_controls), + SND_SOC_DAPM_MUX("SPUS IN4", SND_SOC_NOPM, 0, 0, spus_in4_controls), + SND_SOC_DAPM_MUX("SPUS IN5", SND_SOC_NOPM, 0, 0, spus_in5_controls), + SND_SOC_DAPM_MUX("SPUS IN6", SND_SOC_NOPM, 0, 0, spus_in6_controls), + SND_SOC_DAPM_MUX("SPUS IN7", SND_SOC_NOPM, 0, 0, spus_in7_controls), + SND_SOC_DAPM_MUX("SPUS ASRC0", SND_SOC_NOPM, 0, 0, spus_asrc0_controls), + SND_SOC_DAPM_MUX("SPUS ASRC1", SND_SOC_NOPM, 0, 0, spus_asrc1_controls), + SND_SOC_DAPM_MUX("SPUS ASRC2", SND_SOC_NOPM, 0, 0, spus_asrc2_controls), + SND_SOC_DAPM_MUX("SPUS ASRC3", SND_SOC_NOPM, 0, 0, spus_asrc3_controls), + SND_SOC_DAPM_MUX("SPUS ASRC4", SND_SOC_NOPM, 0, 0, spus_asrc4_controls), + SND_SOC_DAPM_MUX("SPUS ASRC5", SND_SOC_NOPM, 0, 0, spus_asrc5_controls), + SND_SOC_DAPM_MUX("SPUS ASRC6", SND_SOC_NOPM, 0, 0, spus_asrc6_controls), + SND_SOC_DAPM_MUX("SPUS ASRC7", SND_SOC_NOPM, 0, 0, spus_asrc7_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT0", SND_SOC_NOPM, 0, 0, spus_out0_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT1", SND_SOC_NOPM, 0, 0, spus_out1_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT2", SND_SOC_NOPM, 0, 0, spus_out2_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT3", SND_SOC_NOPM, 0, 0, spus_out3_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT4", SND_SOC_NOPM, 0, 0, spus_out4_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT5", SND_SOC_NOPM, 0, 0, spus_out5_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT6", SND_SOC_NOPM, 0, 0, spus_out6_controls), + SND_SOC_DAPM_DEMUX("SPUS OUT7", SND_SOC_NOPM, 0, 0, spus_out7_controls), + + SND_SOC_DAPM_PGA("SPUS OUT0-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT1-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT2-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT3-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT4-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT5-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT6-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT7-SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT0-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT1-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT2-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT3-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT4-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT5-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT6-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT7-SIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT0-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT1-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT2-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT3-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT4-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT5-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT6-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPUS OUT7-SIFS2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER_E("SIFS0", SND_SOC_NOPM, 0, 0, NULL, 0, + abox_flush_mixp, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX_E("SIFS1", SND_SOC_NOPM, 0, 0, sifs1_controls, + abox_flush_sifs1, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX_E("SIFS2", SND_SOC_NOPM, 0, 0, sifs2_controls, + abox_flush_sifs2, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("UAIF0 SPK", SND_SOC_NOPM, 0, 0, uaif0_spk_controls), + SND_SOC_DAPM_MUX("UAIF1 SPK", SND_SOC_NOPM, 0, 0, uaif1_spk_controls), + SND_SOC_DAPM_MUX("UAIF2 SPK", SND_SOC_NOPM, 0, 0, uaif2_spk_controls), + SND_SOC_DAPM_MUX("UAIF3 SPK", SND_SOC_NOPM, 0, 0, uaif3_spk_controls), + SND_SOC_DAPM_MUX("UAIF4 SPK", SND_SOC_NOPM, 0, 0, uaif4_spk_controls), + SND_SOC_DAPM_MUX("DSIF SPK", SND_SOC_NOPM, 0, 0, dsif_spk_controls), + + SND_SOC_DAPM_MUX("RSRC0", SND_SOC_NOPM, 0, 0, rsrc0_controls), + SND_SOC_DAPM_MUX("RSRC1", SND_SOC_NOPM, 0, 0, rsrc1_controls), + SND_SOC_DAPM_MUX("NSRC0", SND_SOC_NOPM, 0, 0, nsrc0_controls), + SND_SOC_DAPM_MUX("NSRC1", SND_SOC_NOPM, 0, 0, nsrc1_controls), + SND_SOC_DAPM_MUX("NSRC2", SND_SOC_NOPM, 0, 0, nsrc2_controls), + SND_SOC_DAPM_MUX("NSRC3", SND_SOC_NOPM, 0, 0, nsrc3_controls), + + SND_SOC_DAPM_PGA("PIFS0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PIFS1", SND_SOC_NOPM, 0, 0, NULL, 0), + SOC_MIXER_E_ARRAY("RECP", SND_SOC_NOPM, 0, 0, recp_controls, + abox_flush_recp, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SPUM ASRC0", SND_SOC_NOPM, 0, 0, spum_asrc0_controls), + SND_SOC_DAPM_MUX("SPUM ASRC1", SND_SOC_NOPM, 0, 0, spum_asrc1_controls), + SND_SOC_DAPM_MUX("SPUM ASRC2", SND_SOC_NOPM, 0, 0, spum_asrc2_controls), + SND_SOC_DAPM_MUX("SPUM ASRC3", SND_SOC_NOPM, 0, 0, spum_asrc3_controls), + SND_SOC_DAPM_DEMUX_E("SIFM0", SND_SOC_NOPM, 0, 0, sifm0_controls, + abox_flush_sifm0, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DEMUX_E("SIFM1", SND_SOC_NOPM, 0, 0, sifm1_controls, + abox_flush_sifm1, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DEMUX_E("SIFM2", SND_SOC_NOPM, 0, 0, sifm2_controls, + abox_flush_sifm2, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DEMUX_E("SIFM3", SND_SOC_NOPM, 0, 0, sifm3_controls, + abox_flush_sifm3, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA("SIFM0-SIFMS", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFM1-SIFMS", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFM2-SIFMS", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SIFM3-SIFMS", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("SIFMS", SND_SOC_NOPM, 0, 0, sifms_controls), + + SND_SOC_DAPM_AIF_IN("UAIF0IN", "UAIF0 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("UAIF1IN", "UAIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("UAIF2IN", "UAIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("UAIF3IN", "UAIF3 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("UAIF4IN", "UAIF4 Capture", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT("UAIF0OUT", "UAIF0 Playback", 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("UAIF1OUT", "UAIF1 Playback", 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("UAIF2OUT", "UAIF2 Playback", 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("UAIF3OUT", "UAIF3 Playback", 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("UAIF4OUT", "UAIF4 Playback", 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("DSIFOUT", "DSIF Playback", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route abox_cmpnt_dapm_routes[] = { + /* sink, control, source */ + {"SIFSM", NULL, "SPUSM"}, + {"SIFSM-SPUS IN0", "SPUS IN0", "SIFSM"}, + {"SIFSM-SPUS IN1", "SPUS IN1", "SIFSM"}, + {"SIFSM-SPUS IN2", "SPUS IN2", "SIFSM"}, + {"SIFSM-SPUS IN3", "SPUS IN3", "SIFSM"}, + {"SIFSM-SPUS IN4", "SPUS IN4", "SIFSM"}, + {"SIFSM-SPUS IN5", "SPUS IN5", "SIFSM"}, + {"SIFSM-SPUS IN6", "SPUS IN6", "SIFSM"}, + {"SIFSM-SPUS IN7", "SPUS IN7", "SIFSM"}, + + {"SPUS IN0", "RDMA", "RDMA0 Playback"}, + {"SPUS IN0", "SIFSM", "SIFSM-SPUS IN0"}, + {"SPUS IN1", "RDMA", "RDMA1 Playback"}, + {"SPUS IN1", "SIFSM", "SIFSM-SPUS IN1"}, + {"SPUS IN2", "RDMA", "RDMA2 Playback"}, + {"SPUS IN2", "SIFSM", "SIFSM-SPUS IN2"}, + {"SPUS IN3", "RDMA", "RDMA3 Playback"}, + {"SPUS IN3", "SIFSM", "SIFSM-SPUS IN3"}, + {"SPUS IN4", "RDMA", "RDMA4 Playback"}, + {"SPUS IN4", "SIFSM", "SIFSM-SPUS IN4"}, + {"SPUS IN5", "RDMA", "RDMA5 Playback"}, + {"SPUS IN5", "SIFSM", "SIFSM-SPUS IN5"}, + {"SPUS IN6", "RDMA", "RDMA6 Playback"}, + {"SPUS IN6", "SIFSM", "SIFSM-SPUS IN6"}, + {"SPUS IN7", "RDMA", "RDMA7 Playback"}, + {"SPUS IN7", "SIFSM", "SIFSM-SPUS IN7"}, + + {"SPUS ASRC0", "On", "SPUS IN0"}, + {"SPUS ASRC0", "Off", "SPUS IN0"}, + {"SPUS ASRC1", "On", "SPUS IN1"}, + {"SPUS ASRC1", "Off", "SPUS IN1"}, + {"SPUS ASRC2", "On", "SPUS IN2"}, + {"SPUS ASRC2", "Off", "SPUS IN2"}, + {"SPUS ASRC3", "On", "SPUS IN3"}, + {"SPUS ASRC3", "Off", "SPUS IN3"}, + {"SPUS ASRC4", "On", "SPUS IN4"}, + {"SPUS ASRC4", "Off", "SPUS IN4"}, + {"SPUS ASRC5", "On", "SPUS IN5"}, + {"SPUS ASRC5", "Off", "SPUS IN5"}, + {"SPUS ASRC6", "On", "SPUS IN6"}, + {"SPUS ASRC6", "Off", "SPUS IN6"}, + {"SPUS ASRC7", "On", "SPUS IN7"}, + {"SPUS ASRC7", "Off", "SPUS IN7"}, + + {"SPUS OUT0", NULL, "SPUS ASRC0"}, + {"SPUS OUT1", NULL, "SPUS ASRC1"}, + {"SPUS OUT2", NULL, "SPUS ASRC2"}, + {"SPUS OUT3", NULL, "SPUS ASRC3"}, + {"SPUS OUT4", NULL, "SPUS ASRC4"}, + {"SPUS OUT5", NULL, "SPUS ASRC5"}, + {"SPUS OUT6", NULL, "SPUS ASRC6"}, + {"SPUS OUT7", NULL, "SPUS ASRC7"}, + + {"SPUS OUT0-SIFS0", "SIFS0", "SPUS OUT0"}, + {"SPUS OUT1-SIFS0", "SIFS0", "SPUS OUT1"}, + {"SPUS OUT2-SIFS0", "SIFS0", "SPUS OUT2"}, + {"SPUS OUT3-SIFS0", "SIFS0", "SPUS OUT3"}, + {"SPUS OUT4-SIFS0", "SIFS0", "SPUS OUT4"}, + {"SPUS OUT5-SIFS0", "SIFS0", "SPUS OUT5"}, + {"SPUS OUT6-SIFS0", "SIFS0", "SPUS OUT6"}, + {"SPUS OUT7-SIFS0", "SIFS0", "SPUS OUT7"}, + {"SPUS OUT0-SIFS1", "SIFS1", "SPUS OUT0"}, + {"SPUS OUT1-SIFS1", "SIFS1", "SPUS OUT1"}, + {"SPUS OUT2-SIFS1", "SIFS1", "SPUS OUT2"}, + {"SPUS OUT3-SIFS1", "SIFS1", "SPUS OUT3"}, + {"SPUS OUT4-SIFS1", "SIFS1", "SPUS OUT4"}, + {"SPUS OUT5-SIFS1", "SIFS1", "SPUS OUT5"}, + {"SPUS OUT6-SIFS1", "SIFS1", "SPUS OUT6"}, + {"SPUS OUT7-SIFS1", "SIFS1", "SPUS OUT7"}, + {"SPUS OUT0-SIFS2", "SIFS2", "SPUS OUT0"}, + {"SPUS OUT1-SIFS2", "SIFS2", "SPUS OUT1"}, + {"SPUS OUT2-SIFS2", "SIFS2", "SPUS OUT2"}, + {"SPUS OUT3-SIFS2", "SIFS2", "SPUS OUT3"}, + {"SPUS OUT4-SIFS2", "SIFS2", "SPUS OUT4"}, + {"SPUS OUT5-SIFS2", "SIFS2", "SPUS OUT5"}, + {"SPUS OUT6-SIFS2", "SIFS2", "SPUS OUT6"}, + {"SPUS OUT7-SIFS2", "SIFS2", "SPUS OUT7"}, + + {"SIFS0", NULL, "SPUS OUT0-SIFS0"}, + {"SIFS0", NULL, "SPUS OUT1-SIFS0"}, + {"SIFS0", NULL, "SPUS OUT2-SIFS0"}, + {"SIFS0", NULL, "SPUS OUT3-SIFS0"}, + {"SIFS0", NULL, "SPUS OUT4-SIFS0"}, + {"SIFS0", NULL, "SPUS OUT5-SIFS0"}, + {"SIFS0", NULL, "SPUS OUT6-SIFS0"}, + {"SIFS0", NULL, "SPUS OUT7-SIFS0"}, + {"SIFS1", "SPUS OUT0", "SPUS OUT0-SIFS1"}, + {"SIFS1", "SPUS OUT1", "SPUS OUT1-SIFS1"}, + {"SIFS1", "SPUS OUT2", "SPUS OUT2-SIFS1"}, + {"SIFS1", "SPUS OUT3", "SPUS OUT3-SIFS1"}, + {"SIFS1", "SPUS OUT4", "SPUS OUT4-SIFS1"}, + {"SIFS1", "SPUS OUT5", "SPUS OUT5-SIFS1"}, + {"SIFS1", "SPUS OUT6", "SPUS OUT6-SIFS1"}, + {"SIFS1", "SPUS OUT7", "SPUS OUT7-SIFS1"}, + {"SIFS2", "SPUS OUT0", "SPUS OUT0-SIFS2"}, + {"SIFS2", "SPUS OUT1", "SPUS OUT1-SIFS2"}, + {"SIFS2", "SPUS OUT2", "SPUS OUT2-SIFS2"}, + {"SIFS2", "SPUS OUT3", "SPUS OUT3-SIFS2"}, + {"SIFS2", "SPUS OUT4", "SPUS OUT4-SIFS2"}, + {"SIFS2", "SPUS OUT5", "SPUS OUT5-SIFS2"}, + {"SIFS2", "SPUS OUT6", "SPUS OUT6-SIFS2"}, + {"SIFS2", "SPUS OUT7", "SPUS OUT7-SIFS2"}, + + {"SIFS0 Playback", NULL, "SIFS0"}, + {"SIFS1 Playback", NULL, "SIFS1"}, + {"SIFS2 Playback", NULL, "SIFS2"}, + + {"UAIF0 SPK", "SIFS0", "SIFS0"}, + {"UAIF0 SPK", "SIFS1", "SIFS1"}, + {"UAIF0 SPK", "SIFS2", "SIFS2"}, + {"UAIF0 SPK", "SIFMS", "SIFMS"}, + {"UAIF1 SPK", "SIFS0", "SIFS0"}, + {"UAIF1 SPK", "SIFS1", "SIFS1"}, + {"UAIF1 SPK", "SIFS2", "SIFS2"}, + {"UAIF1 SPK", "SIFMS", "SIFMS"}, + {"UAIF2 SPK", "SIFS0", "SIFS0"}, + {"UAIF2 SPK", "SIFS1", "SIFS1"}, + {"UAIF2 SPK", "SIFS2", "SIFS2"}, + {"UAIF2 SPK", "SIFMS", "SIFMS"}, + {"UAIF3 SPK", "SIFS0", "SIFS0"}, + {"UAIF3 SPK", "SIFS1", "SIFS1"}, + {"UAIF3 SPK", "SIFS2", "SIFS2"}, + {"UAIF3 SPK", "SIFMS", "SIFMS"}, + {"UAIF4 SPK", "SIFS0", "SIFS0"}, + {"UAIF4 SPK", "SIFS1", "SIFS1"}, + {"UAIF4 SPK", "SIFS2", "SIFS2"}, + {"UAIF4 SPK", "SIFMS", "SIFMS"}, + {"DSIF SPK", "SIFS1", "SIFS1"}, + {"DSIF SPK", "SIFS2", "SIFS2"}, + + {"RSRC0", "SIFS0", "SIFS0 Capture"}, + {"RSRC0", "SIFS1", "SIFS1 Capture"}, + {"RSRC0", "SIFS2", "SIFS2 Capture"}, + {"RSRC0", "NSRC0", "NSRC0"}, + {"RSRC0", "NSRC1", "NSRC1"}, + {"RSRC0", "NSRC2", "NSRC2"}, + {"RSRC0", "NSRC3", "NSRC3"}, + {"RSRC1", "SIFS0", "SIFS0 Capture"}, + {"RSRC1", "SIFS1", "SIFS1 Capture"}, + {"RSRC1", "SIFS2", "SIFS2 Capture"}, + {"RSRC1", "NSRC0", "NSRC0"}, + {"RSRC1", "NSRC1", "NSRC1"}, + {"RSRC1", "NSRC2", "NSRC2"}, + {"RSRC1", "NSRC3", "NSRC3"}, + + {"NSRC0", "SIFS0", "SIFS0 Capture"}, + {"NSRC0", "SIFS1", "SIFS1 Capture"}, + {"NSRC0", "SIFS2", "SIFS2 Capture"}, + {"NSRC1", "SIFS0", "SIFS0 Capture"}, + {"NSRC1", "SIFS1", "SIFS1 Capture"}, + {"NSRC1", "SIFS2", "SIFS2 Capture"}, + {"NSRC2", "SIFS0", "SIFS0 Capture"}, + {"NSRC2", "SIFS1", "SIFS1 Capture"}, + {"NSRC2", "SIFS2", "SIFS2 Capture"}, + {"NSRC3", "SIFS0", "SIFS0 Capture"}, + {"NSRC3", "SIFS1", "SIFS1 Capture"}, + {"NSRC3", "SIFS2", "SIFS2 Capture"}, + + {"PIFS0", NULL, "RSRC0"}, + {"PIFS1", NULL, "RSRC1"}, + {"RECP", "PIFS0", "PIFS0"}, + {"RECP", "PIFS1", "PIFS1"}, + + {"SPUM ASRC0", "On", "RECP"}, + {"SPUM ASRC0", "Off", "RECP"}, + {"SPUM ASRC1", "On", "NSRC0"}, + {"SPUM ASRC1", "Off", "NSRC0"}, + {"SPUM ASRC2", "On", "NSRC1"}, + {"SPUM ASRC2", "Off", "NSRC1"}, + {"SPUM ASRC3", "On", "NSRC2"}, + {"SPUM ASRC3", "Off", "NSRC2"}, + + {"SIFM0", NULL, "SPUM ASRC1"}, + {"SIFM1", NULL, "SPUM ASRC2"}, + {"SIFM2", NULL, "SPUM ASRC3"}, + {"SIFM3", NULL, "NSRC3"}, + + {"SIFM0-SIFMS", "SIFMS", "SIFM0"}, + {"SIFM1-SIFMS", "SIFMS", "SIFM1"}, + {"SIFM2-SIFMS", "SIFMS", "SIFM2"}, + {"SIFM3-SIFMS", "SIFMS", "SIFM3"}, + + {"SIFMS", "SIFM0", "SIFM0-SIFMS"}, + {"SIFMS", "SIFM1", "SIFM1-SIFMS"}, + {"SIFMS", "SIFM2", "SIFM2-SIFMS"}, + {"SIFMS", "SIFM3", "SIFM3-SIFMS"}, + + {"WDMA0 Capture", NULL, "SPUM ASRC0"}, + {"WDMA1 Capture", "WDMA", "SIFM0"}, + {"WDMA2 Capture", "WDMA", "SIFM1"}, + {"WDMA3 Capture", "WDMA", "SIFM2"}, + {"WDMA4 Capture", "WDMA", "SIFM3"}, +}; + +static bool abox_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ABOX_SYSPOWER_CTRL: + case ABOX_SYSPOWER_STATUS: + case ABOX_SPUS_CTRL2: + case ABOX_SPUS_CTRL3: + case ABOX_SPUM_CTRL2: + case ABOX_SPUM_CTRL3: + case ABOX_UAIF_STATUS(0): + case ABOX_UAIF_STATUS(1): + case ABOX_UAIF_STATUS(2): + case ABOX_UAIF_STATUS(3): + case ABOX_UAIF_STATUS(4): + case ABOX_DSIF_STATUS: + case ABOX_TIMER_CTRL0(0): + case ABOX_TIMER_CTRL1(0): + case ABOX_TIMER_CTRL0(1): + case ABOX_TIMER_CTRL1(1): + case ABOX_TIMER_CTRL0(2): + case ABOX_TIMER_CTRL1(2): + case ABOX_TIMER_CTRL0(3): + case ABOX_TIMER_CTRL1(3): + return true; + default: + return false; + } +} + +static bool abox_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ABOX_IP_INDEX: + case ABOX_VERSION: + case ABOX_SYSPOWER_CTRL: + case ABOX_SYSPOWER_STATUS: + case ABOX_SYSTEM_CONFIG0: + case ABOX_REMAP_MASK: + case ABOX_REMAP_ADDR: + case ABOX_DYN_CLOCK_OFF: + case ABOX_QCHANNEL_DISABLE: + case ABOX_ROUTE_CTRL0: + case ABOX_ROUTE_CTRL1: + case ABOX_ROUTE_CTRL2: + case ABOX_TICK_DIV_RATIO: + case ABOX_SPUS_CTRL0: + case ABOX_SPUS_CTRL1: + case ABOX_SPUS_CTRL2: + case ABOX_SPUS_CTRL3: + case ABOX_SPUS_CTRL_SIFS_CNT0: + case ABOX_SPUS_CTRL_SIFS_CNT1: + case ABOX_SPUM_CTRL0: + case ABOX_SPUM_CTRL1: + case ABOX_SPUM_CTRL2: + case ABOX_SPUM_CTRL3: + case ABOX_UAIF_CTRL0(0): + case ABOX_UAIF_CTRL1(0): + case ABOX_UAIF_STATUS(0): + case ABOX_UAIF_CTRL0(1): + case ABOX_UAIF_CTRL1(1): + case ABOX_UAIF_STATUS(1): + case ABOX_UAIF_CTRL0(2): + case ABOX_UAIF_CTRL1(2): + case ABOX_UAIF_STATUS(2): + case ABOX_UAIF_CTRL0(3): + case ABOX_UAIF_CTRL1(3): + case ABOX_UAIF_STATUS(3): + case ABOX_UAIF_CTRL0(4): + case ABOX_UAIF_CTRL1(4): + case ABOX_UAIF_STATUS(4): + case ABOX_DSIF_CTRL: + case ABOX_DSIF_STATUS: + case ABOX_SPDYIF_CTRL: + case ABOX_TIMER_CTRL0(0): + case ABOX_TIMER_CTRL1(0): + case ABOX_TIMER_CTRL0(1): + case ABOX_TIMER_CTRL1(1): + case ABOX_TIMER_CTRL0(2): + case ABOX_TIMER_CTRL1(2): + case ABOX_TIMER_CTRL0(3): + case ABOX_TIMER_CTRL1(3): + return true; + default: + return false; + } +} + +static bool abox_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ABOX_SYSPOWER_CTRL: + case ABOX_SYSTEM_CONFIG0: + case ABOX_REMAP_MASK: + case ABOX_REMAP_ADDR: + case ABOX_DYN_CLOCK_OFF: + case ABOX_QCHANNEL_DISABLE: + case ABOX_ROUTE_CTRL0: + case ABOX_ROUTE_CTRL1: + case ABOX_ROUTE_CTRL2: + case ABOX_SPUS_CTRL0: + case ABOX_SPUS_CTRL1: + case ABOX_SPUS_CTRL2: + case ABOX_SPUS_CTRL3: + case ABOX_SPUS_CTRL_SIFS_CNT0: + case ABOX_SPUS_CTRL_SIFS_CNT1: + case ABOX_SPUM_CTRL0: + case ABOX_SPUM_CTRL1: + case ABOX_SPUM_CTRL2: + case ABOX_SPUM_CTRL3: + case ABOX_UAIF_CTRL0(0): + case ABOX_UAIF_CTRL1(0): + case ABOX_UAIF_CTRL0(1): + case ABOX_UAIF_CTRL1(1): + case ABOX_UAIF_CTRL0(2): + case ABOX_UAIF_CTRL1(2): + case ABOX_UAIF_CTRL0(3): + case ABOX_UAIF_CTRL1(3): + case ABOX_UAIF_CTRL0(4): + case ABOX_UAIF_CTRL1(4): + case ABOX_DSIF_CTRL: + case ABOX_SPDYIF_CTRL: + case ABOX_TIMER_CTRL0(0): + case ABOX_TIMER_CTRL1(0): + case ABOX_TIMER_CTRL0(1): + case ABOX_TIMER_CTRL1(1): + case ABOX_TIMER_CTRL0(2): + case ABOX_TIMER_CTRL1(2): + case ABOX_TIMER_CTRL0(3): + case ABOX_TIMER_CTRL1(3): + return true; + default: + return false; + } +} + +static const struct reg_default abox_reg_defaults_8895[] = { + {0x0000, 0x41424F58}, + {0x0010, 0x00000000}, + {0x0014, 0x00000000}, + {0x0020, 0x00000000}, + {0x0024, 0xFFF00000}, + {0x0028, 0x13F00000}, + {0x0030, 0x7FFFFFFF}, + {0x0040, 0x00000000}, + {0x0044, 0x00000000}, + {0x0048, 0x00000000}, + {0x0200, 0x00000000}, + {0x0204, 0x00000000}, + {0x0208, 0x00000000}, + {0x020C, 0x00000000}, + {0x0220, 0x00000000}, + {0x0224, 0x00000000}, + {0x0228, 0x00000000}, + {0x022C, 0x00000000}, + {0x0230, 0x00000000}, + {0x0234, 0x00000000}, + {0x0238, 0x00000000}, + {0x023C, 0x00000000}, + {0x0240, 0x00000000}, + {0x0260, 0x00000000}, + {0x0300, 0x00000000}, + {0x0304, 0x00000000}, + {0x0308, 0x00000000}, + {0x030C, 0x00000000}, + {0x0320, 0x00000000}, + {0x0324, 0x00000000}, + {0x0328, 0x00000000}, + {0x032C, 0x00000000}, + {0x0330, 0x00000000}, + {0x0334, 0x00000000}, + {0x0338, 0x00000000}, + {0x033C, 0x00000000}, + {0x0340, 0x00000000}, + {0x0344, 0x00000000}, + {0x0348, 0x00000000}, + {0x0500, 0x01000000}, + {0x0504, 0x00000000}, + {0x050C, 0x00000000}, + {0x0510, 0x01000000}, + {0x0514, 0x00000000}, + {0x051C, 0x00000000}, + {0x0520, 0x01000000}, + {0x0524, 0x00000000}, + {0x052C, 0x00000000}, + {0x0530, 0x01000000}, + {0x0534, 0x00000000}, + {0x053C, 0x00000000}, + {0x0540, 0x01000000}, + {0x0544, 0x00000000}, + {0x054C, 0x00000000}, + {0x0550, 0x00000000}, + {0x0554, 0x00000000}, +}; + +static const struct reg_default abox_reg_defaults_9810[] = { + {0x0000, 0x41424F58}, + {0x0010, 0x00000000}, + {0x0014, 0x00000000}, + {0x0020, 0x00004444}, + {0x0024, 0xFFF00000}, + {0x0028, 0x17D00000}, + {0x0030, 0x7FFFFFFF}, + {0x0038, 0x00000000}, + {0x0040, 0x00000000}, + {0x0044, 0x00000000}, + {0x0048, 0x00000000}, + {0x0200, 0x00000000}, + {0x0204, 0x00000000}, + {0x0208, 0x00000000}, + {0x020C, 0x00000000}, + {0x0220, 0x00000000}, + {0x0224, 0x00000000}, + {0x0228, 0x00000000}, + {0x022C, 0x00000000}, + {0x0230, 0x00000000}, + {0x0234, 0x00000000}, + {0x0238, 0x00000000}, + {0x023C, 0x00000000}, + {0x0240, 0x00000000}, + {0x0260, 0x00000000}, + {0x0280, 0x00000000}, + {0x0284, 0x00000000}, + {0x0300, 0x00000000}, + {0x0304, 0x00000000}, + {0x0308, 0x00000000}, + {0x030C, 0x00000000}, + {0x0320, 0x00000000}, + {0x0324, 0x00000000}, + {0x0328, 0x00000000}, + {0x032C, 0x00000000}, + {0x0330, 0x00000000}, + {0x0334, 0x00000000}, + {0x0338, 0x00000000}, + {0x033C, 0x00000000}, + {0x0340, 0x00000000}, + {0x0344, 0x00000000}, + {0x0348, 0x00000000}, + {0x0500, 0x01000010}, + {0x0504, 0x00000000}, + {0x050C, 0x00000000}, + {0x0510, 0x01000010}, + {0x0514, 0x00000000}, + {0x051C, 0x00000000}, + {0x0520, 0x01000010}, + {0x0524, 0x00000000}, + {0x052C, 0x00000000}, + {0x0530, 0x01000010}, + {0x0534, 0x00000000}, + {0x053C, 0x00000000}, + {0x0540, 0x01000010}, + {0x0544, 0x00000000}, + {0x054C, 0x00000000}, + {0x0550, 0x00000000}, + {0x0554, 0x00000000}, +}; + +static const struct reg_default abox_reg_defaults_9610[] = { + {0x0000, 0x41424F58}, + {0x0010, 0x00000000}, + {0x0014, 0x00000000}, + {0x0020, 0x00000000}, + {0x0024, 0xFFF00000}, + {0x0028, 0x14B00000}, + {0x0030, 0x7FFFFFFF}, + {0x0038, 0x00000000}, + {0x0040, 0x00000000}, + {0x0044, 0x00000000}, + {0x0048, 0x00000000}, + {0x0050, 0x000004E1}, + {0x0200, 0x00000000}, + {0x0204, 0x00000000}, + {0x0208, 0x00000000}, + {0x020C, 0x00000000}, + {0x0220, 0x00000000}, + {0x0224, 0x00000000}, + {0x0228, 0x00000000}, + {0x022C, 0x00000000}, + {0x0230, 0x00000000}, + {0x0234, 0x00000000}, + {0x0238, 0x00000000}, + {0x023C, 0x00000000}, + {0x0240, 0x00000000}, + {0x0244, 0x00000000}, + {0x0248, 0x00000000}, + {0x024C, 0x00000000}, + {0x0250, 0x00000000}, + {0x0254, 0x00000000}, + {0x0258, 0x00000000}, + {0x025C, 0x00000000}, + {0x0260, 0x00000000}, + {0x0280, 0x00000000}, + {0x0284, 0x00000000}, + {0x0300, 0x00000000}, + {0x0304, 0x00000000}, + {0x0308, 0x00000000}, + {0x030C, 0x00000000}, + {0x0320, 0x00000000}, + {0x0324, 0x00000000}, + {0x0328, 0x00000000}, + {0x032C, 0x00000000}, + {0x0330, 0x00000000}, + {0x0334, 0x00000000}, + {0x0338, 0x00000000}, + {0x033C, 0x00000000}, + {0x0340, 0x00000000}, + {0x0344, 0x00000000}, + {0x0348, 0x00000000}, + {0x0500, 0x01000010}, + {0x0504, 0x00000000}, + {0x050C, 0x00000000}, + {0x0510, 0x01000010}, + {0x0514, 0x00000000}, + {0x051C, 0x00000000}, + {0x0520, 0x01000010}, + {0x0524, 0x00000000}, + {0x052C, 0x00000000}, + {0x0530, 0x01000010}, + {0x0534, 0x00000000}, + {0x053C, 0x00000000}, + {0x0540, 0x01000010}, + {0x0544, 0x00000000}, + {0x054C, 0x00000000}, + {0x0550, 0x00000000}, + {0x0554, 0x00000000}, + {0x0560, 0x00000000}, +}; + +static struct regmap_config abox_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = ABOX_MAX_REGISTERS, + .volatile_reg = abox_volatile_reg, + .readable_reg = abox_readable_reg, + .writeable_reg = abox_writeable_reg, + .cache_type = REGCACHE_RBTREE, + .fast_io = true, +}; + +static const struct snd_soc_component_driver abox_cmpnt = { + .probe = abox_cmpnt_probe, + .remove = abox_cmpnt_remove, + .controls = abox_cmpnt_controls, + .num_controls = ARRAY_SIZE(abox_cmpnt_controls), + .dapm_widgets = abox_cmpnt_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(abox_cmpnt_dapm_widgets), + .dapm_routes = abox_cmpnt_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(abox_cmpnt_dapm_routes), + .probe_order = SND_SOC_COMP_ORDER_FIRST, +}; + +struct abox_name_rate_format { + const char *name; + int stream; + const enum ABOX_CONFIGMSG rate; + const enum ABOX_CONFIGMSG format; + +}; + +static const struct abox_name_rate_format abox_nrf[] = { + {"SIFS0", SNDRV_PCM_STREAM_PLAYBACK, SET_MIXER_SAMPLE_RATE, + SET_MIXER_FORMAT}, + {"SIFS1", SNDRV_PCM_STREAM_PLAYBACK, SET_OUT1_SAMPLE_RATE, + SET_OUT1_FORMAT}, + {"SIFS2", SNDRV_PCM_STREAM_PLAYBACK, SET_OUT2_SAMPLE_RATE, + SET_OUT2_FORMAT}, + {"RECP", SNDRV_PCM_STREAM_CAPTURE, SET_RECP_SAMPLE_RATE, + SET_RECP_FORMAT}, + {"SIFM0", SNDRV_PCM_STREAM_CAPTURE, SET_INMUX0_SAMPLE_RATE, + SET_INMUX0_FORMAT}, + {"SIFM1", SNDRV_PCM_STREAM_CAPTURE, SET_INMUX1_SAMPLE_RATE, + SET_INMUX1_FORMAT}, + {"SIFM2", SNDRV_PCM_STREAM_CAPTURE, SET_INMUX2_SAMPLE_RATE, + SET_INMUX2_FORMAT}, + {"SIFM3", SNDRV_PCM_STREAM_CAPTURE, SET_INMUX3_SAMPLE_RATE, + SET_INMUX3_FORMAT}, +}; + +static bool abox_find_nrf_stream(const struct snd_soc_dapm_widget *w, + int stream, enum ABOX_CONFIGMSG *rate, + enum ABOX_CONFIGMSG *format) +{ + struct snd_soc_component *cmpnt = w->dapm->component; + const char *name_prefix = cmpnt ? cmpnt->name_prefix : NULL; + size_t prefix_len = name_prefix ? strlen(name_prefix) + 1 : 0; + const char *name = w->name + prefix_len; + const struct abox_name_rate_format *nrf; + + for (nrf = abox_nrf; nrf - abox_nrf < ARRAY_SIZE(abox_nrf); nrf++) { + if ((nrf->stream == stream) && (strcmp(nrf->name, name) == 0)) { + *rate = nrf->rate; + *format = nrf->format; + return true; + } + } + + return false; +} + +static bool __maybe_unused abox_find_nrf(const struct snd_soc_dapm_widget *w, + enum ABOX_CONFIGMSG *rate, enum ABOX_CONFIGMSG *format) +{ + struct snd_soc_component *cmpnt = w->dapm->component; + const char *name_prefix = cmpnt ? cmpnt->name_prefix : NULL; + size_t prefix_len = name_prefix ? strlen(name_prefix) + 1 : 0; + const char *name = w->name + prefix_len; + const struct abox_name_rate_format *nrf; + + for (nrf = abox_nrf; nrf - abox_nrf < ARRAY_SIZE(abox_nrf); nrf++) { + if (strcmp(nrf->name, name) == 0) { + *rate = nrf->rate; + *format = nrf->format; + return true; + } + } + + return false; +} + +int abox_hw_params_fixup_helper(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params, int stream) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_soc_component *cmpnt = dai->component; + struct device *dev = is_abox(dai->dev) ? dai->dev : dai->dev->parent; + struct abox_data *data = dev_get_drvdata(dev); + struct snd_soc_dapm_widget *w, *w_tgt = NULL; + struct snd_soc_dapm_path *path; + LIST_HEAD(widget_list); + enum ABOX_CONFIGMSG msg_rate, msg_format; + unsigned int rate, channels, width; + snd_pcm_format_t format; + + dev_info(dev, "%s[%s](%d)\n", __func__, dai->name, stream); + + if (params_channels(params) < 1) { + dev_info(dev, "channel is fixed from %d to 2\n", + params_channels(params)); + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = 2; + } + + if (params_width(params) < 16) { + dev_info(dev, "width is fixed from %d to 16\n", + params_width(params)); + params_set_format(params, SNDRV_PCM_FORMAT_S16); + } + + snd_soc_dapm_mutex_lock(snd_soc_component_get_dapm(cmpnt)); + + /* + * For snd_soc_dapm_connected_{output,input}_ep fully discover the graph + * we need to reset the cached number of inputs and outputs. + */ + list_for_each_entry(w, &cmpnt->card->widgets, list) { + w->endpoints[SND_SOC_DAPM_DIR_IN] = -1; + w->endpoints[SND_SOC_DAPM_DIR_OUT] = -1; + } + + if (!dai->playback_widget) + goto skip_playback; + snd_soc_dapm_widget_for_each_source_path(dai->playback_widget, path) { + if (path->connect) { + w = path->node[SND_SOC_DAPM_DIR_IN]; + snd_soc_dapm_connected_input_ep(w, &widget_list); + } + } +skip_playback: + if (!dai->capture_widget) + goto skip_capture; + snd_soc_dapm_widget_for_each_sink_path(dai->capture_widget, path) { + if (path->connect) { + w = path->node[SND_SOC_DAPM_DIR_OUT]; + snd_soc_dapm_connected_output_ep(w, &widget_list); + } + } +skip_capture: + + /* find current params */ + list_for_each_entry(w, &widget_list, work_list) { + dev_dbg(dev, "%s\n", w->name); + if (!abox_find_nrf_stream(w, stream, &msg_rate, &msg_format)) + continue; + + format = abox_get_sif_format(data, msg_format); + width = snd_pcm_format_width(format); + rate = abox_get_sif_rate(data, msg_rate); + channels = abox_get_sif_channels(data, msg_format); + dev_dbg(dev, "%s: %s: find %d bit, %u channel, %uHz\n", + __func__, w->name, width, channels, rate); + w_tgt = w; + break; + } + + if (!w_tgt) + goto unlock; + + /* channel mixing isn't supported */ + abox_set_sif_channels(data, msg_format, params_channels(params)); + + /* override formats to UAIF format, if it is connected */ + if (abox_if_hw_params_fixup(rtd, params, stream) >= 0) + abox_set_sif_auto_config(data, msg_rate, true); + + if (!abox_get_sif_auto_config(data, msg_rate)) + goto unlock; + + format = params_format(params); + width = params_width(params); + rate = params_rate(params); + channels = params_channels(params); + + if (dai->sample_width && dai->sample_width != width) { + width = dai->sample_width; + abox_set_sif_width(data, msg_format, dai->sample_width); + format = abox_get_sif_format(data, msg_format); + } + + if (dai->channels && dai->channels != channels) + channels = dai->channels; + + if (dai->rate && dai->rate != rate) + rate = dai->rate; + + dev_dbg(dev, "%s: set to %u bit, %u channel, %uHz\n", __func__, + width, channels, rate); +unlock: + snd_soc_dapm_mutex_unlock(snd_soc_component_get_dapm(cmpnt)); + + if (!w_tgt) + goto out; + abox_sample_rate_put_ipc(dev, rate, msg_rate); + abox_sif_format_put_ipc(dev, format, channels, msg_format); + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = + abox_get_sif_rate(data, msg_rate); + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = + abox_get_sif_channels(data, msg_format); + snd_mask_none(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT)); + snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + abox_get_sif_format(data, msg_format)); + dev_info(dev, "%s: %s: %d bit, %u channel, %uHz\n", + __func__, w_tgt->name, + abox_get_sif_width(data, msg_format), + abox_get_sif_channels(data, msg_format), + abox_get_sif_rate(data, msg_rate)); +out: + return 0; +} +EXPORT_SYMBOL(abox_hw_params_fixup_helper); + +static struct pm_qos_request abox_pm_qos_aud; +static struct pm_qos_request abox_pm_qos_int; +static struct pm_qos_request abox_pm_qos_mif; +static struct pm_qos_request abox_pm_qos_lit; +static struct pm_qos_request abox_pm_qos_big; + +unsigned int abox_get_requiring_int_freq_in_khz(void) +{ + struct abox_data *data = p_abox_data; + unsigned int gear; + unsigned int int_freq; + + if (data == NULL) + return 0; + + gear = data->cpu_gear; + + if (gear <= ARRAY_SIZE(data->pm_qos_int)) + int_freq = data->pm_qos_int[gear - 1]; + else + int_freq = 0; + + return int_freq; +} +EXPORT_SYMBOL(abox_get_requiring_int_freq_in_khz); + +unsigned int abox_get_requiring_aud_freq_in_khz(void) +{ + struct abox_data *data = p_abox_data; + unsigned int gear; + unsigned int aud_freq; + + if (data == NULL) + return 0; + + gear = data->cpu_gear; + + if (gear <= ARRAY_SIZE(data->pm_qos_aud)) + aud_freq = data->pm_qos_aud[gear - 1]; + else + aud_freq = 0; + + return aud_freq; +} +EXPORT_SYMBOL(abox_get_requiring_aud_freq_in_khz); + +bool abox_cpu_gear_idle(struct device *dev, struct abox_data *data, void *id) +{ + struct abox_qos_request *request; + + dev_dbg(dev, "%s(%p)\n", __func__, id); + + for (request = data->cpu_gear_requests; + request - data->cpu_gear_requests < + ARRAY_SIZE(data->cpu_gear_requests) + && request->id; + request++) { + if (id == request->id) { + if (request->value >= ABOX_CPU_GEAR_MIN) + return true; + else + return false; + } + } + + return true; +} + +static void abox_check_cpu_gear(struct device *dev, + struct abox_data *data, + const void *old_id, unsigned int old_gear, + const void *id, unsigned int gear) +{ + struct device *dev_abox = &data->pdev->dev; + + if (id != (void *)ABOX_CPU_GEAR_BOOT) + return; + + if (data->calliope_state == CALLIOPE_ENABLING) + abox_boot_done(dev_abox, data->calliope_version); + + if (old_id != id) { + if (gear < ABOX_CPU_GEAR_MIN) { + /* new */ + dev_dbg(dev, "%s(%p): new\n", __func__, id); + pm_wakeup_event(dev_abox, BOOT_DONE_TIMEOUT_MS); + } + } else { + if ((old_gear >= ABOX_CPU_GEAR_MIN) && + (gear < ABOX_CPU_GEAR_MIN)) { + /* on */ + dev_dbg(dev, "%s(%p): on\n", __func__, id); + pm_wakeup_event(dev_abox, BOOT_DONE_TIMEOUT_MS); + } else if ((old_gear < ABOX_CPU_GEAR_MIN) && + (gear >= ABOX_CPU_GEAR_MIN)) { + /* off */ + dev_dbg(dev, "%s(%p): off\n", __func__, id); + pm_relax(dev); + } + } +} + +static void abox_notify_cpu_gear(struct abox_data *data, unsigned int freq) +{ + struct device *dev = &data->pdev->dev; + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + unsigned long long time = sched_clock(); + unsigned long rem = do_div(time, NSEC_PER_SEC); + + switch (data->calliope_state) { + case CALLIOPE_ENABLING: + case CALLIOPE_ENABLED: + dev_dbg(dev, "%s\n", __func__); + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_CHANGED_GEAR; + system_msg->param1 = (int)freq; + system_msg->param2 = (int)time; /* SEC */ + system_msg->param3 = (int)rem; /* NSEC */ + abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); + break; + case CALLIOPE_DISABLING: + case CALLIOPE_DISABLED: + default: + /* notification to passing by context is not needed */ + break; + } +} + +static void abox_change_cpu_gear_legacy(struct device *dev, + struct abox_data *data) +{ + struct abox_qos_request *request; + unsigned int gear = UINT_MAX; + int ret; + bool increasing; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->cpu_gear_requests; + request - data->cpu_gear_requests < + ARRAY_SIZE(data->cpu_gear_requests) + && request->id; + request++) { + if (gear > request->value) + gear = request->value; + + dev_dbg(dev, "id=%p, value=%u, gear=%u\n", request->id, + request->value, gear); + } + + if (data->cpu_gear == gear) + goto skip; + + increasing = (gear < data->cpu_gear); + data->cpu_gear = gear; + + if (increasing) { + if (gear <= ARRAY_SIZE(data->pm_qos_int)) + pm_qos_update_request(&abox_pm_qos_int, + data->pm_qos_int[gear - 1]); + else + pm_qos_update_request(&abox_pm_qos_int, 0); + } + + if (gear >= ABOX_CPU_GEAR_MIN) { + ret = clk_set_rate(data->clk_pll, 0); + if (ret < 0) + dev_warn(dev, "setting pll clock to 0 is failed: %d\n", + ret); + dev_info(dev, "pll clock: %lu\n", clk_get_rate(data->clk_pll)); + + ret = clk_set_rate(data->clk_cpu, AUD_PLL_RATE_KHZ); + if (ret < 0) + dev_warn(dev, "setting cpu clock gear to %d is failed: %d\n", + gear, ret); + } else { + ret = clk_set_rate(data->clk_cpu, AUD_PLL_RATE_KHZ / gear); + if (ret < 0) + dev_warn(dev, "setting cpu clock gear to %d is failed: %d\n", + gear, ret); + + if (clk_get_rate(data->clk_pll) <= AUD_PLL_RATE_HZ_BYPASS) { + ret = clk_set_rate(data->clk_pll, + AUD_PLL_RATE_HZ_FOR_48000); + if (ret < 0) + dev_warn(dev, "setting pll clock to 0 is failed: %d\n", + ret); + dev_info(dev, "pll clock: %lu\n", + clk_get_rate(data->clk_pll)); + } + } + dev_info(dev, "cpu clock: %lukHz\n", clk_get_rate(data->clk_cpu)); + + if (!increasing) { + if (gear <= ARRAY_SIZE(data->pm_qos_int)) + pm_qos_update_request(&abox_pm_qos_int, + data->pm_qos_int[gear - 1]); + else + pm_qos_update_request(&abox_pm_qos_int, 0); + } +skip: + abox_notify_cpu_gear(data, clk_get_rate(data->clk_cpu) * 1000); +} + +static void abox_change_cpu_gear(struct device *dev, struct abox_data *data) +{ + struct abox_qos_request *request; + unsigned int gear = UINT_MAX; + s32 freq; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->cpu_gear_requests; + request - data->cpu_gear_requests < + ARRAY_SIZE(data->cpu_gear_requests) + && request->id; + request++) { + if (gear > request->value) + gear = request->value; + + dev_dbg(dev, "id=%p, value=%u, gear=%u\n", request->id, + request->value, gear); + } + + if ((data->cpu_gear >= ABOX_CPU_GEAR_MIN) && + (gear < ABOX_CPU_GEAR_MIN)) { + /* first cpu gear request */ + clk_set_rate(data->clk_pll, AUD_PLL_RATE_HZ_FOR_48000); + dev_info(dev, "pll clock: %lu\n", clk_get_rate(data->clk_pll)); + pm_runtime_get(dev); + } + + if (data->cpu_gear != gear) { + freq = (gear <= ARRAY_SIZE(data->pm_qos_aud)) ? + data->pm_qos_aud[gear - 1] : 0; + pm_qos_update_request(&abox_pm_qos_aud, freq); + } + + dev_info(dev, "pm qos request aud: req=%dkHz ret=%dkHz\n", freq, + pm_qos_request(abox_pm_qos_aud.pm_qos_class)); + abox_notify_cpu_gear(data, + pm_qos_request(abox_pm_qos_aud.pm_qos_class) * 1000); + + if ((data->cpu_gear < ABOX_CPU_GEAR_MIN) && + (gear >= ABOX_CPU_GEAR_MIN)) { + /* no more cpu gear request */ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + clk_set_rate(data->clk_pll, 0); + dev_info(dev, "pll clock: %lu\n", clk_get_rate(data->clk_pll)); + } + + data->cpu_gear = gear; +} + +static void abox_change_cpu_gear_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + change_cpu_gear_work); + + if (IS_ENABLED(CONFIG_SOC_EXYNOS8895)) + abox_change_cpu_gear_legacy(&data->pdev->dev, data); + else + abox_change_cpu_gear(&data->pdev->dev, data); +} + +int abox_request_cpu_gear(struct device *dev, struct abox_data *data, + const void *id, unsigned int gear) +{ + struct abox_qos_request *request; + const void *old_id; + unsigned int old_gear; + + dev_info(dev, "%s(%p, %u)\n", __func__, id, gear); + + for (request = data->cpu_gear_requests; + request - data->cpu_gear_requests < + ARRAY_SIZE(data->cpu_gear_requests) + && request->id && request->id != id; + request++) { + } + + old_id = request->id; + old_gear = request->value; + request->value = gear; + wmb(); /* value is read after id in reading function */ + request->id = id; + + if (request - data->cpu_gear_requests >= + ARRAY_SIZE(data->cpu_gear_requests)) { + dev_err(dev, "%s: out of index. id=%p, gear=%u\n", __func__, + id, gear); + return -ENOMEM; + } + + queue_work(data->gear_workqueue, &data->change_cpu_gear_work); + abox_check_cpu_gear(dev, data, old_id, old_gear, id, gear); + + return 0; +} + +void abox_cpu_gear_barrier(struct abox_data *data) +{ + flush_work(&data->change_cpu_gear_work); +} + +int abox_request_cpu_gear_sync(struct device *dev, struct abox_data *data, + const void *id, unsigned int gear) +{ + int ret = abox_request_cpu_gear(dev, data, id, gear); + + abox_cpu_gear_barrier(data); + return ret; +} + +void abox_clear_cpu_gear_requests(struct device *dev, struct abox_data *data) +{ + struct abox_qos_request *req; + size_t len = ARRAY_SIZE(data->cpu_gear_requests); + + dev_info(dev, "%s\n", __func__); + + for (req = data->cpu_gear_requests; req - data->cpu_gear_requests < len + && req->id; req++) { + if (req->value < ABOX_CPU_GEAR_MIN) + abox_request_cpu_gear(dev, data, req->id, + ABOX_CPU_GEAR_MIN); + } +} + +static void abox_change_int_freq_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + change_int_freq_work); + struct device *dev = &data->pdev->dev; + struct abox_qos_request *request; + unsigned int freq = 0; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->int_requests; request - data->int_requests < + ARRAY_SIZE(data->int_requests) && request->id; + request++) { + if (freq < request->value) + freq = request->value; + + dev_dbg(dev, "id=%p, value=%u, freq=%u\n", request->id, + request->value, freq); + } + + data->int_freq = freq; + pm_qos_update_request(&abox_pm_qos_int, data->int_freq); + + dev_info(dev, "pm qos request int: %dHz\n", pm_qos_request( + abox_pm_qos_int.pm_qos_class)); +} + +static int abox_request_int_freq(struct device *dev, struct abox_data *data, + void *id, unsigned int int_freq) +{ + struct abox_qos_request *request; + + dev_info(dev, "%s(%p, %u)\n", __func__, id, int_freq); + + if (!id) + id = (void *)DEFAULT_INT_FREQ_ID; + + for (request = data->int_requests; request - data->int_requests < + ARRAY_SIZE(data->int_requests) && request->id && + request->id != id; request++) { + } + + request->value = int_freq; + wmb(); /* value is read after id in reading function */ + request->id = id; + + if (request - data->int_requests >= ARRAY_SIZE(data->int_requests)) { + dev_err(dev, "%s: out of index. id=%p, int_freq=%u\n", __func__, + id, int_freq); + return -ENOMEM; + } + + schedule_work(&data->change_int_freq_work); + + return 0; +} + +static void abox_change_mif_freq_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + change_mif_freq_work); + struct device *dev = &data->pdev->dev; + struct abox_qos_request *request; + unsigned int freq = 0; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->mif_requests; request - data->mif_requests < + ARRAY_SIZE(data->mif_requests) && request->id; + request++) { + if (freq < request->value) + freq = request->value; + + dev_dbg(dev, "id=%p, value=%u, freq=%u\n", request->id, + request->value, freq); + } + + data->mif_freq = freq; + pm_qos_update_request(&abox_pm_qos_mif, data->mif_freq); + + dev_info(dev, "pm qos request mif: %dHz\n", pm_qos_request( + abox_pm_qos_mif.pm_qos_class)); +} + +static int abox_request_mif_freq(struct device *dev, struct abox_data *data, + void *id, unsigned int mif_freq) +{ + struct abox_qos_request *request; + + dev_info(dev, "%s(%p, %u)\n", __func__, id, mif_freq); + + if (!id) + id = (void *)DEFAULT_MIF_FREQ_ID; + + for (request = data->mif_requests; request - data->mif_requests < + ARRAY_SIZE(data->mif_requests) && request->id && + request->id != id; request++) { + } + + request->value = mif_freq; + wmb(); /* value is read after id in reading function */ + request->id = id; + + if (request - data->mif_requests >= ARRAY_SIZE(data->mif_requests)) { + dev_err(dev, "%s: out of index. id=%p, mif_freq=%u\n", __func__, + id, mif_freq); + return -ENOMEM; + } + + schedule_work(&data->change_mif_freq_work); + + return 0; +} + +static void abox_change_lit_freq_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + change_lit_freq_work); + struct device *dev = &data->pdev->dev; + size_t array_size = ARRAY_SIZE(data->lit_requests); + struct abox_qos_request *request; + unsigned int freq = 0; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->lit_requests; + request - data->lit_requests < array_size && + request->id; request++) { + if (freq < request->value) + freq = request->value; + + dev_dbg(dev, "id=%p, value=%u, freq=%u\n", request->id, + request->value, freq); + } + + data->lit_freq = freq; + pm_qos_update_request(&abox_pm_qos_lit, data->lit_freq); + + dev_info(dev, "pm qos request little: %dkHz\n", + pm_qos_request(abox_pm_qos_lit.pm_qos_class)); +} + +int abox_request_lit_freq(struct device *dev, struct abox_data *data, + void *id, unsigned int freq) +{ + size_t array_size = ARRAY_SIZE(data->lit_requests); + struct abox_qos_request *request; + + if (!id) + id = (void *)DEFAULT_LIT_FREQ_ID; + + for (request = data->lit_requests; + request - data->lit_requests < array_size && + request->id && request->id != id; request++) { + } + + if ((request->id == id) && (request->value == freq)) + return 0; + + request->value = freq; + wmb(); /* value is read after id in reading function */ + request->id = id; + + dev_info(dev, "%s(%p, %u)\n", __func__, id, freq); + + if (request - data->lit_requests >= ARRAY_SIZE(data->lit_requests)) { + dev_err(dev, "%s: out of index. id=%p, freq=%u\n", + __func__, id, freq); + return -ENOMEM; + } + + schedule_work(&data->change_lit_freq_work); + + return 0; +} + +static void abox_change_big_freq_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + change_big_freq_work); + struct device *dev = &data->pdev->dev; + size_t array_size = ARRAY_SIZE(data->big_requests); + struct abox_qos_request *request; + unsigned int freq = 0; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->big_requests; + request - data->big_requests < array_size && + request->id; request++) { + if (freq < request->value) + freq = request->value; + + dev_dbg(dev, "id=%p, value=%u, freq=%u\n", request->id, + request->value, freq); + } + + data->big_freq = freq; + pm_qos_update_request(&abox_pm_qos_big, data->big_freq); + + dev_info(dev, "pm qos request big: %dkHz\n", + pm_qos_request(abox_pm_qos_big.pm_qos_class)); +} + +int abox_request_big_freq(struct device *dev, struct abox_data *data, + void *id, unsigned int freq) +{ + size_t array_size = ARRAY_SIZE(data->big_requests); + struct abox_qos_request *request; + + if (!id) + id = (void *)DEFAULT_BIG_FREQ_ID; + + for (request = data->big_requests; + request - data->big_requests < array_size && + request->id && request->id != id; request++) { + } + + if ((request->id == id) && (request->value == freq)) + return 0; + + dev_info(dev, "%s(%p, %u)\n", __func__, id, freq); + + request->value = freq; + wmb(); /* value is read after id in reading function */ + request->id = id; + + if (request - data->big_requests >= ARRAY_SIZE(data->big_requests)) { + dev_err(dev, "%s: out of index. id=%p, freq=%u\n", + __func__, id, freq); + return -ENOMEM; + } + + schedule_work(&data->change_big_freq_work); + + return 0; +} + +static void abox_change_hmp_boost_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + change_hmp_boost_work); + struct device *dev = &data->pdev->dev; + size_t array_size = ARRAY_SIZE(data->hmp_requests); + struct abox_qos_request *request; + unsigned int on = 0; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->hmp_requests; + request - data->hmp_requests < array_size && + request->id; request++) { + if (request->value) + on = request->value; + + dev_dbg(dev, "id=%p, value=%u, on=%u\n", request->id, + request->value, on); + } + + if (data->hmp_boost != on) { + dev_info(dev, "request hmp boost: %d\n", on); + + data->hmp_boost = on; +#ifdef CONFIG_SCHED_HMP + set_hmp_boost(on); +#endif + } +} + +int abox_request_hmp_boost(struct device *dev, struct abox_data *data, + void *id, unsigned int on) +{ + size_t array_size = ARRAY_SIZE(data->hmp_requests); + struct abox_qos_request *request; + + if (!id) + id = (void *)DEFAULT_HMP_BOOST_ID; + + for (request = data->hmp_requests; + request - data->hmp_requests < array_size && + request->id && request->id != id; request++) { + } + + if ((request->id == id) && (request->value == on)) + return 0; + + dev_info(dev, "%s(%p, %u)\n", __func__, id, on); + + request->value = on; + wmb(); /* value is read after id in reading function */ + request->id = id; + + if (request - data->hmp_requests >= ARRAY_SIZE(data->hmp_requests)) { + dev_err(dev, "%s: out of index. id=%p, on=%u\n", + __func__, id, on); + return -ENOMEM; + } + + schedule_work(&data->change_hmp_boost_work); + + return 0; +} + +void abox_request_dram_on(struct platform_device *pdev_abox, void *id, bool on) +{ + struct device *dev = &pdev_abox->dev; + struct abox_data *data = platform_get_drvdata(pdev_abox); + struct abox_dram_request *request; + unsigned int val = 0x0; + + dev_dbg(dev, "%s(%d)\n", __func__, on); + + for (request = data->dram_requests; request - data->dram_requests < + ARRAY_SIZE(data->dram_requests) && request->id && + request->id != id; request++) { + } + + request->on = on; + wmb(); /* on is read after id in reading function */ + request->id = id; + + for (request = data->dram_requests; request - data->dram_requests < + ARRAY_SIZE(data->dram_requests) && request->id; + request++) { + if (request->on) { + val = ABOX_SYSPOWER_CTRL_MASK; + break; + } + } + + regmap_write(data->regmap, ABOX_SYSPOWER_CTRL, val); + dev_dbg(dev, "%s: SYSPOWER_CTRL=%08x\n", __func__, + ({regmap_read(data->regmap, ABOX_SYSPOWER_CTRL, &val); + val; })); +} + +int abox_iommu_map(struct device *dev, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + struct abox_data *data = dev_get_drvdata(dev); + int ret; + + dev_info(dev, "%s(%lx, %pa, %zx)\n", __func__, iova, &paddr, size); + + ret = iommu_map(data->iommu_domain, iova, paddr, size, 0); + if (ret < 0) { + dev_err(dev, "Failed to iommu_map: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(abox_iommu_map); + +int abox_iommu_unmap(struct device *dev, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + struct abox_data *data = dev_get_drvdata(dev); + int ret; + + dev_info(dev, "%s(%lx, %pa, %zx)\n", __func__, iova, &paddr, size); + + ret = iommu_unmap(data->iommu_domain, iova, size); + if (ret < 0) { + dev_err(dev, "Failed to iommu_unmap: %d\n", ret); + return ret; + } + + exynos_sysmmu_tlb_invalidate(data->iommu_domain, iova, size); + + return 0; +} +EXPORT_SYMBOL(abox_iommu_unmap); + +int abox_register_irq_handler(struct device *dev, int ipc_id, + abox_irq_handler_t irq_handler, void *dev_id) +{ + struct abox_data *data = dev_get_drvdata(dev); + struct abox_irq_action *irq_action = NULL; + bool new_handler = true; + + if (ipc_id >= IPC_ID_COUNT) + return -EINVAL; + + list_for_each_entry(irq_action, &data->irq_actions, list) { + if (irq_action->irq == ipc_id && irq_action->dev_id == dev_id) { + new_handler = false; + break; + } + } + + if (new_handler) { + irq_action = devm_kzalloc(dev, sizeof(struct abox_irq_action), + GFP_KERNEL); + if (IS_ERR_OR_NULL(irq_action)) { + dev_err(dev, "%s: kmalloc fail\n", __func__); + return -ENOMEM; + } + irq_action->irq = ipc_id; + irq_action->dev_id = dev_id; + list_add_tail(&irq_action->list, &data->irq_actions); + } + + irq_action->irq_handler = irq_handler; + + return 0; +} +EXPORT_SYMBOL(abox_register_irq_handler); + +static int abox_control_asrc(struct snd_soc_dapm_widget *w, bool on) +{ + struct snd_kcontrol *kcontrol; + struct snd_soc_component *cmpnt; + struct soc_enum *e; + unsigned int reg, mask, val; + + if (!w || !w->name || !w->num_kcontrols) + return -EINVAL; + + kcontrol = w->kcontrols[0]; + cmpnt = w->dapm->component; + e = (struct soc_enum *)kcontrol->private_value; + reg = e->reg; + mask = e->mask << e->shift_l; + val = (on ? 1 : 0) << e->shift_l; + + return snd_soc_component_update_bits(cmpnt, reg, mask, val); +} + +static bool abox_is_asrc_widget(struct snd_soc_dapm_widget *w) +{ + return w->name && !!strstr(w->name, "ASRC"); +} + +int abox_try_to_asrc_off(struct device *dev, struct abox_data *data, + struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dai *dai = fe->cpu_dai; + struct snd_soc_component *cmpnt = dai->component; + struct snd_soc_dapm_widget *w, *w_asrc = NULL; + LIST_HEAD(widget_list); + enum ABOX_CONFIGMSG rate, format; + unsigned int out_rate = 0, out_width = 0; + unsigned int pcm_rate, pcm_width; + + if (!abox_test_quirk(data, ABOX_QUIRK_TRY_TO_ASRC_OFF)) + return 0; + + dev_dbg(dev, "%s(%s)\n", __func__, dai->name); + + pcm_rate = params_rate(&fe->dpcm[stream].hw_params); + pcm_width = params_width(&fe->dpcm[stream].hw_params); + + snd_soc_dapm_mutex_lock(snd_soc_component_get_dapm(cmpnt)); + /* + * For snd_soc_dapm_connected_{output,input}_ep fully discover the graph + * we need to reset the cached number of inputs and outputs. + */ + list_for_each_entry(w, &cmpnt->card->widgets, list) { + w->endpoints[SND_SOC_DAPM_DIR_IN] = -1; + w->endpoints[SND_SOC_DAPM_DIR_OUT] = -1; + } + if (dai->playback_widget) + snd_soc_dapm_connected_output_ep(dai->playback_widget, + &widget_list); + if (dai->capture_widget) + snd_soc_dapm_connected_input_ep(dai->capture_widget, + &widget_list); + + list_for_each_entry(w, &widget_list, work_list) { + dev_dbg(dev, "%s", w->name); + + if (abox_find_nrf_stream(w, stream, &rate, &format)) { + out_rate = abox_get_sif_rate(data, rate); + out_width = abox_get_sif_width(data, format); + dev_dbg(dev, "%s: rate=%u, width=%u\n", + w->name, out_rate, out_width); + } + + if (abox_is_asrc_widget(w)) { + w_asrc = w; + dev_dbg(dev, "%s is asrc\n", w->name); + } + + if (w_asrc && out_rate && out_width) + break; + } + snd_soc_dapm_mutex_unlock(snd_soc_component_get_dapm(cmpnt)); + + if (!w_asrc || !out_rate || !out_width) { + dev_warn(dev, "%s: incomplete path: w_asrc=%p, out_rate=%u, out_width=%u", + __func__, w_asrc, out_rate, out_width); + return -EINVAL; + } + + return abox_control_asrc(w_asrc, (pcm_rate != out_rate) || + (pcm_width != out_width)); +} + +static int abox_register_if_routes(struct device *dev, + const struct snd_soc_dapm_route *route_base, int num, + struct snd_soc_dapm_context *dapm, const char *name) +{ + struct snd_soc_dapm_route *route; + int i; + + route = devm_kmemdup(dev, route_base, sizeof(*route_base) * num, + GFP_KERNEL); + if (!route) { + dev_err(dev, "%s: insufficient memory\n", __func__); + return -EINVAL; + } + + for (i = 0; i < num; i++) { + if (route[i].sink) + route[i].sink = devm_kasprintf(dev, GFP_KERNEL, + route[i].sink, name); + if (route[i].control) + route[i].control = devm_kasprintf(dev, GFP_KERNEL, + route[i].control, name); + if (route[i].source) + route[i].source = devm_kasprintf(dev, GFP_KERNEL, + route[i].source, name); + } + + snd_soc_dapm_add_routes(dapm, route, num); + devm_kfree(dev, route); + + return 0; +} + +int abox_register_if(struct platform_device *pdev_abox, + struct platform_device *pdev_if, unsigned int id, + struct snd_soc_dapm_context *dapm, const char *name, + bool playback, bool capture) +{ + struct device *dev = &pdev_if->dev; + struct abox_data *data = platform_get_drvdata(pdev_abox); + int ret; + + static const struct snd_soc_dapm_route route_base_pla[] = { + /* sink, control, source */ + {"%s Playback", NULL, "%s SPK"}, + }; + + static const struct snd_soc_dapm_route route_base_cap[] = { + /* sink, control, source */ + {"SPUSM", "%s", "%s Capture"}, + {"NSRC0", "%s", "%s Capture"}, + {"NSRC1", "%s", "%s Capture"}, + {"NSRC2", "%s", "%s Capture"}, + {"NSRC3", "%s", "%s Capture"}, + }; + + if (id >= ARRAY_SIZE(data->pdev_if)) { + dev_err(dev, "%s: invalid id(%u)\n", __func__, id); + return -EINVAL; + } + + if (data->cmpnt->name_prefix && dapm->component->name_prefix && + strcmp(data->cmpnt->name_prefix, + dapm->component->name_prefix)) { + dev_err(dev, "%s: name prefix is different: %s != %s\n", + __func__, data->cmpnt->name_prefix, + dapm->component->name_prefix); + return -EINVAL; + } + + data->pdev_if[id] = pdev_if; + if (id > data->if_count) + data->if_count = id + 1; + + if (playback) { + ret = abox_register_if_routes(dev, route_base_pla, + ARRAY_SIZE(route_base_pla), dapm, name); + if (ret < 0) + return ret; + } + + if (capture) { + ret = abox_register_if_routes(dev, route_base_cap, + ARRAY_SIZE(route_base_cap), dapm, name); + if (ret < 0) + return ret; + } + + return 0; +} + +int abox_register_rdma(struct platform_device *pdev_abox, + struct platform_device *pdev_rdma, unsigned int id) +{ + struct abox_data *data = platform_get_drvdata(pdev_abox); + + if (id < ARRAY_SIZE(data->pdev_rdma)) { + data->pdev_rdma[id] = pdev_rdma; + if (id > data->rdma_count) + data->rdma_count = id + 1; + } else { + dev_err(&data->pdev->dev, "%s: invalid id(%u)\n", __func__, id); + return -EINVAL; + } + + return 0; +} + +int abox_register_wdma(struct platform_device *pdev_abox, + struct platform_device *pdev_wdma, unsigned int id) +{ + struct abox_data *data = platform_get_drvdata(pdev_abox); + + if (id < ARRAY_SIZE(data->pdev_wdma)) { + data->pdev_wdma[id] = pdev_wdma; + if (id > data->wdma_count) + data->wdma_count = id + 1; + } else { + dev_err(&data->pdev->dev, "%s: invalid id(%u)\n", __func__, id); + return -EINVAL; + } + + return 0; +} + +static int abox_component_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_component_kcontrol_value *value = + (void *)kcontrol->private_value; + + dev_dbg(dev, "%s(%s)\n", __func__, kcontrol->id.name); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = value->control->count; + uinfo->value.integer.min = value->control->min; + uinfo->value.integer.max = value->control->max; + return 0; +} + +static ABOX_IPC_MSG abox_component_control_get_msg; + +static int abox_component_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_data *data = dev_get_drvdata(dev); + struct abox_component_kcontrol_value *value = + (void *)kcontrol->private_value; + ABOX_IPC_MSG *msg = &abox_component_control_get_msg; + struct IPC_SYSTEM_MSG *system_msg = &msg->msg.system; + int i, ret; + + dev_dbg(dev, "%s\n", __func__); + + if (value->cache_only) { + for (i = 0; i < value->control->count; i++) + ucontrol->value.integer.value[i] = value->cache[i]; + return 0; + } + + msg->ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_REQUEST_COMPONENT_CONTROL; + system_msg->param1 = value->desc->id; + system_msg->param2 = value->control->id; + ret = abox_request_ipc(dev, msg->ipcid, msg, sizeof(*msg), 0, 1); + if (ret < 0) + return ret; + + ret = wait_event_timeout(data->ipc_wait_queue, + system_msg->msgtype == ABOX_REPORT_COMPONENT_CONTROL, + msecs_to_jiffies(1000)); + if (system_msg->msgtype != ABOX_REPORT_COMPONENT_CONTROL) + return -ETIME; + + for (i = 0; i < value->control->count; i++) { + long val = (long)system_msg->bundle.param_s32[i]; + + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int abox_component_control_put_ipc(struct device *dev, + struct abox_component_kcontrol_value *value) +{ + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + int i; + + dev_dbg(dev, "%s\n", __func__); + + for (i = 0; i < value->control->count; i++) { + int val = value->cache[i]; + char *name = value->control->name; + + system_msg->bundle.param_s32[i] = val; + dev_dbg(dev, "%s: %s[%d] <= %d", __func__, name, i, val); + } + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_UPDATE_COMPONENT_CONTROL; + system_msg->param1 = value->desc->id; + system_msg->param2 = value->control->id; + + return abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); +} + +static int abox_component_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_component_kcontrol_value *value = + (void *)kcontrol->private_value; + int i; + + dev_dbg(dev, "%s\n", __func__); + + for (i = 0; i < value->control->count; i++) { + int val = (int)ucontrol->value.integer.value[i]; + char *name = kcontrol->id.name; + + value->cache[i] = val; + dev_dbg(dev, "%s: %s[%d] <= %d", __func__, name, i, val); + } + + return abox_component_control_put_ipc(dev, value); +} + +#define ABOX_COMPONENT_KCONTROL(xname, xdesc, xcontrol) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = abox_component_control_info, \ + .get = abox_component_control_get, \ + .put = abox_component_control_put, \ + .private_value = \ + (unsigned long)&(struct abox_component_kcontrol_value) \ + {.desc = xdesc, .control = xcontrol} } + +struct snd_kcontrol_new abox_component_kcontrols[] = { + ABOX_COMPONENT_KCONTROL(NULL, NULL, NULL), +}; + +static void abox_register_component_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + register_component_work); + struct device *dev = &data->pdev->dev; + struct abox_component *component; + int i; + + dev_dbg(dev, "%s\n", __func__); + + for (component = data->components; ((component - data->components) < + ARRAY_SIZE(data->components)); component++) { + struct ABOX_COMPONENT_DESCRIPTIOR *desc = component->desc; + + if (!component->desc || component->registered) + continue; + + for (i = 0; i < desc->control_count; i++) { + struct ABOX_COMPONENT_CONTROL *control = + &desc->controls[i]; + struct abox_component_kcontrol_value *value; + char kcontrol_name[64]; + + value = devm_kzalloc(dev, sizeof(*value) + + (control->count * + sizeof(value->cache[0])), GFP_KERNEL); + if (IS_ERR_OR_NULL(value)) { + dev_err(dev, "%s: kmalloc fail\n", __func__); + continue; + } + value->desc = desc; + value->control = control; + list_add_tail(&value->list, &component->value_list); + + snprintf(kcontrol_name, sizeof(kcontrol_name), "%s %s", + desc->name, control->name); + + abox_component_kcontrols[0].name = devm_kstrdup(dev, + kcontrol_name, GFP_KERNEL); + abox_component_kcontrols[0].private_value = + (unsigned long)value; + if (data->cmpnt) { + snd_soc_add_component_controls(data->cmpnt, + abox_component_kcontrols, 1); + } + } + component->registered = true; + } +} + + +static int abox_register_component(struct device *dev, + struct ABOX_COMPONENT_DESCRIPTIOR *desc) +{ + struct abox_data *data = dev_get_drvdata(dev); + struct abox_component *component; + + dev_dbg(dev, "%s(%d, %s)\n", __func__, desc->id, desc->name); + + for (component = data->components; + ((component - data->components) < + ARRAY_SIZE(data->components)) && + component->desc && component->desc != desc; + component++) { + } + + if (!component->desc) { + component->desc = desc; + INIT_LIST_HEAD(&component->value_list); + schedule_work(&data->register_component_work); + } + + return 0; +} + +static void abox_restore_components(struct device *dev, struct abox_data *data) +{ + struct abox_component *component; + struct abox_component_kcontrol_value *value; + size_t len = ARRAY_SIZE(data->components); + + dev_dbg(dev, "%s\n", __func__); + + for (component = data->components; + (component - data->components) < len && + component->registered; component++) { + list_for_each_entry(value, &component->value_list, list) { + abox_component_control_put_ipc(dev, value); + value->cache_only = false; + } + } +} + +static void abox_cache_components(struct device *dev, struct abox_data *data) +{ + struct abox_component *component; + struct abox_component_kcontrol_value *value; + size_t len = ARRAY_SIZE(data->components); + + dev_dbg(dev, "%s\n", __func__); + + for (component = data->components; + (component - data->components) < len && + component->registered; component++) { + list_for_each_entry(value, &component->value_list, list) { + value->cache_only = true; + } + } +} + +static bool abox_is_calliope_incompatible(struct device *dev) +{ + struct abox_data *data = dev_get_drvdata(dev); + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + + memcpy(&msg, data->sram_base + 0x30040, 0x3C); + + return ((system_msg->param3 >> 24) == 'A'); +} + +static void abox_restore_data(struct device *dev) +{ + struct abox_data *data = dev_get_drvdata(dev); + int i; + + dev_info(dev, "%s\n", __func__); + + for (i = SET_MIXER_SAMPLE_RATE; i <= SET_INMUX4_SAMPLE_RATE; i++) + abox_sample_rate_put_ipc(dev, + data->sif_rate[abox_sif_idx(i)], i); + for (i = SET_MIXER_FORMAT; i <= SET_INMUX4_FORMAT; i++) + abox_sif_format_put_ipc(dev, + data->sif_format[abox_sif_idx(i)], + data->sif_channels[abox_sif_idx(i)], i); + abox_erap_handler_put_ipc(dev, ERAP_ECHO_CANCEL, + data->erap_status[ERAP_ECHO_CANCEL]); + abox_erap_handler_put_ipc(dev, ERAP_VI_SENSE, + data->erap_status[ERAP_VI_SENSE]); + abox_audio_mode_put_ipc(dev, data->audio_mode); + abox_sound_type_put_ipc(dev, data->sound_type); + abox_restore_components(dev, data); + abox_effect_restore(); +} + +static void abox_boot_done_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, + boot_done_work); + struct platform_device *pdev = data->pdev; + struct device *dev = &pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + abox_cpu_pm_ipc(dev, true); + abox_restore_data(dev); + abox_request_cpu_gear(dev, data, (void *)DEFAULT_CPU_GEAR_ID, + ABOX_CPU_GEAR_MIN); + abox_request_dram_on(pdev, dev, false); +} + +static void abox_boot_done(struct device *dev, unsigned int version) +{ + struct abox_data *data = dev_get_drvdata(dev); + char ver_char[4]; + + dev_dbg(dev, "%s\n", __func__); + + data->calliope_version = version; + memcpy(ver_char, &version, sizeof(ver_char)); + dev_info(dev, "Calliope is ready to sing (version:%c%c%c%c)\n", + ver_char[3], ver_char[2], ver_char[1], ver_char[0]); + data->calliope_state = CALLIOPE_ENABLED; + schedule_work(&data->boot_done_work); + + wake_up(&data->ipc_wait_queue); +} + +static irqreturn_t abox_dma_irq_handler(int irq, struct abox_data *data) +{ + struct device *dev = &data->pdev->dev; + int id; + struct platform_device **pdev_dma; + struct abox_platform_data *platform_data; + + dev_dbg(dev, "%s(%d)\n", __func__, irq); + + switch (irq) { + case RDMA0_BUF_EMPTY: + id = 0; + pdev_dma = data->pdev_rdma; + break; + case RDMA1_BUF_EMPTY: + id = 1; + pdev_dma = data->pdev_rdma; + break; + case RDMA2_BUF_EMPTY: + id = 2; + pdev_dma = data->pdev_rdma; + break; + case RDMA3_BUF_EMPTY: + id = 3; + pdev_dma = data->pdev_rdma; + break; + case WDMA0_BUF_FULL: + id = 0; + pdev_dma = data->pdev_wdma; + break; + case WDMA1_BUF_FULL: + id = 1; + pdev_dma = data->pdev_wdma; + break; + default: + return IRQ_NONE; + } + + if (unlikely(!pdev_dma[id])) { + dev_err(dev, "spurious dma irq: irq=%d id=%d\n", irq, id); + return IRQ_HANDLED; + } + + platform_data = platform_get_drvdata(pdev_dma[id]); + if (unlikely(!platform_data)) { + dev_err(dev, "dma irq with null data: irq=%d id=%d\n", irq, id); + return IRQ_HANDLED; + } + + platform_data->pointer = 0; + snd_pcm_period_elapsed(platform_data->substream); + + return IRQ_HANDLED; +} + +static irqreturn_t abox_registered_ipc_handler(struct device *dev, int irq, + struct abox_data *data, ABOX_IPC_MSG *msg, bool broadcast) +{ + struct abox_irq_action *action; + irqreturn_t ret = IRQ_NONE; + + dev_dbg(dev, "%s: irq=%d\n", __func__, irq); + + list_for_each_entry(action, &data->irq_actions, list) { + if (action->irq != irq) + continue; + + ret = action->irq_handler(irq, action->dev_id, msg); + if (!broadcast && ret == IRQ_HANDLED) + break; + } + + return ret; +} + +static void abox_system_ipc_handler(struct device *dev, + struct abox_data *data, ABOX_IPC_MSG *msg) +{ + struct IPC_SYSTEM_MSG *system_msg = &msg->msg.system; + int ret; + + dev_dbg(dev, "msgtype=%d\n", system_msg->msgtype); + + switch (system_msg->msgtype) { + case ABOX_BOOT_DONE: + if (abox_is_calliope_incompatible(dev)) + dev_err(dev, "Calliope is not compatible with the driver\n"); + + abox_boot_done(dev, system_msg->param3); + abox_registered_ipc_handler(dev, IPC_SYSTEM, data, msg, true); + break; + case ABOX_CHANGE_GEAR: + abox_request_cpu_gear(dev, data, + (void *)(long)system_msg->param2, + system_msg->param1); + break; + case ABOX_END_L2C_CONTROL: + data->l2c_controlled = true; + wake_up(&data->ipc_wait_queue); + break; + case ABOX_REQUEST_L2C: + { + void *id = (void *)(long)system_msg->param2; + bool on = !!system_msg->param1; + + abox_request_l2c(dev, data, id, on); + break; + } + case ABOX_REQUEST_SYSCLK: + switch (system_msg->param2) { + default: + /* fall through */ + case 0: + abox_request_mif_freq(dev, data, + (void *)(long)system_msg->param3, + system_msg->param1); + break; + case 1: + abox_request_int_freq(dev, data, + (void *)(long)system_msg->param3, + system_msg->param1); + break; + } + break; + case ABOX_REPORT_LOG: + ret = abox_log_register_buffer(dev, system_msg->param1, + abox_addr_to_kernel_addr(data, + system_msg->param2)); + if (ret < 0) { + dev_err(dev, "log buffer registration failed: %u, %u\n", + system_msg->param1, system_msg->param2); + } + break; + case ABOX_FLUSH_LOG: + break; + case ABOX_REPORT_DUMP: + ret = abox_dump_register_buffer(dev, system_msg->param1, + system_msg->bundle.param_bundle, + abox_addr_to_kernel_addr(data, + system_msg->param2), + abox_addr_to_phys_addr(data, + system_msg->param2), + system_msg->param3); + if (ret < 0) { + dev_err(dev, "dump buffer registration failed: %u, %u\n", + system_msg->param1, system_msg->param2); + } + break; + case ABOX_FLUSH_DUMP: + abox_dump_period_elapsed(system_msg->param1, + system_msg->param2); + break; + case ABOX_END_CLAIM_SRAM: + data->ima_claimed = true; + wake_up(&data->ipc_wait_queue); + break; + case ABOX_END_RECLAIM_SRAM: + data->ima_claimed = false; + wake_up(&data->ipc_wait_queue); + break; + case ABOX_REPORT_COMPONENT: + abox_register_component(dev, + abox_addr_to_kernel_addr(data, + system_msg->param1)); + break; + case ABOX_REPORT_COMPONENT_CONTROL: + abox_component_control_get_msg = *msg; + wake_up(&data->ipc_wait_queue); + break; + case ABOX_REPORT_FAULT: + { + const char *type; + + switch (system_msg->param1) { + case 1: + type = "data abort"; + break; + case 2: + type = "prefetch abort"; + break; + case 3: + type = "os error"; + break; + case 4: + type = "vss error"; + break; + case 5: + type = "undefined exception"; + break; + default: + type = "unknown error"; + break; + } + dev_err(dev, "%s(%08X, %08X, %08X) is reported from calliope\n", + type, system_msg->param1, system_msg->param2, + system_msg->param3); + + switch (system_msg->param1) { + case 1: + case 2: + abox_dbg_print_gpr_from_addr(dev, data, + abox_addr_to_kernel_addr(data, + system_msg->bundle.param_s32[0])); + abox_dbg_dump_gpr_from_addr(dev, + abox_addr_to_kernel_addr(data, + system_msg->bundle.param_s32[0]), + ABOX_DBG_DUMP_FIRMWARE, type); + abox_dbg_dump_mem(dev, data, ABOX_DBG_DUMP_FIRMWARE, + type); + break; + case 4: + abox_dbg_print_gpr(dev, data); + abox_dbg_dump_gpr(dev, data, ABOX_DBG_DUMP_VSS, type); + abox_dbg_dump_mem(dev, data, ABOX_DBG_DUMP_VSS, type); + break; + default: + abox_dbg_print_gpr(dev, data); + abox_dbg_dump_gpr(dev, data, ABOX_DBG_DUMP_FIRMWARE, + type); + abox_dbg_dump_mem(dev, data, ABOX_DBG_DUMP_FIRMWARE, + type); + break; + } + abox_failsafe_report(dev); + break; + } + default: + dev_warn(dev, "Redundant system message: %d(%d, %d, %d)\n", + system_msg->msgtype, system_msg->param1, + system_msg->param2, system_msg->param3); + break; + } +} + +static void abox_playback_ipc_handler(struct device *dev, + struct abox_data *data, ABOX_IPC_MSG *msg) +{ + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask; + struct abox_platform_data *platform_data; + int id = pcmtask_msg->channel_id; + + dev_dbg(dev, "msgtype=%d\n", pcmtask_msg->msgtype); + + if ((id >= ARRAY_SIZE(data->pdev_rdma)) || !data->pdev_rdma[id]) { + irqreturn_t ret; + + ret = abox_registered_ipc_handler(dev, IPC_PCMPLAYBACK, data, + msg, false); + if (ret != IRQ_HANDLED) + dev_err(dev, "pcm playback irq: id=%d\n", id); + return; + } + + platform_data = platform_get_drvdata(data->pdev_rdma[id]); + + switch (pcmtask_msg->msgtype) { + case PCM_PLTDAI_POINTER: + platform_data->pointer = pcmtask_msg->param.pointer; + snd_pcm_period_elapsed(platform_data->substream); + break; + case PCM_PLTDAI_ACK: + platform_data->ack_enabled = !!pcmtask_msg->param.trigger; + break; + default: + dev_warn(dev, "Redundant pcmtask message: %d\n", + pcmtask_msg->msgtype); + break; + } +} + +static void abox_capture_ipc_handler(struct device *dev, + struct abox_data *data, ABOX_IPC_MSG *msg) +{ + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask; + struct abox_platform_data *platform_data; + int id = pcmtask_msg->channel_id; + + dev_dbg(dev, "msgtype=%d\n", pcmtask_msg->msgtype); + + if ((id >= ARRAY_SIZE(data->pdev_wdma)) || (!data->pdev_wdma[id])) { + irqreturn_t ret; + + ret = abox_registered_ipc_handler(dev, IPC_PCMCAPTURE, data, + msg, false); + if (ret != IRQ_HANDLED) + dev_err(dev, "pcm capture irq: id=%d\n", id); + return; + } + + platform_data = platform_get_drvdata(data->pdev_wdma[id]); + + switch (pcmtask_msg->msgtype) { + case PCM_PLTDAI_POINTER: + platform_data->pointer = pcmtask_msg->param.pointer; + snd_pcm_period_elapsed(platform_data->substream); + break; + case PCM_PLTDAI_ACK: + platform_data->ack_enabled = !!pcmtask_msg->param.trigger; + break; + default: + dev_warn(dev, "Redundant pcmtask message: %d\n", + pcmtask_msg->msgtype); + break; + } +} + +static void abox_offload_ipc_handler(struct device *dev, + struct abox_data *data, ABOX_IPC_MSG *msg) +{ + struct IPC_OFFLOADTASK_MSG *offloadtask_msg = &msg->msg.offload; + int id = offloadtask_msg->channel_id; + struct abox_platform_data *platform_data; + + if (id != 5) { + dev_warn(dev, "%s: unknown channel id(%d)\n", __func__, id); + id = 5; + } + platform_data = platform_get_drvdata(data->pdev_rdma[id]); + + if (platform_data->compr_data.isr_handler) + platform_data->compr_data.isr_handler(data->pdev_rdma[id]); + else + dev_warn(dev, "Redundant offload message on rdma[%d]", id); +} + +static irqreturn_t abox_irq_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct abox_data *data = platform_get_drvdata(pdev); + ABOX_IPC_MSG msg; + + if (abox_dma_irq_handler(irq, data) == IRQ_HANDLED) + goto out; + + memcpy(&msg, data->sram_base + data->ipc_rx_offset, sizeof(msg)); + writel(0, data->sram_base + data->ipc_rx_ack_offset); + + dev_dbg(dev, "%s: irq=%d, ipcid=%d\n", __func__, irq, msg.ipcid); + + switch (irq) { + case IPC_SYSTEM: + abox_system_ipc_handler(dev, data, &msg); + break; + case IPC_PCMPLAYBACK: + abox_playback_ipc_handler(dev, data, &msg); + break; + case IPC_PCMCAPTURE: + abox_capture_ipc_handler(dev, data, &msg); + break; + case IPC_OFFLOAD: + abox_offload_ipc_handler(dev, data, &msg); + break; + default: + abox_registered_ipc_handler(dev, irq, data, &msg, false); + break; + } +out: + abox_log_schedule_flush_all(dev); + + dev_dbg(dev, "%s: exit\n", __func__); + return IRQ_HANDLED; +} + +static int abox_cpu_pm_ipc(struct device *dev, bool resume) +{ + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system = &msg.msg.system; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + msg.ipcid = IPC_SYSTEM; + system->msgtype = resume ? ABOX_RESUME : ABOX_SUSPEND; + ret = abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), + 1, 1); + if (!resume) { + int i = 1000; + unsigned int val; + + do { + exynos_pmu_read(ABOX_CPU_STANDBY, &val); + } while (--i && !(val & ABOX_CPU_STANDBY_WFI_MASK)); + + if (!(val & ABOX_CPU_STANDBY_WFI_MASK)) { + dev_warn(dev, "calliope suspend time out\n"); + ret = -ETIME; + } + } + + return ret; +} + +static void abox_pad_retention(bool retention) +{ + if (retention) { +#ifndef EMULATOR + exynos_pmu_update(GPIO_MODE_ABOX_SYS_PWR_REG, 0x1, 0x1); +#else + update_mask_value(pmu_alive + GPIO_MODE_ABOX_SYS_PWR_REG, + 0x1, 0x1); +#endif + } else { +#ifndef EMULATOR + exynos_pmu_update(PAD_RETENTION_ABOX_OPTION, + 0x10000000, 0x10000000); + exynos_pmu_update(GPIO_MODE_ABOX_SYS_PWR_REG, 0x1, 0x1); +#else + update_mask_value(pmu_alive + PAD_RETENTION_ABOX_OPTION, + 0x10000000, 0x10000000); + update_mask_value(pmu_alive + GPIO_MODE_ABOX_SYS_PWR_REG, + 0x1, 0x1); +#endif + } +} + +static void abox_cpu_power(bool on) +{ + pr_info("%s(%d)\n", __func__, on); + +#ifndef EMULATOR + exynos_pmu_update(ABOX_CPU_CONFIGURATION, ABOX_CPU_LOCAL_PWR_CFG, + on ? ABOX_CPU_LOCAL_PWR_CFG : 0); +#else + update_mask_value(pmu_alive + ABOX_CPU_CONFIGURATION, + ABOX_CPU_LOCAL_PWR_CFG, + on ? ABOX_CPU_LOCAL_PWR_CFG : 0); +#endif +} + +static int abox_cpu_enable(bool enable) +{ + unsigned int mask = ABOX_CPU_OPTION_ENABLE_CPU_MASK; + unsigned int val = (enable ? mask : 0); + unsigned int status = 0; + unsigned long after; + + + pr_info("%s(%d)\n", __func__, enable); + +#ifndef EMULATOR + exynos_pmu_update(ABOX_CPU_OPTION, mask, val); +#else + update_mask_value(pmu_alive + ABOX_CPU_OPTION, mask, val); +#endif + if (enable) { + after = jiffies + LIMIT_IN_JIFFIES; + do { +#ifndef EMULATOR + exynos_pmu_read(ABOX_CPU_STATUS, &status); +#else + status = readl(pmu_alive + ABOX_CPU_STATUS); +#endif + } while (((status & ABOX_CPU_STATUS_STATUS_MASK) + != ABOX_CPU_STATUS_STATUS_MASK) + && time_is_after_eq_jiffies(after)); + if (time_is_before_jiffies(after)) { + pr_err("abox cpu enable timeout\n"); + return -ETIME; + } + } + + return 0; + +} + +static void abox_save_register(struct abox_data *data) +{ + regcache_cache_only(data->regmap, true); + regcache_mark_dirty(data->regmap); +} + +static void abox_restore_register(struct abox_data *data) +{ + regcache_cache_only(data->regmap, false); + regcache_sync(data->regmap); +} + +static void abox_reload_extra_firmware(struct abox_data *data, const char *name) +{ + struct platform_device *pdev = data->pdev; + struct device *dev = &pdev->dev; + struct abox_extra_firmware *ext_fw; + int ret; + + dev_dbg(dev, "%s(%s)\n", __func__, name); + + for (ext_fw = data->firmware_extra; ext_fw - data->firmware_extra < + ARRAY_SIZE(data->firmware_extra); ext_fw++) { + if (!ext_fw->name || strcmp(ext_fw->name, name)) + continue; + + release_firmware(ext_fw->firmware); + ret = request_firmware(&ext_fw->firmware, ext_fw->name, dev); + if (ret < 0) { + dev_err(dev, "%s: %s request failed\n", __func__, + ext_fw->name); + break; + } + dev_info(dev, "%s is reloaded at %p (%zu)\n", name, + ext_fw->firmware->data, + ext_fw->firmware->size); + } +} + +static void abox_request_extra_firmware(struct abox_data *data) +{ + struct platform_device *pdev = data->pdev; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct abox_extra_firmware *ext_fw; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + ext_fw = data->firmware_extra; + for_each_child_of_node(np, child_np) { + const char *status; + + status = of_get_property(child_np, "status", NULL); + if (status && strcmp("okay", status) && strcmp("ok", status)) + continue; + + ret = of_property_read_string(child_np, "samsung,name", + &ext_fw->name); + if (ret < 0) + continue; + + ret = of_property_read_u32(child_np, "samsung,area", + &ext_fw->area); + if (ret < 0) + continue; + + ret = of_property_read_u32(child_np, "samsung,offset", + &ext_fw->offset); + if (ret < 0) + continue; + + dev_dbg(dev, "%s: name=%s, area=%u, offset=%u\n", __func__, + ext_fw->name, ext_fw->area, ext_fw->offset); + + if (!ext_fw->firmware) { + dev_dbg(dev, "%s: request %s\n", __func__, + ext_fw->name); + ret = request_firmware(&ext_fw->firmware, + ext_fw->name, dev); + if (ret < 0) + dev_err(dev, "%s: %s request failed\n", + __func__, ext_fw->name); + } + ext_fw++; + } + +} + +static void abox_download_extra_firmware(struct abox_data *data) +{ + struct device *dev = &data->pdev->dev; + struct abox_extra_firmware *ext_fw; + void __iomem *base; + size_t size; + + dev_dbg(dev, "%s\n", __func__); + + for (ext_fw = data->firmware_extra; ext_fw - data->firmware_extra < + ARRAY_SIZE(data->firmware_extra); ext_fw++) { + if (!ext_fw->firmware) + continue; + + switch (ext_fw->area) { + case 0: + base = data->sram_base; + size = data->sram_size; + break; + case 1: + base = data->dram_base; + size = DRAM_FIRMWARE_SIZE; + break; + case 2: + base = phys_to_virt(shm_get_vss_base()); + size = shm_get_vss_size(); + break; + default: + dev_err(dev, "%s: area is invalid name=%s, area=%u, offset=%u\n", + __func__, ext_fw->name, ext_fw->area, + ext_fw->offset); + continue; + } + + if (ext_fw->offset + ext_fw->firmware->size > size) { + dev_err(dev, "%s: firmware is too large name=%s, area=%u, offset=%u\n", + __func__, ext_fw->name, ext_fw->area, + ext_fw->offset); + continue; + } + + memcpy(base + ext_fw->offset, ext_fw->firmware->data, + ext_fw->firmware->size); + dev_info(dev, "%s is downloaded at area %u offset %u\n", + ext_fw->name, ext_fw->area, ext_fw->offset); + } +} + +static int abox_request_firmware(struct device *dev, + const struct firmware **fw, const char *name) +{ + int ret; + + dev_dbg(dev, "%s\n", __func__); + + release_firmware(*fw); + ret = request_firmware(fw, name, dev); + if (ret < 0) { + dev_err(dev, "%s: %s request failed\n", __func__, name); + } else { + dev_info(dev, "%s is loaded at %p (%zu)\n", name, + (*fw)->data, (*fw)->size); + } + + return ret; +} + +static void abox_complete_sram_firmware_request(const struct firmware *fw, + void *context) +{ + struct platform_device *pdev = context; + struct device *dev = &pdev->dev; + struct abox_data *data = platform_get_drvdata(pdev); + + if (!fw) { + dev_err(dev, "Failed to request firmware\n"); + return; + } + + if (data->firmware_sram) + release_firmware(data->firmware_sram); + + data->firmware_sram = fw; + + dev_info(dev, "SRAM firmware loaded at %p (%zu)\n", fw->data, fw->size); + + abox_request_firmware(dev, &data->firmware_dram, "calliope_dram.bin"); + abox_request_firmware(dev, &data->firmware_iva, "calliope_iva.bin"); + abox_request_extra_firmware(data); + + if (abox_test_quirk(data, ABOX_QUIRK_OFF_ON_SUSPEND)) + if (pm_runtime_active(dev)) + abox_enable(dev); +} + +static int abox_download_firmware(struct platform_device *pdev) +{ + struct abox_data *data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + dev_info(dev, "%s\n", __func__); + + if (unlikely(!data->firmware_sram)) { + request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, + "calliope_sram.bin", + dev, + GFP_KERNEL, + pdev, + abox_complete_sram_firmware_request); + dev_warn(dev, "SRAM firmware downloading is deferred\n"); + return -EAGAIN; + } + memcpy_toio(data->sram_base, data->firmware_sram->data, + data->firmware_sram->size); + memset_io(data->sram_base + data->firmware_sram->size, 0, + data->sram_size - data->firmware_sram->size); + + if (unlikely(!data->firmware_dram)) { + dev_warn(dev, "DRAM firmware downloading is defferred\n"); + return -EAGAIN; + } + memcpy(data->dram_base, data->firmware_dram->data, + data->firmware_dram->size); + memset(data->dram_base + data->firmware_dram->size, 0, + DRAM_FIRMWARE_SIZE - data->firmware_dram->size); + + if (unlikely(!data->firmware_iva)) { + dev_warn(dev, "IVA firmware is not loaded\n"); + } else { + memcpy(data->iva_base, data->firmware_iva->data, + data->firmware_iva->size); + memset(data->iva_base + data->firmware_iva->size, 0, + IVA_FIRMWARE_SIZE - data->firmware_iva->size); + } + + abox_download_extra_firmware(data); + + return 0; +} + +static void abox_cfg_gpio(struct device *dev, const char *name) +{ + struct abox_data *data = dev_get_drvdata(dev); + struct pinctrl_state *pin_state; + int ret; + + dev_info(dev, "%s(%s)\n", __func__, name); + + if (!data->pinctrl) + return; + + pin_state = pinctrl_lookup_state(data->pinctrl, name); + if (IS_ERR(pin_state)) { + dev_err(dev, "Couldn't find pinctrl %s\n", name); + } else { + ret = pinctrl_select_state(data->pinctrl, pin_state); + if (ret < 0) + dev_err(dev, "Unable to configure pinctrl %s\n", name); + } +} + +#undef MANUAL_SECURITY_CHANGE +#ifdef MANUAL_SECURITY_CHANGE +static void work_temp_function(struct work_struct *work) +{ + exynos_smc(0x82000701, 0, 0, 0); + pr_err("%s: ABOX_CA7 security changed!!!\n", __func__); +} +static DECLARE_DELAYED_WORK(work_temp, work_temp_function); +#endif + +#undef IVA_SRAM_SHARING +#ifdef IVA_SRAM_SHARING +#include + +int abox_ima_claim(struct device *dev, struct abox_data *data, + phys_addr_t *addr) +{ + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + phys_addr_t paddr; + int ret; + + dev_info(dev, "%s\n", __func__); + + mutex_lock(&data->ima_lock); + + if (data->ima_claimed) { + mutex_unlock(&data->ima_lock); + return 0; + } + + data->ima_vaddr = ima_alloc(data->ima_client, IVA_FIRMWARE_SIZE, 0); + if (IS_ERR_OR_NULL(data->ima_vaddr)) { + dev_err(dev, "%s: ima_alloc failed: %ld\n", __func__, + PTR_ERR(data->ima_vaddr)); + ret = data->ima_vaddr ? PTR_ERR(data->ima_vaddr) : -ENOMEM; + goto error; + } + paddr = ima_get_dma_addr(data->ima_client, data->ima_vaddr); + if (addr) + *addr = paddr; + + ret = iommu_map(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), paddr, + IVA_FIRMWARE_SIZE, 0); + if (ret < 0) { + dev_err(dev, "%s: iommu mapping failed(%d)\n", __func__, + ret); + goto error; + } + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_START_CLAIM_SRAM; + system_msg->param1 = ABOX_IVA_MEMORY_PREPARE; + system_msg->param2 = IOVA_IVA(ABOX_IVA_MEMORY_PREPARE); + system_msg->param3 = IVA_FIRMWARE_SIZE; + ret = abox_request_ipc(&data->pdev->dev, msg.ipcid, &msg, + sizeof(msg), 0, 0); + if (ret < 0) + goto error; + + ret = wait_event_timeout(data->ipc_wait_queue, + data->ima_claimed, msecs_to_jiffies(1000)); + if (data->ima_claimed) { + ret = 0; + } else { + dev_err(dev, "IVA memory claim failed\n"); + ret = -ETIME; + goto error; + } + + iommu_unmap(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY), + IVA_FIRMWARE_SIZE); + iommu_unmap(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), + IVA_FIRMWARE_SIZE); + exynos_sysmmu_tlb_invalidate(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY), IVA_FIRMWARE_SIZE); + exynos_sysmmu_tlb_invalidate(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), IVA_FIRMWARE_SIZE); + + ret = iommu_map(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY), + paddr, IVA_FIRMWARE_SIZE, 0); + if (ret < 0) { + dev_err(dev, "%s: iommu mapping failed(%d)\n", __func__, + ret); + goto error; + } + ret = iommu_map(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), + data->iva_base_phys, IVA_FIRMWARE_SIZE, 0); + if (ret < 0) { + dev_err(dev, "%s: iommu mapping failed(%d)\n", __func__, + ret); + goto error; + } + + system_msg->msgtype = ABOX_REPORT_SRAM; + system_msg->param1 = ABOX_IVA_MEMORY; + system_msg->param2 = IOVA_IVA(ABOX_IVA_MEMORY); + system_msg->param3 = IVA_FIRMWARE_SIZE; + ret = abox_request_ipc(&data->pdev->dev, msg.ipcid, &msg, + sizeof(msg), 0, 1); + if (ret < 0) + goto error; + + mutex_unlock(&data->ima_lock); + return ret; + +error: + iommu_unmap(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY), + IVA_FIRMWARE_SIZE); + iommu_unmap(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), + IVA_FIRMWARE_SIZE); + exynos_sysmmu_tlb_invalidate(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY), IVA_FIRMWARE_SIZE); + exynos_sysmmu_tlb_invalidate(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), IVA_FIRMWARE_SIZE); + iommu_map(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY), + data->iva_base_phys, IVA_FIRMWARE_SIZE, 0); + ima_free(data->ima_client, data->ima_vaddr); + mutex_unlock(&data->ima_lock); + return ret; +} + +static int abox_ima_reclaim(struct ima_client *client, struct device *dev, + void *priv) +{ + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + struct abox_data *data = priv; + long ret; + + dev_info(dev, "%s\n", __func__); + + mutex_lock(&data->ima_lock); + + if (!data->ima_claimed) { + ret = 0; + goto error; + } + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_START_RECLAIM_SRAM; + system_msg->param1 = ABOX_IVA_MEMORY; + system_msg->param2 = IOVA_IVA(ABOX_IVA_MEMORY_PREPARE); + system_msg->param3 = IVA_FIRMWARE_SIZE; + + abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); + + ret = wait_event_timeout(data->ipc_wait_queue, + !data->ima_claimed, msecs_to_jiffies(1000)); + if (!data->ima_claimed) { + ret = 0; + } else { + dev_err(dev, "IVA memory reclamation failed\n"); + ret = -ETIME; + } + + iommu_unmap(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY), + IVA_FIRMWARE_SIZE); + iommu_unmap(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), + IVA_FIRMWARE_SIZE); + exynos_sysmmu_tlb_invalidate(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY), IVA_FIRMWARE_SIZE); + exynos_sysmmu_tlb_invalidate(data->iommu_domain, + IOVA_IVA(ABOX_IVA_MEMORY_PREPARE), IVA_FIRMWARE_SIZE); + + ret = iommu_map(data->iommu_domain, IOVA_IVA(ABOX_IVA_MEMORY), + data->iva_base_phys, IVA_FIRMWARE_SIZE, 0); + if (ret < 0) { + dev_err(dev, "%s: iommu mapping failed(%ld)\n", __func__, + ret); + goto error; + } + + ima_free(data->ima_client, data->ima_vaddr); + + system_msg->msgtype = ABOX_REPORT_DRAM; + system_msg->param1 = ABOX_IVA_MEMORY; + system_msg->param2 = IOVA_IVA(ABOX_IVA_MEMORY); + system_msg->param3 = IVA_FIRMWARE_SIZE; + ret = abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 1); + if (ret < 0) + goto error; + +error: + mutex_unlock(&data->ima_lock); + return (int)ret; +} + +static int abox_ima_init(struct device *dev, struct abox_data *data) +{ + dev_dbg(dev, "%s\n", __func__); + + mutex_init(&data->ima_lock); + data->ima_client = ima_create_client(dev, abox_ima_reclaim, data); + if (IS_ERR(data->ima_client)) { + dev_err(dev, "ima_create_client failed: %ld\n", + PTR_ERR(data->ima_client)); + return PTR_ERR(data->ima_client); + } + + return 0; +} +#else +int abox_ima_claim(struct device *dev, struct abox_data *data, + phys_addr_t *addr) +{ + return 0; +} + +static int abox_ima_reclaim(struct ima_client *client, struct device *dev, + void *priv) +{ + return 0; +} + +static int abox_ima_init(struct device *dev, struct abox_data *data) +{ + return 0; +} +#endif +static void __abox_control_l2c(struct abox_data *data, bool enable) +{ + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + struct device *dev = &data->pdev->dev; + + if (data->l2c_enabled == enable) + return; + + dev_info(dev, "%s(%d)\n", __func__, enable); + + data->l2c_controlled = false; + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_START_L2C_CONTROL; + system_msg->param1 = enable ? 1 : 0; + + if (enable) { + vts_acquire_sram(data->pdev_vts, 0); + + abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 1, 0); + wait_event_timeout(data->ipc_wait_queue, + data->l2c_controlled, LIMIT_IN_JIFFIES); + if (!data->l2c_controlled) + dev_err(dev, "l2c enable failed\n"); + } else { + abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 1, 0); + wait_event_timeout(data->ipc_wait_queue, + data->l2c_controlled, LIMIT_IN_JIFFIES); + if (!data->l2c_controlled) + dev_err(dev, "l2c disable failed\n"); + + vts_release_sram(data->pdev_vts, 0); + } + + data->l2c_enabled = enable; +} + +static void abox_l2c_work_func(struct work_struct *work) +{ + struct abox_data *data = container_of(work, struct abox_data, l2c_work); + struct platform_device *pdev = data->pdev; + struct device *dev = &pdev->dev; + size_t length = ARRAY_SIZE(data->l2c_requests); + struct abox_l2c_request *request; + bool enable = false; + + dev_dbg(dev, "%s\n", __func__); + + for (request = data->l2c_requests; + request - data->l2c_requests < length + && request->id; + request++) { + if (request->on) { + enable = true; + break; + } + } + + __abox_control_l2c(data, enable); +} + +int abox_request_l2c(struct device *dev, struct abox_data *data, + void *id, bool on) +{ + struct abox_l2c_request *request; + size_t length = ARRAY_SIZE(data->l2c_requests); + + if (!abox_test_quirk(data, ABOX_QUIRK_SHARE_VTS_SRAM)) + return 0; + + dev_info(dev, "%s(%p, %d)\n", __func__, id, on); + + for (request = data->l2c_requests; + request - data->l2c_requests < length + && request->id && request->id != id; + request++) { + } + + request->on = on; + wmb(); /* on is read after id in reading function */ + request->id = id; + + if (request - data->l2c_requests >= ARRAY_SIZE(data->l2c_requests)) { + dev_err(dev, "%s: out of index. id=%p, on=%d\n", + __func__, id, on); + return -ENOMEM; + } + + schedule_work(&data->l2c_work); + + return 0; +} + +int abox_request_l2c_sync(struct device *dev, struct abox_data *data, + void *id, bool on) +{ + if (!abox_test_quirk(data, ABOX_QUIRK_SHARE_VTS_SRAM)) + return 0; + + abox_request_l2c(dev, data, id, on); + flush_work(&data->l2c_work); + return 0; +} + +static void abox_clear_l2c_requests(struct device *dev, struct abox_data *data) +{ + struct abox_l2c_request *req; + size_t len = ARRAY_SIZE(data->l2c_requests); + + if (!abox_test_quirk(data, ABOX_QUIRK_SHARE_VTS_SRAM)) + return; + + dev_info(dev, "%s\n", __func__); + + for (req = data->l2c_requests; req - data->l2c_requests < len && + req->id; req++) { + req->on = false; + } + + __abox_control_l2c(data, false); +} + +static bool abox_is_timer_set(struct abox_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, ABOX_TIMER_CTRL1(0), &val); + if (ret < 0) + val = 0; + + return !!val; +} + +static void abox_start_timer(struct abox_data *data) +{ + struct regmap *regmap = data->regmap; + + regmap_write(regmap, ABOX_TIMER_CTRL0(0), 1 << ABOX_TIMER_START_L); + regmap_write(regmap, ABOX_TIMER_CTRL0(1), 1 << ABOX_TIMER_START_L); + regmap_write(regmap, ABOX_TIMER_CTRL0(2), 1 << ABOX_TIMER_START_L); + regmap_write(regmap, ABOX_TIMER_CTRL0(3), 1 << ABOX_TIMER_START_L); +} + +static int abox_enable(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct abox_data *data = dev_get_drvdata(dev); + unsigned int i, value; + bool has_reset; + int ret = 0; + + dev_info(dev, "%s\n", __func__); + + abox_gic_enable_irq(data->dev_gic); + + abox_request_cpu_gear_sync(dev, data, (void *)DEFAULT_CPU_GEAR_ID, + ABOX_CPU_GEAR_MAX); + + if (is_secure_gic()) { + exynos_pmu_write(ABOX_MAGIC, 0); + ret = exynos_smc(0x82000501, 0, 0, 0); + dev_dbg(dev, "%s: smc ret=%d\n", __func__, ret); + + for (i = 1000; i; i--) { + exynos_pmu_read(ABOX_MAGIC, &value); + if (value == ABOX_MAGIC_VALUE) + break; + } + if (value != ABOX_MAGIC_VALUE) + dev_warn(dev, "%s: abox magic timeout\n", __func__); + abox_cpu_enable(false); + abox_cpu_power(false); + } + + if (abox_test_quirk(data, ABOX_QUIRK_SHARE_VTS_SRAM)) { + writel(0x1, data->sysreg_base + ABOX_SYSREG_MISC_CON); + writel(0x1, data->sysreg_base + ABOX_SYSREG_L2_CACHE_CON); + } + + ret = clk_enable(data->clk_cpu); + if (ret < 0) { + dev_err(dev, "Failed to enable cpu clock: %d\n", ret); + goto error; + } + + ret = clk_set_rate(data->clk_audif, AUDIF_RATE_HZ); + if (ret < 0) { + dev_err(dev, "Failed to set audif clock: %d\n", ret); + goto error; + } + dev_info(dev, "audif clock: %lu\n", clk_get_rate(data->clk_audif)); + + ret = clk_enable(data->clk_audif); + if (ret < 0) { + dev_err(dev, "Failed to enable audif clock: %d\n", ret); + goto error; + } + + abox_cfg_gpio(dev, "default"); + + abox_restore_register(data); + has_reset = !abox_is_timer_set(data); + if (!has_reset) { + dev_info(dev, "wakeup from WFI\n"); + abox_start_timer(data); + } else { + abox_gic_init_gic(data->dev_gic); + + ret = abox_download_firmware(pdev); + if (ret < 0) { + if (ret != -EAGAIN) + dev_err(dev, "Failed to download firmware\n"); + else + ret = 0; + + abox_request_cpu_gear(dev, data, + (void *)DEFAULT_CPU_GEAR_ID, + ABOX_CPU_GEAR_MIN); + goto error; + } + } + + abox_request_dram_on(pdev, dev, true); + if (has_reset) { + abox_cpu_power(true); + abox_cpu_enable(true); + } + data->calliope_state = CALLIOPE_ENABLING; + + abox_pad_retention(false); +#ifdef MANUAL_SECURITY_CHANGE + schedule_delayed_work(&work_temp, msecs_to_jiffies(3000)); +#endif + + data->enabled = true; + + if (has_reset) + pm_wakeup_event(dev, BOOT_DONE_TIMEOUT_MS); + else + abox_boot_done(dev, data->calliope_version); + +error: + return ret; +} + +static int abox_disable(struct device *dev) +{ + struct abox_data *data = dev_get_drvdata(dev); + enum calliope_state state = data->calliope_state; + + dev_info(dev, "%s\n", __func__); + + /* AUD_PLL must be normal during suspend */ + clk_set_rate(data->clk_pll, AUD_PLL_RATE_HZ_FOR_48000); + + data->calliope_state = CALLIOPE_DISABLING; + abox_cache_components(dev, data); + abox_ima_reclaim(data->ima_client, dev, data); + abox_clear_l2c_requests(dev, data); + flush_work(&data->boot_done_work); + flush_work(&data->l2c_work); + if (state != CALLIOPE_DISABLED) + abox_cpu_pm_ipc(dev, false); + data->calliope_state = CALLIOPE_DISABLED; + abox_log_drain_all(dev); + + abox_save_register(data); + abox_cfg_gpio(dev, "idle"); + abox_pad_retention(true); + data->enabled = false; + clk_disable(data->clk_cpu); + abox_gic_disable_irq(data->dev_gic); + abox_failsafe_report_reset(dev); + + return 0; +} + +void abox_poweroff(void) +{ + struct platform_device *pdev = p_abox_data->pdev; + struct device *dev = &pdev->dev; + struct abox_data *data = dev_get_drvdata(dev); + + if (data->calliope_state == CALLIOPE_DISABLED) { + dev_dbg(dev, "already disabled\n"); + return; + } + dev_info(dev, "%s\n", __func__); + + abox_disable(dev); + + //exynos_sysmmu_control(dev, false); +} + +static int abox_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + + //p_abox_data->enabled = false; + abox_disable(dev); + + return 0; +} + +static int abox_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + + //exynos_sysmmu_control(dev, true); + + return abox_enable(dev); +} + +static int abox_suspend(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + /* nothing to do */ + return 0; +} + +static int abox_resume(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + /* nothing to do */ + return 0; +} + +static int abox_qos_notifier(struct notifier_block *nb, + unsigned long action, void *nb_data) +{ + struct abox_data *data = container_of(nb, struct abox_data, qos_nb); + struct device *dev = &data->pdev->dev; + long value = (long)action; + long qos_class = (long)nb_data; + unsigned long aclk = clk_get_rate(data->clk_bus); + unsigned int sifs_cnt0, sifs_cnt1, cnt_val, rate, pwidth, channels; + unsigned long sifs0_cnt, sifs1_cnt, sifs2_cnt; + int ret; + + dev_dbg(dev, "%s(%ldkHz, %ld)\n", __func__, value, qos_class); + + ret = regmap_read(data->regmap, ABOX_SPUS_CTRL_SIFS_CNT0, &sifs_cnt0); + if (ret < 0) { + dev_err(dev, "%s: SPUS_CTRL_SIFS_CNT0 read fail: %d\n", + __func__, ret); + goto out; + } + ret = regmap_read(data->regmap, ABOX_SPUS_CTRL_SIFS_CNT1, &sifs_cnt1); + if (ret < 0) { + dev_err(dev, "%s: SPUS_CTRL_SIFS_CNT1 read fail: %d\n", + __func__, ret); + goto out; + } + + sifs0_cnt = (sifs_cnt0 & ABOX_SIFS0_CNT_VAL_MASK) >> + ABOX_SIFS0_CNT_VAL_L; + sifs1_cnt = (sifs_cnt0 & ABOX_SIFS1_CNT_VAL_MASK) >> + ABOX_SIFS1_CNT_VAL_L; + sifs2_cnt = (sifs_cnt1 & ABOX_SIFS2_CNT_VAL_MASK) >> + ABOX_SIFS2_CNT_VAL_L; + + if (sifs0_cnt) { + rate = abox_get_sif_rate(data, SET_MIXER_SAMPLE_RATE); + pwidth = abox_get_sif_physical_width(data, SET_MIXER_FORMAT); + channels = abox_get_sif_channels(data, SET_MIXER_FORMAT); + cnt_val = abox_sifsx_cnt_val(aclk, rate, pwidth, channels); + dev_info(dev, "%s: %s <= %u\n", __func__, "SIFS0_CNT_VAL", + cnt_val); + ret = regmap_update_bits(data->regmap, ABOX_SPUS_CTRL_SIFS_CNT0, + ABOX_SIFS0_CNT_VAL_MASK, + (unsigned int)cnt_val << ABOX_SIFS0_CNT_VAL_L); + if (ret < 0) + dev_err(dev, "regmap update failed: %d\n", ret); + } + if (sifs1_cnt) { + rate = abox_get_sif_rate(data, SET_OUT1_SAMPLE_RATE); + pwidth = abox_get_sif_physical_width(data, SET_OUT1_FORMAT); + channels = abox_get_sif_channels(data, SET_OUT1_FORMAT); + cnt_val = abox_sifsx_cnt_val(aclk, rate, pwidth, channels); + dev_info(dev, "%s: %s <= %u\n", __func__, "SIFS0_CNT_VAL", + cnt_val); + ret = regmap_update_bits(data->regmap, ABOX_SPUS_CTRL_SIFS_CNT0, + ABOX_SIFS1_CNT_VAL_MASK, + (unsigned int)cnt_val << ABOX_SIFS1_CNT_VAL_L); + if (ret < 0) + dev_err(dev, "regmap update failed: %d\n", ret); + } + if (sifs2_cnt) { + rate = abox_get_sif_rate(data, SET_OUT2_SAMPLE_RATE); + pwidth = abox_get_sif_physical_width(data, SET_OUT2_FORMAT); + channels = abox_get_sif_channels(data, SET_OUT2_FORMAT); + cnt_val = abox_sifsx_cnt_val(aclk, rate, pwidth, channels); + dev_info(dev, "%s: %s <= %u\n", __func__, "SIFS0_CNT_VAL", + cnt_val); + ret = regmap_update_bits(data->regmap, ABOX_SPUS_CTRL_SIFS_CNT1, + ABOX_SIFS2_CNT_VAL_MASK, + (unsigned int)cnt_val << ABOX_SIFS2_CNT_VAL_L); + if (ret < 0) + dev_err(dev, "regmap update failed: %d\n", ret); + } +out: + return NOTIFY_DONE; +} + +static int abox_pm_notifier(struct notifier_block *nb, + unsigned long action, void *nb_data) +{ + struct abox_data *data = container_of(nb, struct abox_data, pm_nb); + struct device *dev = &data->pdev->dev; + int ret; + + dev_dbg(dev, "%s(%lu)\n", __func__, action); + + switch (action) { + case PM_SUSPEND_PREPARE: + if (data->audio_mode != MODE_IN_CALL) { + enum calliope_state state; + + pm_runtime_barrier(dev); + state = data->calliope_state; + if (state == CALLIOPE_ENABLING) { + dev_info(dev, "calliope state: %d\n", state); + return NOTIFY_BAD; + } + /* clear cpu gears to abox power off */ + abox_clear_cpu_gear_requests(dev, data); + abox_cpu_gear_barrier(data); + flush_workqueue(data->ipc_workqueue); + if (abox_test_quirk(data, ABOX_QUIRK_OFF_ON_SUSPEND)) { + ret = pm_runtime_put_sync_suspend(dev); + if (ret < 0) { + pm_runtime_get(dev); + dev_info(dev, "runtime suspend: %d\n", + ret); + return NOTIFY_BAD; + } + } else { + ret = pm_runtime_suspend(dev); + if (ret < 0) { + dev_info(dev, "runtime suspend: %d\n", + ret); + return NOTIFY_BAD; + } + } + } + break; + case PM_POST_SUSPEND: + if (abox_test_quirk(data, ABOX_QUIRK_OFF_ON_SUSPEND)) + pm_runtime_get_sync(&data->pdev->dev); + break; + default: + /* Nothing to do */ + break; + } + return NOTIFY_DONE; +} + +static int abox_modem_notifier(struct notifier_block *nb, + unsigned long action, void *nb_data) +{ + struct abox_data *data = container_of(nb, struct abox_data, modem_nb); + struct device *dev = &data->pdev->dev; + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + + dev_info(&data->pdev->dev, "%s(%lu)\n", __func__, action); + + switch (action) { + case MODEM_EVENT_ONLINE: + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_START_VSS; + abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 1, 0); + break; + } + + return NOTIFY_DONE; +} + +#ifdef CONFIG_EXYNOS_ITMON +static int abox_itmon_notifier(struct notifier_block *nb, + unsigned long action, void *nb_data) +{ + struct abox_data *data = container_of(nb, struct abox_data, itmon_nb); + struct device *dev = &data->pdev->dev; + struct itmon_notifier *itmon_data = nb_data; + + if (itmon_data && itmon_data->dest && (strncmp("ABOX", itmon_data->dest, + sizeof("ABOX") - 1) == 0)) { + dev_info(dev, "%s(%lu)\n", __func__, action); + data->enabled = false; + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} +#endif + +static ssize_t calliope_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct abox_data *data = dev_get_drvdata(dev); + unsigned int version = be32_to_cpu(data->calliope_version); + + memcpy(buf, &version, sizeof(version)); + buf[4] = '\n'; + buf[5] = '\0'; + + return 6; +} + +static ssize_t calliope_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ABOX_IPC_MSG msg = {0,}; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_REQUEST_DEBUG; + ret = sscanf(buf, "%10d,%10d,%10d,%739s", &system_msg->param1, + &system_msg->param2, &system_msg->param3, + system_msg->bundle.param_bundle); + if (ret < 0) + return ret; + + ret = abox_request_ipc(dev, msg.ipcid, &msg, sizeof(msg), 0, 0); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t calliope_cmd_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + static const char cmd_reload_ext_bin[] = "RELOAD EXT BIN"; + static const char cmd_failsafe[] = "FAILSAFE"; + static const char cmd_cpu_gear[] = "CPU GEAR"; + struct abox_data *data = dev_get_drvdata(dev); + char name[80]; + + dev_dbg(dev, "%s(%s)\n", __func__, buf); + if (!strncmp(cmd_reload_ext_bin, buf, sizeof(cmd_reload_ext_bin) - 1)) { + dev_dbg(dev, "reload ext bin\n"); + if (sscanf(buf, "RELOAD EXT BIN:%63s", name) == 1) + abox_reload_extra_firmware(data, name); + } else if (!strncmp(cmd_failsafe, buf, sizeof(cmd_failsafe) - 1)) { + dev_dbg(dev, "failsafe\n"); + abox_failsafe_report(dev); + } else if (!strncmp(cmd_cpu_gear, buf, sizeof(cmd_cpu_gear) - 1)) { + unsigned int gear; + int ret; + + dev_info(dev, "set clk\n"); + ret = kstrtouint(buf + sizeof(cmd_cpu_gear), 10, &gear); + if (!ret) { + dev_info(dev, "gear = %u\n", gear); + pm_runtime_get_sync(dev); + abox_request_cpu_gear(dev, data, + (void *)TEST_CPU_GEAR_ID, gear); + dev_info(dev, "bus clk = %lu\n", clk_get_rate(data->clk_bus)); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + } + } + + return count; +} + +static DEVICE_ATTR_RO(calliope_version); +static DEVICE_ATTR_WO(calliope_debug); +static DEVICE_ATTR_WO(calliope_cmd); + +static int samsung_abox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *np_tmp; + struct platform_device *pdev_tmp; + struct abox_data *data; + phys_addr_t paddr; + int ret, i; + + dev_info(dev, "%s\n", __func__); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + data->pdev = pdev; + p_abox_data = data; + + abox_probe_quirks(data, np); + init_waitqueue_head(&data->ipc_wait_queue); + spin_lock_init(&data->ipc_queue_lock); + device_init_wakeup(dev, true); + data->cpu_gear = ABOX_CPU_GEAR_MIN; + data->cpu_gear_min = 3; /* default value from kangchen */ + for (i = 0; i < ARRAY_SIZE(data->sif_rate); i++) { + data->sif_rate_min[i] = data->sif_rate[i] = 48000; + data->sif_format_min[i] = data->sif_format[i] = + SNDRV_PCM_FORMAT_S16; + data->sif_channels_min[i] = data->sif_channels[i] = 2; + } + INIT_WORK(&data->ipc_work, abox_process_ipc); + INIT_WORK(&data->change_cpu_gear_work, abox_change_cpu_gear_work_func); + INIT_WORK(&data->change_int_freq_work, abox_change_int_freq_work_func); + INIT_WORK(&data->change_mif_freq_work, abox_change_mif_freq_work_func); + INIT_WORK(&data->change_lit_freq_work, abox_change_lit_freq_work_func); + INIT_WORK(&data->change_big_freq_work, abox_change_big_freq_work_func); + INIT_WORK(&data->change_hmp_boost_work, + abox_change_hmp_boost_work_func); + INIT_WORK(&data->register_component_work, + abox_register_component_work_func); + INIT_WORK(&data->boot_done_work, abox_boot_done_work_func); + INIT_WORK(&data->l2c_work, abox_l2c_work_func); + INIT_DELAYED_WORK(&data->tickle_work, abox_tickle_work_func); + INIT_LIST_HEAD(&data->irq_actions); + + data->gear_workqueue = alloc_ordered_workqueue("abox_gear", + WQ_FREEZABLE | WQ_MEM_RECLAIM); + if (!data->gear_workqueue) { + dev_err(dev, "Couldn't create workqueue %s\n", "abox_gear"); + return -ENOMEM; + } + + data->ipc_workqueue = alloc_ordered_workqueue("abox_ipc", + WQ_MEM_RECLAIM); + if (!data->ipc_workqueue) { + dev_err(dev, "Couldn't create workqueue %s\n", "abox_ipc"); + return -ENOMEM; + } + + data->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(data->pinctrl)) { + dev_err(dev, "Couldn't get pins (%li)\n", + PTR_ERR(data->pinctrl)); + data->pinctrl = NULL; + } + + data->sfr_base = devm_request_and_map_byname(pdev, "sfr", + NULL, NULL); + if (IS_ERR(data->sfr_base)) + return PTR_ERR(data->sfr_base); + + data->sysreg_base = devm_request_and_map_byname(pdev, "sysreg", + NULL, NULL); + if (IS_ERR(data->sysreg_base)) + return PTR_ERR(data->sysreg_base); + + data->sram_base = devm_request_and_map_byname(pdev, "sram", + &data->sram_base_phys, &data->sram_size); + if (IS_ERR(data->sram_base)) + return PTR_ERR(data->sram_base); + + data->iommu_domain = get_domain_from_dev(dev); + if (IS_ERR(data->iommu_domain)) { + dev_err(dev, "Unable to get iommu domain\n"); + return PTR_ERR(data->iommu_domain); + } + + ret = iommu_attach_device(data->iommu_domain, dev); + if (ret < 0) { + dev_err(dev, "Unable to attach device to iommu (%d)\n", ret); + return ret; + } + + data->dram_base = dmam_alloc_coherent(dev, DRAM_FIRMWARE_SIZE, + &data->dram_base_phys, GFP_KERNEL); + if (IS_ERR_OR_NULL(data->dram_base)) { + dev_err(dev, "Failed to allocate coherent memory: %ld\n", + PTR_ERR(data->dram_base)); + return PTR_ERR(data->dram_base); + } + dev_info(&pdev->dev, "%s(%pa) is mapped on %p with size of %d\n", + "dram firmware", &data->dram_base_phys, data->dram_base, + DRAM_FIRMWARE_SIZE); + iommu_map(data->iommu_domain, IOVA_DRAM_FIRMWARE, data->dram_base_phys, + DRAM_FIRMWARE_SIZE, 0); + + data->iva_base = dmam_alloc_coherent(dev, IVA_FIRMWARE_SIZE, + &data->iva_base_phys, GFP_KERNEL); + if (IS_ERR_OR_NULL(data->iva_base)) { + dev_err(dev, "Failed to allocate coherent memory: %ld\n", + PTR_ERR(data->iva_base)); + return PTR_ERR(data->iva_base); + } + dev_info(&pdev->dev, "%s(%pa) is mapped on %p with size of %d\n", + "iva firmware", &data->iva_base_phys, data->iva_base, + IVA_FIRMWARE_SIZE); + iommu_map(data->iommu_domain, IOVA_IVA_FIRMWARE, data->iva_base_phys, + IVA_FIRMWARE_SIZE, 0); + + paddr = shm_get_vss_base(); + dev_info(&pdev->dev, "%s(%pa) is mapped on %p with size of %d\n", + "vss firmware", &paddr, phys_to_virt(paddr), + shm_get_vss_size()); + iommu_map(data->iommu_domain, IOVA_VSS_FIRMWARE, paddr, + shm_get_vss_size(), 0); + + paddr = shm_get_vparam_base(); + dev_info(&pdev->dev, "%s(%pa) is mapped on %p with size of %d\n", + "vss parameter", &paddr, phys_to_virt(paddr), + shm_get_vparam_size()); + iommu_map(data->iommu_domain, IOVA_VSS_PARAMETER, paddr, + shm_get_vparam_size(), 0); + + iommu_map(data->iommu_domain, 0x10000000, 0x10000000, PAGE_SIZE, 0); + iovmm_set_fault_handler(&pdev->dev, abox_iommu_fault_handler, data); + + data->clk_pll = devm_clk_get_and_prepare(pdev, "pll"); + if (IS_ERR(data->clk_pll)) + return PTR_ERR(data->clk_pll); + + data->clk_audif = devm_clk_get_and_prepare(pdev, "audif"); + if (IS_ERR(data->clk_audif)) + return PTR_ERR(data->clk_audif); + + data->clk_cpu = devm_clk_get_and_prepare(pdev, "cpu"); + if (IS_ERR(data->clk_cpu)) + return PTR_ERR(data->clk_cpu); + + data->clk_bus = devm_clk_get_and_prepare(pdev, "bus"); + if (IS_ERR(data->clk_bus)) + return PTR_ERR(data->clk_bus); + + ret = of_property_read_u32(np, "uaif_max_div", &data->uaif_max_div); + if (ret < 0) { + dev_warn(dev, "Failed to read %s: %d\n", "uaif_max_div", ret); + data->uaif_max_div = 32; + } + + ret = of_property_read_u32(np, "ipc_tx_offset", &data->ipc_tx_offset); + if (ret < 0) { + dev_err(dev, "Failed to read %s: %d\n", "ipc_tx_offset", ret); + return ret; + } + + ret = of_property_read_u32(np, "ipc_rx_offset", &data->ipc_rx_offset); + if (ret < 0) { + dev_err(dev, "Failed to read %s: %d\n", "ipc_rx_offset", ret); + return ret; + } + + ret = of_property_read_u32(np, "ipc_tx_ack_offset", + &data->ipc_tx_ack_offset); + if (ret < 0) { + dev_err(dev, "Failed to read %s: %d\n", "ipc_tx_ack_offset", + ret); + return ret; + } + + ret = of_property_read_u32(np, "ipc_rx_ack_offset", + &data->ipc_rx_ack_offset); + if (ret < 0) { + dev_err(dev, "Failed to read %s: %d\n", "ipc_rx_ack_offset", + ret); + return ret; + } + + ret = of_property_read_u32_array(np, "pm_qos_int", data->pm_qos_int, + ARRAY_SIZE(data->pm_qos_int)); + if (ret < 0) + dev_warn(dev, "Failed to read %s: %d\n", "pm_qos_int", ret); + + ret = of_property_read_u32_array(np, "pm_qos_aud", data->pm_qos_aud, + ARRAY_SIZE(data->pm_qos_aud)); + if (ret < 0) { + dev_warn(dev, "Failed to read %s: %d\n", "pm_qos_aud", ret); + } else { + for (i = 0; i < ARRAY_SIZE(data->pm_qos_aud); i++) { + if (!data->pm_qos_aud[i]) { + data->cpu_gear_min = i; + break; + } + } + } + + np_tmp = of_parse_phandle(np, "abox_gic", 0); + if (!np_tmp) { + dev_err(dev, "Failed to get abox_gic device node\n"); + return -EPROBE_DEFER; + } + pdev_tmp = of_find_device_by_node(np_tmp); + if (!pdev_tmp) { + dev_err(dev, "Failed to get abox_gic platform device\n"); + return -EPROBE_DEFER; + } + data->dev_gic = &pdev_tmp->dev; + + if (abox_test_quirk(data, ABOX_QUIRK_SHARE_VTS_SRAM)) { + np_tmp = of_parse_phandle(np, "vts", 0); + if (!np_tmp) { + dev_err(dev, "Failed to get vts device node\n"); + return -EPROBE_DEFER; + } + data->pdev_vts = of_find_device_by_node(np_tmp); + if (!data->pdev_vts) { + dev_err(dev, "Failed to get vts platform device\n"); + return -EPROBE_DEFER; + } + } + +#ifdef EMULATOR + pmu_alive = ioremap(0x16480000, 0x10000); +#endif + pm_qos_add_request(&abox_pm_qos_aud, PM_QOS_AUD_THROUGHPUT, 0); + pm_qos_add_request(&abox_pm_qos_int, PM_QOS_DEVICE_THROUGHPUT, 0); + pm_qos_add_request(&abox_pm_qos_mif, PM_QOS_BUS_THROUGHPUT, 0); + pm_qos_add_request(&abox_pm_qos_lit, PM_QOS_CLUSTER0_FREQ_MIN, 0); + pm_qos_add_request(&abox_pm_qos_big, PM_QOS_CLUSTER1_FREQ_MIN, 0); + + for (i = 0; i < ABOX_GIC_IRQ_COUNT; i++) + abox_gic_register_irq_handler(data->dev_gic, i, + abox_irq_handler, pdev); + + if (IS_ENABLED(CONFIG_SOC_EXYNOS8895)) { + abox_regmap_config.reg_defaults = abox_reg_defaults_8895; + abox_regmap_config.num_reg_defaults = + ARRAY_SIZE(abox_reg_defaults_8895); + } else if (IS_ENABLED(CONFIG_SOC_EXYNOS9810)) { + abox_regmap_config.reg_defaults = abox_reg_defaults_9810; + abox_regmap_config.num_reg_defaults = + ARRAY_SIZE(abox_reg_defaults_9810); + } else if (IS_ENABLED(CONFIG_SOC_EXYNOS9610)) { + abox_regmap_config.reg_defaults = abox_reg_defaults_9610; + abox_regmap_config.num_reg_defaults = + ARRAY_SIZE(abox_reg_defaults_9610); + } + data->regmap = devm_regmap_init_mmio(dev, + data->sfr_base, + &abox_regmap_config); + + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1); + pm_runtime_use_autosuspend(dev); + pm_runtime_get(dev); + + data->qos_nb.notifier_call = abox_qos_notifier; + pm_qos_add_notifier(PM_QOS_AUD_THROUGHPUT, &data->qos_nb); + + data->pm_nb.notifier_call = abox_pm_notifier; + register_pm_notifier(&data->pm_nb); + + data->modem_nb.notifier_call = abox_modem_notifier; + register_modem_event_notifier(&data->modem_nb); + +#ifdef CONFIG_EXYNOS_ITMON + data->itmon_nb.notifier_call = abox_itmon_notifier; + itmon_notifier_chain_register(&data->itmon_nb); +#endif + + abox_ima_init(dev, data); + abox_failsafe_init(dev); + + ret = device_create_file(dev, &dev_attr_calliope_version); + if (ret < 0) + dev_warn(dev, "Failed to create file: %s\n", "version"); + + ret = device_create_file(dev, &dev_attr_calliope_debug); + if (ret < 0) + dev_warn(dev, "Failed to create file: %s\n", "debug"); + + ret = device_create_file(dev, &dev_attr_calliope_cmd); + if (ret < 0) + dev_warn(dev, "Failed to create file: %s\n", "cmd"); + + atomic_notifier_chain_register(&panic_notifier_list, + &abox_panic_notifier); + + ret = snd_soc_register_component(dev, &abox_cmpnt, abox_dais, + ARRAY_SIZE(abox_dais)); + if (ret < 0) + dev_err(dev, "component register failed:%d\n", ret); + + dev_info(dev, "%s: probe complete\n", __func__); + + of_platform_populate(np, NULL, NULL, dev); + + return 0; +} + +static int samsung_abox_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct abox_data *data = platform_get_drvdata(pdev); + + dev_info(dev, "%s\n", __func__); + + pm_runtime_disable(dev); +#ifndef CONFIG_PM + abox_runtime_suspend(dev); +#endif + device_init_wakeup(dev, false); + destroy_workqueue(data->ipc_workqueue); + pm_qos_remove_request(&abox_pm_qos_aud); + pm_qos_remove_request(&abox_pm_qos_int); + pm_qos_remove_request(&abox_pm_qos_mif); + pm_qos_remove_request(&abox_pm_qos_lit); + pm_qos_remove_request(&abox_pm_qos_big); + snd_soc_unregister_component(dev); + iommu_unmap(data->iommu_domain, IOVA_DRAM_FIRMWARE, DRAM_FIRMWARE_SIZE); +#ifdef EMULATOR + iounmap(pmu_alive); +#endif + return 0; +} + +static void samsung_abox_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_info(dev, "%s\n", __func__); + pm_runtime_disable(dev); +} + +static const struct of_device_id samsung_abox_match[] = { + { + .compatible = "samsung,abox", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_match); + +static const struct dev_pm_ops samsung_abox_pm = { + SET_SYSTEM_SLEEP_PM_OPS(abox_suspend, abox_resume) + SET_RUNTIME_PM_OPS(abox_runtime_suspend, abox_runtime_resume, NULL) +}; + +static struct platform_driver samsung_abox_driver = { + .probe = samsung_abox_probe, + .remove = samsung_abox_remove, + .shutdown = samsung_abox_shutdown, + .driver = { + .name = "samsung-abox", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_match), + .pm = &samsung_abox_pm, + }, +}; + +module_platform_driver(samsung_abox_driver); + +static int __init samsung_abox_late_initcall(void) +{ + pr_info("%s\n", __func__); + + if (p_abox_data && p_abox_data->pdev) { + if (!abox_test_quirk(p_abox_data, ABOX_QUIRK_OFF_ON_SUSPEND)) + pm_runtime_put(&p_abox_data->pdev->dev); + } else { + pr_err("%s: p_abox_data or pdev is null", __func__); + } + + return 0; +} +late_initcall(samsung_abox_late_initcall); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box Driver"); +MODULE_ALIAS("platform:samsung-abox"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox.h b/sound/soc/samsung/abox/abox.h new file mode 100644 index 000000000000..4b1eefc0d47f --- /dev/null +++ b/sound/soc/samsung/abox/abox.h @@ -0,0 +1,1048 @@ +/* sound/soc/samsung/abox/abox.h + * + * ALSA SoC - Samsung Abox driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_H +#define __SND_SOC_ABOX_H + +#include +#include + +#define ABOX_MASK(name) (GENMASK(ABOX_##name##_H, ABOX_##name##_L)) +#define ABOX_MASK_ARG(name, x) (GENMASK(ABOX_##name##_H(x), ABOX_##name##_L(x))) +#define ABOX_IDX_ARG(name, o, x) (ABOX_##name##_BASE + \ + (x * ABOX_##name##_INTERVAL) + o) +#define ABOX_L_ARG(name, o, x) ABOX_IDX_ARG(name, o, x) +#define ABOX_H_ARG(name, o, x) ABOX_IDX_ARG(name, o, x) + +/* System */ +#define ABOX_IP_INDEX (0x0000) +#define ABOX_VERSION (0x0004) +#define ABOX_SYSPOWER_CTRL (0x0010) +#define ABOX_SYSPOWER_STATUS (0x0014) +#define ABOX_SYSTEM_CONFIG0 (0x0020) +#define ABOX_REMAP_MASK (0x0024) +#define ABOX_REMAP_ADDR (0x0028) +#define ABOX_DYN_CLOCK_OFF (0x0030) +#define ABOX_QCHANNEL_DISABLE (0x0038) +#define ABOX_ROUTE_CTRL0 (0x0040) +#define ABOX_ROUTE_CTRL1 (0x0044) +#define ABOX_ROUTE_CTRL2 (0x0048) +#define ABOX_TICK_DIV_RATIO (0x0050) +/* ABOX_SYSPOWER_CTRL */ +#define ABOX_SYSPOWER_CTRL_L (0) +#define ABOX_SYSPOWER_CTRL_H (0) +#define ABOX_SYSPOWER_CTRL_MASK ABOX_MASK(SYSPOWER_CTRL) +/* ABOX_SYSPOWER_STATUS */ +#define ABOX_SYSPOWER_STATUS_L (0) +#define ABOX_SYSPOWER_STATUS_H (0) +#define ABOX_SYSPOWER_STATUS_MASK ABOX_MASK(SYSPOWER_STATUS) +/* ABOX_DYN_CLOCK_OFF */ +#define ABOX_DYN_CLOCK_OFF_L (0) +#define ABOX_DYN_CLOCK_OFF_H (30) +#define ABOX_DYN_CLOCK_OFF_MASK ABOX_MASK(DYN_CLOCK_OFF) +/* ABOX_QCHANNEL_DISABLE */ +#define ABOX_QCHANNEL_DISABLE_BASE (0) +#define ABOX_QCHANNEL_DISABLE_INTERVAL (1) +#define ABOX_QCHANNEL_DISABLE_L(x) ABOX_L_ARG(QCHANNEL_DISABLE, 0, x) +#define ABOX_QCHANNEL_DISABLE_H(x) ABOX_H_ARG(QCHANNEL_DISABLE, 0, x) +#define ABOX_QCHANNEL_DISABLE_MASK(x) ABOX_MASK_ARG(QCHANNEL_DISABLE, x) +/* ABOX_ROUTE_CTRL0 */ +#define ABOX_ROUTE_DSIF_L (20) +#define ABOX_ROUTE_DSIF_H (23) +#define ABOX_ROUTE_DSIF_MASK ABOX_MASK(ROUTE_DSIF) +#define ABOX_ROUTE_UAIF_SPK_BASE (0) +#define ABOX_ROUTE_UAIF_SPK_INTERVAL (4) +#define ABOX_ROUTE_UAIF_SPK_L(x) ABOX_L_ARG(ROUTE_UAIF_SPK, 0, x) +#define ABOX_ROUTE_UAIF_SPK_H(x) ABOX_H_ARG(ROUTE_UAIF_SPK, 3, x) +#define ABOX_ROUTE_UAIF_SPK_MASK(x) ABOX_MASK_ARG(ROUTE_UAIF_SPK, x) +/* ABOX_ROUTE_CTRL1 */ +#define ABOX_ROUTE_SPUSM_L (16) +#define ABOX_ROUTE_SPUSM_H (19) +#define ABOX_ROUTE_SPUSM_MASK ABOX_MASK(ROUTE_SPUSM) +#define ABOX_ROUTE_NSRC_BASE (0) +#define ABOX_ROUTE_NSRC_INTERVAL (4) +#define ABOX_ROUTE_NSRC_L(x) ABOX_L_ARG(ROUTE_NSRC, 0, x) +#define ABOX_ROUTE_NSRC_H(x) ABOX_H_ARG(ROUTE_NSRC, 3, x) +#define ABOX_ROUTE_NSRC_MASK(x) ABOX_MASK_ARG(ROUTE_NSRC, x) +/* ABOX_ROUTE_CTRL2 */ +#define ABOX_ROUTE_RSRC_BASE (0) +#define ABOX_ROUTE_RSRC_INTERVAL (4) +#define ABOX_ROUTE_RSRC_L(x) ABOX_L_ARG(ROUTE_RSRC, 0, x) +#define ABOX_ROUTE_RSRC_H(x) ABOX_H_ARG(ROUTE_RSRC, 3, x) +#define ABOX_ROUTE_RSRC_MASK(x) ABOX_MASK_ARG(ROUTE_RSRC, x) + +/* SPUS */ +#define ABOX_SPUS_CTRL0 (0x0200) +#define ABOX_SPUS_CTRL1 (0x0204) +#define ABOX_SPUS_CTRL2 (0x0208) +#define ABOX_SPUS_CTRL3 (0x020C) +#define ABOX_SPUS_CTRL_SIFS_CNT0 (0x0280) +#define ABOX_SPUS_CTRL_SIFS_CNT1 (0x0284) +/* ABOX_SPUS_CTRL0 */ +#define ABOX_FUNC_CHAIN_SRC_BASE (0) +#define ABOX_FUNC_CHAIN_SRC_INTERVAL (4) +#define ABOX_FUNC_CHAIN_SRC_IN_L(x) ABOX_L_ARG(FUNC_CHAIN_SRC, 3, x) +#define ABOX_FUNC_CHAIN_SRC_IN_H(x) ABOX_H_ARG(FUNC_CHAIN_SRC, 3, x) +#define ABOX_FUNC_CHAIN_SRC_IN_MASK(x) ABOX_MASK_ARG(FUNC_CHAIN_SRC_IN, x) +#define ABOX_FUNC_CHAIN_SRC_OUT_L(x) ABOX_L_ARG(FUNC_CHAIN_SRC, 1, x) +#define ABOX_FUNC_CHAIN_SRC_OUT_H(x) ABOX_H_ARG(FUNC_CHAIN_SRC, 2, x) +#define ABOX_FUNC_CHAIN_SRC_OUT_MASK(x) ABOX_MASK_ARG(FUNC_CHAIN_SRC_OUT, x) +#define ABOX_FUNC_CHAIN_SRC_ASRC_L(x) ABOX_L_ARG(FUNC_CHAIN_SRC, 0, x) +#define ABOX_FUNC_CHAIN_SRC_ASRC_H(x) ABOX_H_ARG(FUNC_CHAIN_SRC, 0, x) +#define ABOX_FUNC_CHAIN_SRC_ASRC_MASK(x) ABOX_MASK_ARG(\ + FUNC_CHAIN_SRC_ASRC, x) +/* ABOX_SPUS_CTRL1 */ +#define ABOX_SIFM_IN_SEL_L (22) +#define ABOX_SIFM_IN_SEL_H (24) +#define ABOX_SIFM_IN_SEL_MASK (ABOX_MASK(SIFM_IN_SEL)) +#define ABOX_SIFS_OUT2_SEL_L (19) +#define ABOX_SIFS_OUT2_SEL_H (21) +#define ABOX_SIFS_OUT2_SEL_MASK (ABOX_MASK(SIFS_OUT2_SEL)) +#define ABOX_SIFS_OUT1_SEL_L (16) +#define ABOX_SIFS_OUT1_SEL_H (18) +#define ABOX_SIFS_OUT1_SEL_MASK (ABOX_MASK(SIFS_OUT1_SEL)) +#define ABOX_SPUS_MIXP_FORMAT_L (0) +#define ABOX_SPUS_MIXP_FORMAT_H (4) +#define ABOX_SPUS_MIXP_FORMAT_MASK (ABOX_MASK(SPUS_MIXP_FORMAT)) +/* ABOX_SPUS_CTRL2 */ +#define ABOX_SPUS_MIXP_FLUSH_L (0) +#define ABOX_SPUS_MIXP_FLUSH_H (0) +#define ABOX_SPUS_MIXP_FLUSH_MASK (ABOX_MASK(SPUS_MIXP_FLUSH)) +/* ABOX_SPUS_CTRL3 */ +#define ABOX_SPUS_SIFM_FLUSH_L (2) +#define ABOX_SPUS_SIFM_FLUSH_H (2) +#define ABOX_SPUS_SIFM_FLUSH_MASK (ABOX_MASK(SPUS_SIFM_FLUSH)) +#define ABOX_SPUS_SIFS2_FLUSH_L (1) +#define ABOX_SPUS_SIFS2_FLUSH_H (1) +#define ABOX_SPUS_SIFS2_FLUSH_MASK (ABOX_MASK(SPUS_SIFS2_FLUSH)) +#define ABOX_SPUS_SIFS1_FLUSH_L (0) +#define ABOX_SPUS_SIFS1_FLUSH_H (0) +#define ABOX_SPUS_SIFS1_FLUSH_MASK (ABOX_MASK(SPUS_SIFS1_FLUSH)) +/* ABOX_SPUS_CTRL_SIFS_CNT0 */ +#define ABOX_SIFS1_CNT_VAL_L (16) +#define ABOX_SIFS1_CNT_VAL_H (31) +#define ABOX_SIFS1_CNT_VAL_MASK (ABOX_MASK(SIFS1_CNT_VAL)) +#define ABOX_SIFS0_CNT_VAL_L (0) +#define ABOX_SIFS0_CNT_VAL_H (15) +#define ABOX_SIFS0_CNT_VAL_MASK (ABOX_MASK(SIFS0_CNT_VAL)) +/* ABOX_SPUS_CTRL_SIFS_CNT1 */ +#define ABOX_SIFS2_CNT_VAL_L (0) +#define ABOX_SIFS2_CNT_VAL_H (15) +#define ABOX_SIFS2_CNT_VAL_MASK (ABOX_MASK(SIFS2_CNT_VAL)) + +/* SPUM */ +#define ABOX_SPUM_CTRL0 (0x0300) +#define ABOX_SPUM_CTRL1 (0x0304) +#define ABOX_SPUM_CTRL2 (0x0308) +#define ABOX_SPUM_CTRL3 (0x030C) + +/* ABOX_SPUM_CTRL0 */ +#define ABOX_FUNC_CHAIN_NSRC_BASE (4) +#define ABOX_FUNC_CHAIN_NSRC_INTERVAL (4) +#define ABOX_FUNC_CHAIN_NSRC_OUT_L(x) ABOX_L_ARG(FUNC_CHAIN_NSRC, 3, x) +#define ABOX_FUNC_CHAIN_NSRC_OUT_H(x) ABOX_H_ARG(FUNC_CHAIN_NSRC, 3, x) +#define ABOX_FUNC_CHAIN_NSRC_OUT_MASK(x) ABOX_MASK_ARG(\ + FUNC_CHAIN_NSRC_OUT, x) +#define ABOX_FUNC_CHAIN_NSRC_ASRC_L(x) ABOX_L_ARG(FUNC_CHAIN_NSRC, 0, x) +#define ABOX_FUNC_CHAIN_NSRC_ASRC_H(x) ABOX_H_ARG(FUNC_CHAIN_NSRC, 0, x) +#define ABOX_FUNC_CHAIN_NSRC_ASRC_MASK(x) ABOX_MASK_ARG(\ + FUNC_CHAIN_NSRC_ASRC, x) +#define ABOX_FUNC_CHAIN_RSRC_RECP_L (1) +#define ABOX_FUNC_CHAIN_RSRC_RECP_H (1) +#define ABOX_FUNC_CHAIN_RSRC_RECP_MASK ABOX_MASK(FUNC_CHAIN_RSRC_RECP) +#define ABOX_FUNC_CHAIN_RSRC_ASRC_L (0) +#define ABOX_FUNC_CHAIN_RSRC_ASRC_H (0) +#define ABOX_FUNC_CHAIN_RSRC_ASRC_MASK ABOX_MASK(FUNC_CHAIN_RSRC_ASRC) +/* ABOX_SPUM_CTRL1 */ +#define ABOX_SIFS_OUT_SEL_L (16) +#define ABOX_SIFS_OUT_SEL_H (18) +#define ABOX_SIFS_OUT_SEL_MASK (ABOX_MASK(SIFS_OUT_SEL)) +#define ABOX_RECP_SRC_FORMAT_L (8) +#define ABOX_RECP_SRC_FORMAT_H (12) +#define ABOX_RECP_SRC_FORMAT_MASK (ABOX_MASK(RECP_SRC_FORMAT)) +#define ABOX_RECP_SRC_VALID_L (0) +#define ABOX_RECP_SRC_VALID_H (1) +#define ABOX_RECP_SRC_VALID_MASK (ABOX_MASK(RECP_SRC_VALID)) +/* ABOX_SPUM_CTRL2 */ +#define ABOX_SPUM_RECP_FLUSH_L (0) +#define ABOX_SPUM_RECP_FLUSH_H (0) +#define ABOX_SPUM_RECP_FLUSH_MASK (ABOX_MASK(SPUM_RECP_FLUSH)) +/* ABOX_SPUM_CTRL3 */ +#define ABOX_SPUM_SIFM3_FLUSH_L (3) +#define ABOX_SPUM_SIFM3_FLUSH_H (3) +#define ABOX_SPUM_SIFM3_FLUSH_MASK (ABOX_MASK(SPUM_SIFM3_FLUSH)) +#define ABOX_SPUM_SIFM2_FLUSH_L (2) +#define ABOX_SPUM_SIFM2_FLUSH_H (2) +#define ABOX_SPUM_SIFM2_FLUSH_MASK (ABOX_MASK(SPUM_SIFM2_FLUSH)) +#define ABOX_SPUM_SIFM1_FLUSH_L (1) +#define ABOX_SPUM_SIFM1_FLUSH_H (1) +#define ABOX_SPUM_SIFM1_FLUSH_MASK (ABOX_MASK(SPUM_SIFM1_FLUSH)) +#define ABOX_SPUM_SIFM0_FLUSH_L (0) +#define ABOX_SPUM_SIFM0_FLUSH_H (0) +#define ABOX_SPUM_SIFM0_FLUSH_MASK (ABOX_MASK(SPUM_SIFM0_FLUSH)) + + +/* UAIF */ +#define ABOX_UAIF_BASE (0x0500) +#define ABOX_UAIF_INTERVAL (0x0010) +#define ABOX_UAIF_CTRL0(x) ABOX_IDX_ARG(UAIF, 0x0, x) +#define ABOX_UAIF_CTRL1(x) ABOX_IDX_ARG(UAIF, 0x4, x) +#define ABOX_UAIF_STATUS(x) ABOX_IDX_ARG(UAIF, 0xC, x) +/* ABOX_UAIF?_CTRL0 */ +#define ABOX_START_FIFO_DIFF_MIC_L (28) +#define ABOX_START_FIFO_DIFF_MIC_H (31) +#define ABOX_START_FIFO_DIFF_MIC_MASK (ABOX_MASK(START_FIFO_DIFF_MIC)) +#define ABOX_START_FIFO_DIFF_SPK_L (24) +#define ABOX_START_FIFO_DIFF_SPK_H (27) +#define ABOX_START_FIFO_DIFF_SPK_MASK (ABOX_MASK(START_FIFO_DIFF_SPK)) +#define ABOX_DATA_MODE_L (4) +#define ABOX_DATA_MODE_H (4) +#define ABOX_DATA_MODE_MASK (ABOX_MASK(DATA_MODE)) +#define ABOX_IRQ_MODE_L (3) +#define ABOX_IRQ_MODE_H (3) +#define ABOX_IRQ_MODE_MASK (ABOX_MASK(IRQ_MODE)) +#define ABOX_MODE_L (2) +#define ABOX_MODE_H (2) +#define ABOX_MODE_MASK (ABOX_MASK(MODE)) +#define ABOX_MIC_ENABLE_L (1) +#define ABOX_MIC_ENABLE_H (1) +#define ABOX_MIC_ENABLE_MASK (ABOX_MASK(MIC_ENABLE)) +#define ABOX_SPK_ENABLE_L (0) +#define ABOX_SPK_ENABLE_H (0) +#define ABOX_SPK_ENABLE_MASK (ABOX_MASK(SPK_ENABLE)) +/* ABOX_UAIF?_CTRL1 */ +#define ABOX_FORMAT_L (24) +#define ABOX_FORMAT_H (28) +#define ABOX_FORMAT_MASK (ABOX_MASK(FORMAT)) +#define ABOX_BCLK_POLARITY_L (23) +#define ABOX_BCLK_POLARITY_H (23) +#define ABOX_BCLK_POLARITY_MASK (ABOX_MASK(BCLK_POLARITY)) +#define ABOX_WS_MODE_L (22) +#define ABOX_WS_MODE_H (22) +#define ABOX_WS_MODE_MASK (ABOX_MASK(WS_MODE)) +#define ABOX_WS_POLAR_L (21) +#define ABOX_WS_POLAR_H (21) +#define ABOX_WS_POLAR_MASK (ABOX_MASK(WS_POLAR)) +#define ABOX_SLOT_MAX_L (18) +#define ABOX_SLOT_MAX_H (20) +#define ABOX_SLOT_MAX_MASK (ABOX_MASK(SLOT_MAX)) +#define ABOX_SBIT_MAX_L (12) +#define ABOX_SBIT_MAX_H (17) +#define ABOX_SBIT_MAX_MASK (ABOX_MASK(SBIT_MAX)) +#define ABOX_VALID_STR_L (6) +#define ABOX_VALID_STR_H (11) +#define ABOX_VALID_STR_MASK (ABOX_MASK(VALID_STR)) +#define ABOX_VALID_END_L (0) +#define ABOX_VALID_END_H (5) +#define ABOX_VALID_END_MASK (ABOX_MASK(VALID_END)) +/* ABOX_UAIF?_STATUS */ +#define ABOX_ERROR_OF_MIC_L (1) +#define ABOX_ERROR_OF_MIC_H (1) +#define ABOX_ERROR_OF_MIC_MASK (ABOX_MASK(ERROR_OF_MIC)) +#define ABOX_ERROR_OF_SPK_L (0) +#define ABOX_ERROR_OF_SPK_H (0) +#define ABOX_ERROR_OF_SPK_MASK (ABOX_MASK(ERROR_OF_SPK)) + +/* DSIF */ +#define ABOX_DSIF_CTRL (0x0550) +#define ABOX_DSIF_STATUS (0x0554) +/* ABOX_DSIF_CTRL */ +#define ABOX_DSIF_BCLK_POLARITY_L (2) +#define ABOX_DSIF_BCLK_POLARITY_H (2) +#define ABOX_DSIF_BCLK_POLARITY_MASK (ABOX_MASK(DSIF_BCLK_POLARITY)) +#define ABOX_ORDER_L (1) +#define ABOX_ORDER_H (1) +#define ABOX_ORDER_MASK (ABOX_MASK(ORDER)) +#define ABOX_ENABLE_L (0) +#define ABOX_ENABLE_H (0) +#define ABOX_ENABLE_MASK (ABOX_MASK(ENABLE)) +/* ABOX_DSIF_STATUS */ +#define ABOX_ERROR_L (0) +#define ABOX_ERROR_H (0) +#define ABOX_ERROR_MASK (ABOX_MASK(ERROR)) + +/* SPDY */ +#define ABOX_SPDYIF_CTRL (0x0560) +/* ABOX_SPDYIF_CTRL */ +#define ABOX_START_FIFO_DIFF_L (1) +#define ABOX_START_FIFO_DIFF_H (4) +#define ABOX_START_FIFO_DIFF_MASK (ABOX_MASK(START_FIFO_DIFF)) +/* same with DSIF + * #define ABOX_ENABLE_L (0) + * #define ABOX_ENABLE_H (0) + * #define ABOX_ENABLE_MASK (ABOX_MASK(ENABLE)) + */ + +/* TIMER */ +#define ABOX_TIMER_BASE (0x0600) +#define ABOX_TIMER_INTERVAL (0x0020) +#define ABOX_TIMER_CTRL0(x) ABOX_IDX_ARG(TIMER, 0x0, x) +#define ABOX_TIMER_CTRL1(x) ABOX_IDX_ARG(TIMER, 0x4, x) +/* ABOX_TIMER?_CTRL0 */ +#define ABOX_TIMER_FLUSH_L (1) +#define ABOX_TIMER_FLUSH_H (1) +#define ABOX_TIMER_FLUSH_MASK (ABOX_MASK(TIMER_FLUSH)) +#define ABOX_TIMER_START_L (0) +#define ABOX_TIMER_START_H (0) +#define ABOX_TIMER_START_MASK (ABOX_MASK(TIMER_START)) +/* ABOX_TIMER?_CTRL1 */ +#define ABOX_TIMER_MODE_L (0) +#define ABOX_TIMER_MODE_H (0) +#define ABOX_TIMER_MODE_MASK (ABOX_MASK(TIMER_MODE)) + +/* RDMA */ +#define ABOX_RDMA_BASE (0x1000) +#define ABOX_RDMA_INTERVAL (0x0100) +#define ABOX_RDMA_CTRL0 (0x00) +#define ABOX_RDMA_CTRL1 (0x04) +#define ABOX_RDMA_BUF_STR (0x08) +#define ABOX_RDMA_BUF_END (0x0C) +#define ABOX_RDMA_BUF_OFFSET (0x10) +#define ABOX_RDMA_STR_POINT (0x14) +#define ABOX_RDMA_VOL_FACTOR (0x18) +#define ABOX_RDMA_VOL_CHANGE (0x1C) +#ifdef CONFIG_SOC_EXYNOS8895 +#define ABOX_RDMA_STATUS (0x20) +#else +#define ABOX_RDMA_SBACK_LIMIT (0x20) +#define ABOX_RDMA_STATUS (0x30) +#endif +/* ABOX_RDMA_CTRL0 */ +#define ABOX_RDMA_ENABLE_L (0) +#define ABOX_RDMA_ENABLE_H (0) +#define ABOX_RDMA_ENABLE_MASK (ABOX_MASK(RDMA_ENABLE)) +/* ABOX_RDMA_STATUS */ +#define ABOX_RDMA_PROGRESS_L (31) +#define ABOX_RDMA_PROGRESS_H (31) +#define ABOX_RDMA_PROGRESS_MASK (ABOX_MASK(RDMA_PROGRESS)) +#define ABOX_RDMA_RBUF_OFFSET_L (16) +#define ABOX_RDMA_RBUF_OFFSET_H (28) +#define ABOX_RDMA_RBUF_OFFSET_MASK (ABOX_MASK(RDMA_RBUF_OFFSET)) +#define ABOX_RDMA_RBUF_CNT_L (0) +#define ABOX_RDMA_RBUF_CNT_H (12) +#define ABOX_RDMA_RBUF_CNT_MASK (ABOX_MASK(RDMA_RBUF_CNT)) + +/* WDMA */ +#define ABOX_WDMA_BASE (0x2000) +#define ABOX_WDMA_INTERVAL (0x0100) +#define ABOX_WDMA_CTRL (0x00) +#define ABOX_WDMA_BUF_STR (0x08) +#define ABOX_WDMA_BUF_END (0x0C) +#define ABOX_WDMA_BUF_OFFSET (0x10) +#define ABOX_WDMA_STR_POINT (0x14) +#define ABOX_WDMA_VOL_FACTOR (0x18) +#define ABOX_WDMA_VOL_CHANGE (0x1C) +#ifdef CONFIG_SOC_EXYNOS8895 +#define ABOX_WDMA_STATUS (0x20) +#else +#define ABOX_WDMA_SBACK_LIMIT (0x20) +#define ABOX_WDMA_STATUS (0x30) +#endif +/* ABOX_WDMA_CTRL */ +#define ABOX_WDMA_ENABLE_L (0) +#define ABOX_WDMA_ENABLE_H (0) +#define ABOX_WDMA_ENABLE_MASK (ABOX_MASK(WDMA_ENABLE)) +/* ABOX_WDMA_STATUS */ +#define ABOX_WDMA_PROGRESS_L (31) +#define ABOX_WDMA_PROGRESS_H (31) +#define ABOX_WDMA_PROGRESS_MASK (ABOX_MASK(WDMA_PROGRESS)) +#define ABOX_WDMA_RBUF_OFFSET_L (16) +#define ABOX_WDMA_RBUF_OFFSET_H (28) +#define ABOX_WDMA_RBUF_OFFSET_MASK (ABOX_MASK(WDMA_RBUF_OFFSET)) +#define ABOX_WDMA_RBUF_CNT_L (0) +#define ABOX_WDMA_RBUF_CNT_H (12) +#define ABOX_WDMA_RBUF_CNT_MASK (ABOX_MASK(WDMA_RBUF_CNT)) + +/* CA7 */ +#define ABOX_CPU_R(x) (0x2C00 + (x * 0x4)) +#define ABOX_CPU_PC (0x2C3C) +#define ABOX_CPU_L2C_STATUS (0x2C40) + +#define ABOX_MAX_REGISTERS (0x2D0C) + +/* SYSREG */ +#define ABOX_SYSREG_L2_CACHE_CON (0x0328) +#define ABOX_SYSREG_MISC_CON (0x032C) + +#define BUFFER_BYTES_MAX (SZ_128K) +#define PERIOD_BYTES_MIN (SZ_128) +#define PERIOD_BYTES_MAX (BUFFER_BYTES_MAX / 2) + +#define DRAM_FIRMWARE_SIZE (SZ_8M + SZ_4M) +#define IOVA_DRAM_FIRMWARE (0x80000000) +#define IOVA_RDMA_BUFFER_BASE (0x81000000) +#define IOVA_RDMA_BUFFER(x) (IOVA_RDMA_BUFFER_BASE + (SZ_1M * x)) +#define IOVA_WDMA_BUFFER_BASE (0x82000000) +#define IOVA_WDMA_BUFFER(x) (IOVA_WDMA_BUFFER_BASE + (SZ_1M * x)) +#define IOVA_COMPR_BUFFER_BASE (0x83000000) +#define IOVA_COMPR_BUFFER(x) (IOVA_COMPR_BUFFER_BASE + (SZ_1M * x)) +#define IOVA_VDMA_BUFFER_BASE (0x84000000) +#define IOVA_VDMA_BUFFER(x) (IOVA_VDMA_BUFFER_BASE + (SZ_1M * x)) +#define IVA_FIRMWARE_SIZE (SZ_512K) +#define IOVA_IVA_FIRMWARE (0x90000000) +#define IOVA_IVA_BASE (IOVA_IVA_FIRMWARE) +#define IOVA_IVA(x) (IOVA_IVA_BASE + (SZ_16M * x)) +#define IOVA_VSS_FIRMWARE (0xA0000000) +#define IOVA_VSS_PARAMETER (0xA1000000) +#define IOVA_BLUETOOTH (0xB0000000) +#define IOVA_DUMP_BUFFER (0xD0000000) +#define PHSY_VSS_FIRMWARE (0xFEE00000) +#define PHSY_VSS_SIZE (SZ_4M + SZ_2M) + +#define AUD_PLL_RATE_HZ_FOR_48000 (1179648040) +#define AUD_PLL_RATE_HZ_FOR_44100 (1083801600) + +#define LIMIT_IN_JIFFIES (msecs_to_jiffies(1000)) + +#define ABOX_CPU_GEAR_CALL_VSS (0xCA11) +#define ABOX_CPU_GEAR_CALL_KERNEL (0xCA12) +#define ABOX_CPU_GEAR_CALL ABOX_CPU_GEAR_CALL_VSS +#define ABOX_CPU_GEAR_BOOT (0xB00D) +#define ABOX_CPU_GEAR_MAX (1) +#define ABOX_CPU_GEAR_MIN (12) + +#define ABOX_DMA_TIMEOUT_NS (40000000) + +#define ABOX_SAMPLING_RATES (SNDRV_PCM_RATE_KNOT) +#define ABOX_SAMPLE_FORMATS (SNDRV_PCM_FMTBIT_S16\ + | SNDRV_PCM_FMTBIT_S24\ + | SNDRV_PCM_FMTBIT_S32) +#define ABOX_WDMA_SAMPLE_FORMATS (SNDRV_PCM_FMTBIT_S16\ + | SNDRV_PCM_FMTBIT_S24\ + | SNDRV_PCM_FMTBIT_S32) + +#define set_mask_value(id, mask, value) \ + {id = (typeof(id))((id & ~mask) | (value & mask)); } + +#define set_value_by_name(id, name, value) \ + set_mask_value(id, name##_MASK, value << name##_L) + +#define ABOX_SUPPLEMENT_SIZE (SZ_128) +#define ABOX_IPC_QUEUE_SIZE (SZ_64) + +#define CALLIOPE_VERSION(class, year, month, minor) \ + ((class << 24) | \ + ((year - 1 + 'A') << 16) | \ + ((month - 1 + 'A') << 8) | \ + ((minor + '0') << 0)) + +enum abox_sram { + ABOX_IVA_MEMORY, + ABOX_IVA_MEMORY_PREPARE, +}; + +enum abox_dai { + ABOX_RDMA0, + ABOX_RDMA1, + ABOX_RDMA2, + ABOX_RDMA3, + ABOX_RDMA4, + ABOX_RDMA5, + ABOX_RDMA6, + ABOX_RDMA7, + ABOX_WDMA0, + ABOX_WDMA1, + ABOX_WDMA2, + ABOX_WDMA3, + ABOX_WDMA4, + ABOX_UAIF0, + ABOX_UAIF1, + ABOX_UAIF2, + ABOX_UAIF3, + ABOX_UAIF4, + ABOX_DSIF, + ABOX_SPDY, + ABOX_SIFS0, /* Virtual DAI */ + ABOX_SIFS1, /* Virtual DAI */ + ABOX_SIFS2, /* Virtual DAI */ +}; + +#define ABOX_DAI_COUNT (ABOX_DSIF - ABOX_UAIF0 + 1) + +enum calliope_state { + CALLIOPE_DISABLED, + CALLIOPE_DISABLING, + CALLIOPE_ENABLING, + CALLIOPE_ENABLED, + CALLIOPE_STATE_COUNT, +}; + +enum audio_mode { + MODE_NORMAL, + MODE_RINGTONE, + MODE_IN_CALL, + MODE_IN_COMMUNICATION, + MODE_IN_VIDEOCALL, +}; + +enum sound_type { + SOUND_TYPE_VOICE, + SOUND_TYPE_SPEAKER, + SOUND_TYPE_HEADSET, + SOUND_TYPE_BTVOICE, + SOUND_TYPE_USB, +}; + +enum qchannel { + ABOX_CCLK_CA7, + ABOX_ACLK, + ABOX_BCLK_UAIF0, + ABOX_BCLK_UAIF1, + ABOX_BCLK_UAIF2, + ABOX_BCLK_UAIF3, + ABOX_BCLK_DSIF, + ABOX_CCLK_ATB, + ABOX_CCLK_ASB, +}; + + +#define ABOX_QUIRK_TRY_TO_ASRC_OFF (1 << 0) +#define ABOX_QUIRK_SHARE_VTS_SRAM (1 << 1) +#define ABOX_QUIRK_OFF_ON_SUSPEND (1 << 2) +#define ABOX_QUIRK_SCSC_BT (1 << 3) +#define ABOX_QUIRK_SCSC_BT_HACK (1 << 4) +#define ABOX_QUIRK_STR_TRY_TO_ASRC_OFF "try to asrc off" +#define ABOX_QUIRK_STR_SHARE_VTS_SRAM "share vts sram" +#define ABOX_QUIRK_STR_OFF_ON_SUSPEND "off on suspend" +#define ABOX_QUIRK_STR_SCSC_BT "scsc bt" +#define ABOX_QUIRK_STR_SCSC_BT_HACK "scsc bt hack" + +struct abox_ipc { + struct device *dev; + int hw_irq; + unsigned long long put_time; + unsigned long long get_time; + ABOX_IPC_MSG msg; +}; + +struct abox_irq_action { + struct list_head list; + int irq; + abox_irq_handler_t irq_handler; + void *dev_id; +}; + +struct abox_qos_request { + const void *id; + unsigned int value; +}; + +struct abox_dram_request { + void *id; + bool on; +}; + +struct abox_l2c_request { + void *id; + bool on; +}; + +struct abox_extra_firmware { + const struct firmware *firmware; + const char *name; + u32 area; + u32 offset; +}; + +struct abox_component { + struct ABOX_COMPONENT_DESCRIPTIOR *desc; + bool registered; + struct list_head value_list; +}; + +struct abox_component_kcontrol_value { + struct ABOX_COMPONENT_DESCRIPTIOR *desc; + struct ABOX_COMPONENT_CONTROL *control; + struct list_head list; + bool cache_only; + int cache[]; +}; + +struct abox_data { + struct platform_device *pdev; + struct snd_soc_component *cmpnt; + struct regmap *regmap; + void __iomem *sfr_base; + void __iomem *sysreg_base; + void __iomem *sram_base; + phys_addr_t sram_base_phys; + size_t sram_size; + void *dram_base; + dma_addr_t dram_base_phys; + void *iva_base; + dma_addr_t iva_base_phys; + void *dump_base; + phys_addr_t dump_base_phys; + struct iommu_domain *iommu_domain; + unsigned int ipc_tx_offset; + unsigned int ipc_rx_offset; + unsigned int ipc_tx_ack_offset; + unsigned int ipc_rx_ack_offset; + unsigned int mailbox_offset; + unsigned int if_count; + unsigned int rdma_count; + unsigned int wdma_count; + unsigned int calliope_version; + const struct firmware *firmware_sram; + const struct firmware *firmware_dram; + const struct firmware *firmware_iva; + struct abox_extra_firmware firmware_extra[8]; + struct device *dev_gic; + struct device *dev_bt; + struct platform_device *pdev_if[8]; + struct platform_device *pdev_rdma[8]; + struct platform_device *pdev_wdma[5]; + struct platform_device *pdev_vts; + struct workqueue_struct *gear_workqueue; + struct workqueue_struct *ipc_workqueue; + struct work_struct ipc_work; + struct abox_ipc ipc_queue[ABOX_IPC_QUEUE_SIZE]; + int ipc_queue_start; + int ipc_queue_end; + spinlock_t ipc_queue_lock; + wait_queue_head_t ipc_wait_queue; + struct clk *clk_pll; + struct clk *clk_audif; + struct clk *clk_cpu; + struct clk *clk_bus; + unsigned int uaif_max_div; + struct pinctrl *pinctrl; + unsigned long quirks; + unsigned int cpu_gear; + unsigned int cpu_gear_min; + struct abox_qos_request cpu_gear_requests[32]; + struct work_struct change_cpu_gear_work; + unsigned int int_freq; + struct abox_qos_request int_requests[16]; + struct work_struct change_int_freq_work; + unsigned int mif_freq; + struct abox_qos_request mif_requests[16]; + struct work_struct change_mif_freq_work; + unsigned int lit_freq; + struct abox_qos_request lit_requests[16]; + struct work_struct change_lit_freq_work; + unsigned int big_freq; + struct abox_qos_request big_requests[16]; + struct work_struct change_big_freq_work; + unsigned int hmp_boost; + struct abox_qos_request hmp_requests[16]; + struct work_struct change_hmp_boost_work; + struct abox_dram_request dram_requests[16]; + unsigned long audif_rates[ABOX_DAI_COUNT]; + unsigned int sif_rate[SET_INMUX4_SAMPLE_RATE - + SET_MIXER_SAMPLE_RATE + 1]; + snd_pcm_format_t sif_format[SET_INMUX4_FORMAT - + SET_MIXER_FORMAT + 1]; + unsigned int sif_channels[SET_INMUX4_FORMAT - + SET_MIXER_FORMAT + 1]; + unsigned int sif_rate_min[SET_INMUX4_SAMPLE_RATE - + SET_MIXER_SAMPLE_RATE + 1]; + snd_pcm_format_t sif_format_min[SET_INMUX4_FORMAT - + SET_MIXER_FORMAT + 1]; + unsigned int sif_channels_min[SET_INMUX4_FORMAT - + SET_MIXER_FORMAT + 1]; + bool sif_auto_config[SET_INMUX4_SAMPLE_RATE - + SET_MIXER_SAMPLE_RATE + 1]; + unsigned int erap_status[ERAP_TYPE_COUNT]; + struct work_struct register_component_work; + struct abox_component components[16]; + struct list_head irq_actions; + bool enabled; + enum calliope_state calliope_state; + bool l2c_controlled; + bool l2c_enabled; + struct abox_l2c_request l2c_requests[8]; + struct work_struct l2c_work; + struct notifier_block qos_nb; + struct notifier_block pm_nb; + struct notifier_block modem_nb; + struct notifier_block itmon_nb; + int pm_qos_int[5]; + int pm_qos_aud[5]; + struct ima_client *ima_client; + void *ima_vaddr; + bool ima_claimed; + struct mutex ima_lock; + struct work_struct boot_done_work; + struct delayed_work tickle_work; + enum audio_mode audio_mode; + enum sound_type sound_type; +}; + +struct abox_compr_data { + /* compress offload */ + struct snd_compr_stream *cstream; + + void *dma_area; + size_t dma_size; + dma_addr_t dma_addr; + + unsigned int block_num; + unsigned int handle_id; + unsigned int codec_id; + unsigned int channels; + unsigned int sample_rate; + + unsigned int byte_offset; + u64 copied_total; + u64 received_total; + + bool start; + bool eos; + bool created; + + bool effect_on; + + wait_queue_head_t flush_wait; + wait_queue_head_t exit_wait; + wait_queue_head_t ipc_wait; + + uint32_t stop_ack; + uint32_t exit_ack; + + spinlock_t lock; + spinlock_t cmd_lock; + + int (*isr_handler)(void *data); + + struct snd_compr_params codec_param; + + /* effect offload */ + unsigned int out_sample_rate; +}; + +enum abox_platform_type { + PLATFORM_NORMAL, + PLATFORM_CALL, + PLATFORM_COMPRESS, + PLATFORM_REALTIME, + PLATFORM_VI_SENSING, + PLATFORM_SYNC, +}; + +enum abox_rate { + RATE_SUHQA, + RATE_UHQA, + RATE_NORMAL, + RATE_COUNT, +}; + +static inline bool abox_test_quirk(struct abox_data *data, unsigned long quirk) +{ + return !!(data->quirks & quirk); +} + +/** + * Get sampling rate type + * @param[in] rate sampling rate in Hz + * @return rate type in enum abox_rate + */ +static inline enum abox_rate abox_get_rate_type(unsigned int rate) +{ + if (rate < 176400) + return RATE_NORMAL; + else if (rate >= 176400 && rate <= 192000) + return RATE_UHQA; + else + return RATE_SUHQA; +} + +/** + * Get SFR of sample format + * @param[in] bit_depth count of bit in sample + * @param[in] channels count of channel + * @return SFR of sample format + */ +static inline u32 abox_get_format(int bit_depth, int channels) +{ + u32 ret = (channels - 1); + + switch (bit_depth) { + case 16: + ret |= 1 << 3; + break; + case 24: + ret |= 2 << 3; + break; + case 32: + ret |= 3 << 3; + break; + default: + break; + } + + pr_debug("%s(%d, %d): %u\n", __func__, bit_depth, channels, ret); + + return ret; +} + +/** + * Get enum IPC_ID from SNDRV_PCM_STREAM_* + * @param[in] stream SNDRV_PCM_STREAM_* + * @return IPC_PCMPLAYBACK or IPC_PCMCAPTURE + */ +static inline enum IPC_ID abox_stream_to_ipcid(int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + return IPC_PCMPLAYBACK; + else if (stream == SNDRV_PCM_STREAM_CAPTURE) + return IPC_PCMCAPTURE; + else + return -EINVAL; +} + +/** + * Get SNDRV_PCM_STREAM_* from enum IPC_ID + * @param[in] ipcid IPC_PCMPLAYBACK or IPC_PCMCAPTURE + * @return SNDRV_PCM_STREAM_* + */ +static inline int abox_ipcid_to_stream(enum IPC_ID ipcid) +{ + if (ipcid == IPC_PCMPLAYBACK) + return SNDRV_PCM_STREAM_PLAYBACK; + else if (ipcid == IPC_PCMCAPTURE) + return SNDRV_PCM_STREAM_CAPTURE; + else + return -EINVAL; +} + +struct abox_platform_data { + void __iomem *sfr_base; + void __iomem *mailbox_base; + unsigned int id; + unsigned int pointer; + int pm_qos_lit[RATE_COUNT]; + int pm_qos_big[RATE_COUNT]; + int pm_qos_hmp[RATE_COUNT]; + struct platform_device *pdev_abox; + struct abox_data *abox_data; + struct snd_pcm_substream *substream; + enum abox_platform_type type; + bool ack_enabled; + struct abox_compr_data compr_data; + struct regmap *mailbox; + bool scsc_bt; +}; + +/** + * get pointer to abox_data (internal use only) + * @return pointer to abox_data + */ +extern struct abox_data *abox_get_abox_data(void); + +/** + * get physical address from abox virtual address + * @param[in] data pointer to abox_data structure + * @param[in] addr abox virtual address + * @return physical address + */ +extern phys_addr_t abox_addr_to_phys_addr(struct abox_data *data, + unsigned int addr); + +/** + * get kernel address from abox virtual address + * @param[in] data pointer to abox_data structure + * @param[in] addr abox virtual address + * @return kernel address + */ +extern void *abox_addr_to_kernel_addr(struct abox_data *data, + unsigned int addr); + +/** + * check specific cpu gear request is idle + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @return true if it is idle or not has been requested, false on otherwise + */ +extern bool abox_cpu_gear_idle(struct device *dev, struct abox_data *data, + void *id); + +/** + * Request abox cpu clock level + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @param[in] gear gear level (cpu clock = aud pll rate / gear) + * @return error code if any + */ +extern int abox_request_cpu_gear(struct device *dev, struct abox_data *data, + const void *id, unsigned int gear); + +/** + * Wait for pending cpu gear change + * @param[in] data pointer to abox_data structure + */ +extern void abox_cpu_gear_barrier(struct abox_data *data); + +/** + * Request abox cpu clock level synchronously + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @param[in] gear gear level (cpu clock = aud pll rate / gear) + * @return error code if any + */ +extern int abox_request_cpu_gear_sync(struct device *dev, + struct abox_data *data, const void *id, unsigned int gear); + +/** + * Clear abox cpu clock requests + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + */ +extern void abox_clear_cpu_gear_requests(struct device *dev, + struct abox_data *data); + +/** + * Request LITTLE cluster clock level + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @param[in] freq frequency in kHz + * @return error code if any + */ +extern int abox_request_lit_freq(struct device *dev, struct abox_data *data, + void *id, unsigned int freq); + +/** + * Request big cluster clock level + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @param[in] freq frequency in kHz + * @return error code if any + */ +extern int abox_request_big_freq(struct device *dev, struct abox_data *data, + void *id, unsigned int freq); + +/** + * Request hmp boost + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @param[in] on 1 on boost, 0 on otherwise. + * @return error code if any + */ +extern int abox_request_hmp_boost(struct device *dev, struct abox_data *data, + void *id, unsigned int on); +/** + * Register uaif or dsif to abox + * @param[in] pdev_abox pointer to abox platform device + * @param[in] pdev_xif pointer to abox uaif or dsif device + * @param[in] id number + * @param[in] dapm dapm context of the uaif or dsif + * @param[in] name dai name + * @param[in] playback true if dai has playback capability + * @param[in] capture true if dai has capture capability + * @return error code if any + */ +extern int abox_register_if(struct platform_device *pdev_abox, + struct platform_device *pdev_if, unsigned int id, + struct snd_soc_dapm_context *dapm, const char *name, + bool playback, bool capture); + +/** + * Try to turn off ASRC when sampling rate auto control is enabled + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] fe pointer to snd_soc_pcm_runtime + * @param[in] stream SNDRV_PCM_STREAM_PLAYBACK or CAPUTURE + * @return error code if any + */ +extern int abox_try_to_asrc_off(struct device *dev, struct abox_data *data, + struct snd_soc_pcm_runtime *fe, int stream); + +/** + * Register rdma to abox + * @param[in] pdev_abox pointer to abox platform device + * @param[in] pdev_rdma pointer to abox rdma platform device + * @param[in] id number + * @return error code if any + */ +extern int abox_register_rdma(struct platform_device *pdev_abox, + struct platform_device *pdev_rdma, unsigned int id); + +/** + * Register wdma to abox + * @param[in] data pointer to abox_data structure + * @param[in] ipcid id of ipc + * @param[in] irq_handler irq handler to register + * @param[in] dev_id cookie which would be summitted with irq_handler + * @return error code if any + */ +extern int abox_register_wdma(struct platform_device *pdev_abox, + struct platform_device *pdev_wdma, unsigned int id); + +/** + * Register uaif to abox + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id id of the uaif + * @param[in] rate sampling rate + * @param[in] channels number of channels + * @param[in] width number of bit in sample + * @return error code if any + */ +extern int abox_register_bclk_usage(struct device *dev, struct abox_data *data, + enum abox_dai id, unsigned int rate, unsigned int channels, + unsigned int width); + +/** + * Request or release l2 cache + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @param[in] on true for requesting, false on otherwise + * @return error code if any + */ +extern int abox_request_l2c(struct device *dev, struct abox_data *data, + void *id, bool on); + +/** + * Request or release l2 cache synchronously + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] id key which is used as unique handle + * @param[in] on true for requesting, false on otherwise + * @return error code if any + */ +extern int abox_request_l2c_sync(struct device *dev, struct abox_data *data, + void *id, bool on); + +/** + * Request or release dram during cpuidle (count based API) + * @param[in] pdev_abox pointer to abox platform device + * @param[in] id key which is used as unique handle + * @param[in] on true for requesting, false on otherwise + */ +extern void abox_request_dram_on(struct platform_device *pdev_abox, void *id, + bool on); + +/** + * claim IVA memory + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[out] addr optional argument to get physical address + */ +extern int abox_ima_claim(struct device *dev, struct abox_data *data, + phys_addr_t *addr); + +/** + * disable or enable qchannel of a clock + * @param[in] dev pointer to struct dev which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] clk clock id + * @param[in] disable disable or enable + */ +extern int abox_disable_qchannel(struct device *dev, struct abox_data *data, + enum qchannel clk, int disable); +#endif /* __SND_SOC_ABOX_H */ diff --git a/sound/soc/samsung/abox/abox_bt.c b/sound/soc/samsung/abox/abox_bt.c new file mode 100644 index 000000000000..155990346bb1 --- /dev/null +++ b/sound/soc/samsung/abox/abox_bt.c @@ -0,0 +1,230 @@ +/* sound/soc/samsung/abox/abox_bt.c + * + * ALSA SoC Audio Layer - Samsung Abox SCSC B/T driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include + +#include +#include "../../../../drivers/iommu/exynos-iommu.h" + +#include "abox.h" +#include "abox_bt.h" + +static int abox_bt_sco_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_bt_data *data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + enum bt_sco dir = (enum bt_sco)mc->reg; + + dev_dbg(dev, "%s(%d)\n", __func__, dir); + + ucontrol->value.integer.value[0] = data->active[dir]; + return 0; +} + +static int abox_bt_sco_put_ipc(struct device *dev, + enum bt_sco dir, unsigned int val) +{ + struct device *dev_abox = dev->parent; + struct abox_bt_data *data = dev_get_drvdata(dev); + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system_msg = &msg.msg.system; + int ret; + + dev_dbg(dev, "%s(%d, %u)\n", __func__, dir, val); + + if (!data->paddr_bt) { + dev_warn(dev, "B/T SCO isn't ready yet\n"); + return -EIO; + } + + msg.ipcid = IPC_SYSTEM; + system_msg->msgtype = ABOX_BT_SCO_ENABLE; + system_msg->param1 = val; + system_msg->param2 = IOVA_BLUETOOTH; + system_msg->param3 = dir; + ret = abox_request_ipc(dev_abox, msg.ipcid, &msg, sizeof(msg), 0, 0); + + data->active[dir] = val; + + return ret; +} + +static int abox_bt_sco_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + enum bt_sco dir = (enum bt_sco)mc->reg; + unsigned int val = (unsigned int)ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(%d, %u)\n", __func__, dir, val); + + return abox_bt_sco_put_ipc(dev, dir, val); +} + +static const struct snd_kcontrol_new abox_bt_controls[] = { + SOC_SINGLE_EXT("BT SCO SPK Enable", BT_SCO_SPK, 0, 1, 0, + abox_bt_sco_get, abox_bt_sco_put), + SOC_SINGLE_EXT("BT SCO MIC Enable", BT_SCO_MIC, 0, 1, 0, + abox_bt_sco_get, abox_bt_sco_put), +}; + +static const struct snd_soc_component_driver abox_bt_cmpnt = { + .controls = abox_bt_controls, + .num_controls = ARRAY_SIZE(abox_bt_controls), +}; + +unsigned int abox_bt_get_rate(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + + return scsc_bt_audio_get_rate(0); +} + +unsigned int abox_bt_get_buf_iova(struct device *dev, int stream) +{ + struct abox_bt_data *data = dev_get_drvdata(dev); + bool tx = (stream == SNDRV_PCM_STREAM_PLAYBACK); + phys_addr_t paddr_buf = scsc_bt_audio_get_paddr_buf(tx); + + dev_dbg(dev, "%s\n", __func__); + + if (paddr_buf && data->paddr_bt) + return IOVA_BLUETOOTH + (paddr_buf - data->paddr_bt); + else + return 0; +} + +bool abox_bt_active(struct device *dev, int stream) +{ + struct abox_bt_data *data = dev_get_drvdata(dev); + enum bt_sco dir; + + dev_dbg(dev, "%s\n", __func__); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = BT_SCO_MIC; + else + dir = BT_SCO_SPK; + + return data->active[dir]; +} + +static int abox_bt_iommu_map(struct device *dev, phys_addr_t paddr, size_t size) +{ + struct abox_bt_data *data = dev_get_drvdata(dev); + struct iommu_domain *domain = data->abox_data->iommu_domain; + + dev_info(dev, "%s(%pa, %#zx)\n", __func__, &paddr, size); + + data->paddr_bt = paddr; + return iommu_map(domain, IOVA_BLUETOOTH, paddr, size, 0); +} + +static void abox_bt_iommu_unmap(struct device *dev, size_t size) +{ + struct abox_bt_data *data = dev_get_drvdata(dev); + struct iommu_domain *domain = data->abox_data->iommu_domain; + + dev_info(dev, "%s(%#zx)\n", __func__, size); + + iommu_unmap(domain, IOVA_BLUETOOTH, size); + exynos_sysmmu_tlb_invalidate(domain, IOVA_BLUETOOTH, size); +} + +static int samsung_abox_bt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *dev_abox = dev->parent; + struct abox_data *abox_data = dev_get_drvdata(dev_abox); + struct abox_bt_data *data; + struct resource *res; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + dev_set_drvdata(dev, data); + data->dev = dev; + data->abox_data = abox_data; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mailbox"); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to get %s\n", "mailbox"); + return -EINVAL; + } + abox_iommu_map(dev_abox, res->start, res->start, resource_size(res)); + + ret = scsc_bt_audio_register(dev, abox_bt_iommu_map, + abox_bt_iommu_unmap); + if (ret < 0) + return -EPROBE_DEFER; + + ret = devm_snd_soc_register_component(dev, &abox_bt_cmpnt, NULL, 0); + if (ret < 0) { + dev_err(dev, "component register failed:%d\n", ret); + return ret; + } + + abox_data->dev_bt = dev; + + return ret; +} + +static int samsung_abox_bt_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + scsc_bt_audio_unregister(dev); + + return 0; +} + +static const struct of_device_id samsung_abox_bt_match[] = { + { + .compatible = "samsung,abox-bt", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_bt_match); + +static struct platform_driver samsung_abox_bt_driver = { + .probe = samsung_abox_bt_probe, + .remove = samsung_abox_bt_remove, + .driver = { + .name = "samsung-abox-bt", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_bt_match), + }, +}; + +module_platform_driver(samsung_abox_bt_driver); + +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box SCSC Bluetooth Driver"); +MODULE_ALIAS("platform:samsung-abox-bt"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_bt.h b/sound/soc/samsung/abox/abox_bt.h new file mode 100644 index 000000000000..573c95c5f828 --- /dev/null +++ b/sound/soc/samsung/abox/abox_bt.h @@ -0,0 +1,54 @@ +/* sound/soc/samsung/abox/abox_bt.h + * + * ALSA SoC Audio Layer - Samsung Abox SCSC B/T driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_BT_H +#define __SND_SOC_ABOX_BT_H + +#include + +enum bt_sco { + BT_SCO_ALL, + BT_SCO_MIC, + BT_SCO_SPK, + BT_SCO_COUNT, +}; + +struct abox_bt_data { + struct device *dev; + struct abox_data *abox_data; + phys_addr_t paddr_bt; + bool active[BT_SCO_COUNT]; +}; + +/** + * Get rate of bluetooth audio interface + * @param[in] dev pointer to abox_bt device + * @return sample rate or 0 + */ +extern unsigned int abox_bt_get_rate(struct device *dev); + +/** + * Get IO virtual address of bluetooth tx buffer + * @param[in] dev pointer to abox_bt device + * @param[in] stream SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE + * @return IO virtual address or 0 + */ +extern unsigned int abox_bt_get_buf_iova(struct device *dev, int stream); + +/** + * Returns true when the bt stream is active + * @param[in] dev pointer to abox_bt device + * @param[in] stream SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE + * @return true or false + */ +extern bool abox_bt_active(struct device *dev, int stream); + +#endif /* __SND_SOC_ABOX_BT_H */ diff --git a/sound/soc/samsung/abox/abox_dbg.c b/sound/soc/samsung/abox/abox_dbg.c new file mode 100644 index 000000000000..1d99ea693926 --- /dev/null +++ b/sound/soc/samsung/abox/abox_dbg.c @@ -0,0 +1,461 @@ +/* sound/soc/samsung/abox/abox_dbg.c + * + * ALSA SoC Audio Layer - Samsung Abox Debug driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "abox_dbg.h" +#include "abox_gic.h" + +static struct dentry *abox_dbg_root_dir __read_mostly; + +struct dentry *abox_dbg_get_root_dir(void) +{ + pr_debug("%s\n", __func__); + + if (abox_dbg_root_dir == NULL) + abox_dbg_root_dir = debugfs_create_dir("abox", NULL); + + return abox_dbg_root_dir; +} + +void abox_dbg_print_gpr_from_addr(struct device *dev, struct abox_data *data, + unsigned int *addr) +{ + int i; + char version[4]; + + memcpy(version, &data->calliope_version, sizeof(version)); + + dev_info(dev, "========================================\n"); + dev_info(dev, "A-Box CPU register dump (%c%c%c%c)\n", + version[3], version[2], version[1], version[0]); + dev_info(dev, "----------------------------------------\n"); + for (i = 0; i <= 14; i++) + dev_info(dev, "CA7_R%02d : %08x\n", i, *addr++); + dev_info(dev, "CA7_PC : %08x\n", *addr++); + dev_info(dev, "========================================\n"); +} + +void abox_dbg_print_gpr(struct device *dev, struct abox_data *data) +{ + int i; + char version[4]; + + memcpy(version, &data->calliope_version, sizeof(version)); + + dev_info(dev, "========================================\n"); + dev_info(dev, "A-Box CPU register dump (%c%c%c%c)\n", + version[3], version[2], version[1], version[0]); + dev_info(dev, "----------------------------------------\n"); + for (i = 0; i <= 14; i++) { + dev_info(dev, "CA7_R%02d : %08x\n", i, + readl(data->sfr_base + ABOX_CPU_R(i))); + } + dev_info(dev, "CA7_PC : %08x\n", + readl(data->sfr_base + ABOX_CPU_PC)); + dev_info(dev, "CA7_L2C_STATUS : %08x\n", + readl(data->sfr_base + ABOX_CPU_L2C_STATUS)); + dev_info(dev, "========================================\n"); +} + +struct abox_dbg_dump { + char sram[SZ_512K]; + char iva[IVA_FIRMWARE_SIZE]; + char dram[DRAM_FIRMWARE_SIZE]; + u32 sfr[SZ_64K / sizeof(u32)]; + u32 sfr_gic_gicd[SZ_4K / sizeof(u32)]; + unsigned int gpr[17]; + long long time; + char reason[SZ_32]; +}; + +struct abox_dbg_dump_min { + char sram[SZ_512K]; + char iva[IVA_FIRMWARE_SIZE]; + u32 sfr[SZ_64K / sizeof(u32)]; + u32 sfr_gic_gicd[SZ_4K / sizeof(u32)]; + unsigned int gpr[17]; + long long time; + char reason[SZ_32]; +}; + +static struct abox_dbg_dump (*p_abox_dbg_dump)[ABOX_DBG_DUMP_COUNT]; +static struct abox_dbg_dump_min (*p_abox_dbg_dump_min)[ABOX_DBG_DUMP_COUNT]; +static struct reserved_mem *abox_rmem; + +static int __init abox_rmem_setup(struct reserved_mem *rmem) +{ + pr_info("%s: base=%pa, size=%pa\n", __func__, &rmem->base, &rmem->size); + + abox_rmem = rmem; + if (sizeof(*p_abox_dbg_dump) <= abox_rmem->size) + p_abox_dbg_dump = phys_to_virt(abox_rmem->base); + else if (sizeof(*p_abox_dbg_dump_min) <= abox_rmem->size) + p_abox_dbg_dump_min = phys_to_virt(abox_rmem->base); + + return 0; +} + +RESERVEDMEM_OF_DECLARE(abox_rmem, "exynos,abox_rmem", abox_rmem_setup); + +void abox_dbg_dump_gpr_from_addr(struct device *dev, unsigned int *addr, + enum abox_dbg_dump_src src, const char *reason) +{ + int i; + + dev_dbg(dev, "%s\n", __func__); + + if (!abox_is_on()) { + dev_info(dev, "%s is skipped due to no power\n", __func__); + return; + } + + if (p_abox_dbg_dump) { + struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src]; + + p_dump->time = sched_clock(); + strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); + for (i = 0; i <= 14; i++) + p_dump->gpr[i] = *addr++; + p_dump->gpr[i++] = *addr++; + } else if (p_abox_dbg_dump_min) { + struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src]; + + p_dump->time = sched_clock(); + strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); + for (i = 0; i <= 14; i++) + p_dump->gpr[i] = *addr++; + p_dump->gpr[i++] = *addr++; + } +} + +void abox_dbg_dump_gpr(struct device *dev, struct abox_data *data, + enum abox_dbg_dump_src src, const char *reason) +{ + int i; + + dev_dbg(dev, "%s\n", __func__); + + if (!abox_is_on()) { + dev_info(dev, "%s is skipped due to no power\n", __func__); + return; + } + + if (p_abox_dbg_dump) { + struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src]; + + p_dump->time = sched_clock(); + strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); + for (i = 0; i <= 14; i++) + p_dump->gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i)); + p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC); + p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS); + } else if (p_abox_dbg_dump_min) { + struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src]; + + p_dump->time = sched_clock(); + strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); + for (i = 0; i <= 14; i++) + p_dump->gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i)); + p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC); + p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS); + } +} + +void abox_dbg_dump_mem(struct device *dev, struct abox_data *data, + enum abox_dbg_dump_src src, const char *reason) +{ + struct abox_gic_data *gic_data = dev_get_drvdata(data->dev_gic); + + dev_dbg(dev, "%s\n", __func__); + + if (!abox_is_on()) { + dev_info(dev, "%s is skipped due to no power\n", __func__); + return; + } + + if (p_abox_dbg_dump) { + struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src]; + + p_dump->time = sched_clock(); + strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); + memcpy_fromio(p_dump->sram, data->sram_base, data->sram_size); + if (data->ima_claimed) + memcpy_fromio(p_dump->iva, data->ima_vaddr, + sizeof(p_dump->iva)); + else + memcpy(p_dump->iva, data->iva_base, + sizeof(p_dump->iva)); + memcpy(p_dump->dram, data->dram_base, sizeof(p_dump->dram)); + memcpy_fromio(p_dump->sfr, data->sfr_base, sizeof(p_dump->sfr)); + memcpy_fromio(p_dump->sfr_gic_gicd, gic_data->gicd_base, + sizeof(p_dump->sfr_gic_gicd)); + } else if (p_abox_dbg_dump_min) { + struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src]; + + p_dump->time = sched_clock(); + strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); + memcpy_fromio(p_dump->sram, data->sram_base, data->sram_size); + if (data->ima_claimed) + memcpy_fromio(p_dump->iva, data->ima_vaddr, + sizeof(p_dump->iva)); + else + memcpy(p_dump->iva, data->iva_base, + sizeof(p_dump->iva)); + memcpy_fromio(p_dump->sfr, data->sfr_base, sizeof(p_dump->sfr)); + memcpy_fromio(p_dump->sfr_gic_gicd, gic_data->gicd_base, + sizeof(p_dump->sfr_gic_gicd)); + } +} + +void abox_dbg_dump_gpr_mem(struct device *dev, struct abox_data *data, + enum abox_dbg_dump_src src, const char *reason) +{ + abox_dbg_dump_gpr(dev, data, src, reason); + abox_dbg_dump_mem(dev, data, src, reason); +} + +struct abox_dbg_dump_simple { + char sram[SZ_512K]; + char iva[IVA_FIRMWARE_SIZE]; + u32 sfr[SZ_64K / sizeof(u32)]; + u32 sfr_gic_gicd[SZ_4K / sizeof(u32)]; + unsigned int gpr[17]; + long long time; + char reason[SZ_32]; +}; + +static struct abox_dbg_dump_simple abox_dump_simple; + +void abox_dbg_dump_simple(struct device *dev, struct abox_data *data, + const char *reason) +{ + struct abox_gic_data *gic_data = dev_get_drvdata(data->dev_gic); + int i; + + dev_info(dev, "%s\n", __func__); + + if (!abox_is_on()) { + dev_info(dev, "%s is skipped due to no power\n", __func__); + return; + } + + abox_dump_simple.time = sched_clock(); + strncpy(abox_dump_simple.reason, reason, + sizeof(abox_dump_simple.reason) - 1); + for (i = 0; i <= 14; i++) + abox_dump_simple.gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i)); + abox_dump_simple.gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC); + abox_dump_simple.gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS); + memcpy_fromio(abox_dump_simple.sram, data->sram_base, data->sram_size); + if (data->ima_claimed) { + memcpy_fromio(abox_dump_simple.iva, data->ima_vaddr, + sizeof(abox_dump_simple.iva)); + } else { + memcpy(abox_dump_simple.iva, data->iva_base, + sizeof(abox_dump_simple.iva)); + } + memcpy_fromio(abox_dump_simple.sfr, data->sfr_base, + sizeof(abox_dump_simple.sfr)); + memcpy_fromio(abox_dump_simple.sfr_gic_gicd, gic_data->gicd_base, + sizeof(abox_dump_simple.sfr_gic_gicd)); +} + +static atomic_t abox_error_count = ATOMIC_INIT(0); + +void abox_dbg_report_status(struct device *dev, bool ok) +{ + char env[32] = {0,}; + char *envp[2] = {env, NULL}; + + dev_info(dev, "%s\n", __func__); + + if (ok) + atomic_set(&abox_error_count, 0); + else + atomic_inc(&abox_error_count); + + snprintf(env, sizeof(env), "ERR_CNT=%d", + atomic_read(&abox_error_count)); + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); +} + +int abox_dbg_get_error_count(struct device *dev) +{ + int count = atomic_read(&abox_error_count); + + dev_dbg(dev, "%s: %d\n", __func__, count); + + return count; +} + +static ssize_t calliope_sram_read(struct file *file, struct kobject *kobj, + struct bin_attribute *battr, char *buf, + loff_t off, size_t size) +{ + struct device *dev = kobj_to_dev(kobj); + struct device *dev_abox = dev->parent; + + dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size); + + if (pm_runtime_get_if_in_use(dev_abox) > 0) { + memcpy_fromio(buf, battr->private + off, size); + pm_runtime_put(dev_abox); + } else { + memset(buf, 0x0, size); + } + + return size; +} + +static ssize_t calliope_iva_read(struct file *file, struct kobject *kobj, + struct bin_attribute *battr, char *buf, + loff_t off, size_t size) +{ + struct device *dev = kobj_to_dev(kobj); + + dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size); + + memcpy(buf, battr->private + off, size); + return size; +} + +static ssize_t calliope_dram_read(struct file *file, struct kobject *kobj, + struct bin_attribute *battr, char *buf, + loff_t off, size_t size) +{ + struct device *dev = kobj_to_dev(kobj); + + dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size); + + memcpy(buf, battr->private + off, size); + return size; +} + +/* size will be updated later */ +static BIN_ATTR_RO(calliope_sram, 0); +static BIN_ATTR_RO(calliope_iva, IVA_FIRMWARE_SIZE); +static BIN_ATTR_RO(calliope_dram, DRAM_FIRMWARE_SIZE); +static struct bin_attribute *calliope_bin_attrs[] = { + &bin_attr_calliope_sram, + &bin_attr_calliope_iva, + &bin_attr_calliope_dram, +}; + +static ssize_t gpr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct abox_data *data = dev_get_drvdata(dev->parent); + char version[4]; + char *pbuf = buf; + int i; + + if (!abox_is_on()) { + dev_info(dev, "%s is skipped due to no power\n", __func__); + return -EFAULT; + } + + memcpy(version, &data->calliope_version, sizeof(version)); + + pbuf += sprintf(pbuf, "========================================\n"); + pbuf += sprintf(pbuf, "A-Box CPU register dump (%c%c%c%c)\n", + version[3], version[2], version[1], version[0]); + pbuf += sprintf(pbuf, "----------------------------------------\n"); + for (i = 0; i <= 14; i++) { + pbuf += sprintf(pbuf, "CA7_R%02d : %08x\n", i, + readl(data->sfr_base + ABOX_CPU_R(i))); + } + pbuf += sprintf(pbuf, "CA7_PC : %08x\n", + readl(data->sfr_base + ABOX_CPU_PC)); + pbuf += sprintf(pbuf, "CA7_L2C_STATUS : %08x\n", + readl(data->sfr_base + ABOX_CPU_L2C_STATUS)); + pbuf += sprintf(pbuf, "========================================\n"); + + return pbuf - buf; +} + +static DEVICE_ATTR_RO(gpr); + +static int samsung_abox_debug_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *abox_dev = dev->parent; + struct abox_data *data = dev_get_drvdata(abox_dev); + int i, ret; + + dev_dbg(dev, "%s\n", __func__); + + if (abox_rmem == NULL) + return -ENOMEM; + + dev_info(dev, "%s(%pa) is mapped on %p with size of %pa\n", + "dump buffer", &abox_rmem->base, + phys_to_virt(abox_rmem->base), &abox_rmem->size); + iommu_map(data->iommu_domain, IOVA_DUMP_BUFFER, abox_rmem->base, + abox_rmem->size, 0); + data->dump_base = phys_to_virt(abox_rmem->base); + data->dump_base_phys = abox_rmem->base; + ret = device_create_file(dev, &dev_attr_gpr); + bin_attr_calliope_sram.size = data->sram_size; + bin_attr_calliope_sram.private = data->sram_base; + bin_attr_calliope_iva.private = data->iva_base; + bin_attr_calliope_dram.private = data->dram_base; + for (i = 0; i < ARRAY_SIZE(calliope_bin_attrs); i++) { + struct bin_attribute *battr = calliope_bin_attrs[i]; + + ret = device_create_bin_file(dev, battr); + if (ret < 0) + dev_warn(dev, "Failed to create file: %s\n", + battr->attr.name); + } + + return ret; +} + +static int samsung_abox_debug_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + return 0; +} + +static const struct of_device_id samsung_abox_debug_match[] = { + { + .compatible = "samsung,abox-debug", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_debug_match); + +static struct platform_driver samsung_abox_debug_driver = { + .probe = samsung_abox_debug_probe, + .remove = samsung_abox_debug_remove, + .driver = { + .name = "samsung-abox-debug", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_debug_match), + }, +}; + +module_platform_driver(samsung_abox_debug_driver); + +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box Debug Driver"); +MODULE_ALIAS("platform:samsung-abox-debug"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_dbg.h b/sound/soc/samsung/abox/abox_dbg.h new file mode 100644 index 000000000000..152f1c4e616c --- /dev/null +++ b/sound/soc/samsung/abox/abox_dbg.h @@ -0,0 +1,102 @@ +/* sound/soc/samsung/abox/abox_dbg.h + * + * ALSA SoC - Samsung Abox Debug driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_DEBUG_H +#define __SND_SOC_ABOX_DEBUG_H + +#include "abox.h" + +enum abox_dbg_dump_src { + ABOX_DBG_DUMP_KERNEL, + ABOX_DBG_DUMP_FIRMWARE, + ABOX_DBG_DUMP_VSS, + ABOX_DBG_DUMP_COUNT, +}; + +/** + * Initialize abox debug driver + * @return dentry of abox debugfs root directory + */ +extern struct dentry *abox_dbg_get_root_dir(void); + +/** + * print gpr into the kmsg from memory + * @param[in] dev pointer to device which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] addr pointer to gpr dump + */ +extern void abox_dbg_print_gpr_from_addr(struct device *dev, + struct abox_data *data, unsigned int *addr); + +/** + * print gpr into the kmsg + * @param[in] dev pointer to device which invokes this API + * @param[in] data pointer to abox_data structure + */ +extern void abox_dbg_print_gpr(struct device *dev, struct abox_data *data); + +/** + * dump gpr from memory + * @param[in] dev pointer to device which invokes this API + * @param[in] addr pointer to gpr dump + * @param[in] src source of the dump request + * @param[in] reason reason description + */ +extern void abox_dbg_dump_gpr_from_addr(struct device *dev, unsigned int *addr, + enum abox_dbg_dump_src src, const char *reason); + +/** + * dump gpr + * @param[in] dev pointer to device which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] src source of the dump request + * @param[in] reason reason description + */ +extern void abox_dbg_dump_gpr(struct device *dev, struct abox_data *data, + enum abox_dbg_dump_src src, const char *reason); + +/** + * dump memory + * @param[in] dev pointer to device which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] src source of the dump request + * @param[in] reason reason description + */ +extern void abox_dbg_dump_mem(struct device *dev, struct abox_data *data, + enum abox_dbg_dump_src src, const char *reason); + +/** + * dump gpr and memory + * @param[in] dev pointer to device which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] src source of the dump request + * @param[in] reason reason description + */ +extern void abox_dbg_dump_gpr_mem(struct device *dev, struct abox_data *data, + enum abox_dbg_dump_src src, const char *reason); + +/** + * dump gpr and memory except DRAM + * @param[in] dev pointer to device which invokes this API + * @param[in] data pointer to abox_data structure + * @param[in] reason reason description + */ +extern void abox_dbg_dump_simple(struct device *dev, struct abox_data *data, + const char *reason); + +/** + * Push status of the abox + * @param[in] dev pointer to abox device + * @param[in] ok true for okay, false on otherwise + */ +extern void abox_dbg_report_status(struct device *dev, bool ok); + +#endif /* __SND_SOC_ABOX_DEBUG_H */ diff --git a/sound/soc/samsung/abox/abox_dump.c b/sound/soc/samsung/abox/abox_dump.c new file mode 100644 index 000000000000..8513ad39e856 --- /dev/null +++ b/sound/soc/samsung/abox/abox_dump.c @@ -0,0 +1,674 @@ +/* sound/soc/samsung/abox/abox_dump.c + * + * ALSA SoC Audio Layer - Samsung Abox Internal Buffer Dumping driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include + +#include "abox_util.h" +#include "abox.h" +#include "abox_dbg.h" +#include "abox_log.h" + +#define BUFFER_MAX (SZ_32) +#define NAME_LENGTH (SZ_32) + +struct abox_dump_buffer_info { + struct device *dev; + struct list_head list; + int id; + char name[NAME_LENGTH]; + struct mutex lock; + struct snd_dma_buffer buffer; + struct snd_pcm_substream *substream; + size_t pointer; + bool started; + bool auto_started; + bool file_created; + struct file *filp; + ssize_t auto_pointer; + struct work_struct auto_work; +}; + +static struct device *abox_dump_dev_abox; +static struct abox_dump_buffer_info abox_dump_list[BUFFER_MAX]; +static LIST_HEAD(abox_dump_list_head); + +static struct abox_dump_buffer_info *abox_dump_get_buffer_info(int id) +{ + struct abox_dump_buffer_info *info; + + list_for_each_entry(info, &abox_dump_list_head, list) { + if (info->id == id) + return info; + } + + return NULL; +} + +static struct abox_dump_buffer_info *abox_dump_get_buffer_info_by_name( + const char *name) +{ + struct abox_dump_buffer_info *info; + + list_for_each_entry(info, &abox_dump_list_head, list) { + if (strncmp(info->name, name, sizeof(info->name)) == 0) + return info; + } + + return NULL; +} + +static void abox_dump_request_dump(int id) +{ + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + ABOX_IPC_MSG msg; + struct IPC_SYSTEM_MSG *system = &msg.msg.system; + bool start = info->started || info->auto_started; + + dev_dbg(abox_dump_dev_abox, "%s(%d)\n", __func__, id); + + msg.ipcid = IPC_SYSTEM; + system->msgtype = ABOX_REQUEST_DUMP; + system->param1 = id; + system->param2 = start ? 1 : 0; + abox_request_ipc(abox_dump_dev_abox, msg.ipcid, &msg, sizeof(msg), + 0, 0); +} + +static ssize_t abox_dump_auto_read(struct file *file, char __user *data, + size_t count, loff_t *ppos, bool enable) +{ + struct abox_dump_buffer_info *info; + char buffer[SZ_256] = {0,}, *buffer_p = buffer; + + dev_dbg(abox_dump_dev_abox, "%s(%zu, %lld, %d)\n", __func__, count, + *ppos, enable); + + list_for_each_entry(info, &abox_dump_list_head, list) { + if (info->auto_started == enable) { + buffer_p += snprintf(buffer_p, sizeof(buffer) - + (buffer_p - buffer), + "%d(%s) ", info->id, info->name); + } + } + snprintf(buffer_p, 2, "\n"); + + return simple_read_from_buffer(data, count, ppos, buffer, + buffer_p - buffer); +} + +static ssize_t abox_dump_auto_write(struct file *file, const char __user *data, + size_t count, loff_t *ppos, bool enable) +{ + char buffer[SZ_256] = {0,}, name[NAME_LENGTH]; + char *p_buffer = buffer, *token = NULL; + unsigned int id; + struct abox_dump_buffer_info *info; + ssize_t ret; + + dev_dbg(abox_dump_dev_abox, "%s(%zu, %lld, %d)\n", __func__, count, + *ppos, enable); + + ret = simple_write_to_buffer(buffer, sizeof(buffer), ppos, data, count); + if (ret < 0) + return ret; + + while ((token = strsep(&p_buffer, " ")) != NULL) { + if (sscanf(token, "%11u", &id) == 1) + info = abox_dump_get_buffer_info(id); + else if (sscanf(token, "%31s", name) == 1) + info = abox_dump_get_buffer_info_by_name(name); + else + info = NULL; + + if (IS_ERR_OR_NULL(info)) { + dev_err(abox_dump_dev_abox, "invalid argument\n"); + continue; + } + + if (info->auto_started != enable) { + struct device *dev = info->dev; + struct platform_device *pdev_abox; + + pdev_abox = to_platform_device(abox_dump_dev_abox); + if (enable) { + abox_request_dram_on(pdev_abox, dev, true); + pm_runtime_get(dev); + } else { + pm_runtime_put(dev); + abox_request_dram_on(pdev_abox, dev, false); + } + } + + info->auto_started = enable; + if (enable) { + info->file_created = false; + info->auto_pointer = -1; + } + + abox_dump_request_dump(info->id); + } + + return count; +} + +static ssize_t abox_dump_auto_start_read(struct file *file, + char __user *data, size_t count, loff_t *ppos) +{ + return abox_dump_auto_read(file, data, count, ppos, true); +} + +static ssize_t abox_dump_auto_start_write(struct file *file, + const char __user *data, size_t count, loff_t *ppos) +{ + return abox_dump_auto_write(file, data, count, ppos, true); +} + +static ssize_t abox_dump_auto_stop_read(struct file *file, + char __user *data, size_t count, loff_t *ppos) +{ + return abox_dump_auto_read(file, data, count, ppos, false); +} + +static ssize_t abox_dump_auto_stop_write(struct file *file, + const char __user *data, size_t count, loff_t *ppos) +{ + return abox_dump_auto_write(file, data, count, ppos, false); +} + +static const struct file_operations abox_dump_auto_start_fops = { + .read = abox_dump_auto_start_read, + .write = abox_dump_auto_start_write, +}; + +static const struct file_operations abox_dump_auto_stop_fops = { + .read = abox_dump_auto_stop_read, + .write = abox_dump_auto_stop_write, +}; + +static int __init samsung_abox_dump_late_initcall(void) +{ + pr_info("%s\n", __func__); + + debugfs_create_file("dump_auto_start", 0660, abox_dbg_get_root_dir(), + NULL, &abox_dump_auto_start_fops); + debugfs_create_file("dump_auto_stop", 0660, abox_dbg_get_root_dir(), + NULL, &abox_dump_auto_stop_fops); + + return 0; +} +late_initcall(samsung_abox_dump_late_initcall); + +static struct snd_soc_dai_link abox_dump_dai_links[BUFFER_MAX]; + +static struct snd_soc_card abox_dump_card = { + .name = "abox_dump", + .owner = THIS_MODULE, + .dai_link = abox_dump_dai_links, + .num_links = 0, +}; + +static void abox_dump_auto_dump_work_func(struct work_struct *work) +{ + struct abox_dump_buffer_info *info = container_of(work, + struct abox_dump_buffer_info, auto_work); + struct device *dev = info->dev; + int id = info->id; + + if (info->auto_started) { + mm_segment_t old_fs; + char filename[SZ_64]; + struct file *filp; + + sprintf(filename, "/data/abox_dump-%d.raw", id); + + old_fs = get_fs(); + set_fs(KERNEL_DS); + if (likely(info->file_created)) { + filp = filp_open(filename, O_RDWR | O_APPEND | O_CREAT, + 0660); + dev_dbg(dev, "appended\n"); + } else { + filp = filp_open(filename, O_RDWR | O_TRUNC | O_CREAT, + 0660); + info->file_created = true; + dev_dbg(dev, "created\n"); + } + if (!IS_ERR_OR_NULL(filp)) { + void *area = info->buffer.area; + size_t bytes = info->buffer.bytes; + size_t pointer = info->pointer; + bool first = false; + + if (unlikely(info->auto_pointer < 0)) { + info->auto_pointer = pointer; + first = true; + } + dev_dbg(dev, "%pad, %p, %zx, %zx)\n", + &info->buffer.addr, area, bytes, + info->auto_pointer); + if (pointer < info->auto_pointer || first) { + vfs_write(filp, area + info->auto_pointer, + bytes - info->auto_pointer, + &filp->f_pos); + dev_dbg(dev, "vfs_write(%p, %zx)\n", + area + info->auto_pointer, + bytes - info->auto_pointer); + info->auto_pointer = 0; + } + vfs_write(filp, area + info->auto_pointer, + pointer - info->auto_pointer, + &filp->f_pos); + dev_dbg(dev, "vfs_write(%p, %zx)\n", + area + info->auto_pointer, + pointer - info->auto_pointer); + info->auto_pointer = pointer; + + vfs_fsync(filp, 1); + filp_close(filp, NULL); + } else { + dev_err(dev, "dump file %d open error: %ld\n", id, + PTR_ERR(filp)); + } + + set_fs(old_fs); + } +} + +void abox_dump_register_buffer_work_func(struct work_struct *work) +{ + int id; + struct abox_dump_buffer_info *info; + + if (!abox_dump_card.dev) { + platform_device_register_data(abox_dump_dev_abox, + "samsung-abox-dump", -1, NULL, 0); + } + + dev_dbg(abox_dump_card.dev, "%s\n", __func__); + + for (info = &abox_dump_list[0]; (info - &abox_dump_list[0]) < + ARRAY_SIZE(abox_dump_list); info++) { + id = info->id; + if (info->dev && !abox_dump_get_buffer_info(id)) { + dev_info(info->dev, "%s[%d]\n", __func__, id); + list_add_tail(&info->list, &abox_dump_list_head); + platform_device_register_data(info->dev, + "samsung-abox-dump", id, NULL, 0); + } + } +} + +static DECLARE_WORK(abox_dump_register_buffer_work, + abox_dump_register_buffer_work_func); + +int abox_dump_register_buffer(struct device *dev, int id, const char *name, + void *area, phys_addr_t addr, size_t bytes) +{ + struct abox_dump_buffer_info *info; + + dev_dbg(dev, "%s[%d] %p(%pa)\n", __func__, id, area, &addr); + + if (id < 0 || id >= ARRAY_SIZE(abox_dump_list)) { + dev_err(dev, "invalid id: %d\n", id); + return -EINVAL; + } + + if (abox_dump_get_buffer_info(id)) { + dev_dbg(dev, "already registered dump: %d\n", id); + return 0; + } + + info = &abox_dump_list[id]; + mutex_init(&info->lock); + info->id = id; + strncpy(info->name, name, sizeof(info->name) - 1); + info->buffer.area = area; + info->buffer.addr = addr; + info->buffer.bytes = bytes; + INIT_WORK(&info->auto_work, abox_dump_auto_dump_work_func); + abox_dump_dev_abox = info->dev = dev; + schedule_work(&abox_dump_register_buffer_work); + + return 0; +} +EXPORT_SYMBOL(abox_dump_register_buffer); + +static struct snd_pcm_hardware abox_dump_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID, + .formats = ABOX_SAMPLE_FORMATS, + .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 384000, + .channels_min = 1, + .channels_max = 8, + .periods_min = 2, + .periods_max = 32, +}; + +static int abox_dump_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + struct snd_dma_buffer *dmab = &substream->dma_buffer; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + abox_dump_hardware.buffer_bytes_max = dmab->bytes; + abox_dump_hardware.period_bytes_min = dmab->bytes / + abox_dump_hardware.periods_max; + abox_dump_hardware.period_bytes_max = dmab->bytes / + abox_dump_hardware.periods_min; + + snd_soc_set_runtime_hwparams(substream, &abox_dump_hardware); + + info->substream = substream; + + return 0; +} + +static int abox_dump_platform_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + info->substream = NULL; + + return 0; +} + +static int abox_dump_platform_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_platform *platform = rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); +} + +static int abox_dump_platform_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + return snd_pcm_lib_free_pages(substream); +} + +static int abox_dump_platform_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + info->pointer = 0; + + return 0; +} + +static int abox_dump_platform_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + + dev_dbg(dev, "%s[%d](%d)\n", __func__, id, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + info->started = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + info->started = false; + break; + default: + dev_err(dev, "invalid command: %d\n", cmd); + return -EINVAL; + } + + abox_dump_request_dump(id); + + return 0; +} + +void abox_dump_period_elapsed(int id, size_t pointer) +{ + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + struct device *dev = info->dev; + + dev_dbg(dev, "%s[%d](%zx)\n", __func__, id, pointer); + + info->pointer = pointer; + schedule_work(&info->auto_work); + snd_pcm_period_elapsed(info->substream); +} +EXPORT_SYMBOL(abox_dump_period_elapsed); + +static snd_pcm_uframes_t abox_dump_platform_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + return bytes_to_frames(substream->runtime, info->pointer); +} + +static struct snd_pcm_ops abox_dump_platform_ops = { + .open = abox_dump_platform_open, + .close = abox_dump_platform_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = abox_dump_platform_hw_params, + .hw_free = abox_dump_platform_hw_free, + .prepare = abox_dump_platform_prepare, + .trigger = abox_dump_platform_trigger, + .pointer = abox_dump_platform_pointer, +}; + +static void abox_dump_register_card_work_func(struct work_struct *work) +{ + int i; + + pr_debug("%s\n", __func__); + + snd_soc_unregister_card(&abox_dump_card); + for (i = 0; i < abox_dump_card.num_links; i++) { + struct snd_soc_dai_link *link = &abox_dump_card.dai_link[i]; + + if (link->name) + continue; + + link->name = link->stream_name = + kasprintf(GFP_KERNEL, "dummy%d", i); + link->cpu_name = "snd-soc-dummy"; + link->cpu_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->no_pcm = 1; + } + snd_soc_register_card(&abox_dump_card); +} + +DECLARE_DELAYED_WORK(abox_dump_register_card_work, + abox_dump_register_card_work_func); + +static void abox_dump_add_dai_link(struct device *dev) +{ + int id = to_platform_device(dev)->id; + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + struct snd_soc_dai_link *link = &abox_dump_dai_links[id]; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + if (id > ARRAY_SIZE(abox_dump_dai_links)) { + dev_err(dev, "Too many dump request\n"); + return; + } + + cancel_delayed_work_sync(&abox_dump_register_card_work); + info->dev = dev; + kfree(link->name); + link->name = link->stream_name = kstrdup(info->name, GFP_KERNEL); + link->cpu_name = "snd-soc-dummy"; + link->cpu_dai_name = "snd-soc-dummy-dai"; + link->platform_name = dev_name(dev); + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->ignore_suspend = 1; + link->ignore_pmdown_time = 1; + link->no_pcm = 0; + link->capture_only = true; + + if (abox_dump_card.num_links <= id) + abox_dump_card.num_links = id + 1; + + schedule_delayed_work(&abox_dump_register_card_work, HZ); +} + +static int abox_dump_platform_probe(struct snd_soc_platform *platform) +{ + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + return 0; +} + +static int abox_dump_platform_new(struct snd_soc_pcm_runtime *runtime) +{ + struct device *dev = runtime->platform->dev; + struct snd_pcm *pcm = runtime->pcm; + struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_dma_buffer *dmab = &substream->dma_buffer; + int id = to_platform_device(dev)->id; + struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id); + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + dmab->dev.type = SNDRV_DMA_TYPE_DEV; + dmab->dev.dev = dev; + dmab->area = info->buffer.area; + dmab->addr = info->buffer.addr; + dmab->bytes = info->buffer.bytes; + + return 0; +} + +static void abox_dump_platform_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *runtime = pcm->private_data; + struct device *dev = runtime->platform->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); +} + +struct snd_soc_platform_driver abox_dump_platform = { + .probe = abox_dump_platform_probe, + .ops = &abox_dump_platform_ops, + .pcm_new = abox_dump_platform_new, + .pcm_free = abox_dump_platform_free, +}; + +static int samsung_abox_dump_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + if (id >= 0) { + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); + + devm_snd_soc_register_platform(dev, &abox_dump_platform); + abox_dump_add_dai_link(dev); + } else { + abox_dump_card.dev = &pdev->dev; + } + + return 0; +} + +static int samsung_abox_dump_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + return 0; +} + +static const struct platform_device_id samsung_abox_dump_driver_ids[] = { + { + .name = "samsung-abox-dump", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, samsung_abox_dump_driver_ids); + +static struct platform_driver samsung_abox_dump_driver = { + .probe = samsung_abox_dump_probe, + .remove = samsung_abox_dump_remove, + .driver = { + .name = "samsung-abox-dump", + .owner = THIS_MODULE, + }, + .id_table = samsung_abox_dump_driver_ids, +}; + +module_platform_driver(samsung_abox_dump_driver); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box Internal Buffer Dumping Driver"); +MODULE_ALIAS("platform:samsung-abox-dump"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_dump.h b/sound/soc/samsung/abox/abox_dump.h new file mode 100644 index 000000000000..08346b51eab2 --- /dev/null +++ b/sound/soc/samsung/abox/abox_dump.h @@ -0,0 +1,38 @@ +/* sound/soc/samsung/abox/abox_dump.h + * + * ALSA SoC Audio Layer - Samsung Abox Internal Buffer Dumping driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_DUMP_H +#define __SND_SOC_ABOX_DUMP_H + +#include +#include + +/** + * Report dump data written + * @param[in] id unique buffer id + * @param[in] pointer byte index of the written data + */ +extern void abox_dump_period_elapsed(int id, size_t pointer); + +/** + * Register abox dump buffer + * @param[in] dev pointer to abox device + * @param[in] id unique buffer id + * @param[in] name unique buffer name + * @param[in] area virtual address of the buffer + * @param[in] addr pysical address of the buffer + * @param[in] bytes buffer size in bytes + * @return error code if any + */ +extern int abox_dump_register_buffer(struct device *dev, int id, + const char *name, void *area, phys_addr_t addr, size_t bytes); + +#endif /* __SND_SOC_ABOX_DUMP_H */ diff --git a/sound/soc/samsung/abox/abox_effect.c b/sound/soc/samsung/abox/abox_effect.c new file mode 100644 index 000000000000..09c08269d74d --- /dev/null +++ b/sound/soc/samsung/abox/abox_effect.c @@ -0,0 +1,321 @@ +/* sound/soc/samsung/abox/abox_effect.c + * + * ALSA SoC Audio Layer - Samsung Abox Effect driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "abox.h" +#include "abox_util.h" +#include "abox_effect.h" + +struct abox_ctl_eq_switch { + unsigned int base; + unsigned int count; + unsigned int min; + unsigned int max; +}; + +static int abox_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value; + + dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = params->count; + uinfo->value.integer.min = params->min; + uinfo->value.integer.max = params->max; + return 0; +} + +static int abox_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value; + int i; + int ret = 0; + + dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name); + + pm_runtime_get_sync(dev); + for (i = 0; i < params->count; i++) { + unsigned int reg, val; + + reg = (unsigned int)(params->base + PARAM_OFFSET + + (i * sizeof(u32))); + ret = snd_soc_component_read(cmpnt, reg, &val); + if (ret < 0) { + dev_err(dev, "reading fail at 0x%08X\n", reg); + break; + } + ucontrol->value.integer.value[i] = val; + dev_dbg(dev, "%s[%d] = %u\n", kcontrol->id.name, i, val); + } + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int abox_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value; + int i; + + dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name); + + pm_runtime_get_sync(dev); + for (i = 0; i < params->count; i++) { + unsigned int reg, val; + + reg = (unsigned int)(params->base + PARAM_OFFSET + + (i * sizeof(u32))); + val = (unsigned int)ucontrol->value.integer.value[i]; + snd_soc_component_write(cmpnt, reg, val); + dev_dbg(dev, "%s[%d] <= %u\n", kcontrol->id.name, i, val); + } + snd_soc_component_write(cmpnt, params->base, CHANGE_BIT); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; +} + +#define ABOX_CTL_EQ_SWITCH(xname, xbase, xcount, xmin, xmax) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = abox_ctl_info, .get = abox_ctl_get, \ + .put = abox_ctl_put, .private_value = \ + ((unsigned long)&(struct abox_ctl_eq_switch) \ + {.base = xbase, .count = xcount, \ + .min = xmin, .max = xmax}) } + +#define DECLARE_ABOX_CTL_EQ_SWITCH(name, prefix) \ + ABOX_CTL_EQ_SWITCH(name, prefix##_BASE, \ + prefix##_MAX_COUNT, prefix##_VALUE_MIN, \ + prefix##_VALUE_MAX) + +static const struct snd_kcontrol_new abox_effect_controls[] = { + DECLARE_ABOX_CTL_EQ_SWITCH("SA data", SA), + DECLARE_ABOX_CTL_EQ_SWITCH("Audio DHA data", MYSOUND), + DECLARE_ABOX_CTL_EQ_SWITCH("VSP data", VSP), + DECLARE_ABOX_CTL_EQ_SWITCH("LRSM data", LRSM), + DECLARE_ABOX_CTL_EQ_SWITCH("MSP data", MYSPACE), + DECLARE_ABOX_CTL_EQ_SWITCH("ESA BBoost data", BB), + DECLARE_ABOX_CTL_EQ_SWITCH("ESA EQ data", EQ), + DECLARE_ABOX_CTL_EQ_SWITCH("NXP BDL data", NXPBDL), + DECLARE_ABOX_CTL_EQ_SWITCH("NXP RVB ctx data", NXPRVB_CTX), + DECLARE_ABOX_CTL_EQ_SWITCH("NXP RVB param data", NXPRVB_PARAM), + DECLARE_ABOX_CTL_EQ_SWITCH("SB rotation", SB), + DECLARE_ABOX_CTL_EQ_SWITCH("UPSCALER", UPSCALER), +}; + +#define ABOX_EFFECT_ACCESSIABLE_REG(name, reg) \ + (reg >= name##_BASE && reg <= name##_BASE + PARAM_OFFSET + \ + (name##_MAX_COUNT * sizeof(u32))) + +static bool abox_effect_accessible_reg(struct device *dev, unsigned int reg) +{ + return ABOX_EFFECT_ACCESSIABLE_REG(SA, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(MYSOUND, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(VSP, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(LRSM, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(MYSPACE, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(BB, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(EQ, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(ELPE, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(NXPBDL, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(NXPRVB_CTX, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(NXPRVB_PARAM, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(SB, reg) || + ABOX_EFFECT_ACCESSIABLE_REG(UPSCALER, reg); +} + +#define ABOX_EFFECT_VOLATILE_REG(name, reg) (reg == name##_BASE) + +static bool abox_effect_volatile_reg(struct device *dev, unsigned int reg) +{ + return ABOX_EFFECT_VOLATILE_REG(SA, reg) || + ABOX_EFFECT_VOLATILE_REG(MYSOUND, reg) || + ABOX_EFFECT_VOLATILE_REG(VSP, reg) || + ABOX_EFFECT_VOLATILE_REG(LRSM, reg) || + ABOX_EFFECT_VOLATILE_REG(MYSPACE, reg) || + ABOX_EFFECT_VOLATILE_REG(BB, reg) || + ABOX_EFFECT_VOLATILE_REG(EQ, reg) || + ABOX_EFFECT_VOLATILE_REG(ELPE, reg) || + ABOX_EFFECT_VOLATILE_REG(NXPBDL, reg) || + ABOX_EFFECT_VOLATILE_REG(NXPRVB_CTX, reg) || + ABOX_EFFECT_VOLATILE_REG(NXPRVB_PARAM, reg) || + ABOX_EFFECT_VOLATILE_REG(SB, reg) || + ABOX_EFFECT_VOLATILE_REG(UPSCALER, reg); +} + +static const struct regmap_config abox_effect_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = ABOX_EFFECT_MAX_REGISTERS, + .writeable_reg = abox_effect_accessible_reg, + .readable_reg = abox_effect_accessible_reg, + .volatile_reg = abox_effect_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static const struct snd_soc_component_driver abox_effect = { + .controls = abox_effect_controls, + .num_controls = ARRAY_SIZE(abox_effect_controls), +}; + +static struct abox_effect_data *p_abox_effect_data; + +void abox_effect_restore(void) +{ + if (p_abox_effect_data && p_abox_effect_data->pdev) { + struct device *dev = &p_abox_effect_data->pdev->dev; + + pm_runtime_get(dev); + pm_runtime_put_autosuspend(dev); + } +} + +static int abox_effect_runtime_suspend(struct device *dev) +{ + struct abox_effect_data *data = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + regcache_cache_only(data->regmap, true); + regcache_mark_dirty(data->regmap); + + return 0; +} + +static int abox_effect_runtime_resume(struct device *dev) +{ + struct abox_effect_data *data = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + regcache_cache_only(data->regmap, false); + regcache_sync(data->regmap); + + return 0; +} + +const struct dev_pm_ops samsung_abox_effect_pm = { + SET_RUNTIME_PM_OPS(abox_effect_runtime_suspend, + abox_effect_runtime_resume, NULL) +}; + +static int samsung_abox_effect_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *np_tmp; + struct abox_effect_data *data; + + dev_dbg(dev, "%s\n", __func__); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, data); + p_abox_effect_data = data; + + np_tmp = of_parse_phandle(np, "abox", 0); + if (!np_tmp) { + dev_err(dev, "Failed to get abox device node\n"); + return -EPROBE_DEFER; + } + data->pdev_abox = of_find_device_by_node(np_tmp); + if (!data->pdev_abox) { + dev_err(dev, "Failed to get abox platform device\n"); + return -EPROBE_DEFER; + } + + data->pdev = pdev; + + data->base = devm_not_request_and_map(pdev, "reg", 0, NULL, NULL); + if (IS_ERR(data->base)) { + dev_err(dev, "base address request failed: %ld\n", + PTR_ERR(data->base)); + return PTR_ERR(data->base); + } + + data->regmap = devm_regmap_init_mmio(dev, data->base, + &abox_effect_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "regmap init failed: %ld\n", + PTR_ERR(data->regmap)); + return PTR_ERR(data->regmap); + } + + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + + return devm_snd_soc_register_component(&pdev->dev, &abox_effect, NULL, + 0); +} + +static int samsung_abox_effect_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + pm_runtime_disable(dev); + + return 0; +} + +static const struct of_device_id samsung_abox_effect_match[] = { + { + .compatible = "samsung,abox-effect", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_effect_match); + +static struct platform_driver samsung_abox_effect_driver = { + .probe = samsung_abox_effect_probe, + .remove = samsung_abox_effect_remove, + .driver = { + .name = "samsung-abox-effect", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_effect_match), + .pm = &samsung_abox_effect_pm, + }, +}; + +module_platform_driver(samsung_abox_effect_driver); + +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box Effect Driver"); +MODULE_ALIAS("platform:samsung-abox-effect"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_effect.h b/sound/soc/samsung/abox/abox_effect.h new file mode 100644 index 000000000000..9c67e4af6cae --- /dev/null +++ b/sound/soc/samsung/abox/abox_effect.h @@ -0,0 +1,176 @@ +/* sound/soc/samsung/abox/abox_effect.h + * + * ALSA SoC Audio Layer - Samsung Abox Effect driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_EFFECT_H +#define __SND_SOC_ABOX_EFFECT_H +enum { + SOUNDALIVE = 0, + MYSOUND, + PLAYSPEED, + SOUNDBALANCE, + MYSPACE, + BASSBOOST, + EQUALIZER, + NXPBUNDLE, /* includes BB,EQ, virtualizer, volume */ + NXPREVERB_CTX, /* Reverb context inforamtion */ + NXPREVERB_PARAM, /* Reverb effect parameters */ + SOUNDBOOSTER, +}; + +/* Effect offset */ +#define SA_BASE (0x000) +#define SA_CHANGE_BIT (0x000) +#define SA_OUT_DEVICE (0x010) +#define SA_PRESET (0x014) +#define SA_EQ_BEGIN (0x018) +#define SA_EQ_END (0x038) +#define SA_3D_LEVEL (0x03C) +#define SA_BE_LEVEL (0x040) +#define SA_REVERB (0x044) +#define SA_ROOMSIZE (0x048) +#define SA_CLA_LEVEL (0x04C) +#define SA_VOLUME_LEVEL (0x050) +#define SA_SQUARE_ROW (0x054) +#define SA_SQUARE_COLUMN (0x058) +#define SA_TAB_INFO (0x05C) +#define SA_NEW_UI (0x060) +#define SA_3D_ON (0x064) +#define SA_3D_ANGLE_L (0x068) +#define SA_3D_ANGLE_R (0x06C) +#define SA_3D_GAIN_L (0x070) +#define SA_3D_GAIN_R (0x074) +#define SA_MAX_COUNT (26) +#define SA_VALUE_MIN (0) +#define SA_VALUE_MAX (100) + +#define MYSOUND_BASE (0x100) +#define MYSOUND_CHANGE_BIT (0x100) +#define MYSOUND_DHA_ENABLE (0x110) +#define MYSOUND_GAIN_BEGIN (0x114) +#define MYSOUND_OUT_DEVICE (0x148) +#define MYSOUND_MAX_COUNT (14) +#define MYSOUND_VALUE_MIN (0) +#define MYSOUND_VALUE_MAX (100) + +#define VSP_BASE (0x200) +#define VSP_CHANGE_BIT (0x200) +#define VSP_INDEX (0x210) +#define VSP_MAX_COUNT (2) +#define VSP_VALUE_MIN (0) +#define VSP_VALUE_MAX (1000) + +#define LRSM_BASE (0x300) +#define LRSM_CHANGE_BIT (0x300) +#define LRSM_INDEX0 (0x310) +#define LRSM_INDEX1 (0x320) +#define LRSM_MAX_COUNT (3) +#define LRSM_VALUE_MIN (-50) +#define LRSM_VALUE_MAX (50) + +#define MYSPACE_BASE (0x340) +#define MYSPACE_CHANGE_BIT (0x340) +#define MYSPACE_PRESET (0x350) +#define MYSPACE_MAX_COUNT (2) +#define MYSPACE_VALUE_MIN (0) +#define MYSPACE_VALUE_MAX (5) + +#define BB_BASE (0x400) +#define BB_CHANGE_BIT (0x400) +#define BB_STATUS (0x410) +#define BB_STRENGTH (0x414) +#define BB_MAX_COUNT (2) +#define BB_VALUE_MIN (0) +#define BB_VALUE_MAX (100) + +#define EQ_BASE (0x500) +#define EQ_CHANGE_BIT (0x500) +#define EQ_STATUS (0x510) +#define EQ_PRESET (0x514) +#define EQ_NBAND (0x518) +#define EQ_NBAND_LEVEL (0x51c) /* 10 nband levels */ +#define EQ_NBAND_FREQ (0x544) /* 10 nband frequencies */ +#define EQ_MAX_COUNT (23) +#define EQ_VALUE_MIN (0) +#define EQ_VALUE_MAX (14000) + +/* CommBox ELPE Parameter */ +#define ELPE_BASE (0x600) +#define ELPE_CMD (0x600) +#define ELPE_ARG0 (0x604) +#define ELPE_ARG1 (0x608) +#define ELPE_ARG2 (0x60C) +#define ELPE_ARG3 (0x610) +#define ELPE_ARG4 (0x614) +#define ELPE_ARG5 (0x618) +#define ELPE_ARG6 (0x61C) +#define ELPE_ARG7 (0x620) +#define ELPE_ARG8 (0x624) +#define ELPE_ARG9 (0x628) +#define ELPE_ARG10 (0x62C) +#define ELPE_ARG11 (0x630) +#define ELPE_ARG12 (0x634) +#define ELPE_RET (0x638) +#define ELPE_DONE (0x63C) +#define ELPE_MAX_COUNT (11) + +/* NXP Bundle control parameters */ +#define NXPBDL_BASE (0x700) +#define NXPBDL_CHANGE_BIT (0x700) +#define NXPBDL_MAX_COUNT (35) /* bundle common struct param count */ +#define NXPBDL_VALUE_MIN (0) +#define NXPBDL_VALUE_MAX (192000) + +/* NXP Reverb control parameters */ +#define NXPRVB_PARAM_BASE (0x800) +#define NXPRVB_PARAM_CHANGE_BIT (0x800) +#define NXPRVB_PARAM_MAX_COUNT (10) /* reverb common struct param count */ +#define NXPRVB_PARAM_VALUE_MIN (0) +#define NXPRVB_PARAM_VALUE_MAX (192000) + +/* NXP reverb context parameters */ +#define NXPRVB_CTX_BASE (0x880) +#define NXPRVB_CTX_CHANGE_BIT (0x880) +#define NXPRVB_CTX_MAX_COUNT (7) /* reverb context param count */ +#define NXPRVB_CTX_VALUE_MIN (0) +#define NXPRVB_CTX_VALUE_MAX (192000) + +#define SB_BASE (0x900) +#define SB_CHANGE_BIT (0x900) +#define SB_ROTATION (0x910) +#define SB_MAX_COUNT (1) +#define SB_VALUE_MIN (0) +#define SB_VALUE_MAX (3) + +#define UPSCALER_BASE (0xA00) +#define UPSCALER_CHANGE_BIT (0xA00) +#define UPSCALER_ROTATION (0xA10) +#define UPSCALER_MAX_COUNT (1) +#define UPSCALER_VALUE_MIN (0) +#define UPSCALER_VALUE_MAX (2) + +#define ABOX_EFFECT_MAX_REGISTERS (0xB00) + +#define PARAM_OFFSET (0x10) +#define CHANGE_BIT (1) + +struct abox_effect_data { + struct platform_device *pdev; + struct platform_device *pdev_abox; + void __iomem *base; + struct regmap *regmap; +}; + +/** + * Restore abox effect register map + */ +extern void abox_effect_restore(void); + +#endif /* __SND_SOC_ABOX_EFFECT_H */ diff --git a/sound/soc/samsung/abox/abox_failsafe.c b/sound/soc/samsung/abox/abox_failsafe.c new file mode 100644 index 000000000000..3abebe59cc2b --- /dev/null +++ b/sound/soc/samsung/abox/abox_failsafe.c @@ -0,0 +1,160 @@ +/* sound/soc/samsung/abox/abox_failsafe.c + * + * ALSA SoC Audio Layer - Samsung Abox Failsafe driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include +#include + +#include "abox_util.h" +#include "abox.h" +#include "abox_log.h" + +#define SMART_FAILSAFE +#define WAKE_LOCK_TIMEOUT_MS (4000) + +static int abox_failsafe_reset_count; +static struct device *abox_failsafe_dev; +static atomic_t abox_failsafe_reported; +static bool abox_failsafe_service; + +static int abox_failsafe_start(struct device *dev, struct abox_data *data) +{ + int ret = 0; + + dev_dbg(dev, "%s\n", __func__); + + if (abox_failsafe_service) + pm_runtime_put(dev); + if (!atomic_cmpxchg(&abox_failsafe_reported, 0, 1)) { + dev_info(dev, "%s\n", __func__); + /* prevent sleep during abox recovery */ + pm_wakeup_event(dev, WAKE_LOCK_TIMEOUT_MS); + abox_clear_cpu_gear_requests(dev, data); + } + + return ret; +} + +static int abox_failsafe_end(struct device *dev, struct abox_data *data) +{ + int ret = 0; + + dev_dbg(dev, "%s\n", __func__); + + if (atomic_cmpxchg(&abox_failsafe_reported, 1, 0)) { + dev_info(dev, "%s\n", __func__); + if (abox_test_quirk(data, ABOX_QUIRK_OFF_ON_SUSPEND)) + pm_runtime_get(dev); + pm_relax(dev); + } + + return ret; +} + +static void abox_failsafe_report_work_func(struct work_struct *work) +{ + struct device *dev = abox_failsafe_dev; + char env[32] = {0,}; + char *envp[2] = {env, NULL}; + + dev_dbg(dev, "%s\n", __func__); + pm_runtime_barrier(dev); + snprintf(env, sizeof(env), "COUNT=%d", abox_failsafe_reset_count); + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); +} + +DECLARE_WORK(abox_failsafe_report_work, abox_failsafe_report_work_func); + +#ifdef SMART_FAILSAFE +void abox_failsafe_report(struct device *dev) +{ + dev_dbg(dev, "%s\n", __func__); + + abox_failsafe_dev = dev; + abox_failsafe_reset_count++; + if (abox_failsafe_service) + pm_runtime_get(dev); + schedule_work(&abox_failsafe_report_work); +} +#else +/* TODO: Use SMART_FAILSAFE. + * SMART_FAILSAFE needs support from user space. + */ +void abox_failsafe_report(struct device *dev) +{ + struct abox_data *data = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + abox_failsafe_start(dev, data); +} +#endif +void abox_failsafe_report_reset(struct device *dev) +{ + struct abox_data *data = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + abox_failsafe_end(dev, data); +} + +static int abox_failsafe_reset(struct device *dev, struct abox_data *data) +{ + struct device *dev_abox = &data->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + return abox_failsafe_start(dev_abox, data); +} + +static ssize_t reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + static const char code[] = "CALLIOPE"; + struct abox_data *data = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "%s(%zu)\n", __func__, count); + + if (!strncmp(code, buf, min(sizeof(code) - 1, count))) { + ret = abox_failsafe_reset(dev, data); + if (ret < 0) + return ret; + } + + return count; +} + +static DEVICE_ATTR_WO(reset); +static DEVICE_BOOL_ATTR(service, 0660, abox_failsafe_service); +static DEVICE_INT_ATTR(reset_count, 0660, abox_failsafe_reset_count); + +void abox_failsafe_init(struct device *dev) +{ + int ret; + + ret = device_create_file(dev, &dev_attr_reset); + if (ret < 0) + dev_warn(dev, "%s: %s file creation failed: %d\n", + __func__, "reset", ret); + ret = device_create_file(dev, &dev_attr_service.attr); + if (ret < 0) + dev_warn(dev, "%s: %s file creation failed: %d\n", + __func__, "service", ret); + ret = device_create_file(dev, &dev_attr_reset_count.attr); + if (ret < 0) + dev_warn(dev, "%s: %s file creation failed: %d\n", + __func__, "reset_count", ret); +} diff --git a/sound/soc/samsung/abox/abox_failsafe.h b/sound/soc/samsung/abox/abox_failsafe.h new file mode 100644 index 000000000000..0c05f05f7b03 --- /dev/null +++ b/sound/soc/samsung/abox/abox_failsafe.h @@ -0,0 +1,36 @@ +/* sound/soc/samsung/abox/abox_failsafe.h + * + * ALSA SoC - Samsung Abox Fail-safe driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_FAILSAFE_H +#define __SND_SOC_ABOX_FAILSAFE_H + +#include +#include + +/** + * Report failure to user space + * @param[in] dev pointer to abox device + */ +extern void abox_failsafe_report(struct device *dev); + +/** + * Report reset + * @param[in] dev pointer to abox device + */ +extern void abox_failsafe_report_reset(struct device *dev); + +/** + * Register abox fail-safe + * @param[in] dev pointer to abox device + */ +extern void abox_failsafe_init(struct device *dev); + +#endif /* __SND_SOC_ABOX_FAILSAFE_H */ diff --git a/sound/soc/samsung/abox/abox_gic.c b/sound/soc/samsung/abox/abox_gic.c new file mode 100644 index 000000000000..3205fdc04841 --- /dev/null +++ b/sound/soc/samsung/abox/abox_gic.c @@ -0,0 +1,295 @@ +/* sound/soc/samsung/abox/abox_gic.c + * + * ALSA SoC Audio Layer - Samsung ABOX GIC driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "abox_util.h" +#include "abox_gic.h" + +#define GIC_IS_SECURE_FREE + +void abox_gic_generate_interrupt(struct device *dev, unsigned int irq) +{ +#ifdef GIC_IS_SECURE_FREE + struct abox_gic_data *data = dev_get_drvdata(dev); +#endif + dev_dbg(dev, "%s(%d)\n", __func__, irq); +#ifdef GIC_IS_SECURE_FREE + writel((0x1 << 16) | (irq & 0xF), + data->gicd_base + GIC_DIST_SOFTINT); +#else + dev_dbg(dev, "exynos_smc() is called\n"); + exynos_smc(SMC_CMD_REG, + SMC_REG_ID_SFR_W(0x13EF1000 + GIC_DIST_SOFTINT), + (0x1 << 16) | (hw_irq & 0xF), 0); +#endif +} +EXPORT_SYMBOL(abox_gic_generate_interrupt); + +int abox_gic_register_irq_handler(struct device *dev, unsigned int irq, + irq_handler_t handler, void *dev_id) +{ + struct abox_gic_data *data = dev_get_drvdata(dev); + + dev_info(dev, "%s(%u, %p, %p)\n", __func__, irq, handler, dev_id); + + if (irq >= ARRAY_SIZE(data->handler)) { + dev_err(dev, "invalid irq: %d\n", irq); + return -EINVAL; + } + + data->handler[irq].handler = handler; + data->handler[irq].dev_id = dev_id; + + return 0; +} +EXPORT_SYMBOL(abox_gic_register_irq_handler); + +int abox_gic_unregister_irq_handler(struct device *dev, unsigned int irq) +{ + struct abox_gic_data *data = dev_get_drvdata(dev); + + dev_info(dev, "%s(%u)\n", __func__, irq); + + if (irq >= ARRAY_SIZE(data->handler)) { + dev_err(dev, "invalid irq: %d\n", irq); + return -EINVAL; + } + + data->handler[irq].handler = NULL; + data->handler[irq].dev_id = NULL; + + return 0; +} +EXPORT_SYMBOL(abox_gic_unregister_irq_handler); + +static irqreturn_t __abox_gic_irq_handler(struct abox_gic_data *data, u32 irqnr) +{ + struct abox_gic_irq_handler_t *handler; + + if (irqnr >= ARRAY_SIZE(data->handler)) + return IRQ_NONE; + + handler = &data->handler[irqnr]; + if (!handler->handler) + return IRQ_NONE; + + return handler->handler(irqnr, handler->dev_id); +} + +static irqreturn_t abox_gic_irq_handler(int irq, void *dev_id) +{ + struct device *dev = dev_id; + struct abox_gic_data *data = dev_get_drvdata(dev); + irqreturn_t ret = IRQ_NONE; + u32 irqstat, irqnr; + + dev_dbg(dev, "%s\n", __func__); + + do { + irqstat = readl(data->gicc_base + GIC_CPU_INTACK); + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + dev_dbg(dev, "IAR: %08X\n", irqstat); + + if (likely(irqnr < 16)) { + writel(irqstat, data->gicc_base + GIC_CPU_EOI); + writel(irqstat, data->gicc_base + GIC_CPU_DEACTIVATE); + ret |= __abox_gic_irq_handler(data, irqnr); + continue; + } else if (unlikely(irqnr > 15 && irqnr < 1021)) { + writel(irqstat, data->gicc_base + GIC_CPU_EOI); + ret |= __abox_gic_irq_handler(data, irqnr); + continue; + } + break; + } while (1); + + return ret; +} + +static void abox_gicd_enable(struct device *dev, bool en) +{ + struct abox_gic_data *data = dev_get_drvdata(dev); + void __iomem *gicd_base = data->gicd_base; + + if (en) { + writel(0x1, gicd_base + GIC_DIST_CTRL); + writel(0x0, gicd_base + GIC_DIST_IGROUP + 0x0); + writel(0x0, gicd_base + GIC_DIST_IGROUP + 0x4); + writel(0x0, gicd_base + GIC_DIST_IGROUP + 0x8); + writel(0x0, gicd_base + GIC_DIST_IGROUP + 0xC); + /* Todo: check whether it is really needed + * writel(0xc, gicd_base + GIC_DIST_ENABLE_SET + 0x4); + */ + dev_dbg(dev, "[WRITE]GICD_ISENABLE:en : 0x%x\n", + readl(gicd_base + GIC_DIST_ENABLE_SET + 0x4)); + } else { + writel(0x0, gicd_base + GIC_DIST_CTRL); + /* Todo: check whether it is really needed + * writel(0xc, gicd_base + GIC_DIST_ENABLE_CLEAR + 0x4); + */ + dev_dbg(dev, "[WRITE]GICD_ISENABLE:dis : 0x%x\n", + readl(gicd_base + GIC_DIST_ENABLE_SET + 0x4)); + } +} + +void abox_gic_init_gic(struct device *dev) +{ + struct abox_gic_data *data = dev_get_drvdata(dev); + unsigned long arg; + int i, ret; + + dev_info(dev, "%s\n", __func__); + +#ifdef GIC_IS_SECURE_FREE + writel(0x000000FF, data->gicc_base + GIC_CPU_PRIMASK); + writel(0x3, data->gicd_base + GIC_DIST_CTRL); +#else + arg = SMC_REG_ID_SFR_W(data->gicc_base_phys + GIC_CPU_PRIMASK); + ret = exynos_smc(SMC_CMD_REG, arg, 0x000000FF, 0); + + arg = SMC_REG_ID_SFR_W(data->gicd_base_phys + GIC_DIST_CTRL); + ret = exynos_smc(SMC_CMD_REG, arg, 0x3, 0); +#endif + if (is_secure_gic()) { + for (i = 0; i < 1; i++) { + arg = SMC_REG_ID_SFR_W(data->gicd_base_phys + + GIC_DIST_IGROUP + (i * 4)); + ret = exynos_smc(SMC_CMD_REG, arg, 0xFFFFFFFF, 0); + } + } + for (i = 0; i < 40; i++) { +#ifdef GIC_IS_SECURE_FREE + writel(0x10101010, data->gicd_base + GIC_DIST_PRI + (i * 4)); +#else + arg = SMC_REG_ID_SFR_W(data->gicd_base_phys + + GIC_DIST_PRI + (i * 4)); + ret = exynos_smc(SMC_CMD_REG, arg, 0x10101010, 0); +#endif + } + + writel(0x3, data->gicc_base + GIC_CPU_CTRL); +} +EXPORT_SYMBOL(abox_gic_init_gic); + +int abox_gic_enable_irq(struct device *dev) +{ + struct abox_gic_data *data = dev_get_drvdata(dev); + + if (likely(data->disabled)) { + dev_info(dev, "%s\n", __func__); + + data->disabled = false; + enable_irq(data->irq); + abox_gicd_enable(dev, true); + } + return 0; +} + +int abox_gic_disable_irq(struct device *dev) +{ + struct abox_gic_data *data = dev_get_drvdata(dev); + + if (likely(!data->disabled)) { + dev_info(dev, "%s\n", __func__); + + data->disabled = true; + disable_irq(data->irq); + } + + return 0; +} + +static int samsung_abox_gic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct abox_gic_data *data; + int ret; + + dev_info(dev, "%s\n", __func__); + + data = devm_kzalloc(dev, sizeof(struct abox_gic_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + platform_set_drvdata(pdev, data); + + data->gicd_base = devm_request_and_map_byname(pdev, "gicd", + &data->gicd_base_phys, NULL); + if (IS_ERR(data->gicd_base)) + return PTR_ERR(data->gicd_base); + + data->gicc_base = devm_request_and_map_byname(pdev, "gicc", + &data->gicc_base_phys, NULL); + if (IS_ERR(data->gicc_base)) + return PTR_ERR(data->gicc_base); + + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) { + dev_err(dev, "Failed to get irq\n"); + return data->irq; + } + + ret = devm_request_irq(dev, data->irq, abox_gic_irq_handler, + IRQF_TRIGGER_RISING, pdev->name, dev); + if (ret < 0) { + dev_err(dev, "Failed to request irq\n"); + return ret; + } + +#ifndef CONFIG_PM + abox_gic_resume(dev); +#endif + dev_info(dev, "%s: probe complete\n", __func__); + + return 0; +} + +static int samsung_abox_gic_remove(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s\n", __func__); + + return 0; +} + +static const struct of_device_id samsung_abox_gic_of_match[] = { + { + .compatible = "samsung,abox_gic", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_gic_of_match); + +static struct platform_driver samsung_abox_gic_driver = { + .probe = samsung_abox_gic_probe, + .remove = samsung_abox_gic_remove, + .driver = { + .name = "samsung-abox-gic", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_gic_of_match), + }, +}; + +module_platform_driver(samsung_abox_gic_driver); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box GIC Driver"); +MODULE_ALIAS("platform:samsung-abox-gic"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_gic.h b/sound/soc/samsung/abox/abox_gic.h new file mode 100644 index 000000000000..f68d3ef1e908 --- /dev/null +++ b/sound/soc/samsung/abox/abox_gic.h @@ -0,0 +1,78 @@ +/* sound/soc/samsung/abox/abox_gic.h + * + * ALSA SoC Audio Layer - Samsung Abox GIC driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_GIC_H +#define __SND_SOC_ABOX_GIC_H + +#define ABOX_GIC_IRQ_COUNT 16 + +struct abox_gic_irq_handler_t { + irq_handler_t handler; + void *dev_id; +}; + +struct abox_gic_data { + void __iomem *gicd_base; + void __iomem *gicc_base; + phys_addr_t gicd_base_phys; + phys_addr_t gicc_base_phys; + int irq; + struct abox_gic_irq_handler_t handler[ABOX_GIC_IRQ_COUNT]; + bool disabled; + +}; + +/** + * Generate interrupt + * @param[in] dev pointer to abox_gic device + * @param[in] irq irq number + */ +extern void abox_gic_generate_interrupt(struct device *dev, unsigned int irq); + +/** + * Register interrupt handler + * @param[in] dev pointer to abox_gic device + * @param[in] irq irq number + * @param[in] handler function to be called on interrupt + * @param[in] dev_id cookie for interrupt. + * @return error code or 0 + */ +extern int abox_gic_register_irq_handler(struct device *dev, + unsigned int irq, irq_handler_t handler, void *dev_id); + +/** + * Unregister interrupt handler + * @param[in] dev pointer to abox_gic device + * @param[in] irq irq number + * @return error code or 0 + */ +extern int abox_gic_unregister_irq_handler(struct device *dev, + unsigned int irq); + +/** + * Enable abox gic irq + * @param[in] dev pointer to abox_gic device + */ +extern int abox_gic_enable_irq(struct device *dev); + +/** + * Disable abox gic irq + * @param[in] dev pointer to abox_gic device + */ +extern int abox_gic_disable_irq(struct device *dev); + +/** + * Initialize abox gic + * @param[in] dev pointer to abox_gic device + */ +extern void abox_gic_init_gic(struct device *dev); + +#endif /* __SND_SOC_ABOX_GIC_H */ diff --git a/sound/soc/samsung/abox/abox_if.c b/sound/soc/samsung/abox/abox_if.c new file mode 100644 index 000000000000..1ecb3fe02f59 --- /dev/null +++ b/sound/soc/samsung/abox/abox_if.c @@ -0,0 +1,964 @@ +/* sound/soc/samsung/abox/abox_uaif.c + * + * ALSA SoC Audio Layer - Samsung Abox UAIF driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "abox_util.h" +#include "abox.h" +#include "abox_if.h" + +static int abox_if_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct abox_data *abox_data = data->abox_data; + int ret; + + dev_info(dev, "%s[%c]\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P'); + + abox_request_cpu_gear(dev, abox_data, dai, abox_data->cpu_gear_min); + ret = clk_enable(data->clk_bclk); + if (ret < 0) { + dev_err(dev, "Failed to enable bclk: %d\n", ret); + goto err; + } + ret = clk_enable(data->clk_bclk_gate); + if (ret < 0) { + dev_err(dev, "Failed to enable bclk_gate: %d\n", ret); + goto err; + } +err: + return ret; +} + +static void abox_if_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct abox_data *abox_data = data->abox_data; + + dev_info(dev, "%s[%c]\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P'); + + clk_disable(data->clk_bclk_gate); + clk_disable(data->clk_bclk); + abox_request_cpu_gear(dev, abox_data, dai, ABOX_CPU_GEAR_MIN); +} + +static int abox_if_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct abox_data *abox_data = data->abox_data; + + dev_info(dev, "%s[%c]\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P'); + + abox_register_bclk_usage(dev, abox_data, dai->id, 0, 0, 0); + + return 0; +} + +static int abox_spdy_trigger(struct snd_pcm_substream *substream, + int trigger, struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + int ret = 0; + + dev_info(dev, "%s[%c](%d)\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P', trigger); + + switch (trigger) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = snd_soc_component_update_bits(cmpnt, ABOX_SPDYIF_CTRL, + ABOX_ENABLE_MASK, 1 << ABOX_ENABLE_L); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = snd_soc_component_update_bits(cmpnt, ABOX_SPDYIF_CTRL, + ABOX_ENABLE_MASK, 0 << ABOX_ENABLE_L); + break; + default: + ret = -EINVAL; + } + if (ret < 0) + dev_err(dev, "sfr access failed: %d\n", ret); + + return ret; +} + +static int abox_dsif_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct abox_data *abox_data = data->abox_data; + unsigned int rate; + int ret; + + dev_info(dev, "%s\n", __func__); + + rate = dai->rate; + + ret = abox_register_bclk_usage(dev, abox_data, dai->id, rate, 1, ratio); + if (ret < 0) + dev_err(dev, "Unable to register bclk usage: %d\n", ret); + + ret = clk_set_rate(data->clk_bclk, rate * ratio); + if (ret < 0) { + dev_err(dev, "bclk set error=%d\n", ret); + } else { + dev_info(dev, "rate=%u, bclk=%lu\n", rate, + clk_get_rate(data->clk_bclk)); + } + + return ret; +} + +static int abox_dsif_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + unsigned int ctrl; + int ret = 0; + + dev_info(dev, "%s(0x%08x)\n", __func__, fmt); + + pm_runtime_get_sync(dev); + + snd_soc_component_read(cmpnt, ABOX_DSIF_CTRL, &ctrl); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_PDM: + break; + default: + ret = -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + set_value_by_name(ctrl, ABOX_DSIF_BCLK_POLARITY, 1); + break; + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: + set_value_by_name(ctrl, ABOX_DSIF_BCLK_POLARITY, 0); + break; + default: + ret = -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + ret = -EINVAL; + } + + snd_soc_component_write(cmpnt, ABOX_DSIF_CTRL, ctrl); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int abox_dsif_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + + dev_info(dev, "%s\n", __func__); + + snd_soc_component_update_bits(cmpnt, ABOX_DSIF_CTRL, ABOX_ORDER_MASK, + (tx_slot[0] ? 1 : 0) << ABOX_ORDER_L); + + return 0; +} + +static int abox_dsif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + unsigned int channels, rate, width; + + dev_info(dev, "%s[%c]\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P'); + + channels = params_channels(hw_params); + rate = params_rate(hw_params); + width = params_width(hw_params); + + dev_info(dev, "rate=%u, width=%d, channel=%u, bclk=%lu\n", + rate, + width, + channels, + clk_get_rate(data->clk_bclk)); + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S32: + break; + default: + return -EINVAL; + } + + switch (channels) { + case 2: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int abox_dsif_trigger(struct snd_pcm_substream *substream, + int trigger, struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + + dev_info(dev, "%s[%c](%d)\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P', trigger); + + switch (trigger) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_soc_component_update_bits(cmpnt, ABOX_DSIF_CTRL, + ABOX_ENABLE_MASK, 1 << ABOX_ENABLE_L); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_soc_component_update_bits(cmpnt, ABOX_DSIF_CTRL, + ABOX_ENABLE_MASK, 0 << ABOX_ENABLE_L); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int abox_uaif_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + int id = data->id; + unsigned int ctrl0, ctrl1; + int ret = 0; + + dev_info(dev, "%s(0x%08x)\n", __func__, fmt); + + pm_runtime_get_sync(dev); + + snd_soc_component_read(cmpnt, ABOX_UAIF_CTRL0(id), &ctrl0); + snd_soc_component_read(cmpnt, ABOX_UAIF_CTRL1(id), &ctrl1); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + set_value_by_name(ctrl1, ABOX_WS_MODE, 0); + break; + case SND_SOC_DAIFMT_DSP_A: + set_value_by_name(ctrl1, ABOX_WS_MODE, 1); + break; + default: + ret = -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 1); + set_value_by_name(ctrl1, ABOX_WS_POLAR, 0); + break; + case SND_SOC_DAIFMT_NB_IF: + set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 1); + set_value_by_name(ctrl1, ABOX_WS_POLAR, 1); + break; + case SND_SOC_DAIFMT_IB_NF: + set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 0); + set_value_by_name(ctrl1, ABOX_WS_POLAR, 0); + break; + case SND_SOC_DAIFMT_IB_IF: + set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 0); + set_value_by_name(ctrl1, ABOX_WS_POLAR, 1); + break; + default: + ret = -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + set_value_by_name(ctrl0, ABOX_MODE, 0); + break; + case SND_SOC_DAIFMT_CBS_CFS: + set_value_by_name(ctrl0, ABOX_MODE, 1); + break; + default: + ret = -EINVAL; + } + + snd_soc_component_write(cmpnt, ABOX_UAIF_CTRL0(id), ctrl0); + snd_soc_component_write(cmpnt, ABOX_UAIF_CTRL1(id), ctrl1); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int abox_uaif_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct abox_data *abox_data = data->abox_data; + int id = data->id; + enum qchannel clk; + + dev_info(dev, "%s(%d)\n", __func__, tristate); + + switch (id) { + case 0: + clk = ABOX_BCLK_UAIF0; + break; + case 1: + clk = ABOX_BCLK_UAIF1; + break; + case 2: + clk = ABOX_BCLK_UAIF2; + break; + case 3: + clk = ABOX_BCLK_UAIF3; + break; + default: + return -EINVAL; + } + + return abox_disable_qchannel(dev, abox_data, clk, !tristate); +} + +static int abox_uaif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + int ret; + + dev_dbg(dev, "%s[%c]\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P'); + + ret = abox_if_startup(substream, dai); + if (ret < 0) + goto err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + snd_soc_component_update_bits(cmpnt, ABOX_UAIF_CTRL0(data->id), + ABOX_DATA_MODE_MASK | ABOX_IRQ_MODE_MASK, + (1 << ABOX_DATA_MODE_L) | + (0 << ABOX_IRQ_MODE_L)); +err: + return ret; +} + +static int abox_uaif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct abox_data *abox_data = data->abox_data; + struct snd_soc_component *cmpnt = data->cmpnt; + int id = data->id; + unsigned int ctrl1; + unsigned int channels, rate, width; + int ret; + + dev_info(dev, "%s[%c]\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P'); + + channels = params_channels(hw_params); + rate = params_rate(hw_params); + width = params_width(hw_params); + + ret = abox_register_bclk_usage(dev, abox_data, dai->id, rate, channels, + width); + if (ret < 0) + dev_err(dev, "Unable to register bclk usage: %d\n", ret); + + ret = clk_set_rate(data->clk_bclk, rate * channels * width); + if (ret < 0) { + dev_err(dev, "bclk set error=%d\n", ret); + return ret; + } + + dev_info(dev, "rate=%u, width=%d, channel=%u, bclk=%lu\n", + rate, width, channels, clk_get_rate(data->clk_bclk)); + + ret = snd_soc_component_read(cmpnt, ABOX_UAIF_CTRL1(id), &ctrl1); + if (ret < 0) + dev_err(dev, "sfr access failed: %d\n", ret); + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16: + case SNDRV_PCM_FORMAT_S24: + case SNDRV_PCM_FORMAT_S32: + set_value_by_name(ctrl1, ABOX_SBIT_MAX, (width - 1)); + break; + default: + return -EINVAL; + } + + switch (channels) { + case 2: + set_value_by_name(ctrl1, ABOX_VALID_STR, 0); + set_value_by_name(ctrl1, ABOX_VALID_END, 0); + break; + case 1: + case 4: + case 6: + case 8: + set_value_by_name(ctrl1, ABOX_VALID_STR, (width - 1)); + set_value_by_name(ctrl1, ABOX_VALID_END, (width - 1)); + break; + default: + return -EINVAL; + } + set_value_by_name(ctrl1, ABOX_SLOT_MAX, (channels - 1)); + set_value_by_name(ctrl1, ABOX_FORMAT, abox_get_format(width, channels)); + + ret = snd_soc_component_write(cmpnt, ABOX_UAIF_CTRL1(id), ctrl1); + if (ret < 0) + dev_err(dev, "sfr access failed: %d\n", ret); + + return 0; +} + +static int abox_uaif_trigger(struct snd_pcm_substream *substream, + int trigger, struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + int id = data->id; + unsigned long mask, shift; + int ret = 0; + + dev_info(dev, "%s[%c](%d)\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P', trigger); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + mask = ABOX_MIC_ENABLE_MASK; + shift = ABOX_MIC_ENABLE_L; + } else { + mask = ABOX_SPK_ENABLE_MASK; + shift = ABOX_SPK_ENABLE_L; + } + + switch (trigger) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = snd_soc_component_update_bits(cmpnt, ABOX_UAIF_CTRL0(id), + mask, 1 << shift); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = snd_soc_component_update_bits(cmpnt, + ABOX_UAIF_CTRL0(id), mask, 0 << shift); + } + break; + default: + ret = -EINVAL; + } + if (ret < 0) + dev_err(dev, "sfr access failed: %d\n", ret); + + return ret; +} + +static void abox_uaif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct abox_if_data *data = snd_soc_dai_get_drvdata(dai); + struct snd_soc_component *cmpnt = data->cmpnt; + int id = data->id; + + dev_dbg(dev, "%s[%c]\n", __func__, + (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? + 'C' : 'P'); + + /* dpcm_be_dai_trigger doesn't call trigger stop on paused stream. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_component_update_bits(cmpnt, ABOX_UAIF_CTRL0(id), + ABOX_SPK_ENABLE_MASK, 0 << ABOX_SPK_ENABLE_L); + abox_if_shutdown(substream, dai); +} + +static const struct snd_soc_dai_ops abox_spdy_dai_ops = { + .startup = abox_if_startup, + .shutdown = abox_if_shutdown, + .hw_free = abox_if_hw_free, + .trigger = abox_spdy_trigger, +}; + +static struct snd_soc_dai_driver abox_spdy_dai_drv = { + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 40000, + .rate_max = 40000, + .formats = SNDRV_PCM_FMTBIT_S16, + }, + .ops = &abox_spdy_dai_ops, +}; + +static const struct snd_soc_dai_ops abox_dsif_dai_ops = { + .set_bclk_ratio = abox_dsif_set_bclk_ratio, + .set_fmt = abox_dsif_set_fmt, + .set_channel_map = abox_dsif_set_channel_map, + .startup = abox_if_startup, + .shutdown = abox_if_shutdown, + .hw_params = abox_dsif_hw_params, + .hw_free = abox_if_hw_free, + .trigger = abox_dsif_trigger, +}; + +static struct snd_soc_dai_driver abox_dsif_dai_drv = { + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = SNDRV_PCM_FMTBIT_S32, + }, + .ops = &abox_dsif_dai_ops, +}; + +static const struct snd_soc_dai_ops abox_uaif_dai_ops = { + .set_fmt = abox_uaif_set_fmt, + .set_tristate = abox_uaif_set_tristate, + .startup = abox_uaif_startup, + .shutdown = abox_uaif_shutdown, + .hw_params = abox_uaif_hw_params, + .hw_free = abox_if_hw_free, + .trigger = abox_uaif_trigger, +}; + +static struct snd_soc_dai_driver abox_uaif_dai_drv = { + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + .rates = ABOX_SAMPLING_RATES, + .rate_min = 8000, + .rate_max = 384000, + .formats = ABOX_SAMPLE_FORMATS, + }, + .ops = &abox_uaif_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, +}; + +static int abox_if_config_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_if_data *data = snd_soc_component_get_drvdata(cmpnt); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = data->config[reg]; + + dev_dbg(dev, "%s(0x%08x): %u\n", __func__, reg, val); + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int abox_if_config_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct device *dev = cmpnt->dev; + struct abox_if_data *data = snd_soc_component_get_drvdata(cmpnt); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int val = ucontrol->value.integer.value[0]; + + dev_info(dev, "%s(0x%08x, %u)\n", __func__, reg, val); + + data->config[reg] = val; + + return 0; +} + +static const struct snd_kcontrol_new abox_if_controls[] = { + SOC_SINGLE_EXT("%s width", ABOX_IF_WIDTH, 0, 32, 0, + abox_if_config_get, abox_if_config_put), + SOC_SINGLE_EXT("%s channel", ABOX_IF_CHANNEL, 0, 8, 0, + abox_if_config_get, abox_if_config_put), + SOC_SINGLE_EXT("%s rate", ABOX_IF_RATE, 0, 384000, 0, + abox_if_config_get, abox_if_config_put), +}; + +static int abox_if_cmpnt_probe(struct snd_soc_component *cmpnt) +{ + struct device *dev = cmpnt->dev; + struct abox_if_data *data = snd_soc_component_get_drvdata(cmpnt); + struct snd_kcontrol_new (*controls)[3]; + struct snd_kcontrol_new *control; + int i; + + dev_info(dev, "%s\n", __func__); + + controls = devm_kmemdup(dev, abox_if_controls, + sizeof(abox_if_controls), GFP_KERNEL); + if (!controls) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(*controls); i++) { + control = &(*controls)[i]; + control->name = devm_kasprintf(dev, GFP_KERNEL, control->name, + data->of_data->get_dai_name(data->id)); + } + snd_soc_add_component_controls(cmpnt, *controls, ARRAY_SIZE(*controls)); + + data->cmpnt = cmpnt; + snd_soc_component_init_regmap(cmpnt, data->abox_data->regmap); + abox_register_if(data->abox_data->pdev, to_platform_device(dev), + data->id, snd_soc_component_get_dapm(cmpnt), + data->of_data->get_dai_name(data->id), + !!data->dai_drv->playback.formats, + !!data->dai_drv->capture.formats); + + return 0; +} + +static void abox_if_cmpnt_remove(struct snd_soc_component *cmpnt) +{ + struct device *dev = cmpnt->dev; + + dev_info(dev, "%s\n", __func__); +} + +static const struct snd_soc_component_driver abox_if_cmpnt = { + .probe = abox_if_cmpnt_probe, + .remove = abox_if_cmpnt_remove, +}; + +enum abox_dai abox_spdy_get_dai_id(int id) +{ + return ABOX_SPDY; +} + +const char *abox_spdy_get_dai_name(int id) +{ + return "SPDY"; +} + +const char *abox_spdy_get_str_name(int id, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + "SPDY Playback" : "SPDY Capture"; +} + +enum abox_dai abox_dsif_get_dai_id(int id) +{ + return ABOX_DSIF; +} + +const char *abox_dsif_get_dai_name(int id) +{ + return "DSIF"; +} + +const char *abox_dsif_get_str_name(int id, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + "DSIF Playback" : "DSIF Capture"; +} + +enum abox_dai abox_uaif_get_dai_id(int id) +{ + return ABOX_UAIF0 + id; +} + +const char *abox_uaif_get_dai_name(int id) +{ + static const char * const names[] = { + "UAIF0", "UAIF1", "UAIF2", "UAIF3", "UAIF4", + "UAIF5", "UAIF6", "UAIF7", "UAIF8", "UAIF9", + }; + + return (id < ARRAY_SIZE(names)) ? names[id] : ERR_PTR(-EINVAL); +} + +const char *abox_uaif_get_str_name(int id, int stream) +{ + static const char * const names_pla[] = { + "UAIF0 Playback", "UAIF1 Playback", "UAIF2 Playback", + "UAIF3 Playback", "UAIF4 Playback", "UAIF5 Playback", + "UAIF6 Playback", "UAIF7 Playback", "UAIF8 Playback", + "UAIF9 Playback", + }; + static const char * const names_cap[] = { + "UAIF0 Capture", "UAIF1 Capture", "UAIF2 Capture", + "UAIF3 Capture", "UAIF4 Capture", "UAIF5 Capture", + "UAIF6 Capture", "UAIF7 Capture", "UAIF8 Capture", + "UAIF9 Capture", + }; + const char *ret; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK && id < ARRAY_SIZE(names_pla)) + ret = names_pla[id]; + else if (stream == SNDRV_PCM_STREAM_CAPTURE && + id < ARRAY_SIZE(names_cap)) + ret = names_cap[id]; + else + ret = ERR_PTR(-EINVAL); + + return ret; +} + +static const struct of_device_id samsung_abox_if_match[] = { + { + .compatible = "samsung,abox-uaif", + .data = (void *)&(struct abox_if_of_data){ + .get_dai_id = abox_uaif_get_dai_id, + .get_dai_name = abox_uaif_get_dai_name, + .get_str_name = abox_uaif_get_str_name, + .base_dai_drv = &abox_uaif_dai_drv, + }, + }, + { + .compatible = "samsung,abox-dsif", + .data = (void *)&(struct abox_if_of_data){ + .get_dai_id = abox_dsif_get_dai_id, + .get_dai_name = abox_dsif_get_dai_name, + .get_str_name = abox_dsif_get_str_name, + .base_dai_drv = &abox_dsif_dai_drv, + }, + }, + { + .compatible = "samsung,abox-spdy", + .data = (void *)&(struct abox_if_of_data){ + .get_dai_id = abox_spdy_get_dai_id, + .get_dai_name = abox_spdy_get_dai_name, + .get_str_name = abox_spdy_get_str_name, + .base_dai_drv = &abox_spdy_dai_drv, + }, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_if_match); + +static int samsung_abox_if_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *dev_abox = dev->parent; + struct device_node *np = dev->of_node; + struct abox_if_data *data; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + platform_set_drvdata(pdev, data); + + data->sfr_base = devm_not_request_and_map(pdev, "sfr", 0, NULL, NULL); + if (IS_ERR(data->sfr_base)) + return PTR_ERR(data->sfr_base); + + ret = of_property_read_u32_index(np, "id", 0, &data->id); + if (ret < 0) { + dev_err(dev, "id property reading fail\n"); + return ret; + } + + data->clk_bclk = devm_clk_get_and_prepare(pdev, "bclk"); + if (IS_ERR(data->clk_bclk)) + return PTR_ERR(data->clk_bclk); + + data->clk_bclk_gate = devm_clk_get_and_prepare(pdev, "bclk_gate"); + if (IS_ERR(data->clk_bclk_gate)) + return PTR_ERR(data->clk_bclk_gate); + + data->of_data = of_match_node(samsung_abox_if_match, + pdev->dev.of_node)->data; + data->abox_data = dev_get_drvdata(dev_abox); + data->dai_drv = devm_kzalloc(dev, sizeof(struct snd_soc_dai_driver), + GFP_KERNEL); + if (!data->dai_drv) + return -ENOMEM; + memcpy(data->dai_drv, data->of_data->base_dai_drv, + sizeof(struct snd_soc_dai_driver)); + data->dai_drv->id = data->of_data->get_dai_id(data->id); + data->dai_drv->name = data->of_data->get_dai_name(data->id); + if (data->dai_drv->capture.formats) + data->dai_drv->capture.stream_name = + data->of_data->get_str_name(data->id, + SNDRV_PCM_STREAM_CAPTURE); + if (data->dai_drv->playback.formats) + data->dai_drv->playback.stream_name = + data->of_data->get_str_name(data->id, + SNDRV_PCM_STREAM_PLAYBACK); + + ret = devm_snd_soc_register_component(dev, &abox_if_cmpnt, + data->dai_drv, 1); + if (ret < 0) + return ret; + + pm_runtime_enable(dev); + pm_runtime_no_callbacks(dev); + + return ret; +} + +static int samsung_abox_if_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + return 0; +} + +static struct platform_driver samsung_abox_if_driver = { + .probe = samsung_abox_if_probe, + .remove = samsung_abox_if_remove, + .driver = { + .name = "samsung-abox-if", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_if_match), + }, +}; + +module_platform_driver(samsung_abox_if_driver); + +int abox_if_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params, int stream) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct device *dev = dai->dev; + struct abox_if_data *data = dev_get_drvdata(dev); + unsigned int rate, channels, width; + int ret = 0; + + if (dev->driver != &samsung_abox_if_driver.driver) + return -EINVAL; + + dev_dbg(dev, "%s[%s](%d)\n", __func__, dai->name, stream); + + rate = data->config[ABOX_IF_RATE]; + channels = data->config[ABOX_IF_CHANNEL]; + width = data->config[ABOX_IF_WIDTH]; + + if (rate) + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = rate; + + if (channels) + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = + channels; + + if (width) { + unsigned int format = 0; + + switch (width) { + case 8: + format = SNDRV_PCM_FORMAT_S8; + break; + case 16: + format = SNDRV_PCM_FORMAT_S16; + break; + case 24: + format = SNDRV_PCM_FORMAT_S24; + break; + case 32: + format = SNDRV_PCM_FORMAT_S32; + break; + default: + width = format = 0; + break; + } + + if (format) { + struct snd_mask *mask; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, format); + } + } + + if (rate || channels || width) + dev_info(dev, "%s: %s: %d bit, %u channel, %uHz\n", + __func__, dai->name, width, channels, rate); + else + ret = -EINVAL; + + return ret; +} + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box UAIF/DSIF Driver"); +MODULE_ALIAS("platform:samsung-abox-if"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_if.h b/sound/soc/samsung/abox/abox_if.h new file mode 100644 index 000000000000..4f1205d1836c --- /dev/null +++ b/sound/soc/samsung/abox/abox_if.h @@ -0,0 +1,53 @@ +/* sound/soc/samsung/abox/abox_if.h + * + * ALSA SoC - Samsung Abox UAIF/DSIF driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_IF_H +#define __SND_SOC_ABOX_IF_H + +#include "abox.h" + +enum abox_if_config { + ABOX_IF_WIDTH, + ABOX_IF_CHANNEL, + ABOX_IF_RATE, + ABOX_IF_FMT_COUNT, +}; + +struct abox_if_of_data { + enum abox_dai (*get_dai_id)(int id); + const char *(*get_dai_name)(int id); + const char *(*get_str_name)(int id, int stream); + struct snd_soc_dai_driver *base_dai_drv; +}; + +struct abox_if_data { + int id; + void __iomem *sfr_base; + struct clk *clk_bclk; + struct clk *clk_bclk_gate; + struct snd_soc_component *cmpnt; + struct snd_soc_dai_driver *dai_drv; + struct abox_data *abox_data; + const struct abox_if_of_data *of_data; + unsigned int config[ABOX_IF_FMT_COUNT]; +}; + +/** + * UAIF/DSIF hw params fixup helper + * @param[in] rtd snd_soc_pcm_runtime + * @param[out] params snd_pcm_hw_params + * @param[in] stream SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE + * @return error code if any + */ +extern int abox_if_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params, int stream); + +#endif /* __SND_SOC_ABOX_IF_H */ diff --git a/sound/soc/samsung/abox/abox_log.c b/sound/soc/samsung/abox/abox_log.c new file mode 100644 index 000000000000..ae6942bf4eda --- /dev/null +++ b/sound/soc/samsung/abox/abox_log.c @@ -0,0 +1,426 @@ +/* sound/soc/samsung/abox/abox_log.c + * + * ALSA SoC Audio Layer - Samsung Abox Log driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include + +#include "abox_util.h" +#include "abox.h" +#include "abox_dbg.h" +#include "abox_log.h" + +#undef VERBOSE_LOG + +#undef TEST +#ifdef TEST +#define SIZE_OF_BUFFER (SZ_128) +#else +#define SIZE_OF_BUFFER (SZ_2M) +#endif + +#define S_IRWUG (0660) + +struct abox_log_kernel_buffer { + char *buffer; + unsigned int index; + bool wrap; + bool updated; + wait_queue_head_t wq; +}; + +struct abox_log_buffer_info { + struct list_head list; + struct device *dev; + int id; + bool file_created; + ssize_t file_index; + struct mutex lock; + struct ABOX_LOG_BUFFER *log_buffer; + struct abox_log_kernel_buffer kernel_buffer; +}; + +static LIST_HEAD(abox_log_list_head); +static u32 abox_log_auto_save; + +static void abox_log_memcpy(struct device *dev, + struct abox_log_kernel_buffer *kernel_buffer, + const char *src, size_t size) +{ + size_t left_size = SIZE_OF_BUFFER - kernel_buffer->index; + + dev_dbg(dev, "%s(%zu)\n", __func__, size); + + if (left_size < size) { +#ifdef VERBOSE_LOG + dev_dbg(dev, "0: %s\n", src); +#endif + memcpy(kernel_buffer->buffer + kernel_buffer->index, src, + left_size); + src += left_size; + size -= left_size; + kernel_buffer->index = 0; + kernel_buffer->wrap = true; + } +#ifdef VERBOSE_LOG + dev_dbg(dev, "1: %s\n", src); +#endif + memcpy(kernel_buffer->buffer + kernel_buffer->index, src, size); + kernel_buffer->index += (unsigned int)size; +} + +static void abox_log_file_name(struct device *dev, + struct abox_log_buffer_info *info, char *name, size_t size) +{ + snprintf(name, size, "/data/calliope-%02d.log", info->id); +} + +static void abox_log_file_save(struct device *dev, + struct abox_log_buffer_info *info) +{ + struct ABOX_LOG_BUFFER *log_buffer = info->log_buffer; + unsigned int index_writer = log_buffer->index_writer; + char name[32]; + struct file *filp; + mm_segment_t old_fs; + + dev_dbg(dev, "%s(%d)\n", __func__, info->id); + + abox_log_file_name(dev, info, name, sizeof(name)); + + old_fs = get_fs(); + set_fs(KERNEL_DS); + if (likely(info->file_created)) { + filp = filp_open(name, O_RDWR | O_APPEND | O_CREAT, S_IRWUG); + dev_dbg(dev, "appended\n"); + } else { + filp = filp_open(name, O_RDWR | O_TRUNC | O_CREAT, S_IRWUG); + info->file_created = true; + dev_dbg(dev, "created\n"); + } + if (IS_ERR(filp)) { + dev_warn(dev, "%s: saving log fail\n", __func__); + goto out; + } + + + if (log_buffer->index_reader > index_writer) { + vfs_write(filp, log_buffer->buffer + log_buffer->index_reader, + log_buffer->size - log_buffer->index_reader, + &filp->f_pos); + vfs_write(filp, log_buffer->buffer, index_writer, &filp->f_pos); + } else { + vfs_write(filp, log_buffer->buffer + log_buffer->index_reader, + index_writer - log_buffer->index_reader, &filp->f_pos); + } + + vfs_fsync(filp, 0); + filp_close(filp, NULL); +out: + set_fs(old_fs); + +} + +static void abox_log_flush(struct device *dev, + struct abox_log_buffer_info *info) +{ + struct ABOX_LOG_BUFFER *log_buffer = info->log_buffer; + unsigned int index_writer = log_buffer->index_writer; + struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer; + + if (log_buffer->index_reader == index_writer) + return; + + dev_dbg(dev, "%s(%d): index_writer=%u, index_reader=%u, size=%u\n", + __func__, info->id, index_writer, + log_buffer->index_reader, log_buffer->size); + + mutex_lock(&info->lock); + + if (abox_log_auto_save) + abox_log_file_save(dev, info); + + if (log_buffer->index_reader > index_writer) { + abox_log_memcpy(info->dev, kernel_buffer, + log_buffer->buffer + log_buffer->index_reader, + log_buffer->size - log_buffer->index_reader); + log_buffer->index_reader = 0; + } + abox_log_memcpy(info->dev, kernel_buffer, + log_buffer->buffer + log_buffer->index_reader, + index_writer - log_buffer->index_reader); + log_buffer->index_reader = index_writer; + mutex_unlock(&info->lock); + + kernel_buffer->updated = true; + wake_up_interruptible(&kernel_buffer->wq); + +#ifdef TEST + dev_dbg(dev, "shared_buffer: %s\n", log_buffer->buffer); + dev_dbg(dev, "kernel_buffer: %s\n", info->kernel_buffer.buffer); +#endif +} + +void abox_log_flush_all(struct device *dev) +{ + struct abox_log_buffer_info *info; + + dev_dbg(dev, "%s\n", __func__); + + list_for_each_entry(info, &abox_log_list_head, list) { + abox_log_flush(info->dev, info); + } +} +EXPORT_SYMBOL(abox_log_flush_all); + +static unsigned long abox_log_flush_all_work_rearm_self; +static void abox_log_flush_all_work_func(struct work_struct *work); +static DECLARE_DEFERRABLE_WORK(abox_log_flush_all_work, + abox_log_flush_all_work_func); + +static void abox_log_flush_all_work_func(struct work_struct *work) +{ + abox_log_flush_all(NULL); + schedule_delayed_work(&abox_log_flush_all_work, msecs_to_jiffies(3000)); + set_bit(0, &abox_log_flush_all_work_rearm_self); +} + +void abox_log_schedule_flush_all(struct device *dev) +{ + if (test_and_clear_bit(0, &abox_log_flush_all_work_rearm_self)) + cancel_delayed_work(&abox_log_flush_all_work); + schedule_delayed_work(&abox_log_flush_all_work, msecs_to_jiffies(100)); +} +EXPORT_SYMBOL(abox_log_schedule_flush_all); + +void abox_log_drain_all(struct device *dev) +{ + cancel_delayed_work(&abox_log_flush_all_work); + abox_log_flush_all(dev); +} +EXPORT_SYMBOL(abox_log_drain_all); + +static int abox_log_file_open(struct inode *inode, struct file *file) +{ + struct abox_log_buffer_info *info = inode->i_private; + + dev_dbg(info->dev, "%s\n", __func__); + + info->file_index = -1; + file->private_data = info; + + return 0; +} + +static ssize_t abox_log_file_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct abox_log_buffer_info *info = file->private_data; + struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer; + unsigned int index; + size_t end, size; + bool first = (info->file_index < 0); + int ret; + + dev_dbg(info->dev, "%s(%zu, %lld)\n", __func__, count, *ppos); + + mutex_lock(&info->lock); + + if (first) { + info->file_index = likely(kernel_buffer->wrap) ? + kernel_buffer->index : 0; + } + + do { + index = kernel_buffer->index; + end = ((info->file_index < index) || + ((info->file_index == index) && !first)) ? + index : SIZE_OF_BUFFER; + size = min(end - info->file_index, count); + if (size == 0) { + mutex_unlock(&info->lock); + if (file->f_flags & O_NONBLOCK) { + dev_dbg(info->dev, "non block\n"); + return -EAGAIN; + } + kernel_buffer->updated = false; + + ret = wait_event_interruptible(kernel_buffer->wq, + kernel_buffer->updated); + if (ret != 0) { + dev_dbg(info->dev, "interrupted\n"); + return ret; + } + mutex_lock(&info->lock); + } +#ifdef VERBOSE_LOG + dev_dbg(info->dev, "loop %zu, %zu, %zd, %zu\n", size, end, + info->file_index, count); +#endif + } while (size == 0); + + dev_dbg(info->dev, "start=%zd, end=%zd size=%zd\n", info->file_index, + end, size); + if (copy_to_user(buf, kernel_buffer->buffer + info->file_index, + size)) { + mutex_unlock(&info->lock); + return -EFAULT; + } + + info->file_index += size; + if (info->file_index >= SIZE_OF_BUFFER) + info->file_index = 0; + + mutex_unlock(&info->lock); + + dev_dbg(info->dev, "%s: size = %zd\n", __func__, size); + + return size; +} + +static unsigned int abox_log_file_poll(struct file *file, poll_table *wait) +{ + struct abox_log_buffer_info *info = file->private_data; + struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer; + + dev_dbg(info->dev, "%s\n", __func__); + + poll_wait(file, &kernel_buffer->wq, wait); + return POLLIN | POLLRDNORM; +} + +static const struct file_operations abox_log_fops = { + .open = abox_log_file_open, + .read = abox_log_file_read, + .poll = abox_log_file_poll, + .llseek = generic_file_llseek, + .owner = THIS_MODULE, +}; + +static struct abox_log_buffer_info abox_log_buffer_info_new; + +void abox_log_register_buffer_work_func(struct work_struct *work) +{ + struct device *dev; + int id; + struct ABOX_LOG_BUFFER *buffer; + struct abox_log_buffer_info *info; + char name[16]; + + dev = abox_log_buffer_info_new.dev; + id = abox_log_buffer_info_new.id; + buffer = abox_log_buffer_info_new.log_buffer; + abox_log_buffer_info_new.dev = NULL; + abox_log_buffer_info_new.id = 0; + abox_log_buffer_info_new.log_buffer = NULL; + + dev_info(dev, "%s(%p, %d, %p)\n", __func__, dev, id, buffer); + + info = vmalloc(sizeof(*info)); + mutex_init(&info->lock); + info->id = id; + info->file_created = false; + info->kernel_buffer.buffer = vzalloc(SIZE_OF_BUFFER); + info->kernel_buffer.index = 0; + info->kernel_buffer.wrap = false; + init_waitqueue_head(&info->kernel_buffer.wq); + info->dev = dev; + info->log_buffer = buffer; + list_add_tail(&info->list, &abox_log_list_head); + + snprintf(name, sizeof(name), "log-%02d", id); + debugfs_create_file(name, 0664, abox_dbg_get_root_dir(), info, + &abox_log_fops); +} + +static DECLARE_WORK(abox_log_register_buffer_work, + abox_log_register_buffer_work_func); + +int abox_log_register_buffer(struct device *dev, int id, + struct ABOX_LOG_BUFFER *buffer) +{ + struct abox_log_buffer_info *info; + + dev_dbg(dev, "%s(%d, %p)\n", __func__, id, buffer); + + if (abox_log_buffer_info_new.dev != NULL || + abox_log_buffer_info_new.id > 0 || + abox_log_buffer_info_new.log_buffer != NULL) { + return -EBUSY; + } + + list_for_each_entry(info, &abox_log_list_head, list) { + if (info->id == id) { + dev_dbg(dev, "already registered log: %d\n", id); + return 0; + } + } + + abox_log_buffer_info_new.dev = dev; + abox_log_buffer_info_new.id = id; + abox_log_buffer_info_new.log_buffer = buffer; + schedule_work(&abox_log_register_buffer_work); + + return 0; +} +EXPORT_SYMBOL(abox_log_register_buffer); + +#ifdef TEST +static struct ABOX_LOG_BUFFER *abox_log_test_buffer; +static void abox_log_test_work_func(struct work_struct *work); +DECLARE_DELAYED_WORK(abox_log_test_work, abox_log_test_work_func); +static void abox_log_test_work_func(struct work_struct *work) +{ + struct ABOX_LOG_BUFFER *log = abox_log_test_buffer; + static unsigned int i; + char buffer[32]; + char *buffer_index = buffer; + int size, left; + + pr_debug("%s: %d\n", __func__, i); + + size = snprintf(buffer, sizeof(buffer), "%d ", i++); + + if (log->index_writer + size > log->size) { + left = log->size - log->index_writer; + memcpy(&log->buffer[log->index_writer], buffer_index, left); + log->index_writer = 0; + buffer_index += left; + } + + left = size - (buffer_index - buffer); + memcpy(&log->buffer[log->index_writer], buffer_index, left); + log->index_writer += left; + + abox_log_flush_all(NULL); + + schedule_delayed_work(&abox_log_test_work, msecs_to_jiffies(1000)); +} +#endif + +static int __init samsung_abox_log_late_initcall(void) +{ + pr_info("%s\n", __func__); + + debugfs_create_u32("log_auto_save", S_IRWUG, abox_dbg_get_root_dir(), + &abox_log_auto_save); + +#ifdef TEST + abox_log_test_buffer = vzalloc(SZ_128); + abox_log_test_buffer->size = SZ_64; + abox_log_register_buffer(NULL, 0, abox_log_test_buffer); + schedule_delayed_work(&abox_log_test_work, msecs_to_jiffies(1000)); +#endif + + return 0; +} +late_initcall(samsung_abox_log_late_initcall); diff --git a/sound/soc/samsung/abox/abox_log.h b/sound/soc/samsung/abox/abox_log.h new file mode 100644 index 000000000000..3d8e78952cbc --- /dev/null +++ b/sound/soc/samsung/abox/abox_log.h @@ -0,0 +1,53 @@ +/* sound/soc/samsung/abox/abox_log.h + * + * ALSA SoC - Samsung Abox Log driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_LOG_H +#define __SND_SOC_ABOX_LOG_H + +#include +#include + +/** + * Flush log from all shared memories to kernel memory + * @param[in] dev pointer to abox device + */ +extern void abox_log_flush_all(struct device *dev); + +/** + * Schedule log flush from all shared memories to kernel memory + * @param[in] dev pointer to abox device + */ +extern void abox_log_schedule_flush_all(struct device *dev); + +/** + * drain log and stop scheduling log flush + * @param[in] dev pointer to abox device + */ +extern void abox_log_drain_all(struct device *dev); + +/** + * Flush log from specific shared memory to kernel memory + * @param[in] dev pointer to abox device + * @param[in] id unique buffer id + */ +extern void abox_log_flush_by_id(struct device *dev, int id); + +/** + * Register abox log buffer + * @param[in] dev pointer to abox device + * @param[in] id unique buffer id + * @param[in] buffer pointer to shared buffer + * @return error code if any + */ +extern int abox_log_register_buffer(struct device *dev, int id, + struct ABOX_LOG_BUFFER *buffer); + +#endif /* __SND_SOC_ABOX_LOG_H */ diff --git a/sound/soc/samsung/abox/abox_rdma.c b/sound/soc/samsung/abox/abox_rdma.c new file mode 100644 index 000000000000..049b5d5c858c --- /dev/null +++ b/sound/soc/samsung/abox/abox_rdma.c @@ -0,0 +1,1979 @@ +/* sound/soc/samsung/abox/abox_rdma.c + * + * ALSA SoC Audio Layer - Samsung Abox RDMA driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../../../../drivers/iommu/exynos-iommu.h" +#include +#include "abox_util.h" +#include "abox_gic.h" +#include "abox_dbg.h" +#ifdef CONFIG_SCSC_BT +#include "abox_bt.h" +#endif +#include "abox.h" + +#define COMPR_USE_COPY +#define COMPR_USE_FIXED_MEMORY +#define USE_FIXED_MEMORY + +/* Mailbox between driver and firmware for offload */ +#define COMPR_CMD_CODE (0x0004) +#define COMPR_HANDLE_ID (0x0008) +#define COMPR_IP_TYPE (0x000C) +#define COMPR_SIZE_OF_FRAGMENT (0x0010) +#define COMPR_PHY_ADDR_INBUF (0x0014) +#define COMPR_SIZE_OF_INBUF (0x0018) +#define COMPR_LEFT_VOL (0x001C) +#define COMPR_RIGHT_VOL (0x0020) +#define EFFECT_EXT_ON (0x0024) +#define COMPR_ALPA_NOTI (0x0028) +#define COMPR_PARAM_RATE (0x0034) +#define COMPR_PARAM_SAMPLE (0x0038) +#define COMPR_PARAM_CH (0x003C) +#define COMPR_RENDERED_PCM_SIZE (0x004C) +#define COMPR_RETURN_CMD (0x0040) +#define COMPR_IP_ID (0x0044) +#define COMPR_SIZE_OUT_DATA (0x0048) +#define COMPR_UPSCALE (0x0050) +#define COMPR_CPU_LOCK_LV (0x0054) +#define COMPR_CHECK_CMD (0x0058) +#define COMPR_CHECK_RUNNING (0x005C) +#define COMPR_ACK (0x0060) +#define COMPR_INTR_ACK (0x0064) +#define COMPR_INTR_DMA_ACK (0x0068) +#define COMPR_MAX COMPR_INTR_DMA_ACK + +/* COMPR_UPSCALE */ +#define COMPR_BIT_SHIFT (0) +#define COMPR_BIT_MASK (0xFF) +#define COMPR_CH_SHIFT (8) +#define COMPR_CH_MASK (0xF) +#define COMPR_RATE_SHIFT (12) +#define COMPR_RATE_MASK (0xFFFFF) + +/* Interrupt type */ +#define INTR_WAKEUP (0x0) +#define INTR_READY (0x1000) +#define INTR_DMA (0x2000) +#define INTR_CREATED (0x3000) +#define INTR_DECODED (0x4000) +#define INTR_RENDERED (0x5000) +#define INTR_FLUSH (0x6000) +#define INTR_PAUSED (0x6001) +#define INTR_EOS (0x7000) +#define INTR_DESTROY (0x8000) +#define INTR_FX_EXT (0x9000) +#define INTR_EFF_REQUEST (0xA000) +#define INTR_SET_CPU_LOCK (0xC000) +#define INTR_FW_LOG (0xFFFF) + +#define COMPRESSED_LR_VOL_MAX_STEPS 0x2000 + +enum SEIREN_CMDTYPE { + /* OFFLOAD */ + CMD_COMPR_CREATE = 0x50, + CMD_COMPR_DESTROY, + CMD_COMPR_SET_PARAM, + CMD_COMPR_WRITE, + CMD_COMPR_READ, + CMD_COMPR_START, + CMD_COMPR_STOP, + CMD_COMPR_PAUSE, + CMD_COMPR_EOS, + CMD_COMPR_GET_VOLUME, + CMD_COMPR_SET_VOLUME, + CMD_COMPR_CA5_WAKEUP, + CMD_COMPR_HPDET_NOTIFY, +}; + +enum OFFLOAD_IPTYPE { + COMPR_MP3 = 0x0, + COMPR_AAC = 0x1, + COMPR_FLAC = 0x2, +}; + +static const struct snd_compr_caps abox_rdma_compr_caps = { + .direction = SND_COMPRESS_PLAYBACK, + .min_fragment_size = SZ_4K, + .max_fragment_size = SZ_32K, + .min_fragments = 1, + .max_fragments = 5, + .num_codecs = 3, + .codecs = { + SND_AUDIOCODEC_MP3, + SND_AUDIOCODEC_AAC, + SND_AUDIOCODEC_FLAC + }, +}; + +static void abox_rdma_mailbox_write(struct device *dev, u32 index, u32 value) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + int ret; + + dev_dbg(dev, "%s(0x%x, 0x%x)\n", __func__, index, value); + + if (!regmap) { + dev_err(dev, "%s: regmap is null\n", __func__); + return; + } + + pm_runtime_get(dev); + ret = regmap_write(regmap, index, value); + if (ret < 0) + dev_warn(dev, "%s(%u) failed: %d\n", __func__, index, ret); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} + +static u32 abox_rdma_mailbox_read(struct device *dev, u32 index) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + int ret; + u32 val = 0; + + dev_dbg(dev, "%s(0x%x)\n", __func__, index); + + if (!regmap) { + dev_err(dev, "%s: regmap is null\n", __func__); + return 0; + } + + pm_runtime_get(dev); + ret = regmap_read(regmap, index, &val); + if (ret < 0) + dev_warn(dev, "%s(%u) failed: %d\n", __func__, index, ret); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return val; +} + +static void abox_mailbox_save(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + if (regmap) { + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + } +} + +static void abox_mailbox_restore(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + if (regmap) { + regcache_cache_only(regmap, false); + regcache_sync(regmap); + } +} + +static bool abox_mailbox_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case COMPR_ACK: + case COMPR_INTR_ACK: + case COMPR_INTR_DMA_ACK: + case COMPR_RETURN_CMD: + case COMPR_SIZE_OUT_DATA: + case COMPR_IP_ID: + case COMPR_RENDERED_PCM_SIZE: + return true; + default: + return false; + } +} + +static bool abox_mailbox_rw_reg(struct device *dev, unsigned int reg) +{ + return true; +} + +static const struct regmap_config abox_mailbox_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = COMPR_MAX, + .volatile_reg = abox_mailbox_volatile_reg, + .readable_reg = abox_mailbox_rw_reg, + .writeable_reg = abox_mailbox_rw_reg, + .cache_type = REGCACHE_FLAT, + .fast_io = true, +}; + +static int abox_rdma_request_ipc(struct abox_platform_data *data, + ABOX_IPC_MSG *msg, int atomic, int sync) +{ + struct device *dev_abox = &data->pdev_abox->dev; + + return abox_request_ipc(dev_abox, msg->ipcid, msg, sizeof(*msg), + atomic, sync); +} + +static int abox_rdma_mailbox_send_cmd(struct device *dev, unsigned int cmd) +{ + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct device *dev_abox = &platform_data->pdev_abox->dev; + struct abox_compr_data *data = &platform_data->compr_data; + ABOX_IPC_MSG ipc; + int ret, n, ack; + + dev_dbg(dev, "%s(%x)\n", __func__, cmd); + + switch (cmd) { + case CMD_COMPR_CREATE: + dev_dbg(dev, "%s: CMD_COMPR_CREATE %d\n", __func__, cmd); + break; + case CMD_COMPR_DESTROY: + dev_dbg(dev, "%s: CMD_COMPR_DESTROY %d\n", __func__, cmd); + break; + case CMD_COMPR_SET_PARAM: + dev_dbg(dev, "%s: CMD_COMPR_SET_PARAM %d\n", __func__, cmd); +#ifdef CONFIG_SND_ESA_SA_EFFECT + abox_rdma_mailbox_write(dev, abox_data, COMPR_PARAM_RATE, + data->out_sample_rate); +#endif + break; + case CMD_COMPR_WRITE: + dev_dbg(dev, "%s: CMD_COMPR_WRITE %d\n", __func__, cmd); + break; + case CMD_COMPR_READ: + dev_dbg(dev, "%s: CMD_COMPR_READ %d\n", __func__, cmd); + break; + case CMD_COMPR_START: + dev_dbg(dev, "%s: CMD_COMPR_START %d\n", __func__, cmd); + break; + case CMD_COMPR_STOP: + dev_dbg(dev, "%s: CMD_COMPR_STOP %d\n", __func__, cmd); + break; + case CMD_COMPR_PAUSE: + dev_dbg(dev, "%s: CMD_COMPR_PAUSE %d\n", __func__, cmd); + break; + case CMD_COMPR_EOS: + dev_dbg(dev, "%s: CMD_COMPR_EOS %d\n", __func__, cmd); + break; + case CMD_COMPR_GET_VOLUME: + dev_dbg(dev, "%s: CMD_COMPR_GET_VOLUME %d\n", __func__, cmd); + break; + case CMD_COMPR_SET_VOLUME: + dev_dbg(dev, "%s: CMD_COMPR_SET_VOLUME %d\n", __func__, cmd); + break; + case CMD_COMPR_CA5_WAKEUP: + dev_dbg(dev, "%s: CMD_COMPR_CA5_WAKEUP %d\n", __func__, cmd); + break; + case CMD_COMPR_HPDET_NOTIFY: + dev_dbg(dev, "%s: CMD_COMPR_HPDET_NOTIFY %d\n", __func__, cmd); + break; + default: + dev_err(dev, "%s: unknown cmd %d\n", __func__, cmd); + return -EINVAL; + } + + spin_lock(&data->cmd_lock); + + abox_rdma_mailbox_write(dev, COMPR_HANDLE_ID, data->handle_id); + abox_rdma_mailbox_write(dev, COMPR_CMD_CODE, cmd); + ret = abox_request_ipc(dev_abox, IPC_OFFLOAD, &ipc, 0, 1, 1); + + for (n = 0, ack = 0; n < 2000; n++) { + /* Wait for ACK */ + if (abox_rdma_mailbox_read(dev, COMPR_ACK)) { + ack = 1; + break; + } + udelay(100); + } + /* clear ACK */ + abox_rdma_mailbox_write(dev, COMPR_ACK, 0); + + spin_unlock(&data->cmd_lock); + + if (!ack) { + dev_err(dev, "%s: No ack error!(%x)", __func__, cmd); + ret = -EFAULT; + } + + return ret; +} + +static void abox_rdma_compr_clear_intr_ack(struct device *dev) +{ + abox_rdma_mailbox_write(dev, COMPR_INTR_ACK, 0); +} + +static int abox_rdma_compr_isr_handler(void *priv) +{ + struct platform_device *pdev = priv; + struct device *dev = &pdev->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct abox_compr_data *data = &platform_data->compr_data; + int id = platform_data->id; + unsigned long flags; + u32 val, fw_stat; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + val = abox_rdma_mailbox_read(dev, COMPR_RETURN_CMD); + + if (val == 1) + dev_err(dev, "%s: There is possibility of firmware CMD fail %u\n", + __func__, val); + fw_stat = val >> 16; + dev_dbg(dev, "fw_stat(%08x), val(%08x)\n", fw_stat, val); + + switch (fw_stat) { + case INTR_CREATED: + dev_info(dev, "INTR_CREATED\n"); + abox_rdma_compr_clear_intr_ack(dev); + data->created = true; + break; + case INTR_DECODED: + /* check the error */ + val &= 0xFF; + if (val) { + dev_err(dev, "INTR_DECODED err(%d)\n", val); + } else if (data->cstream && data->cstream->runtime) { + /* update copied total bytes */ + u32 size = abox_rdma_mailbox_read(dev, + COMPR_SIZE_OUT_DATA); + struct snd_compr_stream *cstream = data->cstream; + struct snd_compr_runtime *runtime = cstream->runtime; + + dev_dbg(dev, "INTR_DECODED(%d)\n", size); + + spin_lock_irqsave(&data->lock, flags); + /* update copied total bytes */ + data->copied_total += size; + data->byte_offset += size; + if (data->byte_offset >= runtime->buffer_size) + data->byte_offset -= runtime->buffer_size; + spin_unlock_irqrestore(&data->lock, flags); + + snd_compr_fragment_elapsed(data->cstream); + + if (!data->start && + runtime->state != SNDRV_PCM_STATE_PAUSED) { + /* Writes must be restarted from _copy() */ + dev_err(dev, "%s: write_done received while not started(%d)", + __func__, runtime->state); + } else { + u64 bytes_available = data->received_total - + data->copied_total; + + dev_dbg(dev, "%s: current free bufsize(%llu)\n", + __func__, runtime->buffer_size - + bytes_available); + + if (bytes_available < runtime->fragment_size) { + dev_dbg(dev, "%s: WRITE_DONE Insufficient data to send.(avail:%llu)\n", + __func__, bytes_available); + } + } + } else { + dev_dbg(dev, "%s: INTR_DECODED after compress offload end\n", + __func__); + } + abox_rdma_compr_clear_intr_ack(dev); + break; + case INTR_FLUSH: + /* check the error */ + val &= 0xFF; + if (val) { + dev_err(dev, "INTR_FLUSH err(%d)\n", val); + } else { + /* flush done */ + data->stop_ack = 1; + wake_up_interruptible(&data->flush_wait); + } + abox_rdma_compr_clear_intr_ack(dev); + break; + case INTR_PAUSED: + /* check the error */ + val &= 0xFF; + if (val) + dev_err(dev, "INTR_PAUSED err(%d)\n", val); + + abox_rdma_compr_clear_intr_ack(dev); + break; + case INTR_EOS: + if (data->eos) { + if (data->copied_total != data->received_total) + dev_err(dev, "%s: EOS is not sync!(%llu/%llu)\n", + __func__, data->copied_total, + data->received_total); + + /* ALSA Framework callback to notify drain complete */ + snd_compr_drain_notify(data->cstream); + data->eos = 0; + dev_info(dev, "%s: DATA_CMD_EOS wake up\n", __func__); + } + abox_rdma_compr_clear_intr_ack(dev); + break; + case INTR_DESTROY: + /* check the error */ + val &= 0xFF; + if (val) { + dev_err(dev, "INTR_DESTROY err(%d)\n", val); + } else { + /* destroied */ + data->exit_ack = 1; + wake_up_interruptible(&data->exit_wait); + } + abox_rdma_compr_clear_intr_ack(dev); + break; + case INTR_FX_EXT: + /* To Do */ + abox_rdma_compr_clear_intr_ack(dev); + break; +#ifdef CONFIG_SND_SAMSUNG_ELPE + case INTR_EFF_REQUEST: + /*To Do */ + abox_rdma_compr_clear_intr_ack(dev); + break; +#endif + } + + wake_up_interruptible(&data->ipc_wait); + + return IRQ_HANDLED; +} + +static int abox_rdma_compr_set_param(struct platform_device *pdev, + struct snd_compr_runtime *runtime) +{ + struct device *dev = &pdev->dev; + struct abox_platform_data *platform_data = platform_get_drvdata(pdev); + struct abox_compr_data *data = &platform_data->compr_data; + int id = platform_data->id; + int ret; + + dev_info(dev, "%s[%d] buffer: %p(%llu)\n", __func__, id, + runtime->buffer, runtime->buffer_size); + +#ifdef COMPR_USE_FIXED_MEMORY + /* free memory allocated by ALSA */ + kfree(runtime->buffer); + + runtime->buffer = data->dma_area; + if (runtime->buffer_size > data->dma_size) { + dev_err(dev, "allocated buffer size is smaller than requested(%llu > %zu)\n", + runtime->buffer_size, data->dma_size); + ret = -ENOMEM; + goto error; + } +#else +#ifdef COMPR_USE_COPY + runtime->buffer = dma_alloc_coherent(dev, runtime->buffer_size, + &data->dma_addr, GFP_KERNEL); + if (!runtime->buffer) { + dev_err(dev, "dma memory allocation failed (size=%llu)\n", + runtime->buffer_size); + ret = -ENOMEM; + goto error; + } +#else + data->dma_addr = dma_map_single(dev, runtime->buffer, + runtime->buffer_size, DMA_TO_DEVICE); + ret = dma_mapping_error(dev, data->dma_addr); + if (ret) { + dev_err(dev, "dma memory mapping failed(%d)\n", ret); + goto error; + } +#endif + ret = iommu_map(platform_data->abox_data->iommu_domain, + IOVA_COMPR_BUFFER(id), virt_to_phys(runtime->buffer), + round_up(runtime->buffer_size, PAGE_SIZE), 0); + if (ret < 0) { + dev_err(dev, "iommu mapping failed(%d)\n", ret); + goto error; + } +#endif + /* set buffer information at mailbox */ + abox_rdma_mailbox_write(dev, COMPR_SIZE_OF_INBUF, runtime->buffer_size); + abox_rdma_mailbox_write(dev, COMPR_PHY_ADDR_INBUF, + IOVA_COMPR_BUFFER(id)); + abox_rdma_mailbox_write(dev, COMPR_PARAM_SAMPLE, data->sample_rate); + abox_rdma_mailbox_write(dev, COMPR_PARAM_CH, data->channels); + abox_rdma_mailbox_write(dev, COMPR_IP_TYPE, data->codec_id << 16); + data->created = false; + ret = abox_rdma_mailbox_send_cmd(dev, CMD_COMPR_SET_PARAM); + if (ret < 0) { + dev_err(dev, "CMD_COMPR_SET_PARAM failed(%d)\n", ret); + goto error; + } + + /* wait until the parameter is set up */ + ret = wait_event_interruptible_timeout(data->ipc_wait, + data->created, msecs_to_jiffies(1000)); + if (!ret) { + dev_err(dev, "%s: compress set param timed out!!! (%d)\n", + __func__, ret); + abox_rdma_mailbox_write(dev, COMPR_INTR_ACK, 0); + ret = -EBUSY; + goto error; + } + + /* created instance */ + data->handle_id = abox_rdma_mailbox_read(dev, COMPR_IP_ID); + dev_info(dev, "%s: codec id:0x%x, ret_val:0x%x, handle_id:0x%x\n", + __func__, data->codec_id, + abox_rdma_mailbox_read(dev, COMPR_RETURN_CMD), + data->handle_id); + + dev_info(dev, "%s: allocated buffer address (0x%pad), size(0x%llx)\n", + __func__, &data->dma_addr, runtime->buffer_size); +#ifdef CONFIG_SND_ESA_SA_EFFECT + data->effect_on = false; +#endif + + return 0; + +error: + return ret; +} + +static int abox_rdma_compr_open(struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct abox_compr_data *data = &platform_data->compr_data; + struct abox_data *abox_data = platform_data->abox_data; + int id = platform_data->id; + + dev_info(dev, "%s[%d]\n", __func__, id); + + /* init runtime data */ + data->cstream = stream; + data->byte_offset = 0; + data->copied_total = 0; + data->received_total = 0; + data->sample_rate = 44100; + data->channels = 0x3; /* stereo channel mask */ + + data->eos = false; + data->start = false; + data->created = false; + + pm_runtime_get_sync(dev); + abox_request_cpu_gear(dev, abox_data, dev, abox_data->cpu_gear_min); + + return 0; +} + +static int abox_rdma_compr_free(struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct abox_compr_data *data = &platform_data->compr_data; + struct abox_data *abox_data = platform_data->abox_data; + int id = platform_data->id; + int ret = 0; + + dev_info(dev, "%s[%d]\n", __func__, id); + + if (data->eos) { + /* ALSA Framework callback to notify drain complete */ + snd_compr_drain_notify(stream); + data->eos = 0; + dev_dbg(dev, "%s Call Drain notify to wakeup\n", __func__); + } + + if (data->created) { + data->created = false; + data->exit_ack = 0; + + ret = abox_rdma_mailbox_send_cmd(dev, CMD_COMPR_DESTROY); + if (ret) { + dev_err(dev, "%s: can't send CMD_COMPR_DESTROY (%d)\n", + __func__, ret); + } else { + ret = wait_event_interruptible_timeout(data->exit_wait, + data->exit_ack, msecs_to_jiffies(1000)); + if (!ret) + dev_err(dev, "%s: CMD_DESTROY timed out!!!\n", + __func__); + } + } + +#ifdef COMPR_USE_FIXED_MEMORY + /* prevent kfree in ALSA */ + stream->runtime->buffer = NULL; +#else +{ + struct snd_compr_runtime *runtime = stream->runtime; + + iommu_unmap(abox_data->iommu_domain, IOVA_COMPR_BUFFER(id), + round_up(runtime->buffer_size, PAGE_SIZE)); + exynos_sysmmu_tlb_invalidate(abox_data->iommu_domain, + (dma_addr_t)IOVA_COMPR_BUFFER(id), + round_up(runtime->buffer_size, PAGE_SIZE)); + +#ifdef COMPR_USE_COPY + dma_free_coherent(dev, runtime->buffer_size, runtime->buffer, + data->dma_addr); + runtime->buffer = NULL; +#else + dma_unmap_single(dev, data->dma_addr, runtime->buffer_size, + DMA_TO_DEVICE); +#endif +} +#endif + + abox_request_cpu_gear(dev, abox_data, dev, ABOX_CPU_GEAR_MIN); + pm_runtime_mark_last_busy(dev); + pm_runtime_put(dev); + + return ret; +} + +static int abox_rdma_compr_set_params(struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct platform_device *pdev = to_platform_device(dev); + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct abox_compr_data *data = &platform_data->compr_data; + int id = platform_data->id; + int ret = 0; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + /* COMPR set_params */ + memcpy(&data->codec_param, params, sizeof(data->codec_param)); + + data->byte_offset = 0; + data->copied_total = 0; + data->channels = data->codec_param.codec.ch_in; + data->sample_rate = data->codec_param.codec.sample_rate; + + if (data->sample_rate == 0 || + data->channels == 0) { + dev_err(dev, "%s: invalid parameters: sample(%u), ch(%u)\n", + __func__, data->sample_rate, data->channels); + return -EINVAL; + } + + switch (params->codec.id) { + case SND_AUDIOCODEC_MP3: + data->codec_id = COMPR_MP3; + break; + case SND_AUDIOCODEC_AAC: + data->codec_id = COMPR_AAC; + break; + case SND_AUDIOCODEC_FLAC: + data->codec_id = COMPR_FLAC; + break; + default: + dev_err(dev, "%s: unknown codec id %d\n", __func__, + params->codec.id); + break; + } + + ret = abox_rdma_compr_set_param(pdev, runtime); + if (ret) { + dev_err(dev, "%s: esa_compr_set_param fail(%d)\n", __func__, + ret); + return ret; + } + + dev_info(dev, "%s: sample rate:%u, channels:%u\n", __func__, + data->sample_rate, data->channels); + return 0; +} + +static int abox_rdma_compr_set_metadata(struct snd_compr_stream *stream, + struct snd_compr_metadata *metadata) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + int id = platform_data->id; + + dev_info(dev, "%s[%d]\n", __func__, id); + + if (!metadata) + return -EINVAL; + + if (metadata->key == SNDRV_COMPRESS_ENCODER_PADDING) + dev_dbg(dev, "%s: got encoder padding %u", __func__, + metadata->value[0]); + else if (metadata->key == SNDRV_COMPRESS_ENCODER_DELAY) + dev_dbg(dev, "%s: got encoder delay %u", __func__, + metadata->value[0]); + + return 0; +} + +static int abox_rdma_compr_trigger(struct snd_compr_stream *stream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct abox_compr_data *data = &platform_data->compr_data; + int id = platform_data->id; + int ret = 0; + + dev_info(dev, "%s[%d](%d)\n", __func__, id, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev_info(dev, "SNDRV_PCM_TRIGGER_PAUSE_PUSH\n"); + ret = abox_rdma_mailbox_send_cmd(dev, CMD_COMPR_PAUSE); + if (ret < 0) + dev_err(dev, "%s: pause cmd failed(%d)\n", __func__, + ret); + + abox_request_dram_on(platform_data->pdev_abox, dev, false); + break; + case SNDRV_PCM_TRIGGER_STOP: + dev_info(dev, "SNDRV_PCM_TRIGGER_STOP\n"); + + if (data->eos) { + /* ALSA Framework callback to notify drain complete */ + snd_compr_drain_notify(stream); + data->eos = 0; + dev_dbg(dev, "interrupt drain and eos wait queues\n"); + } + + data->stop_ack = 0; + ret = abox_rdma_mailbox_send_cmd(dev, CMD_COMPR_STOP); + if (ret < 0) + dev_err(dev, "%s: stop cmd failed (%d)\n", + __func__, ret); + + ret = wait_event_interruptible_timeout(data->flush_wait, + data->stop_ack, msecs_to_jiffies(1000)); + if (!ret) { + dev_err(dev, "CMD_STOP cmd timeout(%d)\n", ret); + ret = -ETIMEDOUT; + } else { + ret = 0; + } + + data->start = false; + + /* reset */ + data->stop_ack = 0; + data->byte_offset = 0; + data->copied_total = 0; + data->received_total = 0; + + abox_request_dram_on(platform_data->pdev_abox, dev, false); + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev_info(dev, "%s: %s", __func__, + (cmd == SNDRV_PCM_TRIGGER_START) ? + "SNDRV_PCM_TRIGGER_START" : + "SNDRV_PCM_TRIGGER_PAUSE_RELEASE"); + + abox_request_dram_on(platform_data->pdev_abox, dev, true); + + data->start = 1; + ret = abox_rdma_mailbox_send_cmd(dev, CMD_COMPR_START); + if (ret < 0) + dev_err(dev, "%s: start cmd failed\n", __func__); + + break; + case SND_COMPR_TRIGGER_NEXT_TRACK: + pr_info("%s: SND_COMPR_TRIGGER_NEXT_TRACK\n", __func__); + break; + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_DRAIN: + dev_info(dev, "%s: %s", __func__, + (cmd == SND_COMPR_TRIGGER_DRAIN) ? + "SND_COMPR_TRIGGER_DRAIN" : + "SND_COMPR_TRIGGER_PARTIAL_DRAIN"); + /* Make sure all the data is sent to F/W before sending EOS */ + if (!data->start) { + dev_err(dev, "%s: stream is not in started state\n", + __func__); + ret = -EPERM; + break; + } + + data->eos = 1; + dev_dbg(dev, "%s: CMD_EOS\n", __func__); + ret = abox_rdma_mailbox_send_cmd(dev, CMD_COMPR_EOS); + if (ret < 0) + dev_err(dev, "%s: can't send eos (%d)\n", __func__, + ret); + else + pr_info("%s: Out of %s Drain", __func__, + (cmd == SND_COMPR_TRIGGER_DRAIN ? + "Full" : "Partial")); + + break; + default: + break; + } + + return 0; +} + +static int abox_rdma_compr_pointer(struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct abox_compr_data *data = &platform_data->compr_data; + int id = platform_data->id; + unsigned int num_channel; + u32 pcm_size; + unsigned long flags; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + spin_lock_irqsave(&data->lock, flags); + tstamp->sampling_rate = data->sample_rate; + tstamp->byte_offset = data->byte_offset; + tstamp->copied_total = data->copied_total; + spin_unlock_irqrestore(&data->lock, flags); + + pcm_size = abox_rdma_mailbox_read(dev, COMPR_RENDERED_PCM_SIZE); + + /* set the number of channels */ + num_channel = hweight32(data->channels); + + if (pcm_size) { + tstamp->pcm_io_frames = pcm_size / (2 * num_channel); + dev_dbg(dev, "%s: pcm_size(%u), frame_count(%u), copied_total(%u)\n", + __func__, pcm_size, tstamp->pcm_io_frames, + tstamp->copied_total); + + } + + return 0; +} + +static int abox_rdma_compr_mmap(struct snd_compr_stream *stream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + int id = platform_data->id; + struct snd_compr_runtime *runtime = stream->runtime; + + dev_info(dev, "%s[%d]\n", __func__, id); + + return dma_mmap_writecombine(dev, vma, + runtime->buffer, + virt_to_phys(runtime->buffer), + runtime->buffer_size); +} + +static int abox_rdma_compr_ack(struct snd_compr_stream *stream, size_t bytes) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct abox_compr_data *data = &platform_data->compr_data; + int id = platform_data->id; + int ret; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + /* write mp3 data to firmware */ + data->received_total += bytes; + abox_rdma_mailbox_write(dev, COMPR_SIZE_OF_FRAGMENT, bytes); + ret = abox_rdma_mailbox_send_cmd(dev, CMD_COMPR_WRITE); + + return ret; +} + +#ifdef COMPR_USE_COPY +static int abox_compr_write_data(struct snd_compr_stream *stream, + const char __user *buf, size_t count) +{ + void *dstn; + size_t copy; + struct snd_compr_runtime *runtime = stream->runtime; + /* 64-bit Modulus */ + u64 app_pointer = div64_u64(runtime->total_bytes_available, + runtime->buffer_size); + app_pointer = runtime->total_bytes_available - + (app_pointer * runtime->buffer_size); + dstn = runtime->buffer + app_pointer; + + pr_debug("copying %ld at %lld\n", + (unsigned long)count, app_pointer); + + if (count < runtime->buffer_size - app_pointer) { + if (copy_from_user(dstn, buf, count)) + return -EFAULT; + } else { + copy = runtime->buffer_size - app_pointer; + if (copy_from_user(dstn, buf, copy)) + return -EFAULT; + if (copy_from_user(runtime->buffer, buf + copy, count - copy)) + return -EFAULT; + } + abox_rdma_compr_ack(stream, count); + + return count; +} + +static int abox_rdma_compr_copy(struct snd_compr_stream *stream, + char __user *buf, size_t count) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + int id = platform_data->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + return abox_compr_write_data(stream, buf, count); +} +#endif + +static int abox_rdma_compr_get_caps(struct snd_compr_stream *stream, + struct snd_compr_caps *caps) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + int id = platform_data->id; + + dev_info(dev, "%s[%d]\n", __func__, id); + + memcpy(caps, &abox_rdma_compr_caps, sizeof(*caps)); + + return 0; +} + +static int abox_rdma_compr_get_codec_caps(struct snd_compr_stream *stream, + struct snd_compr_codec_caps *codec) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + int id = platform_data->id; + + dev_info(dev, "%s[%d]\n", __func__, id); + + return 0; +} + +static void __abox_rdma_compr_get_hw_params_legacy(struct device *dev, + struct snd_pcm_hw_params *params, unsigned int upscale) +{ + struct snd_mask *format_mask; + struct snd_interval *rate_interval; + + rate_interval = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + format_mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + switch (upscale) { + default: + /* fallback */ + case 0: + /* 48kHz 16bit */ + rate_interval->min = 48000; + snd_mask_set(format_mask, SNDRV_PCM_FORMAT_S16); + dev_info(dev, "%s: 48kHz 16bit\n", __func__); + break; + case 1: + /* 192kHz 24bit */ + rate_interval->min = 192000; + snd_mask_set(format_mask, SNDRV_PCM_FORMAT_S24); + dev_info(dev, "%s: 192kHz 24bit\n", __func__); + break; + case 2: + /* 48kHz 24bit */ + rate_interval->min = 48000; + snd_mask_set(format_mask, SNDRV_PCM_FORMAT_S24); + dev_info(dev, "%s: 48kHz 24bit\n", __func__); + break; + case 3: + /* 384kHz 32bit */ + rate_interval->min = 384000; + snd_mask_set(format_mask, SNDRV_PCM_FORMAT_S32); + dev_info(dev, "%s: 384kHz 32bit\n", __func__); + break; + } +} + +static void __abox_rdma_compr_get_hw_params(struct device *dev, + struct snd_pcm_hw_params *params, unsigned int upscale) +{ + unsigned int bit, ch, rate; + struct snd_mask *format_mask; + struct snd_interval *rate_interval, *ch_interval; + + format_mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + ch_interval = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + rate_interval = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + bit = (upscale >> COMPR_BIT_SHIFT) & COMPR_BIT_MASK; + ch = (upscale >> COMPR_CH_SHIFT) & COMPR_CH_MASK; + rate = (upscale >> COMPR_RATE_SHIFT) & COMPR_RATE_MASK; + + switch (bit) { + default: + /* fallback */ + case 16: + snd_mask_set(format_mask, SNDRV_PCM_FORMAT_S16); + break; + case 24: + snd_mask_set(format_mask, SNDRV_PCM_FORMAT_S24); + break; + case 32: + snd_mask_set(format_mask, SNDRV_PCM_FORMAT_S32); + break; + } + ch_interval->min = ch ? ch : 2; + rate_interval->min = rate ? rate : 48000; + + dev_info(dev, "%s: %ubit %uch %uHz\n", __func__, bit, ch, rate); +} + +static int abox_rdma_compr_get_hw_params(struct snd_compr_stream *stream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + int id = platform_data->id; + unsigned int upscale; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + upscale = abox_rdma_mailbox_read(dev, COMPR_UPSCALE); + if (upscale <= 0xF) + __abox_rdma_compr_get_hw_params_legacy(dev, params, upscale); + else + __abox_rdma_compr_get_hw_params(dev, params, upscale); + + return 0; +} + +static struct snd_compr_ops abox_rdma_compr_ops = { + .open = abox_rdma_compr_open, + .free = abox_rdma_compr_free, + .set_params = abox_rdma_compr_set_params, + .set_metadata = abox_rdma_compr_set_metadata, + .trigger = abox_rdma_compr_trigger, + .pointer = abox_rdma_compr_pointer, +#ifdef COMPR_USE_COPY + .copy = abox_rdma_compr_copy, +#endif + .mmap = abox_rdma_compr_mmap, + .ack = abox_rdma_compr_ack, + .get_caps = abox_rdma_compr_get_caps, + .get_codec_caps = abox_rdma_compr_get_codec_caps, + .get_hw_params = abox_rdma_compr_get_hw_params, +}; + +static int abox_rdma_compr_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = platform_data->id; + unsigned int volumes[2]; + + volumes[0] = (unsigned int)ucontrol->value.integer.value[0]; + volumes[1] = (unsigned int)ucontrol->value.integer.value[1]; + dev_dbg(dev, "%s[%d]: left_vol=%d right_vol=%d\n", + __func__, id, volumes[0], volumes[1]); + + abox_rdma_mailbox_write(dev, mc->reg, volumes[0]); + abox_rdma_mailbox_write(dev, mc->rreg, volumes[1]); + + return 0; +} + +static int abox_rdma_compr_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int id = platform_data->id; + unsigned int volumes[2]; + + volumes[0] = abox_rdma_mailbox_read(dev, mc->reg); + volumes[1] = abox_rdma_mailbox_read(dev, mc->rreg); + dev_dbg(dev, "%s[%d]: left_vol=%d right_vol=%d\n", + __func__, id, volumes[0], volumes[1]); + + ucontrol->value.integer.value[0] = volumes[0]; + ucontrol->value.integer.value[1] = volumes[1]; + + return 0; +} + +static const DECLARE_TLV_DB_LINEAR(abox_rdma_compr_vol_gain, 0, + COMPRESSED_LR_VOL_MAX_STEPS); + +static int abox_rdma_compr_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int id = platform_data->id; + unsigned int upscale; + + dev_warn(dev, "%s is deprecated\n", kcontrol->id.name); + + upscale = ucontrol->value.enumerated.item[0]; + dev_dbg(dev, "%s[%d]: scale=%u\n", __func__, id, upscale); + + abox_rdma_mailbox_write(dev, e->reg, upscale); + + return 0; +} + +static int abox_rdma_compr_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); + struct device *dev = platform->dev; + struct abox_platform_data *platform_data = dev_get_drvdata(dev); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int id = platform_data->id; + unsigned int upscale; + + dev_warn(dev, "%s is deprecated\n", kcontrol->id.name); + + upscale = abox_rdma_mailbox_read(dev, e->reg); + dev_dbg(dev, "%s[%d]: upscale=%u\n", __func__, id, upscale); + + if (upscale >= e->items) { + unsigned int bit, rate; + + bit = (upscale >> COMPR_BIT_SHIFT) & COMPR_BIT_MASK ; + rate = (upscale >> COMPR_RATE_SHIFT) & COMPR_RATE_MASK; + + if (rate == 384000) { + upscale = 3; + } else if (rate == 192000) { + upscale = 1; + } else if (rate == 48000) { + if (bit == 24) + upscale = 2; + else + upscale = 0; + } else { + upscale = 0; + } + } + + ucontrol->value.enumerated.item[0] = upscale; + + return 0; +} + +static const char * const abox_rdma_compr_format_text[] = { + "48kHz 16bit", + "192kHz 24bit", + "48kHz 24bit", + "384kHz 32bit", +}; + +static SOC_ENUM_SINGLE_DECL(abox_rdma_compr_format, + COMPR_UPSCALE, 0, + abox_rdma_compr_format_text); + +static const struct snd_kcontrol_new abox_rdma_compr_controls[] = { + SOC_DOUBLE_R_EXT_TLV("ComprTx0 Volume", COMPR_LEFT_VOL, COMPR_RIGHT_VOL, + 0, COMPRESSED_LR_VOL_MAX_STEPS, 0, + abox_rdma_compr_vol_get, abox_rdma_compr_vol_put, + abox_rdma_compr_vol_gain), + SOC_ENUM_EXT("ComprTx0 Format", abox_rdma_compr_format, + abox_rdma_compr_format_get, abox_rdma_compr_format_put), + SOC_SINGLE("ComprTx0 Bit", COMPR_UPSCALE, COMPR_BIT_SHIFT, + COMPR_BIT_MASK, 0), + SOC_SINGLE("ComprTx0 Ch", COMPR_UPSCALE, COMPR_CH_SHIFT, + COMPR_CH_MASK, 0), + SOC_SINGLE("ComprTx0 Rate", COMPR_UPSCALE, COMPR_RATE_SHIFT, + COMPR_RATE_MASK, 0), +}; + +static const struct snd_pcm_hardware abox_rdma_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID, + .formats = ABOX_SAMPLE_FORMATS, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = PERIOD_BYTES_MAX, + .periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, +}; + +static irqreturn_t abox_rdma_irq_handler(int irq, void *dev_id, + ABOX_IPC_MSG *msg) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct abox_platform_data *data = platform_get_drvdata(pdev); + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask; + int id = data->id; + + if (id != pcmtask_msg->channel_id) + return IRQ_NONE; + + dev_dbg(dev, "%s[%d]: ipcid=%d, msgtype=%d\n", __func__, id, msg->ipcid, + pcmtask_msg->msgtype); + + switch (pcmtask_msg->msgtype) { + case PCM_PLTDAI_POINTER: + snd_pcm_period_elapsed(data->substream); + break; + default: + dev_warn(dev, "Unknown pcmtask message: %d\n", + pcmtask_msg->msgtype); + break; + } + + return IRQ_HANDLED; +} + +static int abox_rdma_enabled(struct abox_platform_data *data) +{ + return readl(data->sfr_base + ABOX_RDMA_CTRL0) & ABOX_RDMA_ENABLE_MASK; +} + +static void abox_rdma_disable_barrier(struct device *dev, + struct abox_platform_data *data) +{ + int id = data->id; + struct abox_data *abox_data = data->abox_data; + u64 timeout = local_clock() + ABOX_DMA_TIMEOUT_NS; + + while (abox_rdma_enabled(data)) { + if (local_clock() <= timeout) { + cond_resched(); + continue; + } + dev_warn_ratelimited(dev, "RDMA disable timeout[%d]\n", id); + abox_dbg_dump_simple(dev, abox_data, "RDMA disable timeout"); + break; + } +} + +static int abox_rdma_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_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct abox_data *abox_data = data->abox_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int id = data->id; + unsigned int lit_freq, big_freq, hmp_boost; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(dev, "Memory allocation failed (size:%u)\n", + params_buffer_bytes(params)); + return ret; + } + + pcmtask_msg->channel_id = id; +#ifndef USE_FIXED_MEMORY + ret = iommu_map(data->abox_data->iommu_domain, IOVA_RDMA_BUFFER(id), + runtime->dma_addr, round_up(runtime->dma_bytes, + PAGE_SIZE), 0); + if (ret < 0) { + dev_err(dev, "dma buffer iommu map failed\n"); + return ret; + } +#endif + msg.ipcid = IPC_PCMPLAYBACK; + msg.task_id = pcmtask_msg->channel_id = id; + + pcmtask_msg->msgtype = PCM_SET_BUFFER; + pcmtask_msg->param.setbuff.phyaddr = IOVA_RDMA_BUFFER(id); +#ifdef CONFIG_SCSC_BT //if (IS_ENABLED(CONFIG_SCSC_BT)) + if (abox_test_quirk(abox_data, ABOX_QUIRK_SCSC_BT) && data->scsc_bt) { + struct device *dev_bt = abox_data->dev_bt; + int stream = substream->stream; + unsigned int iova = abox_bt_get_buf_iova(dev_bt, stream); + + if (abox_bt_active(dev_bt, stream) && iova) + pcmtask_msg->param.setbuff.phyaddr = iova; + } +#endif + pcmtask_msg->param.setbuff.size = params_period_bytes(params); + pcmtask_msg->param.setbuff.count = params_periods(params); + ret = abox_rdma_request_ipc(data, &msg, 0, 0); + if (ret < 0) + return ret; + + pcmtask_msg->msgtype = PCM_PLTDAI_HW_PARAMS; + pcmtask_msg->param.hw_params.sample_rate = params_rate(params); +#ifdef CONFIG_SCSC_BT //if (IS_ENABLED(CONFIG_SCSC_BT)) + if (abox_test_quirk(abox_data, ABOX_QUIRK_SCSC_BT_HACK) && + data->scsc_bt) { + struct device *dev_bt = abox_data->dev_bt; + int stream = substream->stream; + unsigned int rate = abox_bt_get_rate(dev_bt); + + if (abox_bt_active(dev_bt, stream) && rate) + pcmtask_msg->param.hw_params.sample_rate = rate; + } +#endif + pcmtask_msg->param.hw_params.bit_depth = params_width(params); + pcmtask_msg->param.hw_params.channels = params_channels(params); + ret = abox_rdma_request_ipc(data, &msg, 0, 0); + if (ret < 0) + return ret; + + if (params_rate(params) > 48000) + abox_request_cpu_gear(dev, abox_data, dev, + abox_data->cpu_gear_min - 1); + + lit_freq = data->pm_qos_lit[abox_get_rate_type(params_rate(params))]; + big_freq = data->pm_qos_big[abox_get_rate_type(params_rate(params))]; + hmp_boost = data->pm_qos_hmp[abox_get_rate_type(params_rate(params))]; + abox_request_lit_freq(dev, data->abox_data, dev, lit_freq); + abox_request_big_freq(dev, data->abox_data, dev, big_freq); + abox_request_hmp_boost(dev, data->abox_data, dev, hmp_boost); + + dev_info(dev, "%s:DmaAddr=%pad Total=%zu PrdSz=%u(%u) #Prds=%u dma_area=%p rate=%u, width=%d, channels=%u\n", + snd_pcm_stream_str(substream), &runtime->dma_addr, + runtime->dma_bytes, params_period_size(params), + params_period_bytes(params), params_periods(params), + runtime->dma_area, params_rate(params), + snd_pcm_format_width(params_format(params)), + params_channels(params)); + + return 0; +} + +static int abox_rdma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + msg.ipcid = IPC_PCMPLAYBACK; + pcmtask_msg->msgtype = PCM_PLTDAI_HW_FREE; + msg.task_id = pcmtask_msg->channel_id = id; + abox_rdma_request_ipc(data, &msg, 0, 0); +#ifndef USE_FIXED_MEMORY + iommu_unmap(data->abox_data->iommu_domain, IOVA_RDMA_BUFFER(id), + round_up(substream->runtime->dma_bytes, PAGE_SIZE)); + exynos_sysmmu_tlb_invalidate(data->abox_data->iommu_domain, + (dma_addr_t)IOVA_RDMA_BUFFER(id), + round_up(substream->runtime->dma_bytes, PAGE_SIZE)); +#endif + abox_request_lit_freq(dev, data->abox_data, dev, 0); + abox_request_big_freq(dev, data->abox_data, dev, 0); + abox_request_hmp_boost(dev, data->abox_data, dev, 0); + + switch (data->type) { + default: + abox_rdma_disable_barrier(dev, data); + break; + } + + return snd_pcm_lib_free_pages(substream); +} + +static int abox_rdma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + data->pointer = IOVA_RDMA_BUFFER(id); + + switch (data->type) { + case PLATFORM_CALL: + break; + default: + ret = abox_try_to_asrc_off(dev, data->abox_data, rtd, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret < 0) + dev_warn(dev, "abox_try_to_asrc_off: %d\n", ret); + break; + } + + msg.ipcid = IPC_PCMPLAYBACK; + pcmtask_msg->msgtype = PCM_PLTDAI_PREPARE; + msg.task_id = pcmtask_msg->channel_id = id; + ret = abox_rdma_request_ipc(data, &msg, 0, 0); + + return ret; +} + +static int abox_rdma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct snd_pcm_runtime *runtime = substream->runtime; + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_info(dev, "%s[%d](%d)\n", __func__, id, cmd); + + msg.ipcid = IPC_PCMPLAYBACK; + pcmtask_msg->msgtype = PCM_PLTDAI_TRIGGER; + msg.task_id = pcmtask_msg->channel_id = id; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (memblock_is_memory(runtime->dma_addr)) + abox_request_dram_on(data->pdev_abox, dev, true); + + pcmtask_msg->param.trigger = 1; + ret = abox_rdma_request_ipc(data, &msg, 1, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pcmtask_msg->param.trigger = 0; + ret = abox_rdma_request_ipc(data, &msg, 1, 0); + switch (data->type) { + case PLATFORM_REALTIME: + msg.ipcid = IPC_ERAP; + msg.msg.erap.msgtype = REALTIME_STOP; + ret = abox_rdma_request_ipc(data, &msg, 1, 0); + break; + default: + break; + } + + if (memblock_is_memory(runtime->dma_addr)) + abox_request_dram_on(data->pdev_abox, dev, false); + + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t abox_rdma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct snd_pcm_runtime *runtime = substream->runtime; + int id = data->id; + ssize_t pointer; + u32 status = readl(data->sfr_base + ABOX_RDMA_STATUS); + bool progress = (status & ABOX_RDMA_PROGRESS_MASK) ? true : false; + + if (data->pointer >= IOVA_RDMA_BUFFER(id)) { + pointer = data->pointer - IOVA_RDMA_BUFFER(id); + } else if (((data->type == PLATFORM_NORMAL) || + (data->type == PLATFORM_SYNC)) && progress) { + ssize_t offset, count; + ssize_t buffer_bytes, period_bytes; + + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + period_bytes = snd_pcm_lib_period_bytes(substream); + + offset = (((status & ABOX_RDMA_RBUF_OFFSET_MASK) >> + ABOX_RDMA_RBUF_OFFSET_L) << 4); + count = (status & ABOX_RDMA_RBUF_CNT_MASK); + + while ((offset % period_bytes) && (buffer_bytes >= 0)) { + buffer_bytes -= period_bytes; + if ((buffer_bytes & offset) == offset) + offset = buffer_bytes; + } + + pointer = offset + count; + } else { + pointer = 0; + } + + dev_dbg(dev, "%s[%d]: pointer=%08zx\n", __func__, id, pointer); + + return bytes_to_frames(runtime, pointer); +} + +static int abox_rdma_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct abox_data *abox_data = data->abox_data; + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + if (data->type == PLATFORM_CALL) { + if (abox_cpu_gear_idle(dev, abox_data, + (void *)ABOX_CPU_GEAR_CALL_VSS)) + abox_request_cpu_gear_sync(dev, abox_data, + (void *)ABOX_CPU_GEAR_CALL_KERNEL, + ABOX_CPU_GEAR_MAX); + ret = abox_request_l2c_sync(dev, data->abox_data, dev, true); + if (ret < 0) + return ret; + } + abox_request_cpu_gear(dev, abox_data, dev, abox_data->cpu_gear_min); + + snd_soc_set_runtime_hwparams(substream, &abox_rdma_hardware); + + data->substream = substream; + + msg.ipcid = IPC_PCMPLAYBACK; + pcmtask_msg->msgtype = PCM_PLTDAI_OPEN; + msg.task_id = pcmtask_msg->channel_id = id; + ret = abox_rdma_request_ipc(data, &msg, 0, 0); + + return ret; +} + +static int abox_rdma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct abox_data *abox_data = data->abox_data; + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + data->substream = NULL; + + msg.ipcid = IPC_PCMPLAYBACK; + pcmtask_msg->msgtype = PCM_PLTDAI_CLOSE; + msg.task_id = pcmtask_msg->channel_id = id; + ret = abox_rdma_request_ipc(data, &msg, 0, 1); + + abox_request_cpu_gear(dev, abox_data, dev, ABOX_CPU_GEAR_MIN); + if (data->type == PLATFORM_CALL) { + abox_request_cpu_gear(dev, abox_data, + (void *)ABOX_CPU_GEAR_CALL_KERNEL, + ABOX_CPU_GEAR_MIN); + ret = abox_request_l2c(dev, data->abox_data, dev, false); + if (ret < 0) + return ret; + } + + return ret; +} + +static int abox_rdma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + struct snd_pcm_runtime *runtime = substream->runtime; + + dev_info(dev, "%s[%d]\n", __func__, id); + + return dma_mmap_writecombine(dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static int abox_rdma_ack(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + snd_pcm_uframes_t appl_ofs = appl_ptr % runtime->buffer_size; + ssize_t appl_bytes = frames_to_bytes(runtime, appl_ofs); + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + if (!data->ack_enabled) + return 0; + + dev_dbg(dev, "%s[%d]: %zd\n", __func__, id, appl_bytes); + + msg.ipcid = IPC_PCMPLAYBACK; + pcmtask_msg->msgtype = PCM_PLTDAI_ACK; + pcmtask_msg->param.pointer = (unsigned int)appl_bytes; + msg.task_id = pcmtask_msg->channel_id = id; + + return abox_rdma_request_ipc(data, &msg, 0, 0); +} + +static struct snd_pcm_ops abox_rdma_ops = { + .open = abox_rdma_open, + .close = abox_rdma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = abox_rdma_hw_params, + .hw_free = abox_rdma_hw_free, + .prepare = abox_rdma_prepare, + .trigger = abox_rdma_trigger, + .pointer = abox_rdma_pointer, + .mmap = abox_rdma_mmap, + .ack = abox_rdma_ack, +}; + +static int abox_rdma_new(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_pcm *pcm = runtime->pcm; + struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_soc_platform *platform = runtime->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct iommu_domain *iommu_domain = data->abox_data->iommu_domain; + int id = data->id; + size_t buffer_bytes; + int ret; + + switch (data->type) { + case PLATFORM_NORMAL: + buffer_bytes = BUFFER_BYTES_MAX; + break; + default: + buffer_bytes = BUFFER_BYTES_MAX >> 2; + break; + } + + ret = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, + runtime->cpu_dai->dev, buffer_bytes, buffer_bytes); + if (ret < 0) + return ret; + +#ifdef USE_FIXED_MEMORY + iommu_map(iommu_domain, IOVA_RDMA_BUFFER(id), + substream->dma_buffer.addr, BUFFER_BYTES_MAX, 0); +#endif + + return ret; +} + +static void abox_rdma_free(struct snd_pcm *pcm) +{ +#ifdef USE_FIXED_MEMORY + struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_soc_pcm_runtime *runtime = substream->private_data; + struct snd_soc_platform *platform = runtime->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct iommu_domain *iommu_domain = data->abox_data->iommu_domain; + int id = data->id; + + iommu_unmap(iommu_domain, IOVA_RDMA_BUFFER(id), BUFFER_BYTES_MAX); +#endif + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int abox_rdma_probe(struct snd_soc_platform *platform) +{ + struct device *dev = platform->dev; + struct abox_platform_data *data = + snd_soc_platform_get_drvdata(platform); + int ret; + + if (data->type == PLATFORM_COMPRESS) { + struct abox_compr_data *compr_data = &data->compr_data; + + ret = snd_soc_add_platform_controls(platform, + abox_rdma_compr_controls, + ARRAY_SIZE(abox_rdma_compr_controls)); + if (ret < 0) { + dev_err(dev, "add platform control failed: %d\n", ret); + return ret; + } +#ifdef COMPR_USE_FIXED_MEMORY + compr_data->dma_size = abox_rdma_compr_caps.max_fragments * + abox_rdma_compr_caps.max_fragment_size; + compr_data->dma_area = dmam_alloc_coherent(dev, + compr_data->dma_size, &compr_data->dma_addr, + GFP_KERNEL); + if (compr_data->dma_area == NULL) { + dev_err(dev, "dma memory allocation failed: %lu\n", + PTR_ERR(compr_data->dma_area)); + return -ENOMEM; + } + ret = iommu_map(data->abox_data->iommu_domain, + IOVA_COMPR_BUFFER(data->id), + compr_data->dma_addr, + round_up(compr_data->dma_size, PAGE_SIZE), 0); + if (ret < 0) { + dev_err(dev, "dma memory iommu map failed: %d\n", ret); + return ret; + } +#endif + } + + return 0; +} + +static int abox_rdma_remove(struct snd_soc_platform *platform) +{ + struct abox_platform_data *data = + snd_soc_platform_get_drvdata(platform); + + if (data->type == PLATFORM_COMPRESS) { + struct abox_compr_data *compr_data = &data->compr_data; + + iommu_unmap(data->abox_data->iommu_domain, + IOVA_RDMA_BUFFER(data->id), + round_up(compr_data->dma_size, PAGE_SIZE)); + } + + return 0; +} + +struct snd_soc_platform_driver abox_rdma = { + .probe = abox_rdma_probe, + .remove = abox_rdma_remove, + .compr_ops = &abox_rdma_compr_ops, + .ops = &abox_rdma_ops, + .pcm_new = abox_rdma_new, + .pcm_free = abox_rdma_free, +}; + +static int abox_rdma_runtime_suspend(struct device *dev) +{ + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + + dev_info(dev, "%s[%d]\n", __func__, id); + + abox_mailbox_save(dev); + return 0; +} + +static int abox_rdma_runtime_resume(struct device *dev) +{ + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + + dev_info(dev, "%s[%d]\n", __func__, id); + + abox_mailbox_restore(dev); + return 0; +} + +static int samsung_abox_rdma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct abox_platform_data *data; + int ret; + const char *type; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + + data->sfr_base = devm_not_request_and_map(pdev, "sfr", 0, NULL, NULL); + if (IS_ERR(data->sfr_base)) + return PTR_ERR(data->sfr_base); + + data->pdev_abox = to_platform_device(pdev->dev.parent); + if (!data->pdev_abox) { + dev_err(dev, "Failed to get abox platform device\n"); + return -EPROBE_DEFER; + } + data->abox_data = platform_get_drvdata(data->pdev_abox); + + spin_lock_init(&data->compr_data.lock); + spin_lock_init(&data->compr_data.cmd_lock); + init_waitqueue_head(&data->compr_data.flush_wait); + init_waitqueue_head(&data->compr_data.exit_wait); + init_waitqueue_head(&data->compr_data.ipc_wait); + data->compr_data.isr_handler = abox_rdma_compr_isr_handler; + + abox_register_irq_handler(&data->pdev_abox->dev, IPC_PCMPLAYBACK, + abox_rdma_irq_handler, pdev); + + ret = of_property_read_u32_index(np, "id", 0, &data->id); + if (ret < 0) { + dev_err(dev, "id property reading fail\n"); + return ret; + } + + ret = of_property_read_string(np, "type", &type); + if (ret < 0) + return ret; + + if (!strncmp(type, "call", sizeof("call"))) + data->type = PLATFORM_CALL; + else if (!strncmp(type, "compress", sizeof("compress"))) + data->type = PLATFORM_COMPRESS; + else if (!strncmp(type, "realtime", sizeof("realtime"))) + data->type = PLATFORM_REALTIME; + else if (!strncmp(type, "vi-sensing", sizeof("vi-sensing"))) + data->type = PLATFORM_VI_SENSING; + else if (!strncmp(type, "sync", sizeof("sync"))) + data->type = PLATFORM_SYNC; + else + data->type = PLATFORM_NORMAL; + + data->scsc_bt = !!of_find_property(np, "scsc_bt", NULL); + + ret = of_property_read_u32_array(np, "pm_qos_lit", data->pm_qos_lit, + ARRAY_SIZE(data->pm_qos_lit)); + if (ret < 0) + dev_dbg(dev, "Failed to read %s: %d\n", "pm_qos_lit", ret); + + ret = of_property_read_u32_array(np, "pm_qos_big", data->pm_qos_big, + ARRAY_SIZE(data->pm_qos_big)); + if (ret < 0) + dev_dbg(dev, "Failed to read %s: %d\n", "pm_qos_big", ret); + + ret = of_property_read_u32_array(np, "pm_qos_hmp", data->pm_qos_hmp, + ARRAY_SIZE(data->pm_qos_hmp)); + if (ret < 0) + dev_dbg(dev, "Failed to read %s: %d\n", "pm_qos_hmp", ret); + + abox_register_rdma(data->abox_data->pdev, pdev, data->id); + + if (data->type == PLATFORM_COMPRESS) { + data->mailbox_base = devm_not_request_and_map(pdev, "mailbox", + 1, NULL, NULL); + if (IS_ERR(data->mailbox_base)) + return PTR_ERR(data->mailbox_base); + + data->mailbox = devm_regmap_init_mmio(dev, + data->mailbox_base, + &abox_mailbox_config); + if (IS_ERR(data->mailbox)) + return PTR_ERR(data->mailbox); + + pm_runtime_set_autosuspend_delay(dev, 1); + pm_runtime_use_autosuspend(dev); + } else { + pm_runtime_no_callbacks(dev); + } + pm_runtime_enable(dev); + + return snd_soc_register_platform(&pdev->dev, &abox_rdma); +} + +static int samsung_abox_rdma_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static const struct of_device_id samsung_abox_rdma_match[] = { + { + .compatible = "samsung,abox-rdma", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_rdma_match); + +static const struct dev_pm_ops samsung_abox_rdma_pm = { + SET_RUNTIME_PM_OPS(abox_rdma_runtime_suspend, + abox_rdma_runtime_resume, NULL) +}; + +static struct platform_driver samsung_abox_rdma_driver = { + .probe = samsung_abox_rdma_probe, + .remove = samsung_abox_rdma_remove, + .driver = { + .name = "samsung-abox-rdma", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_rdma_match), + .pm = &samsung_abox_rdma_pm, + }, +}; + +module_platform_driver(samsung_abox_rdma_driver); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box RDMA Driver"); +MODULE_ALIAS("platform:samsung-abox-rdma"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_util.c b/sound/soc/samsung/abox/abox_util.c new file mode 100644 index 000000000000..4875035dc9eb --- /dev/null +++ b/sound/soc/samsung/abox/abox_util.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include + +#include "abox_util.h" + +void __iomem *devm_not_request_and_map(struct platform_device *pdev, + const char *name, unsigned int num, phys_addr_t *phys_addr, + size_t *size) +{ + struct resource *res; + void __iomem *ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, num); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to get %s\n", name); + return ERR_PTR(-EINVAL); + } + if (phys_addr) + *phys_addr = res->start; + if (size) + *size = resource_size(res); + + ret = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(ret)) { + dev_err(&pdev->dev, "Failed to map %s\n", name); + return ERR_PTR(-EFAULT); + } + + dev_dbg(&pdev->dev, "%s: %s(%p) is mapped on %p with size of %zu", + __func__, name, (void *)res->start, ret, + (size_t)resource_size(res)); + + return ret; +} + +void __iomem *devm_request_and_map(struct platform_device *pdev, + const char *name, unsigned int num, phys_addr_t *phys_addr, + size_t *size) +{ + struct resource *res; + void __iomem *ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, num); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to get %s\n", name); + return ERR_PTR(-EINVAL); + } + if (phys_addr) + *phys_addr = res->start; + if (size) + *size = resource_size(res); + + res = devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), name); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to request %s\n", name); + return ERR_PTR(-EFAULT); + } + + ret = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(ret)) { + dev_err(&pdev->dev, "Failed to map %s\n", name); + return ERR_PTR(-EFAULT); + } + + dev_dbg(&pdev->dev, "%s: %s(%p) is mapped on %p with size of %zu", + __func__, name, (void *)res->start, ret, + (size_t)resource_size(res)); + + return ret; +} + +void __iomem *devm_request_and_map_byname(struct platform_device *pdev, + const char *name, phys_addr_t *phys_addr, size_t *size) +{ + struct resource *res; + void __iomem *ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to get %s\n", name); + return ERR_PTR(-EINVAL); + } + if (phys_addr) + *phys_addr = res->start; + if (size) + *size = resource_size(res); + + res = devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), name); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to request %s\n", name); + return ERR_PTR(-EFAULT); + } + + ret = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(ret)) { + dev_err(&pdev->dev, "Failed to map %s\n", name); + return ERR_PTR(-EFAULT); + } + + dev_dbg(&pdev->dev, "%s: %s(%p) is mapped on %p with size of %zu", + __func__, name, (void *)res->start, ret, + (size_t)resource_size(res)); + + return ret; +} + +struct clk *devm_clk_get_and_prepare(struct platform_device *pdev, + const char *name) +{ + struct device *dev = &pdev->dev; + struct clk *clk; + int ret; + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + dev_err(dev, "Failed to get clock %s\n", name); + goto error; + } + + ret = clk_prepare(clk); + if (ret < 0) { + dev_err(dev, "Failed to prepare clock %s\n", name); + goto error; + } + +error: + return clk; +} + +u32 readl_phys(phys_addr_t addr) +{ + u32 ret; + void __iomem *virt = ioremap(addr, 0x4); + + ret = readl(virt); + pr_debug("%pa = %08x\n", &addr, ret); + iounmap(virt); + + return ret; +} + +void writel_phys(unsigned int val, phys_addr_t addr) +{ + void __iomem *virt = ioremap(addr, 0x4); + + writel(val, virt); + pr_debug("%pa <= %08x\n", &addr, val); + iounmap(virt); +} + +bool is_secure_gic(void) +{ + pr_debug("%s: %08x, %08x\n", __func__, readl_phys(0x10000000), + readl_phys(0x10000010)); + return (readl_phys(0x10000000) == 0xE8895000) && + (readl_phys(0x10000010) == 0x0); +} + +u64 width_range_to_bits(unsigned int width_min, unsigned int width_max) +{ + static const struct { + unsigned int width; + u64 format; + } map[] = { + { 8, SNDRV_PCM_FMTBIT_S8 }, + { 16, SNDRV_PCM_FMTBIT_S16 }, + { 24, SNDRV_PCM_FMTBIT_S24 }, + { 32, SNDRV_PCM_FMTBIT_S32 }, + }; + + int i; + u64 fmt = 0; + + for (i = 0; i < ARRAY_SIZE(map); i++) { + if (map[i].width >= width_min && map[i].width <= width_max) + fmt |= map[i].format; + } + + return fmt; +} + +char substream_to_char(struct snd_pcm_substream *substream) +{ + return (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 'p' : 'c'; +} \ No newline at end of file diff --git a/sound/soc/samsung/abox/abox_util.h b/sound/soc/samsung/abox/abox_util.h new file mode 100644 index 000000000000..cecd95a13d03 --- /dev/null +++ b/sound/soc/samsung/abox/abox_util.h @@ -0,0 +1,137 @@ +/* sound/soc/samsung/abox/abox_util.h + * + * ALSA SoC - Samsung Abox utility + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_UTIL_H +#define __SND_SOC_ABOX_UTIL_H + +#include + +/** + * ioremap to virtual address but not request + * @param[in] pdev pointer to platform device structure + * @param[in] name name of resource + * @param[in] num index of resource + * @param[out] phys_addr physical address of the resource + * @param[out] size size of the resource + * @return virtual address + */ +extern void __iomem *devm_not_request_and_map(struct platform_device *pdev, + const char *name, unsigned int num, phys_addr_t *phys_addr, + size_t *size); + +/** + * Request memory resource and map to virtual address + * @param[in] pdev pointer to platform device structure + * @param[in] name name of resource + * @param[in] num index of resource + * @param[out] phys_addr physical address of the resource + * @param[out] size size of the resource + * @return virtual address + */ +extern void __iomem *devm_request_and_map(struct platform_device *pdev, + const char *name, unsigned int num, phys_addr_t *phys_addr, + size_t *size); + +/** + * Request memory resource and map to virtual address + * @param[in] pdev pointer to platform device structure + * @param[in] name name of resource + * @param[out] phys_addr physical address of the resource + * @param[out] size size of the resource + * @return virtual address + */ +extern void __iomem *devm_request_and_map_byname(struct platform_device *pdev, + const char *name, phys_addr_t *phys_addr, size_t *size); + +/** + * Request clock and prepare + * @param[in] pdev pointer to platform device structure + * @param[in] name name of clock + * @return pointer to clock + */ +extern struct clk *devm_clk_get_and_prepare(struct platform_device *pdev, + const char *name); + +/** + * Read single long physical address (sleeping function) + * @param[in] addr physical address + * @return value of the physical address + */ +extern u32 readl_phys(phys_addr_t addr); + +/** + * Write single long physical address (sleeping function) + * @param[in] val value + * @param[in] addr physical address + */ +extern void writel_phys(unsigned int val, phys_addr_t addr); + +/** + * Atomically increments @v, if @v was @r, set to 0. + * @param[in] v pointer of type atomic_t + * @param[in] r maximum range of @v. + * @return Returns old value + */ +static inline int atomic_inc_unless_in_range(atomic_t *v, int r) +{ + int ret; + + while ((ret = __atomic_add_unless(v, 1, r)) == r) { + ret = atomic_cmpxchg(v, r, 0); + if (ret == r) + break; + } + + return ret; +} + +/** + * Atomically decrements @v, if @v was 0, set to @r. + * @param[in] v pointer of type atomic_t + * @param[in] r maximum range of @v. + * @return Returns old value + */ +static inline int atomic_dec_unless_in_range(atomic_t *v, int r) +{ + int ret; + + while ((ret = __atomic_add_unless(v, -1, 0)) == 0) { + ret = atomic_cmpxchg(v, 0, r); + if (ret == 0) + break; + } + + return ret; +} + +/** + * Check whether the GIC is secure (sleeping function) + * @return true if the GIC is secure, false on otherwise + */ +extern bool is_secure_gic(void); + +/** + * Get SNDRV_PCM_FMTBIT_* within width_min and width_max. + * @param[in] width_min minimum bit width + * @param[in] width_max maximum bit width + * @return Bitwise and of SNDRV_PCM_FMTBIT_* + */ +extern u64 width_range_to_bits(unsigned int width_min, + unsigned int width_max); + +/** + * Get character from substream direction + * @param[in] substream substream + * @return 'p' if direction is playback. 'c' if not. + */ +extern char substream_to_char(struct snd_pcm_substream *substream); + +#endif /* __SND_SOC_ABOX_UTIL_H */ diff --git a/sound/soc/samsung/abox/abox_vdma.c b/sound/soc/samsung/abox/abox_vdma.c new file mode 100644 index 000000000000..9befb44e8eee --- /dev/null +++ b/sound/soc/samsung/abox/abox_vdma.c @@ -0,0 +1,739 @@ +/* sound/soc/samsung/abox/abox_vdma.c + * + * ALSA SoC Audio Layer - Samsung Abox Virtual DMA driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#undef DEBUG +#include +#include +#include +#include +#include +#include +#include + +#include "abox_util.h" +#include "abox.h" +#include "abox_vdma.h" + +#undef TEST +#define VDMA_COUNT_MAX SZ_32 +#define NAME_LENGTH SZ_32 + + +struct abox_vdma_rtd { + struct snd_dma_buffer buffer; + struct snd_pcm_hardware hardware; + struct snd_pcm_substream *substream; + unsigned long iova; + size_t pointer; + bool ack_enabled; +}; + +struct abox_vdma_info { + struct device *dev; + int id; + char name[NAME_LENGTH]; + struct abox_vdma_rtd rtd[SNDRV_PCM_STREAM_LAST + 1]; +}; + +static struct device *abox_vdma_dev_abox; +static struct abox_vdma_info abox_vdma_list[VDMA_COUNT_MAX]; + +static int abox_vdma_get_idx(int id) +{ + return id - PCMTASK_VDMA_ID_BASE; +} + +static unsigned long abox_vdma_get_iova(int id, int stream) +{ + int idx = abox_vdma_get_idx(id); + long ret; + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + case SNDRV_PCM_STREAM_CAPTURE: + ret = IOVA_VDMA_BUFFER(idx) + (SZ_512K * stream); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct abox_vdma_info *abox_vdma_get_info(int id) +{ + int idx = abox_vdma_get_idx(id); + + if (idx < 0 || idx >= ARRAY_SIZE(abox_vdma_list)) + return NULL; + + return &abox_vdma_list[idx]; +} + +static struct abox_vdma_rtd *abox_vdma_get_rtd(struct abox_vdma_info *info, + int stream) +{ + if (!info || stream < 0 || stream >= ARRAY_SIZE(info->rtd)) + return NULL; + + return &info->rtd[stream]; +} + +static int abox_vdma_request_ipc(ABOX_IPC_MSG *msg, int atomic, int sync) +{ + return abox_request_ipc(abox_vdma_dev_abox, msg->ipcid, msg, + sizeof(*msg), atomic, sync); +} + +int abox_vdma_period_elapsed(struct abox_vdma_info *info, + struct abox_vdma_rtd *rtd, size_t pointer) +{ + dev_dbg(info->dev, "%s[%d:%c](%zx)\n", __func__, info->id, + substream_to_char(rtd->substream), pointer); + + rtd->pointer = pointer - rtd->iova; + snd_pcm_period_elapsed(rtd->substream); + + return 0; +} + +static int abox_vdma_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform); + struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream); + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream)); + + snd_soc_set_runtime_hwparams(substream, &rtd->hardware); + + msg.ipcid = abox_stream_to_ipcid(substream->stream); + msg.task_id = pcmtask_msg->channel_id = id; + pcmtask_msg->msgtype = PCM_PLTDAI_OPEN; + + return abox_vdma_request_ipc(&msg, 0, 0); +} + +static int abox_vdma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream)); + + msg.ipcid = abox_stream_to_ipcid(substream->stream); + msg.task_id = pcmtask_msg->channel_id = id; + pcmtask_msg->msgtype = PCM_PLTDAI_CLOSE; + return abox_vdma_request_ipc(&msg, 0, 0); +} + +static int abox_vdma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform); + struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream); + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + int ret; + + dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream)); + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + msg.ipcid = abox_stream_to_ipcid(substream->stream); + msg.task_id = pcmtask_msg->channel_id = id; + + pcmtask_msg->msgtype = PCM_SET_BUFFER; + pcmtask_msg->param.setbuff.phyaddr = rtd->iova; + pcmtask_msg->param.setbuff.size = params_period_bytes(params); + pcmtask_msg->param.setbuff.count = params_periods(params); + ret = abox_vdma_request_ipc(&msg, 0, 0); + if (ret < 0) + return ret; + + pcmtask_msg->msgtype = PCM_PLTDAI_HW_PARAMS; + pcmtask_msg->param.hw_params.sample_rate = params_rate(params); + pcmtask_msg->param.hw_params.bit_depth = params_width(params); + pcmtask_msg->param.hw_params.channels = params_channels(params); + ret = abox_vdma_request_ipc(&msg, 0, 0); + if (ret < 0) + return ret; + + return ret; +} + +static int abox_vdma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream)); + + msg.ipcid = abox_stream_to_ipcid(substream->stream); + msg.task_id = pcmtask_msg->channel_id = id; + pcmtask_msg->msgtype = PCM_PLTDAI_HW_FREE; + abox_vdma_request_ipc(&msg, 0, 0); + + return snd_pcm_lib_free_pages(substream); +} + +static int abox_vdma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform); + struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream); + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream)); + + rtd->pointer = 0; + + msg.ipcid = abox_stream_to_ipcid(substream->stream); + msg.task_id = pcmtask_msg->channel_id = id; + pcmtask_msg->msgtype = PCM_PLTDAI_PREPARE; + return abox_vdma_request_ipc(&msg, 0, 0); +} + +static int abox_vdma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + struct platform_device *pdev_abox; + int ret; + + dev_info(dev, "%s[%d:%c](%d)\n", __func__, id, + substream_to_char(substream), cmd); + + pdev_abox = to_platform_device(abox_vdma_dev_abox); + + msg.ipcid = abox_stream_to_ipcid(substream->stream); + msg.task_id = pcmtask_msg->channel_id = id; + pcmtask_msg->msgtype = PCM_PLTDAI_TRIGGER; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (memblock_is_memory(substream->runtime->dma_addr)) + abox_request_dram_on(pdev_abox, dev, true); + pcmtask_msg->param.trigger = 1; + ret = abox_vdma_request_ipc(&msg, 1, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pcmtask_msg->param.trigger = 0; + ret = abox_vdma_request_ipc(&msg, 1, 0); + if (memblock_is_memory(substream->runtime->dma_addr)) + abox_request_dram_on(pdev_abox, dev, false); + break; + default: + dev_err(dev, "invalid command: %d\n", cmd); + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t abox_vdma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform); + struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream); + + dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream)); + + return bytes_to_frames(substream->runtime, rtd->pointer); +} + +static int abox_vdma_ack(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *pcm_rtd = substream->runtime; + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_platform *platform = soc_rtd->platform; + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform); + struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream); + snd_pcm_uframes_t appl_ptr = pcm_rtd->control->appl_ptr; + snd_pcm_uframes_t appl_ofs = appl_ptr % pcm_rtd->buffer_size; + ssize_t appl_bytes = frames_to_bytes(pcm_rtd, appl_ofs); + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + if (!rtd->ack_enabled) + return 0; + + dev_dbg(dev, "%s[%d:%c]: %zd\n", __func__, id, + substream_to_char(substream), appl_bytes); + + msg.ipcid = abox_stream_to_ipcid(substream->stream); + msg.task_id = pcmtask_msg->channel_id = id; + pcmtask_msg->msgtype = PCM_PLTDAI_ACK; + pcmtask_msg->param.pointer = (unsigned int)appl_bytes; + + return abox_vdma_request_ipc(&msg, 0, 0); +} + +static struct snd_pcm_ops abox_vdma_platform_ops = { + .open = abox_vdma_open, + .close = abox_vdma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = abox_vdma_hw_params, + .hw_free = abox_vdma_hw_free, + .prepare = abox_vdma_prepare, + .trigger = abox_vdma_trigger, + .pointer = abox_vdma_pointer, + .ack = abox_vdma_ack, +}; + +static int abox_vdma_platform_probe(struct snd_soc_platform *platform) +{ + struct device *dev = platform->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + snd_soc_platform_set_drvdata(platform, abox_vdma_get_info(id)); + return 0; +} + +static int abox_vdma_platform_new(struct snd_soc_pcm_runtime *soc_rtd) +{ + struct device *dev = soc_rtd->platform->dev; + struct device *dev_abox = abox_vdma_dev_abox; + struct snd_pcm *pcm = soc_rtd->pcm; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = abox_vdma_get_info(id); + int i, ret; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) { + struct snd_pcm_substream *substream = pcm->streams[i].substream; + + if (!substream) + continue; + + if (info->rtd[i].iova == 0) + info->rtd[i].iova = abox_vdma_get_iova(id, i); + + if (info->rtd[i].buffer.bytes == 0) + info->rtd[i].buffer.bytes = BUFFER_BYTES_MAX; + + if (info->rtd[i].buffer.addr) { + substream->dma_buffer = info->rtd[i].buffer; + } else { + size_t size = info->rtd[i].buffer.bytes; + + ret = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV, dev_abox, + size, size); + if (ret < 0) + return ret; + } + + if (abox_iova_to_phys(dev_abox, info->rtd[i].iova) == 0) { + ret = abox_iommu_map(dev_abox, info->rtd[i].iova, + substream->dma_buffer.addr, + substream->dma_buffer.bytes); + if (ret < 0) + return ret; + } + + info->rtd[i].substream = substream; + } + + return 0; +} + +static void abox_vdma_platform_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *soc_rtd = pcm->private_data; + struct device *dev = soc_rtd->platform->dev; + struct device *dev_abox = abox_vdma_dev_abox; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = abox_vdma_get_info(id); + int i; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) { + struct snd_pcm_substream *substream = pcm->streams[i].substream; + + if (!substream) + continue; + + info->rtd[i].substream = NULL; + + if (!info->rtd[i].buffer.addr) { + abox_iommu_unmap(dev_abox, info->rtd[i].iova, + substream->dma_buffer.addr, + substream->dma_buffer.bytes); + snd_pcm_lib_preallocate_free(substream); + } + } +} + +struct snd_soc_platform_driver abox_vdma_platform = { + .probe = abox_vdma_platform_probe, + .ops = &abox_vdma_platform_ops, + .pcm_new = abox_vdma_platform_new, + .pcm_free = abox_vdma_platform_free, +}; + +static irqreturn_t abox_vdma_irq_handler(int ipc_id, void *dev_id, + ABOX_IPC_MSG *msg) +{ + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask; + int id = pcmtask_msg->channel_id; + int stream = abox_ipcid_to_stream(ipc_id); + struct abox_vdma_info *info = abox_vdma_get_info(id); + struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, stream); + + if (!info || !rtd) + return IRQ_NONE; + + switch (pcmtask_msg->msgtype) { + case PCM_PLTDAI_POINTER: + abox_vdma_period_elapsed(info, rtd, pcmtask_msg->param.pointer); + break; + case PCM_PLTDAI_ACK: + rtd->ack_enabled = !!pcmtask_msg->param.trigger; + break; + case PCM_PLTDAI_REGISTER: + { + struct PCMTASK_HARDWARE *hardware; + struct device *dev_abox = dev_id; + struct abox_data *data = dev_get_drvdata(dev_abox); + + hardware = &pcmtask_msg->param.hardware; + abox_vdma_register(dev_abox, id, stream, + abox_addr_to_kernel_addr(data, hardware->addr), + abox_addr_to_phys_addr(data, hardware->addr), + hardware); + break; + } + default: + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static struct snd_soc_dai_link abox_vdma_dai_links[VDMA_COUNT_MAX]; +static struct snd_soc_card abox_vdma_card = { + .name = "abox_vdma", + .owner = THIS_MODULE, + .dai_link = abox_vdma_dai_links, + .num_links = 0, +}; + +void abox_vdma_register_work_func(struct work_struct *work) +{ + int id; + struct abox_vdma_info *info; + + dev_dbg(abox_vdma_dev_abox, "%s\n", __func__); + + if (!abox_vdma_card.dev) { + platform_device_register_data(abox_vdma_dev_abox, + "samsung-abox-vdma", -1, NULL, 0); + } + + for (info = abox_vdma_list; (info - abox_vdma_list) < + ARRAY_SIZE(abox_vdma_list); info++) { + id = info->id; + if (info->dev == abox_vdma_dev_abox) { + dev_dbg(info->dev, "%s[%d]\n", __func__, id); + platform_device_register_data(info->dev, + "samsung-abox-vdma", id, NULL, 0); + } + } +} + +static DECLARE_WORK(abox_vdma_register_work, abox_vdma_register_work_func); + +int abox_vdma_register(struct device *dev, int id, int stream, + void *area, phys_addr_t addr, + const struct PCMTASK_HARDWARE *pcm_hardware) +{ + struct abox_vdma_info *info = abox_vdma_get_info(id); + struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, stream); + struct snd_dma_buffer *buffer = &rtd->buffer; + struct snd_pcm_hardware *hardware = &rtd->hardware; + + if (!info || !rtd) + return -EINVAL; + + if (info->dev && rtd->iova) + return -EEXIST; + + dev_info(dev, "%s(%d, %s, %d, %p, %pa, %u)\n", __func__, + id, pcm_hardware->name, stream, area, &addr, + pcm_hardware->buffer_bytes_max); + + info->id = id; + strncpy(info->name, pcm_hardware->name, sizeof(info->name) - 1); + + rtd->iova = pcm_hardware->addr; + + buffer->dev.type = SNDRV_DMA_TYPE_DEV; + buffer->dev.dev = dev; + buffer->area = area; + buffer->addr = addr; + buffer->bytes = pcm_hardware->buffer_bytes_max; + + hardware->info = SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID; + hardware->formats = width_range_to_bits(pcm_hardware->width_min, + pcm_hardware->width_max); + hardware->rates = (pcm_hardware->rate_max > 192000) ? + SNDRV_PCM_RATE_KNOT : snd_pcm_rate_range_to_bits( + pcm_hardware->rate_min, pcm_hardware->rate_max); + hardware->rate_min = pcm_hardware->rate_min; + hardware->rate_max = pcm_hardware->rate_max; + hardware->channels_min = pcm_hardware->channels_min; + hardware->channels_max = pcm_hardware->channels_max; + hardware->buffer_bytes_max = pcm_hardware->buffer_bytes_max; + hardware->period_bytes_min = pcm_hardware->period_bytes_min; + hardware->period_bytes_max = pcm_hardware->period_bytes_max; + hardware->periods_min = pcm_hardware->periods_min; + hardware->periods_max = pcm_hardware->periods_max; + + abox_vdma_dev_abox = info->dev = dev; + schedule_work(&abox_vdma_register_work); + + return 0; +} + +static void abox_vdma_register_card_work_func(struct work_struct *work) +{ + int i; + + dev_dbg(abox_vdma_dev_abox, "%s\n", __func__); + + snd_soc_unregister_card(&abox_vdma_card); + + for (i = 0; i < abox_vdma_card.num_links; i++) { + struct snd_soc_dai_link *link = &abox_vdma_card.dai_link[i]; + + if (link->name) + continue; + + link->name = link->stream_name = + kasprintf(GFP_KERNEL, "dummy%d", i); + link->stream_name = link->name; + link->cpu_name = "snd-soc-dummy"; + link->cpu_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->no_pcm = 1; + } + + snd_soc_register_card(&abox_vdma_card); +} + +DECLARE_DELAYED_WORK(abox_vdma_register_card_work, + abox_vdma_register_card_work_func); + +static int abox_vdma_add_dai_link(struct device *dev) +{ + int id = to_platform_device(dev)->id; + int idx = abox_vdma_get_idx(id); + struct abox_vdma_info *info = abox_vdma_get_info(id); + struct snd_soc_dai_link *link = &abox_vdma_dai_links[idx]; + struct abox_vdma_rtd *playback, *capture; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + if (idx > ARRAY_SIZE(abox_vdma_dai_links)) { + dev_err(dev, "Too many request\n"); + return -ENOMEM; + } + + cancel_delayed_work_sync(&abox_vdma_register_card_work); + + kfree(link->name); + link->name = link->stream_name = kstrdup(info->name, GFP_KERNEL); + link->cpu_name = "snd-soc-dummy"; + link->cpu_dai_name = "snd-soc-dummy-dai"; + link->platform_name = dev_name(dev); + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->ignore_suspend = 1; + link->ignore_pmdown_time = 1; + link->no_pcm = 0; + playback = &info->rtd[SNDRV_PCM_STREAM_PLAYBACK]; + capture = &info->rtd[SNDRV_PCM_STREAM_CAPTURE]; + link->playback_only = playback->buffer.area && !capture->buffer.area; + link->capture_only = !playback->buffer.area && capture->buffer.area; + + if (abox_vdma_card.num_links <= idx) + abox_vdma_card.num_links = idx + 1; + + schedule_delayed_work(&abox_vdma_register_card_work, HZ); + + return 0; +} + +static int samsung_abox_vdma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int id = to_platform_device(dev)->id; + struct abox_vdma_info *info = abox_vdma_get_info(id); + int ret = 0; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + if (id <= 0) { + abox_vdma_card.dev = &pdev->dev; + } else { + info->dev = dev; + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); + devm_snd_soc_register_platform(dev, &abox_vdma_platform); + ret = abox_vdma_add_dai_link(dev); + } + + return ret; +} + +static int samsung_abox_vdma_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int id = to_platform_device(dev)->id; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + return 0; +} + +static const struct platform_device_id samsung_abox_vdma_driver_ids[] = { + { + .name = "samsung-abox-vdma", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, samsung_abox_vdma_driver_ids); + +static struct platform_driver samsung_abox_vdma_driver = { + .probe = samsung_abox_vdma_probe, + .remove = samsung_abox_vdma_remove, + .driver = { + .name = "samsung-abox-vdma", + .owner = THIS_MODULE, + }, + .id_table = samsung_abox_vdma_driver_ids, +}; + +module_platform_driver(samsung_abox_vdma_driver); + +#ifdef TEST +static unsigned char test_buf[4096]; + +static void test_work_func(struct work_struct *work) +{ + struct abox_vdma_info *info = &abox_vdma_list[2]; + static unsigned char i; + int j; + + pr_debug("%s: %d\n", __func__, i); + + for (j = 0; j < 1024; j++, i++) { + test_buf[i % ARRAY_SIZE(test_buf)] = i; + } + abox_vdma_period_elapsed(info, &info->rtd[0], i % ARRAY_SIZE(test_buf)); + abox_vdma_period_elapsed(info, &info->rtd[1], i % ARRAY_SIZE(test_buf)); + schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(1000)); +} +DECLARE_DELAYED_WORK(test_work, test_work_func); + +static const struct PCMTASK_HARDWARE test_hardware = { + .name = "test01", + .addr = 0x12345678, + .width_min = 16, + .width_max = 24, + .rate_min = 48000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4096, + .period_bytes_min = 1024, + .period_bytes_max = 2048, + .periods_min = 2, + .periods_max = 4, +}; +#endif + +static int __init samsung_abox_vdma_initcall(void) +{ + struct abox_data *data = abox_get_abox_data(); + struct device *dev_abox; + + if (!data) + return 0; + + pr_info("%s\n", __func__); + + dev_abox = &abox_get_abox_data()->pdev->dev; + abox_register_irq_handler(dev_abox, IPC_PCMPLAYBACK, + abox_vdma_irq_handler, dev_abox); + abox_register_irq_handler(dev_abox, IPC_PCMCAPTURE, + abox_vdma_irq_handler, dev_abox); +#ifdef TEST + abox_vdma_register(dev_abox, 102, SNDRV_PCM_STREAM_PLAYBACK, test_buf, + virt_to_phys(test_buf), &test_hardware); + abox_vdma_register(dev_abox, 102, SNDRV_PCM_STREAM_CAPTURE, test_buf, + virt_to_phys(test_buf), &test_hardware); + schedule_delayed_work(&test_work, HZ * 5); +#endif + return 0; +} +late_initcall(samsung_abox_vdma_initcall); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box Virtual DMA Driver"); +MODULE_ALIAS("platform:samsung-abox-vdma"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_vdma.h b/sound/soc/samsung/abox/abox_vdma.h new file mode 100644 index 000000000000..807fd0f68a07 --- /dev/null +++ b/sound/soc/samsung/abox/abox_vdma.h @@ -0,0 +1,29 @@ +/* sound/soc/samsung/abox/abox_vdma.h + * + * ALSA SoC Audio Layer - Samsung Abox Virtual DMA driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_ABOX_VDMA_H +#define __SND_SOC_ABOX_VDMA_H + +/** + * Register abox dump buffer + * @param[in] dev pointer to abox device + * @param[in] id unique buffer id + * @param[in] stream SNDRV_PCM_STREAM_* + * @param[in] area virtual address of the buffer + * @param[in] addr pysical address of the buffer + * @param[in] pcm_hardware hardware information of virtual DMA + * @return error code if any + */ +extern int abox_vdma_register(struct device *dev, int id, int stream, + void *area, phys_addr_t addr, + const struct PCMTASK_HARDWARE *pcm_hardware); + +#endif /* __SND_SOC_ABOX_VDMA_H */ diff --git a/sound/soc/samsung/abox/abox_vss.c b/sound/soc/samsung/abox/abox_vss.c new file mode 100644 index 000000000000..c56dad517f0e --- /dev/null +++ b/sound/soc/samsung/abox/abox_vss.c @@ -0,0 +1,68 @@ +/* sound/soc/samsung/abox/abox_vss.c + * + * ALSA SoC Audio Layer - Samsung Abox VSS driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include + +#include "abox.h" + +static unsigned int VSS_MAGIC_OFFSET = 0x500000; + +static int samsung_abox_vss_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + void __iomem *magic_addr; + + dev_dbg(dev, "%s\n", __func__); + + of_property_read_u32(np, "magic_offset", &VSS_MAGIC_OFFSET); + dev_info(dev, "magic_offset = 0x%08X\n", VSS_MAGIC_OFFSET); + magic_addr = phys_to_virt(shm_get_vss_base() + VSS_MAGIC_OFFSET); + writel(0, magic_addr); + return 0; +} + +static int samsung_abox_vss_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + return 0; +} + +static const struct of_device_id samsung_abox_vss_match[] = { + { + .compatible = "samsung,abox-vss", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_vss_match); + +static struct platform_driver samsung_abox_vss_driver = { + .probe = samsung_abox_vss_probe, + .remove = samsung_abox_vss_remove, + .driver = { + .name = "samsung-abox-vss", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_vss_match), + }, +}; + +module_platform_driver(samsung_abox_vss_driver); + +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box VSS Driver"); +MODULE_ALIAS("platform:samsung-abox-vss"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/abox/abox_wdma.c b/sound/soc/samsung/abox/abox_wdma.c new file mode 100644 index 000000000000..e7e193256a83 --- /dev/null +++ b/sound/soc/samsung/abox/abox_wdma.c @@ -0,0 +1,653 @@ +/* sound/soc/samsung/abox/abox_wdma.c + * + * ALSA SoC Audio Layer - Samsung Abox WDMA driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../../../../drivers/iommu/exynos-iommu.h" +#include +#include "abox_util.h" +#include "abox_gic.h" +#include "abox_dbg.h" +#ifdef CONFIG_SCSC_BT +#include "abox_bt.h" +#endif +#include "abox.h" + +#define USE_FIXED_MEMORY + +static const struct snd_pcm_hardware abox_wdma_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID, + .formats = ABOX_WDMA_SAMPLE_FORMATS, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = PERIOD_BYTES_MAX, + .periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, +}; + +static int abox_wdma_request_ipc(struct abox_platform_data *data, + ABOX_IPC_MSG *msg, int atomic, int sync) +{ + struct device *dev_abox = &data->pdev_abox->dev; + + return abox_request_ipc(dev_abox, msg->ipcid, msg, sizeof(*msg), + atomic, sync); +} + +static irqreturn_t abox_wdma_irq_handler(int irq, void *dev_id, + ABOX_IPC_MSG *msg) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct abox_platform_data *data = platform_get_drvdata(pdev); + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask; + int id = data->id; + + if (id != pcmtask_msg->channel_id) + return IRQ_NONE; + + dev_dbg(dev, "%s[%d]: ipcid=%d, msgtype=%d\n", __func__, id, + msg->ipcid, pcmtask_msg->msgtype); + + switch (pcmtask_msg->msgtype) { + case PCM_PLTDAI_POINTER: + snd_pcm_period_elapsed(data->substream); + break; + default: + dev_warn(dev, "Unknown pcmtask message: %d\n", + pcmtask_msg->msgtype); + break; + } + + return IRQ_HANDLED; +} + +static int abox_wdma_enabled(struct abox_platform_data *data) +{ + return readl(data->sfr_base + ABOX_WDMA_CTRL) & ABOX_WDMA_ENABLE_MASK; +} + +static void abox_wdma_disable_barrier(struct device *dev, + struct abox_platform_data *data) +{ + int id = data->id; + struct abox_data *abox_data = data->abox_data; + u64 timeout = local_clock() + ABOX_DMA_TIMEOUT_NS; + + while (abox_wdma_enabled(data)) { + if (local_clock() <= timeout) { + cond_resched(); + continue; + } + dev_warn_ratelimited(dev, "WDMA disable timeout[%d]\n", id); + abox_dbg_dump_simple(dev, abox_data, "WDMA disable timeout"); + break; + } +} + +static int abox_wdma_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_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct abox_data *abox_data = data->abox_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(dev, "Memory allocation failed (size:%u)\n", + params_buffer_bytes(params)); + return ret; + } + + pcmtask_msg->channel_id = id; +#ifndef USE_FIXED_MEMORY + ret = iommu_map(abox_data->iommu_domain, IOVA_WDMA_BUFFER(id), + runtime->dma_addr, round_up(runtime->dma_bytes, + PAGE_SIZE), 0); + if (ret < 0) { + dev_err(dev, "dma buffer iommu map failed\n"); + return ret; + } +#endif + msg.ipcid = IPC_PCMCAPTURE; + msg.task_id = pcmtask_msg->channel_id = id; + + pcmtask_msg->msgtype = PCM_SET_BUFFER; + pcmtask_msg->param.setbuff.phyaddr = IOVA_WDMA_BUFFER(id); +#ifdef CONFIG_SCSC_BT //if (IS_ENABLED(CONFIG_SCSC_BT)) + if (abox_test_quirk(abox_data, ABOX_QUIRK_SCSC_BT) && data->scsc_bt) { + struct device *dev_bt = abox_data->dev_bt; + int stream = substream->stream; + unsigned int iova = abox_bt_get_buf_iova(dev_bt, stream); + + if (abox_bt_active(dev_bt, stream) && iova) + pcmtask_msg->param.setbuff.phyaddr = iova; + } +#endif + pcmtask_msg->param.setbuff.size = params_period_bytes(params); + pcmtask_msg->param.setbuff.count = params_periods(params); + ret = abox_wdma_request_ipc(data, &msg, 0, 0); + if (ret < 0) + return ret; + + pcmtask_msg->msgtype = PCM_PLTDAI_HW_PARAMS; + pcmtask_msg->param.hw_params.sample_rate = params_rate(params); +#ifdef CONFIG_SCSC_BT //if (IS_ENABLED(CONFIG_SCSC_BT)) + if (abox_test_quirk(abox_data, ABOX_QUIRK_SCSC_BT_HACK) && + data->scsc_bt) { + struct device *dev_bt = abox_data->dev_bt; + int stream = substream->stream; + unsigned int rate = abox_bt_get_rate(dev_bt); + + if (abox_bt_active(dev_bt, stream) && rate) + pcmtask_msg->param.hw_params.sample_rate = rate; + } +#endif + pcmtask_msg->param.hw_params.bit_depth = params_width(params); + pcmtask_msg->param.hw_params.channels = params_channels(params); + ret = abox_wdma_request_ipc(data, &msg, 0, 0); + if (ret < 0) + return ret; + + if (params_rate(params) > 48000) + abox_request_cpu_gear(dev, abox_data, dev, + abox_data->cpu_gear_min - 1); + + dev_info(dev, "%s:DmaAddr=%pad Total=%zu PrdSz=%u(%u) #Prds=%u dma_area=%p rate=%u, width=%d, channels=%u\n", + snd_pcm_stream_str(substream), &runtime->dma_addr, + runtime->dma_bytes, params_period_size(params), + params_period_bytes(params), params_periods(params), + runtime->dma_area, params_rate(params), + snd_pcm_format_width(params_format(params)), + params_channels(params)); + + return 0; +} + +static int abox_wdma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + msg.ipcid = IPC_PCMCAPTURE; + pcmtask_msg->msgtype = PCM_PLTDAI_HW_FREE; + msg.task_id = pcmtask_msg->channel_id = id; + abox_wdma_request_ipc(data, &msg, 0, 0); +#ifndef USE_FIXED_MEMORY + iommu_unmap(data->abox_data->iommu_domain, IOVA_WDMA_BUFFER(id), + round_up(substream->runtime->dma_bytes, PAGE_SIZE)); + exynos_sysmmu_tlb_invalidate(data->abox_data->iommu_domain, + (dma_addr_t)IOVA_WDMA_BUFFER(id), + round_up(substream->runtime->dma_bytes, PAGE_SIZE)); +#endif + + switch (data->type) { + default: + abox_wdma_disable_barrier(dev, data); + break; + } + + return snd_pcm_lib_free_pages(substream); +} + +static int abox_wdma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + data->pointer = IOVA_WDMA_BUFFER(id); + + switch (data->type) { + case PLATFORM_CALL: + break; + default: + ret = abox_try_to_asrc_off(dev, data->abox_data, rtd, + SNDRV_PCM_STREAM_CAPTURE); + if (ret < 0) + dev_warn(dev, "abox_try_to_asrc_off: %d\n", ret); + break; + } + + msg.ipcid = IPC_PCMCAPTURE; + pcmtask_msg->msgtype = PCM_PLTDAI_PREPARE; + msg.task_id = pcmtask_msg->channel_id = id; + ret = abox_wdma_request_ipc(data, &msg, 0, 0); + + return ret; +} + +static int abox_wdma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_info(dev, "%s[%d](%d)\n", __func__, id, cmd); + + msg.ipcid = IPC_PCMCAPTURE; + pcmtask_msg->msgtype = PCM_PLTDAI_TRIGGER; + msg.task_id = pcmtask_msg->channel_id = id; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (memblock_is_memory(substream->runtime->dma_addr)) + abox_request_dram_on(data->pdev_abox, dev, true); + + pcmtask_msg->param.trigger = 1; + ret = abox_wdma_request_ipc(data, &msg, 1, 0); + switch (data->type) { + case PLATFORM_REALTIME: + msg.ipcid = IPC_ERAP; + msg.msg.erap.msgtype = REALTIME_START; + ret = abox_wdma_request_ipc(data, &msg, 1, 0); + break; + default: + break; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pcmtask_msg->param.trigger = 0; + ret = abox_wdma_request_ipc(data, &msg, 1, 0); + switch (data->type) { + case PLATFORM_REALTIME: + msg.ipcid = IPC_ERAP; + msg.msg.erap.msgtype = REALTIME_STOP; + ret = abox_wdma_request_ipc(data, &msg, 1, 0); + break; + default: + break; + } + + if (memblock_is_memory(substream->runtime->dma_addr)) + abox_request_dram_on(data->pdev_abox, dev, false); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t abox_wdma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct snd_pcm_runtime *runtime = substream->runtime; + int id = data->id; + ssize_t pointer; + u32 status = readl(data->sfr_base + ABOX_WDMA_STATUS); + bool progress = (status & ABOX_WDMA_PROGRESS_MASK) ? true : false; + + if (data->pointer >= IOVA_WDMA_BUFFER(id)) { + pointer = data->pointer - IOVA_WDMA_BUFFER(id); + } else if (((data->type == PLATFORM_NORMAL) || + (data->type == PLATFORM_SYNC)) && progress) { + ssize_t offset, count; + ssize_t buffer_bytes, period_bytes; + + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + period_bytes = snd_pcm_lib_period_bytes(substream); + + offset = (((status & ABOX_WDMA_RBUF_OFFSET_MASK) >> + ABOX_WDMA_RBUF_OFFSET_L) << 4); + count = (status & ABOX_WDMA_RBUF_CNT_MASK); + + while ((offset % period_bytes) && (buffer_bytes >= 0)) { + buffer_bytes -= period_bytes; + if ((buffer_bytes & offset) == offset) + offset = buffer_bytes; + } + + pointer = offset + count; + } else { + pointer = 0; + } + + dev_dbg(dev, "%s[%d]: pointer=%08zx\n", __func__, id, pointer); + + return bytes_to_frames(runtime, pointer); +} + +static int abox_wdma_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct abox_data *abox_data = data->abox_data; + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + if (data->type == PLATFORM_CALL) { + if (abox_cpu_gear_idle(dev, abox_data, + (void *)ABOX_CPU_GEAR_CALL_VSS)) + abox_request_cpu_gear_sync(dev, abox_data, + (void *)ABOX_CPU_GEAR_CALL_KERNEL, + ABOX_CPU_GEAR_MAX); + ret = abox_request_l2c_sync(dev, abox_data, dev, true); + if (ret < 0) + return ret; + } + abox_request_cpu_gear(dev, abox_data, dev, abox_data->cpu_gear_min); + + snd_soc_set_runtime_hwparams(substream, &abox_wdma_hardware); + + data->substream = substream; + + msg.ipcid = IPC_PCMCAPTURE; + pcmtask_msg->msgtype = PCM_PLTDAI_OPEN; + msg.task_id = pcmtask_msg->channel_id = id; + ret = abox_wdma_request_ipc(data, &msg, 0, 0); + + return ret; +} + +static int abox_wdma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + struct abox_data *abox_data = data->abox_data; + int id = data->id; + int ret; + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + dev_dbg(dev, "%s[%d]\n", __func__, id); + + data->substream = NULL; + + msg.ipcid = IPC_PCMCAPTURE; + pcmtask_msg->msgtype = PCM_PLTDAI_CLOSE; + msg.task_id = pcmtask_msg->channel_id = id; + ret = abox_wdma_request_ipc(data, &msg, 0, 1); + + abox_request_cpu_gear(dev, abox_data, dev, ABOX_CPU_GEAR_MIN); + if (data->type == PLATFORM_CALL) { + abox_request_cpu_gear(dev, abox_data, + (void *)ABOX_CPU_GEAR_CALL_KERNEL, + ABOX_CPU_GEAR_MIN); + ret = abox_request_l2c(dev, abox_data, dev, false); + if (ret < 0) + return ret; + } + + return ret; +} + +static int abox_wdma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + struct snd_pcm_runtime *runtime = substream->runtime; + + dev_info(dev, "%s[%d]\n", __func__, id); + + return dma_mmap_writecombine(dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static int abox_wdma_ack(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + snd_pcm_uframes_t appl_ofs = appl_ptr % runtime->buffer_size; + ssize_t appl_bytes = frames_to_bytes(runtime, appl_ofs); + ABOX_IPC_MSG msg; + struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; + + if (!data->ack_enabled) + return 0; + + dev_dbg(dev, "%s[%d]: %zd\n", __func__, id, appl_bytes); + + msg.ipcid = IPC_PCMCAPTURE; + pcmtask_msg->msgtype = PCM_PLTDAI_ACK; + pcmtask_msg->param.pointer = (unsigned int)appl_bytes; + msg.task_id = pcmtask_msg->channel_id = id; + + return abox_wdma_request_ipc(data, &msg, 0, 0); +} + +static struct snd_pcm_ops abox_wdma_ops = { + .open = abox_wdma_open, + .close = abox_wdma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = abox_wdma_hw_params, + .hw_free = abox_wdma_hw_free, + .prepare = abox_wdma_prepare, + .trigger = abox_wdma_trigger, + .pointer = abox_wdma_pointer, + .mmap = abox_wdma_mmap, + .ack = abox_wdma_ack, +}; + +static int abox_wdma_new(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_pcm *pcm = runtime->pcm; + struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_soc_platform *platform = runtime->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + size_t buffer_bytes; + int ret; + + switch (data->type) { + case PLATFORM_NORMAL: + buffer_bytes = BUFFER_BYTES_MAX; + break; + default: + buffer_bytes = BUFFER_BYTES_MAX >> 2; + break; + } + + ret = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, + runtime->cpu_dai->dev, buffer_bytes, buffer_bytes); + if (ret < 0) + return ret; + +#ifdef USE_FIXED_MEMORY + ret = iommu_map(data->abox_data->iommu_domain, IOVA_WDMA_BUFFER(id), + substream->dma_buffer.addr, BUFFER_BYTES_MAX, 0); +#endif + return ret; +} + +static void abox_wdma_free(struct snd_pcm *pcm) +{ +#ifdef USE_FIXED_MEMORY + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + struct snd_soc_pcm_runtime *runtime = substream->private_data; + struct snd_soc_platform *platform = runtime->platform; + struct device *dev = platform->dev; + struct abox_platform_data *data = dev_get_drvdata(dev); + int id = data->id; + + iommu_unmap(data->abox_data->iommu_domain, IOVA_WDMA_BUFFER(id), + BUFFER_BYTES_MAX); +#endif + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static struct snd_soc_platform_driver abox_wdma = { + .ops = &abox_wdma_ops, + .pcm_new = abox_wdma_new, + .pcm_free = abox_wdma_free, +}; + +static int samsung_abox_wdma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct abox_platform_data *data; + int ret; + const char *type; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + platform_set_drvdata(pdev, data); + + data->sfr_base = devm_not_request_and_map(pdev, "sfr", 0, NULL, NULL); + if (IS_ERR(data->sfr_base)) + return PTR_ERR(data->sfr_base); + + data->pdev_abox = to_platform_device(pdev->dev.parent); + if (!data->pdev_abox) { + dev_err(dev, "Failed to get abox platform device\n"); + return -EPROBE_DEFER; + } + data->abox_data = platform_get_drvdata(data->pdev_abox); + + abox_register_irq_handler(&data->pdev_abox->dev, IPC_PCMCAPTURE, + abox_wdma_irq_handler, pdev); + + ret = of_property_read_u32_index(np, "id", 0, &data->id); + if (ret < 0) { + dev_err(dev, "id property reading fail\n"); + return ret; + } + + ret = of_property_read_string(np, "type", &type); + if (ret < 0) { + dev_err(dev, "type property reading fail\n"); + return ret; + } + if (!strncmp(type, "call", sizeof("call"))) + data->type = PLATFORM_CALL; + else if (!strncmp(type, "compress", sizeof("compress"))) + data->type = PLATFORM_COMPRESS; + else if (!strncmp(type, "realtime", sizeof("realtime"))) + data->type = PLATFORM_REALTIME; + else if (!strncmp(type, "vi-sensing", sizeof("vi-sensing"))) + data->type = PLATFORM_VI_SENSING; + else if (!strncmp(type, "sync", sizeof("sync"))) + data->type = PLATFORM_SYNC; + else + data->type = PLATFORM_NORMAL; + + data->scsc_bt = !!of_find_property(np, "scsc_bt", NULL); + + abox_register_wdma(data->abox_data->pdev, pdev, data->id); + + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); + + return snd_soc_register_platform(&pdev->dev, &abox_wdma); +} + +static int samsung_abox_wdma_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static const struct of_device_id samsung_abox_wdma_match[] = { + { + .compatible = "samsung,abox-wdma", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_abox_wdma_match); + +static struct platform_driver samsung_abox_wdma_driver = { + .probe = samsung_abox_wdma_probe, + .remove = samsung_abox_wdma_remove, + .driver = { + .name = "samsung-abox-wdma", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_abox_wdma_match), + }, +}; + +module_platform_driver(samsung_abox_wdma_driver); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung ASoC A-Box WDMA Driver"); +MODULE_ALIAS("platform:samsung-abox-wdma"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/exynos9610_madera.c b/sound/soc/samsung/exynos9610_madera.c new file mode 100644 index 000000000000..820a7af8f05a --- /dev/null +++ b/sound/soc/samsung/exynos9610_madera.c @@ -0,0 +1,1358 @@ +/* + * Driver for Madera CODECs on Exynos9610 + * + * Copyright 2013 Wolfson Microelectronics + * Copyright 2016 Cirrus Logic + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if IS_ENABLED(CONFIG_SND_SOC_MADERA) +#include +#include +#include "../codecs/madera.h" +#endif + +#define MADERA_BASECLK_48K 49152000 +#define MADERA_BASECLK_44K1 45158400 + +#define MADERA_AMP_RATE 48000 +#define MADERA_AMP_BCLK (MADERA_AMP_RATE * 16 * 4) + +#define EXYNOS_PMU_PMU_DEBUG_OFFSET 0x0A00 +#define MADERA_DAI_ID 0x4735 +#define MADERA_CODEC_MAX 10 +#define MADERA_AUX_MAX 2 + +static unsigned int baserate = MADERA_BASECLK_48K; + +enum FLL_ID { FLL1, FLL2, FLL3, FLLAO }; +enum CLK_ID { SYSCLK, ASYNCCLK, DSPCLK, OPCLK, OUTCLK }; + +/* Used for debugging and test automation */ +static u32 voice_trigger_count; + +/* Debugfs value overrides, default to 0 */ +static unsigned int forced_mclk1; +static unsigned int forced_sysclk; +static unsigned int forced_dspclk; + +struct clk_conf { + int id; + const char *name; + int source; + int rate; + int fout; + + bool valid; +}; + +#define MADERA_MAX_CLOCKS 10 + +struct madera_drvdata { + struct device *dev; + + struct clk_conf fll1_refclk; + struct clk_conf fll2_refclk; + struct clk_conf fllao_refclk; + struct clk_conf sysclk; + struct clk_conf asyncclk; + struct clk_conf dspclk; + struct clk_conf opclk; + struct clk_conf outclk; + + struct notifier_block nb; + + int left_amp_dai; + int right_amp_dai; + struct clk *clk[MADERA_MAX_CLOCKS]; +}; + +static struct madera_drvdata exynos9610_drvdata; + +static int map_fllid_with_name(const char *name) +{ + if (!strcmp(name, "fll1-refclk")) + return FLL1; + else if (!strcmp(name, "fll2-refclk")) + return FLL2; + else if (!strcmp(name, "fll3-refclk")) + return FLL3; + else if (!strcmp(name, "fllao-refclk")) + return FLLAO; + else + return -1; +} + +static int map_clkid_with_name(const char *name) +{ + if (!strcmp(name, "sysclk")) + return SYSCLK; + else if (!strcmp(name, "asyncclk")) + return ASYNCCLK; + else if (!strcmp(name, "dspclk")) + return DSPCLK; + else if (!strcmp(name, "opclk")) + return OPCLK; + else if (!strcmp(name, "outclk")) + return OUTCLK; + else + return -1; +} + +static struct snd_soc_pcm_runtime *madera_get_rtd(struct snd_soc_card *card, + int id) +{ + struct snd_soc_dai_link *dai_link; + struct snd_soc_pcm_runtime *rtd = NULL; + + for (dai_link = card->dai_link; + dai_link - card->dai_link < card->num_links; + dai_link++) { + if (id == dai_link->id) { + rtd = snd_soc_get_pcm_runtime(card, dai_link->name); + break; + } + } + + return rtd; +} + +static int madera_start_fll(struct snd_soc_card *card, + struct clk_conf *config) +{ + struct snd_soc_dai *codec_dai; + struct snd_soc_codec *codec; + unsigned int fsrc = 0, fin = 0, fout = 0, pll_id; + int ret; + + if (!config->valid) + return 0; + + codec_dai = madera_get_rtd(card, MADERA_DAI_ID)->codec_dai; + codec = codec_dai->codec; + + pll_id = map_fllid_with_name(config->name); + switch (pll_id) { + case FLL1: + if (forced_mclk1) { + /* use 32kHz input to avoid overclocking the FLL when + * forcing a specific MCLK frequency into the codec + * FLL calculations + */ +#if IS_ENABLED(CONFIG_SND_SOC_MADERA) + fsrc = MADERA_FLL_SRC_MCLK2; +#endif + fin = forced_mclk1; + } else { + fsrc = config->source; + fin = config->rate; + } + + if (forced_sysclk) + fout = forced_sysclk; + else + fout = config->fout; + break; + case FLL2: + case FLLAO: + fsrc = config->source; + fin = config->rate; + fout = config->fout; + break; + default: + dev_err(card->dev, "Unknown FLLID for %s\n", config->name); + } + + dev_dbg(card->dev, "Setting %s fsrc=%d fin=%uHz fout=%uHz\n", + config->name, fsrc, fin, fout); + + ret = snd_soc_codec_set_pll(codec, config->id, fsrc, fin, fout); + if (ret) + dev_err(card->dev, "Failed to start %s\n", config->name); + + return ret; +} + +static int madera_stop_fll(struct snd_soc_card *card, + struct clk_conf *config) +{ + struct snd_soc_dai *codec_dai; + struct snd_soc_codec *codec; + int ret; + + if (!config->valid) + return 0; + + codec_dai = madera_get_rtd(card, MADERA_DAI_ID)->codec_dai; + codec = codec_dai->codec; + + ret = snd_soc_codec_set_pll(codec, config->id, 0, 0, 0); + if (ret) + dev_err(card->dev, "Failed to stop %s\n", config->name); + + return ret; +} + +static int madera_set_clock(struct snd_soc_card *card, + struct clk_conf *config) +{ + struct snd_soc_dai *aif_dai; + struct snd_soc_codec *codec; + unsigned int freq = 0, clk_id; + int ret; + int dir = SND_SOC_CLOCK_IN; + + if (!config->valid) + return 0; + + aif_dai = madera_get_rtd(card, MADERA_DAI_ID)->codec_dai; + codec = aif_dai->codec; + + clk_id = map_clkid_with_name(config->name); + switch (clk_id) { + case SYSCLK: + if (forced_sysclk) + freq = forced_sysclk; + else + if (config->rate) + freq = config->rate; + else + freq = baserate * 2; + break; + case ASYNCCLK: + freq = config->rate; + break; + case DSPCLK: + if (forced_dspclk) + freq = forced_dspclk; + else + if (config->rate) + freq = config->rate; + else + freq = baserate * 3; + break; + case OPCLK: + freq = config->rate; + dir = SND_SOC_CLOCK_OUT; + break; + case OUTCLK: + freq = config->rate; + break; + default: + dev_err(card->dev, "Unknown Clock ID for %s\n", config->name); + } + + dev_dbg(card->dev, "Setting %s freq to %u Hz\n", config->name, freq); + + ret = snd_soc_codec_set_sysclk(codec, config->id, + config->source, freq, dir); + if (ret) + dev_err(card->dev, "Failed to set %s to %u Hz\n", + config->name, freq); + + return ret; +} + +static int madera_stop_clock(struct snd_soc_card *card, + struct clk_conf *config) +{ + struct snd_soc_dai *aif_dai; + struct snd_soc_codec *codec; + int ret; + + if (!config->valid) + return 0; + + aif_dai = madera_get_rtd(card, MADERA_DAI_ID)->codec_dai; + codec = aif_dai->codec; + + ret = snd_soc_codec_set_sysclk(codec, config->id, 0, 0, 0); + if (ret) + dev_err(card->dev, "Failed to stop %s\n", config->name); + + return ret; +} + +static int madera_set_clocking(struct snd_soc_card *card, + struct madera_drvdata *drvdata) +{ + int ret; + + ret = madera_start_fll(card, &drvdata->fll1_refclk); + if (ret) + return ret; + + if (!drvdata->sysclk.rate) { + ret = madera_set_clock(card, &drvdata->sysclk); + if (ret) + return ret; + } + + if (!drvdata->dspclk.rate) { + ret = madera_set_clock(card, &drvdata->dspclk); + if (ret) + return ret; + } + + ret = madera_set_clock(card, &drvdata->opclk); + if (ret) + return ret; + + return ret; +} + +static const struct snd_soc_ops rdma_ops = { +}; + +static const struct snd_soc_ops wdma_ops = { +}; + +static int madera_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_card *card = rtd->card; + struct madera_drvdata *drvdata = card->drvdata; + unsigned int rate = params_rate(params); + int ret; + + /* Treat sysclk rate zero as automatic mode */ + if (!drvdata->sysclk.rate) { + if (rate % 4000) + baserate = MADERA_BASECLK_44K1; + else + baserate = MADERA_BASECLK_48K; + } + + dev_dbg(card->dev, "Requesting Rate: %dHz, FLL: %dHz\n", rate, + drvdata->sysclk.rate ? drvdata->sysclk.rate : baserate * 2); + + /* Ensure we can't race against set_bias_level */ + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + ret = madera_set_clocking(card, drvdata); + mutex_unlock(&card->dapm_mutex); + + return ret; +} + +static int madera_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + return snd_soc_dai_set_tristate(rtd->cpu_dai, 0); +} + +static void madera_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + snd_soc_dai_set_tristate(rtd->cpu_dai, 1); +} + +static const struct snd_soc_ops uaif0_ops = { + .hw_params = madera_hw_params, + .prepare = madera_prepare, + .shutdown = madera_shutdown, +}; + +static const struct snd_soc_ops uaif_ops = { +}; + +static int dsif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int tx_slot[] = {0, 1}; + + /* bclk ratio 64 for DSD64, 128 for DSD128 */ + snd_soc_dai_set_bclk_ratio(cpu_dai, 64); + + /* channel map 0 1 if left is first, 1 0 if right is first */ + snd_soc_dai_set_channel_map(cpu_dai, 2, tx_slot, 0, NULL); + return 0; +} + +static const struct snd_soc_ops dsif_ops = { + .hw_params = dsif_hw_params, +}; + +static int exynos9610_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_dai *codec_dai; + struct madera_drvdata *drvdata = card->drvdata; + int ret; + + codec_dai = madera_get_rtd(card, MADERA_DAI_ID)->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_OFF) + break; + + ret = madera_set_clocking(card, drvdata); + if (ret) + return ret; + + ret = madera_start_fll(card, &drvdata->fll2_refclk); + if (ret) + return ret; + + ret = madera_start_fll(card, &drvdata->fllao_refclk); + if (ret) + return ret; + break; + default: + break; + } + + return 0; +} + +static int exynos9610_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_dai *codec_dai; + struct madera_drvdata *drvdata = card->drvdata; + int ret; + + codec_dai = madera_get_rtd(card, MADERA_DAI_ID)->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_OFF: + ret = madera_stop_fll(card, &drvdata->fll1_refclk); + if (ret) + return ret; + + ret = madera_stop_fll(card, &drvdata->fll2_refclk); + if (ret) + return ret; + + ret = madera_stop_fll(card, &drvdata->fllao_refclk); + if (ret) + return ret; + + if (!drvdata->sysclk.rate) { + ret = madera_stop_clock(card, &drvdata->sysclk); + if (ret) + return ret; + } + + if (!drvdata->dspclk.rate) { + ret = madera_stop_clock(card, &drvdata->dspclk); + if (ret) + return ret; + } + break; + default: + break; + } + + return 0; +} + +#if IS_ENABLED(CONFIG_SND_SOC_MADERA) +static int madera_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + const struct madera_hpdet_notify_data *hp_inf; + const struct madera_micdet_notify_data *md_inf; + const struct madera_voice_trigger_info *vt_inf; + const struct madera_drvdata *drvdata = + container_of(nb, struct madera_drvdata, nb); + + switch (event) { + case MADERA_NOTIFY_VOICE_TRIGGER: + vt_inf = data; + dev_info(drvdata->dev, "Voice Triggered (core_num=%d)\n", + vt_inf->core_num); + ++voice_trigger_count; + break; + case MADERA_NOTIFY_HPDET: + hp_inf = data; + dev_info(drvdata->dev, "HPDET val=%d.%02d ohms\n", + hp_inf->impedance_x100 / 100, + hp_inf->impedance_x100 % 100); + break; + case MADERA_NOTIFY_MICDET: + md_inf = data; + dev_info(drvdata->dev, "MICDET present=%c val=%d.%02d ohms\n", + md_inf->present ? 'Y' : 'N', + md_inf->impedance_x100 / 100, + md_inf->impedance_x100 % 100); + break; + default: + dev_info(drvdata->dev, "notifier event=0x%lx data=0x%p\n", + event, data); + break; + } + + return NOTIFY_DONE; +} +#endif + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int madera_force_fll1_enable_write(void *data, u64 val) +{ + struct snd_soc_card *card = data; + struct madera_drvdata *drvdata = card->drvdata; + int ret; + + if (val == 0) + ret = madera_stop_fll(card, &drvdata->fll1_refclk); + else + ret = madera_start_fll(card, &drvdata->fll1_refclk); + + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(madera_force_fll1_enable_fops, NULL, + madera_force_fll1_enable_write, "%llu\n"); + +static void madera_init_debugfs(struct snd_soc_card *card) +{ + struct dentry *root; + + if (!card->debugfs_card_root) { + dev_warn(card->dev, "No card debugfs root\n"); + return; + } + + root = debugfs_create_dir("test-automation", card->debugfs_card_root); + if (!root) { + dev_warn(card->dev, "Failed to create debugfs dir\n"); + return; + } + + debugfs_create_u32("voice_trigger_count", S_IRUGO, root, + &voice_trigger_count); + + debugfs_create_u32("forced_mclk1", S_IRUGO | S_IWUGO, root, + &forced_mclk1); + debugfs_create_u32("forced_sysclk", S_IRUGO | S_IWUGO, root, + &forced_sysclk); + debugfs_create_u32("forced_dspclk", S_IRUGO | S_IWUGO, root, + &forced_dspclk); + + debugfs_create_file("force_fll1_enable", S_IWUSR | S_IWGRP, root, card, + &madera_force_fll1_enable_fops); +} +#else +static void madera_init_debugfs(struct snd_soc_card *card) +{ +} +#endif + +static int madera_amp_late_probe(struct snd_soc_card *card, int dai) +{ + struct madera_drvdata *drvdata = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *amp_dai; + struct snd_soc_codec *amp; + int ret; + + if (!dai || !card->dai_link[dai].name) + return 0; + + if (!drvdata->opclk.valid) { + dev_err(card->dev, "OPCLK required to use speaker amp\n"); + return -ENOENT; + } + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[dai].name); + + amp_dai = rtd->codec_dai; + amp = amp_dai->codec; + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 4, 16); + if (ret) + dev_err(card->dev, "Failed to set TDM: %d\n", ret); + + ret = snd_soc_codec_set_sysclk(amp, 0, 0, drvdata->opclk.rate, + SND_SOC_CLOCK_IN); + if (ret != 0) { + dev_err(card->dev, "Failed to set amp SYSCLK: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(amp_dai, 0, MADERA_AMP_BCLK, + SND_SOC_CLOCK_IN); + if (ret != 0) { + dev_err(card->dev, "Failed to set amp DAI clock: %d\n", ret); + return ret; + } + + return 0; +} + +static int exynos9610_late_probe(struct snd_soc_card *card) +{ + struct madera_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *aif_dai; + struct snd_soc_codec *codec; + struct snd_soc_component *cpu; + int ret; + + aif_dai = madera_get_rtd(card, 0)->cpu_dai; + cpu = aif_dai->component; + + aif_dai = madera_get_rtd(card, MADERA_DAI_ID)->codec_dai; + codec = aif_dai->codec; + + ret = snd_soc_dai_set_sysclk(aif_dai, drvdata->sysclk.id, 0, 0); + if (ret != 0) { + dev_err(drvdata->dev, "Failed to set AIF1 clock: %d\n", ret); + return ret; + } + + if (drvdata->sysclk.rate) { + ret = madera_set_clock(card, &drvdata->sysclk); + if (ret) + return ret; + } + + if (drvdata->dspclk.rate) { + ret = madera_set_clock(card, &drvdata->dspclk); + if (ret) + return ret; + } + + ret = madera_set_clock(card, &drvdata->asyncclk); + if (ret) + return ret; + + ret = madera_set_clock(card, &drvdata->outclk); + if (ret) + return ret; + + ret = madera_amp_late_probe(card, drvdata->left_amp_dai); + if (ret) + return ret; + + ret = madera_amp_late_probe(card, drvdata->right_amp_dai); + if (ret) + return ret; + + snd_soc_dapm_ignore_suspend(&card->dapm, "VOUTPUT"); + snd_soc_dapm_ignore_suspend(&card->dapm, "VINPUT1"); + snd_soc_dapm_ignore_suspend(&card->dapm, "VINPUT2"); + snd_soc_dapm_ignore_suspend(&card->dapm, "HEADSETMIC"); + snd_soc_dapm_ignore_suspend(&card->dapm, "RECEIVER"); + snd_soc_dapm_ignore_suspend(&card->dapm, "HEADPHONE"); + snd_soc_dapm_ignore_suspend(&card->dapm, "SPEAKER"); + snd_soc_dapm_ignore_suspend(&card->dapm, "DMIC1"); + snd_soc_dapm_ignore_suspend(&card->dapm, "DMIC2"); + snd_soc_dapm_ignore_suspend(&card->dapm, "DMIC3"); + snd_soc_dapm_ignore_suspend(&card->dapm, "VTS Virtual Output"); + snd_soc_dapm_sync(&card->dapm); + + snd_soc_dapm_ignore_suspend(snd_soc_codec_get_dapm(codec), "AIF1 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_codec_get_dapm(codec), "AIF1 Capture"); + snd_soc_dapm_sync(snd_soc_codec_get_dapm(codec)); + + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA0 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA1 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA2 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA3 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA4 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA5 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA6 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA7 Playback"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA0 Capture"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA1 Capture"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA2 Capture"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA3 Capture"); + snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA4 Capture"); + snd_soc_dapm_sync(snd_soc_component_get_dapm(cpu)); + + madera_init_debugfs(card); + +#if IS_ENABLED(CONFIG_SND_SOC_MADERA) + drvdata->nb.notifier_call = madera_notify; + madera_register_notifier(codec, &drvdata->nb); +#endif + + return 0; +} + +static struct snd_soc_pcm_stream madera_amp_params[] = { + { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = MADERA_AMP_RATE, + .rate_max = MADERA_AMP_RATE, + .channels_min = 1, + .channels_max = 1, + }, +}; + +static struct snd_soc_dai_link exynos9610_dai[] = { + { + .name = "RDMA0", + .stream_name = "RDMA0", + .platform_name = "14a51000.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "RDMA1", + .stream_name = "RDMA1", + .platform_name = "14a51100.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "RDMA2", + .stream_name = "RDMA2", + .platform_name = "14a51200.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "RDMA3", + .stream_name = "RDMA3", + .platform_name = "14a51300.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "RDMA4", + .stream_name = "RDMA4", + .platform_name = "14a51400.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "RDMA5", + .stream_name = "RDMA5", + .platform_name = "14a51500.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "RDMA6", + .stream_name = "RDMA6", + .platform_name = "14a51600.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "RDMA7", + .stream_name = "RDMA7", + .platform_name = "14a51700.abox_rdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &rdma_ops, + .dpcm_playback = 1, + }, + { + .name = "WDMA0", + .stream_name = "WDMA0", + .platform_name = "14a52000.abox_wdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &wdma_ops, + .dpcm_capture = 1, + }, + { + .name = "WDMA1", + .stream_name = "WDMA1", + .platform_name = "14a52100.abox_wdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &wdma_ops, + .dpcm_capture = 1, + }, + { + .name = "WDMA2", + .stream_name = "WDMA2", + .platform_name = "14a52200.abox_wdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &wdma_ops, + .dpcm_capture = 1, + }, + { + .name = "WDMA3", + .stream_name = "WDMA3", + .platform_name = "14a52300.abox_wdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &wdma_ops, + .dpcm_capture = 1, + }, + { + .name = "WDMA4", + .stream_name = "WDMA4", + .platform_name = "14a52400.abox_wdma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dynamic = 1, + .ignore_suspend = 1, + .trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST}, + .ops = &wdma_ops, + .dpcm_capture = 1, + }, +#if IS_ENABLED(SND_SOC_SAMSUNG_DISPLAYPORT) + { + .name = "DP Audio", + .stream_name = "DP Audio", + .cpu_dai_name = "audio_cpu_dummy", + .platform_name = "dp_dma", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + }, +#endif + { + .name = "UAIF0", + .stream_name = "UAIF0", + .platform_name = "snd-soc-dummy", + .id = MADERA_DAI_ID, + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .ops = &uaif0_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "UAIF1", + .stream_name = "UAIF1", + .platform_name = "snd-soc-dummy", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .ops = &uaif_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "UAIF2", + .stream_name = "UAIF2", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .ops = &uaif_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "UAIF4", + .stream_name = "UAIF4", + .platform_name = "snd-soc-dummy", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .ops = &uaif_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "DSIF", + .stream_name = "DSIF", + .cpu_dai_name = "DSIF", + .platform_name = "snd-soc-dummy", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .ops = &dsif_ops, + .dpcm_playback = 1, + }, + { + .name = "SPDY", + .stream_name = "SPDY", + .cpu_dai_name = "SPDY", + .platform_name = "snd-soc-dummy", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .dpcm_capture = 1, + }, + { + .name = "SIFS0", + .stream_name = "SIFS0", + .cpu_dai_name = "SIFS0", + .platform_name = "snd-soc-dummy", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "SIFS1", + .stream_name = "SIFS1", + .cpu_dai_name = "SIFS1", + .platform_name = "snd-soc-dummy", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "SIFS2", + .stream_name = "SIFS2", + .cpu_dai_name = "SIFS2", + .platform_name = "snd-soc-dummy", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = abox_hw_params_fixup_helper, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "codec-left-amp", + .params = madera_amp_params, + }, + +}; + +static const char * const vts_output_texts[] = { + "None", + "DMIC1", +}; + +static const struct snd_kcontrol_new exynos9610_controls[] = { + SOC_DAPM_PIN_SWITCH("DMIC1"), + SOC_DAPM_PIN_SWITCH("DMIC2"), + SOC_DAPM_PIN_SWITCH("DMIC3"), +}; + +static struct snd_soc_dapm_widget exynos9610_widgets[] = { + SND_SOC_DAPM_OUTPUT("VOUTPUT"), + SND_SOC_DAPM_INPUT("VINPUT1"), + SND_SOC_DAPM_INPUT("VINPUT2"), + SND_SOC_DAPM_OUTPUT("VOUTPUTCALL"), + SND_SOC_DAPM_INPUT("VINPUTCALL"), + SND_SOC_DAPM_MIC("DMIC1", NULL), + SND_SOC_DAPM_MIC("DMIC2", NULL), + SND_SOC_DAPM_MIC("DMIC3", NULL), + SND_SOC_DAPM_MIC("HEADSETMIC", NULL), + SND_SOC_DAPM_SPK("RECEIVER", NULL), + SND_SOC_DAPM_HP("HEADPHONE", NULL), + SND_SOC_DAPM_SPK("SPEAKER", NULL), + SND_SOC_DAPM_MIC("BLUETOOTH MIC", NULL), + SND_SOC_DAPM_SPK("BLUETOOTH SPK", NULL), +}; + +static struct snd_soc_codec_conf codec_conf[MADERA_CODEC_MAX]; + +static struct snd_soc_aux_dev aux_dev[MADERA_AUX_MAX]; + +static struct snd_soc_card exynos9610_madera = { + .name = "Exynos9610-Madera", + .owner = THIS_MODULE, + .dai_link = exynos9610_dai, + .num_links = ARRAY_SIZE(exynos9610_dai), + + .late_probe = exynos9610_late_probe, + + .controls = exynos9610_controls, + .num_controls = ARRAY_SIZE(exynos9610_controls), + .dapm_widgets = exynos9610_widgets, + .num_dapm_widgets = ARRAY_SIZE(exynos9610_widgets), + + .set_bias_level = exynos9610_set_bias_level, + .set_bias_level_post = exynos9610_set_bias_level_post, + + .drvdata = (void *)&exynos9610_drvdata, + + .codec_conf = codec_conf, + .num_configs = ARRAY_SIZE(codec_conf), + + .aux_dev = aux_dev, + .num_aux_devs = ARRAY_SIZE(aux_dev), +}; + +static int read_clk_conf(struct device_node *np, + const char * const prop, + struct clk_conf *conf, + bool is_fll) +{ + u32 tmp; + int ret; + + /*Truncate "cirrus," from prop_name to fetch clk_name*/ + conf->name = &prop[7]; + + ret = of_property_read_u32_index(np, prop, 0, &tmp); + if (ret) + return ret; + + conf->id = tmp; + + ret = of_property_read_u32_index(np, prop, 1, &tmp); + if (ret) + return ret; + + if (tmp < 0xffff) + conf->source = tmp; + else + conf->source = -1; + + ret = of_property_read_u32_index(np, prop, 2, &tmp); + if (ret) + return ret; + + conf->rate = tmp; + + if (is_fll) { + ret = of_property_read_u32_index(np, prop, 3, &tmp); + if (ret) + return ret; + conf->fout = tmp; + } + + conf->valid = true; + + return 0; +} + +static int read_dai(struct device_node *np, const char * const prop, + struct device_node **dai, const char **name) +{ + int ret = 0; + + np = of_get_child_by_name(np, prop); + if (!np) + return -ENOENT; + + *dai = of_parse_phandle(np, "sound-dai", 0); + if (!*dai) { + ret = -ENODEV; + goto out; + } + + if (*name == NULL) { + /* Ignoring the return as we don't register DAIs to the platform */ + ret = snd_soc_of_get_dai_name(np, name); + if (ret && !*name) + return ret; + } +out: + of_node_put(np); + + return ret; +} + +static struct clk *xclkout; + +static void control_xclkout(bool on) +{ + if (on) { + clk_prepare_enable(xclkout); + } else { + clk_disable_unprepare(xclkout); + } +} + +static int exynos9610_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &exynos9610_madera; + struct madera_drvdata *drvdata = card->drvdata; + struct device_node *np = pdev->dev.of_node; + struct device_node *dai; + int nlink = 0; + int i, rc, ret; + const char *cur = NULL; + struct property *p; + + card->dev = &pdev->dev; + drvdata->dev = card->dev; + + snd_soc_card_set_drvdata(card, drvdata); + + xclkout = devm_clk_get(&pdev->dev, "xclkout"); + if (IS_ERR(xclkout)) { + dev_err(&pdev->dev, "xclkout get failed\n"); + xclkout = NULL; + } + control_xclkout(true); + dev_info(&pdev->dev, "xclkout is enabled\n"); + + i = 0; + p = of_find_property(np, "clock-names", NULL); + if (p) { + while ((cur = of_prop_next_string(p, cur)) != NULL) { + drvdata->clk[i] = devm_clk_get(drvdata->dev, cur); + if (IS_ERR(drvdata->clk[i])) { + dev_info(drvdata->dev, "Failed to get %s: %ld\n", + cur, PTR_ERR(drvdata->clk[i])); + drvdata->clk[i] = NULL; + break; + } + + clk_prepare_enable(drvdata->clk[i]); + + if (++i == MADERA_MAX_CLOCKS) + break; + } + } + + ret = read_clk_conf(np, "cirrus,sysclk", + &drvdata->sysclk, false); + if (ret) { + dev_err(card->dev, "Failed to parse sysclk: %d\n", ret); + return ret; + } + ret = read_clk_conf(np, "cirrus,asyncclk", + &drvdata->asyncclk, false); + if (ret) + dev_info(card->dev, "Failed to parse asyncclk: %d\n", ret); + + ret = read_clk_conf(np, "cirrus,dspclk", + &drvdata->dspclk, false); + if (ret) { + dev_info(card->dev, "Failed to parse dspclk: %d\n", ret); + } + + ret = read_clk_conf(np, "cirrus,opclk", + &drvdata->opclk, false); + if (ret) + dev_info(card->dev, "Failed to parse opclk: %d\n", ret); + + ret = read_clk_conf(np, "cirrus,fll1-refclk", + &drvdata->fll1_refclk, true); + if (ret) + dev_info(card->dev, "Failed to parse fll1-refclk: %d\n", ret); + + ret = read_clk_conf(np, "cirrus,fll2-refclk", + &drvdata->fll2_refclk, true); + if (ret) + dev_info(card->dev, "Failed to parse fll2-refclk: %d\n", ret); + + ret = read_clk_conf(np, "cirrus,fllao-refclk", + &drvdata->fllao_refclk, true); + if (ret) + dev_info(card->dev, "Failed to parse fllao-refclk: %d\n", ret); + + ret = read_clk_conf(np, "cirrus,outclk", + &drvdata->outclk, false); + if (ret) + dev_info(card->dev, "Failed to parse outclk: %d\n", ret); + + for_each_child_of_node(np, dai) { + if (!exynos9610_dai[nlink].name) + exynos9610_dai[nlink].name = dai->name; + if (!exynos9610_dai[nlink].stream_name) + exynos9610_dai[nlink].stream_name = dai->name; + + if (!exynos9610_dai[nlink].cpu_name) { + ret = read_dai(dai, "cpu", + &exynos9610_dai[nlink].cpu_of_node, + &exynos9610_dai[nlink].cpu_dai_name); + if (ret) { + dev_err(card->dev, + "Failed to parse cpu DAI for %s: %d\n", + dai->name, ret); + return ret; + } + } + + if (!exynos9610_dai[nlink].platform_name) { + ret = read_dai(dai, "platform", + &exynos9610_dai[nlink].platform_of_node, + &exynos9610_dai[nlink].platform_name); + if (ret) { + exynos9610_dai[nlink].platform_of_node = + exynos9610_dai[nlink].cpu_of_node; + dev_info(card->dev, + "Cpu node is used as platform for %s: %d\n", + dai->name, ret); + } + } + + if (!exynos9610_dai[nlink].codec_name) { + ret = read_dai(dai, "codec", + &exynos9610_dai[nlink].codec_of_node, + &exynos9610_dai[nlink].codec_dai_name); + if (ret) { + dev_err(card->dev, + "Failed to parse codec DAI for %s: %d\n", + dai->name, ret); + return ret; + } + } + + if (strstr(dai->name, "left-amp")) { + exynos9610_dai[nlink].params = madera_amp_params; + drvdata->left_amp_dai = nlink; + } else if (strstr(dai->name, "right-amp")) { + exynos9610_dai[nlink].params = madera_amp_params; + drvdata->right_amp_dai = nlink; + } + + exynos9610_dai[nlink].dai_fmt = + snd_soc_of_parse_daifmt(dai, NULL, NULL, NULL); + + if (++nlink == card->num_links) + break; + } + + if (!nlink) { + dev_err(card->dev, "No DAIs specified\n"); + return -EINVAL; + } + + if (of_property_read_bool(np, "samsung,routing")) { + ret = snd_soc_of_parse_audio_routing(card, "samsung,routing"); + if (ret) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(codec_conf); i++) { + codec_conf[i].of_node = of_parse_phandle(np, "samsung,codec", i); + if (IS_ERR_OR_NULL(codec_conf[i].of_node)) { + exynos9610_madera.num_configs = i; + break; + } + + rc = of_property_read_string_index(np, "samsung,prefix", i, + &codec_conf[i].name_prefix); + if (rc < 0) + codec_conf[i].name_prefix = ""; + } + + for (i = 0; i < ARRAY_SIZE(aux_dev); i++) { + aux_dev[i].codec_of_node = of_parse_phandle(np, "samsung,aux", i); + if (IS_ERR_OR_NULL(aux_dev[i].codec_of_node)) { + exynos9610_madera.num_aux_devs = i; + break; + } + } + + ret = devm_snd_soc_register_card(card->dev, card); + if (ret) + dev_err(card->dev, "snd_soc_register_card() failed:%d\n", ret); + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id exynos9610_of_match[] = { + { .compatible = "samsung,exynos9610-madera", }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos9610_of_match); +#endif /* CONFIG_OF */ + +static struct platform_driver exynos9610_audio_driver = { + .driver = { + .name = "exynos9610-madera", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(exynos9610_of_match), + }, + + .probe = exynos9610_audio_probe, +}; + +module_platform_driver(exynos9610_audio_driver); + +MODULE_DESCRIPTION("ALSA SoC Exynos9610 Madera Driver"); +MODULE_AUTHOR("Charles Keepax "); +MODULE_AUTHOR("Gyeongtaek Lee "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:exynos9610-madera"); diff --git a/sound/soc/samsung/vts/Kconfig b/sound/soc/samsung/vts/Kconfig new file mode 100644 index 000000000000..abc1a3767ff7 --- /dev/null +++ b/sound/soc/samsung/vts/Kconfig @@ -0,0 +1,11 @@ +config SND_SOC_SAMSUNG_MAILBOX + bool "Samsung MAILBOX" + select GENERIC_IRQ_CHIP + help + Say Y if you want to use mailbox for voice trigger system. + +config SND_SOC_SAMSUNG_VTS + bool "Samsung VTS" + depends on SND_SOC_SAMSUNG_MAILBOX + help + Say Y if you want to use voice trigger system. diff --git a/sound/soc/samsung/vts/Makefile b/sound/soc/samsung/vts/Makefile new file mode 100644 index 000000000000..8d869d3e9f08 --- /dev/null +++ b/sound/soc/samsung/vts/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_SND_SOC_SAMSUNG_MAILBOX) += mailbox.o +obj-$(CONFIG_SND_SOC_SAMSUNG_VTS) += vts.o vts_dma.o vts_log.o vts_dump.o diff --git a/sound/soc/samsung/vts/mailbox.c b/sound/soc/samsung/vts/mailbox.c new file mode 100644 index 000000000000..f37dbbda65a8 --- /dev/null +++ b/sound/soc/samsung/vts/mailbox.c @@ -0,0 +1,255 @@ +/* sound/soc/samsung/vts/mailbox.c + * + * ALSA SoC - Samsung Mailbox driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +//#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mailbox.h" + +#define DEVICE_NAME "samsung-mailbox-asoc" + +struct mailbox_data { + void __iomem *sfr_base; + struct irq_domain *irq_domain; + int irq; +}; + +int mailbox_generate_interrupt(const struct platform_device *pdev, int hw_irq) +{ + struct mailbox_data *data = platform_get_drvdata(pdev); + + writel(BIT(hw_irq), data->sfr_base + MAILBOX_INTGR0); + dev_dbg(&pdev->dev, "%s: writel(%lx, %lx)", __func__, + BIT(hw_irq), (unsigned long)virt_to_phys(data->sfr_base + MAILBOX_INTGR0)); + + return 0; +} +EXPORT_SYMBOL(mailbox_generate_interrupt); + +void mailbox_write_shared_register(const struct platform_device *pdev, + const u32 *values, int start, int count) +{ + struct mailbox_data *data = platform_get_drvdata(pdev); + u32 __iomem *sfr = data->sfr_base + MAILBOX_ISSR0 + (start * sizeof(u32)); + + dev_dbg(&pdev->dev, "%s(%p, %p, %d, %d)", __func__, pdev, values, start, count); + while (count--) { + writel_relaxed(*values++, sfr++); + } +} +EXPORT_SYMBOL(mailbox_write_shared_register); + +void mailbox_read_shared_register(const struct platform_device *pdev, + u32 *values, int start, int count) +{ + struct mailbox_data *data = platform_get_drvdata(pdev); + u32 __iomem *sfr = data->sfr_base + MAILBOX_ISSR0 + (start * sizeof(u32)); + + dev_dbg(&pdev->dev, "%s(%p, %p, %d, %d)", __func__, pdev, values, start, count); + while (count--) { + *values++ = readl_relaxed(sfr++); + dev_dbg(&pdev->dev, "value=%d\n", *(values - 1)); + } +} +EXPORT_SYMBOL(mailbox_read_shared_register); + +static void mailbox_handle_irq(struct irq_desc *desc) +{ + struct platform_device *pdev = irq_desc_get_handler_data(desc); + struct mailbox_data *data = platform_get_drvdata(pdev); + struct irq_domain *irq_domain = data->irq_domain; + struct irq_chip *chip = irq_desc_get_chip(desc); + + struct irq_chip_generic *gc = irq_get_domain_generic_chip(irq_domain, 0); + u32 stat = readl_relaxed(gc->reg_base + MAILBOX_INTSR1); + + dev_dbg(&pdev->dev, "%s: stat=%08x\n", __func__, stat); + + chained_irq_enter(chip, desc); + + while (stat) { + u32 hwirq = __fls(stat); + generic_handle_irq(irq_find_mapping(irq_domain, gc->irq_base + hwirq)); + stat &= ~(1 << hwirq); + } + + chained_irq_exit(chip, desc); +} + +static int mailbox_suspend(struct device *dev) +{ + return 0; +} + +static int mailbox_resume(struct device *dev) +{ + return 0; +} + +static const struct of_device_id samsung_mailbox_of_match[] = { + { + .compatible = "samsung,mailbox-asoc", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_mailbox_of_match); + +SIMPLE_DEV_PM_OPS(samsung_mailbox_pm, mailbox_suspend, mailbox_resume); + +static void mailbox_irq_enable(struct irq_data *data) +{ + if (vts_is_on()) { + irq_gc_mask_clr_bit(data); + } else { + pr_debug("%s(%d): vts is already off\n", + __func__, data->irq); + } + return; +} + +static void mailbox_irq_disable(struct irq_data *data) +{ + if (vts_is_on()) { + irq_gc_mask_set_bit(data); + } else { + pr_debug("%s(%d): vts is already off\n", + __func__, data->irq); + } + return; +} + +static void __iomem *devm_request_and_ioremap(struct platform_device *pdev, const char *name) +{ + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *result; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (IS_ERR_OR_NULL(res)) { + dev_err(dev, "Failed to get %s\n", name); + return ERR_PTR(-EINVAL); + } + + res = devm_request_mem_region(dev, res->start, resource_size(res), name); + if (IS_ERR_OR_NULL(res)) { + dev_err(dev, "Failed to request %s\n", name); + return ERR_PTR(-EFAULT); + } + + result = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(result)) { + dev_err(dev, "Failed to map %s\n", name); + return ERR_PTR(-EFAULT); + } + + return result; +} + +static int samsung_mailbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct mailbox_data *data; + struct irq_chip_generic *gc; + int result; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (IS_ERR_OR_NULL(data)) { + dev_err(dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, data); + + data->sfr_base = devm_request_and_ioremap(pdev, "sfr"); + if (IS_ERR(data->sfr_base)) { + return PTR_ERR(data->sfr_base); + } + + result = platform_get_irq(pdev, 0); + if (result < 0) { + dev_err(dev, "Failed to get irq resource\n"); + return result; + } + data->irq = result; + + data->irq_domain = irq_domain_add_linear(np, MAILBOX_INT1_OFFSET + MAILBOX_INT1_SIZE, + &irq_generic_chip_ops, NULL); + if (IS_ERR_OR_NULL(data->irq_domain)) { + dev_err(dev, "Failed to add irq domain\n"); + return -ENOMEM; + } + + result = irq_alloc_domain_generic_chips(data->irq_domain, MAILBOX_INT1_OFFSET + MAILBOX_INT1_SIZE, + 1, "mailbox_irq_chip", handle_level_irq, 0, 0, IRQ_GC_INIT_MASK_CACHE); + if (result < 0) { + dev_err(dev, "Failed to allocation generic irq chips\n"); + return result; + } + + gc = irq_get_domain_generic_chip(data->irq_domain, 0); + gc->reg_base = data->sfr_base; + gc->chip_types[0].chip.irq_enable = mailbox_irq_enable; + gc->chip_types[0].chip.irq_disable = mailbox_irq_disable; + gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; + gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit; + gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit; + gc->chip_types[0].regs.mask = MAILBOX_INTMR1; + gc->chip_types[0].regs.ack = MAILBOX_INTCR1; + gc->wake_enabled = 0x0000FFFF; + gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake; + + irq_set_handler_data(data->irq, pdev); + irq_set_chained_handler(data->irq, mailbox_handle_irq); + + dev_info(dev, "Probed successfully\n"); + + return 0; +} + +static int samsung_mailbox_remove(struct platform_device *pdev) +{ + struct mailbox_data *mailbox_data = platform_get_drvdata(pdev); + + dev_info(&pdev->dev, "%s\n", __func__); + + irq_remove_generic_chip(irq_get_domain_generic_chip(mailbox_data->irq_domain, 0), + IRQ_MSK(MAILBOX_INT1_SIZE), 0, 0); + irq_domain_remove(mailbox_data->irq_domain); + + return 0; +} + +static struct platform_driver samsung_mailbox_driver = { + .probe = samsung_mailbox_probe, + .remove = samsung_mailbox_remove, + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_mailbox_of_match), + .pm = &samsung_mailbox_pm, + }, +}; + +module_platform_driver(samsung_mailbox_driver); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_DESCRIPTION("Samsung Mailbox"); +MODULE_ALIAS("platform:samsung-mailbox"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/vts/mailbox.h b/sound/soc/samsung/vts/mailbox.h new file mode 100644 index 000000000000..7606546250f6 --- /dev/null +++ b/sound/soc/samsung/vts/mailbox.h @@ -0,0 +1,88 @@ +/* sound/soc/samsung/vts/mailbox.h + * + * ALSA SoC - Samsung Mailbox driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAILBOX_H +#define __MAILBOX_H + +/* MAILBOX */ +#ifdef CONFIG_SOC_EXYNOS8895 +#define MAILBOX_MCUCTRL (0x0000) +#define MAILBOX_INTGR0 (0x0008) +#define MAILBOX_INTCR0 (0x000C) +#define MAILBOX_INTMR0 (0x0010) +#define MAILBOX_INTSR0 (0x0014) +#define MAILBOX_INTMSR0 (0x0018) +#define MAILBOX_INTGR1 (0x001C) +#define MAILBOX_INTCR1 (0x0020) +#define MAILBOX_INTMR1 (0x0024) +#define MAILBOX_INTSR1 (0x0028) +#define MAILBOX_INTMSR1 (0x002C) +#define MAILBOX_MIF_INIT (0x004c) +#define MAILBOX_IS_VERSION (0x0050) +#define MAILBOX_ISSR0 (0x0080) +#define MAILBOX_ISSR1 (0x0084) +#define MAILBOX_ISSR2 (0x0088) +#define MAILBOX_ISSR3 (0x008C) +#define MAILBOX_SEMAPHORE0 (0x0180) +#define MAILBOX_SEMAPHORE1 (0x0184) +#define MAILBOX_SEMAPHORE2 (0x0188) +#define MAILBOX_SEMAPHORE3 (0x018C) +#define MAILBOX_SEMA0CON (0x01C0) +#define MAILBOX_SEMA0STATE (0x01C8) +#define MAILBOX_SEMA1CON (0x01E0) +#define MAILBOX_SEMA1STATE (0x01E8) +#elif defined(CONFIG_SOC_EXYNOS9810) +#define MAILBOX_MCUCTRL (0x0000) +#define MAILBOX_INTGR0 (0x001C) +#define MAILBOX_INTCR0 (0x0020) +#define MAILBOX_INTMR0 (0x0024) +#define MAILBOX_INTSR0 (0x0028) +#define MAILBOX_INTMSR0 (0x002C) +#define MAILBOX_INTGR1 (0x0008) +#define MAILBOX_INTCR1 (0x000C) +#define MAILBOX_INTMR1 (0x0010) +#define MAILBOX_INTSR1 (0x0014) +#define MAILBOX_INTMSR1 (0x0018) +#define MAILBOX_MIF_INIT (0x004C) +#define MAILBOX_IS_VERSION (0x0050) +#define MAILBOX_ISSR0 (0x0080) +#define MAILBOX_ISSR1 (0x0084) +#define MAILBOX_ISSR2 (0x0088) +#define MAILBOX_ISSR3 (0x008C) +#define MAILBOX_ISSR4 (0x0090) +#define MAILBOX_ISSR5 (0x0094) +#define MAILBOX_SEMAPHORE0 (0x0180) +#define MAILBOX_SEMAPHORE1 (0x0184) +#define MAILBOX_SEMAPHORE2 (0x0188) +#define MAILBOX_SEMAPHORE3 (0x018C) +#define MAILBOX_SEMA0CON (0x01C0) +#define MAILBOX_SEMA0STATE (0x01C8) +#define MAILBOX_SEMA1CON (0x01E0) +#define MAILBOX_SEMA1STATE (0x01E8) +#endif + +/* MAILBOX_MCUCTRL */ +#define MAILBOX_MSWRST_OFFSET (0) +#define MAILBOX_MSWRST_SIZE (1) +/* MAILBOX_INT*R0 */ +#define MAILBOX_INT0_OFFSET (0) +#define MAILBOX_INT0_SIZE (16) +/* MAILBOX_INT*R1 */ +#define MAILBOX_INT1_OFFSET (16) +#define MAILBOX_INT1_SIZE (16) +/* MAILBOX_SEMA?CON */ +#define MAILBOX_INT_EN_OFFSET (3) +#define MAILBOX_INT_EN_SIZE (1) +/* MAILBOX_SEMA?STATE */ +#define MAILBOX_LOCK_OFFSET (0) +#define MAILBOX_LOCK_SIZE (1) + +#endif /* __MAILBOX_H */ diff --git a/sound/soc/samsung/vts/vts.c b/sound/soc/samsung/vts/vts.c new file mode 100644 index 000000000000..ee5608fee685 --- /dev/null +++ b/sound/soc/samsung/vts/vts.c @@ -0,0 +1,2655 @@ +/* sound/soc/samsung/vts/vts.c + * + * ALSA SoC - Samsung VTS driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +//#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "vts.h" +#include "vts_log.h" +#include "vts_dump.h" + +#undef EMULATOR +#ifdef EMULATOR +static void __iomem *pmu_alive; +#define set_mask_value(id, mask, value) do {id = ((id & ~mask) | (value & mask));} while(0); +static void update_mask_value(volatile void __iomem *sfr, + unsigned int mask, unsigned int value) +{ + unsigned int sfr_value = readl(sfr); + set_mask_value(sfr_value, mask, value); + writel(sfr_value, sfr); +} +#endif + + +#ifdef CONFIG_SOC_EXYNOS8895 +#define PAD_RETENTION_VTS_OPTION (0x3148) +#define VTS_CPU_CONFIGURATION (0x24E0) +#define VTS_CPU_LOCAL_PWR_CFG (0x00000001) +#define VTS_CPU_STATUS (0x24E4) +#define VTS_CPU_STATUS_STATUS_MASK (0x00000001) +#define VTS_CPU_STANDBY VTS_CPU_STATUS +#define VTS_CPU_STANDBY_STANDBYWFI_MASK (0x10000000) +#define VTS_CPU_OPTION (0x24E8) +#define VTS_CPU_OPTION_USE_STANDBYWFI_MASK (0x00010000) +#define VTS_CPU_RESET_OPTION VTS_CPU_OPTION +#define VTS_CPU_RESET_OPTION_ENABLE_CPU_MASK (0x00008000) +#elif defined(CONFIG_SOC_EXYNOS9810) +#define PAD_RETENTION_VTS_OPTION (0x4AD8) +#define VTS_CPU_CONFIGURATION (0x4AC4) +#define VTS_CPU_LOCAL_PWR_CFG (0x00000001) +#define VTS_CPU_STATUS (0x4AC8) +#define VTS_CPU_STATUS_STATUS_MASK (0x00000001) +#define VTS_CPU_STANDBY (0x3814) +#define VTS_CPU_STANDBY_STANDBYWFI_MASK (0x10000000) +#define VTS_CPU_OPTION (0x3818) +#define VTS_CPU_OPTION_USE_STANDBYWFI_MASK (0x00010000) +#define VTS_CPU_RESET_OPTION (0x4ACC) +#define VTS_CPU_RESET_OPTION_ENABLE_CPU_MASK (0x10000000) +#endif + +#define LIMIT_IN_JIFFIES (msecs_to_jiffies(1000)) +#define DMIC_CLK_RATE (768000) +#define VTS_TRIGGERED_TIMEOUT_MS (5000) + +/* For only external static functions */ +static struct vts_data *p_vts_data; + +static int vts_start_ipc_transaction_atomic(struct device *dev, struct vts_data *data, int msg, u32 (*values)[3], int sync) +{ + long result = 0; + u32 ack_value = 0; + volatile enum ipc_state *state = &data->ipc_state_ap; + + dev_info(dev, "%s:++ msg:%d, values: 0x%08x, 0x%08x, 0x%08x\n", + __func__, msg, (*values)[0], + (*values)[1], (*values)[2]); + + if (pm_runtime_suspended(dev)) { + dev_warn(dev, "%s: VTS IP is in suspended state, IPC cann't be processed \n", __func__); + return -EINVAL; + } + + if (!data->vts_ready) { + dev_warn(dev, "%s: VTS Firmware Not running\n", __func__); + return -EINVAL; + } + + spin_lock(&data->ipc_spinlock); + + *state = SEND_MSG; + mailbox_write_shared_register(data->pdev_mailbox, *values, 0, 3); + mailbox_generate_interrupt(data->pdev_mailbox, msg); + data->running_ipc = msg; + + if (sync) { + int i; + for (i = 1000; i && (*state != SEND_MSG_OK) && + (*state != SEND_MSG_FAIL) && + (ack_value != (0x1 << msg)); i--) { + mailbox_read_shared_register(data->pdev_mailbox, &ack_value, 3, 1); + dev_dbg(dev, "%s ACK-value: 0x%08x\n", __func__, ack_value); + udelay(50); + } + if (!i) { + dev_warn(dev, "Transaction timeout!! Ack_value:0x%x\n", + ack_value); + } + if (*state == SEND_MSG_OK || ack_value == (0x1 << msg)) { + dev_dbg(dev, "Transaction success Ack_value:0x%x\n", + ack_value); + if (ack_value == (0x1 << VTS_IRQ_AP_TEST_COMMAND) && + ((*values)[0] == VTS_ENABLE_DEBUGLOG || + (*values)[0] == VTS_ENABLE_AUDIODUMP || + (*values)[0] == VTS_ENABLE_LOGDUMP)) { + u32 ackvalues[3] = {0, 0, 0}; + + mailbox_read_shared_register(data->pdev_mailbox, + ackvalues, 0, 2); + dev_info(dev, "%s: offset: 0x%x size:0x%x\n", + __func__, ackvalues[0], ackvalues[1]); + if ((*values)[0] == VTS_ENABLE_DEBUGLOG) { + /* Register debug log buffer */ + vts_register_log_buffer(dev, + ackvalues[0], + ackvalues[1]); + dev_dbg(dev, "%s: Log buffer\n", + __func__); + } else { + u32 dumpmode = + ((*values)[0] == VTS_ENABLE_LOGDUMP ? + VTS_LOG_DUMP : VTS_AUDIO_DUMP); + /* register dump offset & size */ + vts_dump_addr_register(dev, + ackvalues[0], + ackvalues[1], + dumpmode); + dev_dbg(dev, "%s: Dump buffer\n", + __func__); + } + + } + } else { + dev_err(dev, "Transaction failed\n"); + } + result = (*state == SEND_MSG_OK || ack_value) ? 0 : -EIO; + } + + /* Clear running IPC & ACK value */ + ack_value = 0x0; + mailbox_write_shared_register(data->pdev_mailbox, &ack_value, 3, 1); + data->running_ipc = 0; + *state = IDLE; + + spin_unlock(&data->ipc_spinlock); + dev_info(dev, "%s:-- msg:%d \n", __func__, msg); + + return (int)result; +} + +int vts_start_ipc_transaction(struct device *dev, struct vts_data *data, + int msg, u32 (*values)[3], int atomic, int sync) +{ + return vts_start_ipc_transaction_atomic(dev, data, msg, values, sync); +} + +static int vts_ipc_ack(struct vts_data *data, u32 result) +{ + if (!data->vts_ready) + return 0; + + pr_debug("%s(%p, %u)\n", __func__, data, result); + mailbox_write_shared_register(data->pdev_mailbox, &result, 0, 1); + mailbox_generate_interrupt(data->pdev_mailbox, VTS_IRQ_AP_IPC_RECEIVED); + return 0; +} + +int vts_send_ipc_ack(struct vts_data *data, u32 result) +{ + return vts_ipc_ack(data, result); +} + +static void vts_cpu_power(bool on) +{ + pr_info("%s(%d)\n", __func__, on); + +#ifndef EMULATOR + exynos_pmu_update(VTS_CPU_CONFIGURATION, VTS_CPU_LOCAL_PWR_CFG, + on ? VTS_CPU_LOCAL_PWR_CFG : 0); +#else + update_mask_value(pmu_alive + VTS_CPU_CONFIGURATION, VTS_CPU_LOCAL_PWR_CFG, + on ? VTS_CPU_LOCAL_PWR_CFG : 0); +#endif + + if (!on) { +#ifndef EMULATOR + exynos_pmu_update(VTS_CPU_OPTION, + VTS_CPU_OPTION_USE_STANDBYWFI_MASK, + VTS_CPU_OPTION_USE_STANDBYWFI_MASK); +#else + update_mask_value(pmu_alive + VTS_CPU_OPTION, + VTS_CPU_OPTION_USE_STANDBYWFI_MASK, + VTS_CPU_OPTION_USE_STANDBYWFI_MASK); +#endif + } +} + +static int vts_cpu_enable(bool enable) +{ + unsigned int mask = VTS_CPU_RESET_OPTION_ENABLE_CPU_MASK; + unsigned int val = (enable ? mask : 0); + unsigned int status = 0; + unsigned long after; + + pr_info("%s(%d)\n", __func__, enable); + +#ifndef EMULATOR + exynos_pmu_update(VTS_CPU_RESET_OPTION, mask, val); +#else + update_mask_value(pmu_alive + VTS_CPU_RESET_OPTION, mask, val); +#endif + if (enable) { + after = jiffies + LIMIT_IN_JIFFIES; + do { + schedule(); +#ifndef EMULATOR + exynos_pmu_read(VTS_CPU_STATUS, &status); +#else + status = readl(pmu_alive + VTS_CPU_STATUS); +#endif + } while (((status & VTS_CPU_STATUS_STATUS_MASK) + != VTS_CPU_STATUS_STATUS_MASK) + && time_is_after_eq_jiffies(after)); + if (time_is_before_jiffies(after)) { + pr_err("vts cpu enable timeout\n"); + return -ETIME; + } + } + + return 0; +} + +static void vts_reset_cpu(void) +{ +#ifndef EMULATOR + vts_cpu_enable(false); + vts_cpu_power(false); + vts_cpu_power(true); + vts_cpu_enable(true); +#endif +} + +static int vts_download_firmware(struct platform_device *pdev) +{ + struct vts_data *data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + dev_info(dev, "%s\n", __func__); + + if (!data->firmware) { + dev_err(dev, "firmware is not loaded\n"); + return -EAGAIN; + } + + memcpy(data->sram_base, data->firmware->data, data->firmware->size); + dev_info(dev, "firmware is downloaded to %p (size=%zu)\n", data->sram_base, data->firmware->size); + + return 0; +} + +static int vts_wait_for_fw_ready(struct device *dev) +{ + struct vts_data *data = dev_get_drvdata(dev); + int result; + + result = wait_event_timeout(data->ipc_wait_queue, + data->vts_ready, msecs_to_jiffies(3000)); + if (data->vts_ready) { + result = 0; + } else { + dev_err(dev, "VTS Firmware is not ready\n"); + result = -ETIME; + } + + return result; +} + +static void vts_pad_retention(bool retention) +{ + if (!retention) { + exynos_pmu_update(PAD_RETENTION_VTS_OPTION, 0x10000000, 0x10000000); + } +} + +static void vts_cfg_gpio(struct device *dev, const char *name) +{ + struct vts_data *data = dev_get_drvdata(dev); + struct pinctrl_state *pin_state; + int ret; + + dev_info(dev, "%s(%s)\n", __func__, name); + + pin_state = pinctrl_lookup_state(data->pinctrl, name); + if (IS_ERR(pin_state)) { + dev_err(dev, "Couldn't find pinctrl %s\n", name); + } else { + ret = pinctrl_select_state(data->pinctrl, pin_state); + if (ret < 0) + dev_err(dev, "Unable to configure pinctrl %s\n", name); + } +} + +#ifdef CONFIG_SOC_EXYNOS9810 +static u32 vts_set_baaw(void __iomem *sfr_base, u64 base, u32 size) +{ + u32 aligned_size = round_up(size, SZ_4M); + u64 aligned_base = round_down(base, aligned_size); + + writel(VTS_BAAW_BASE / SZ_4K, sfr_base + VTS_BAAW_SRC_START_ADDRESS); + writel((VTS_BAAW_BASE + aligned_size) / SZ_4K, sfr_base + VTS_BAAW_SRC_END_ADDRESS); + writel(aligned_base / SZ_4K, sfr_base + VTS_BAAW_REMAPPED_ADDRESS); + writel(0x80000003, sfr_base + VTS_BAAW_INIT_DONE); + + return base - aligned_base + VTS_BAAW_BASE; +} +#endif + +static int vts_clk_set_rate(struct device *dev, unsigned long combination) +{ + struct vts_data *data = dev_get_drvdata(dev); + unsigned long dmic_rate, dmic_sync, dmic_if; + int result; + + dev_info(dev, "%s(%lu)\n", __func__, combination); + + switch (combination) { + case 2: + dmic_rate = 384000; + dmic_sync = 384000; + dmic_if = 768000; + break; + case 0: + dmic_rate = 512000; + dmic_sync = 512000; + dmic_if = 1024000; + break; + case 1: + dmic_rate = 768000; + dmic_sync = 768000; + dmic_if = 1536000; + break; + case 3: + dmic_rate = 4096000; + dmic_sync = 2048000; + dmic_if = 4096000; + break; + default: + result = -EINVAL; + goto out; + } + + + result = clk_set_rate(data->clk_dmic_if, dmic_if); + if (result < 0) { + dev_err(dev, "Failed to set rate of the clock %s\n", "dmic_if"); + goto out; + } + dev_info(dev, "DMIC IF clock rate: %lu\n", clk_get_rate(data->clk_dmic_if)); + + result = clk_set_rate(data->clk_dmic_sync, dmic_sync); + if (result < 0) { + dev_err(dev, "Failed to set rate of the clock %s\n", "dmic_sync"); + goto out; + } + dev_info(dev, "DMIC SYNC clock rate: %lu\n", clk_get_rate(data->clk_dmic_sync)); + + result = clk_set_rate(data->clk_dmic, dmic_rate); + if (result < 0) { + dev_err(dev, "Failed to set rate of the clock %s\n", "dmic"); + goto out; + } + dev_info(dev, "DMIC clock rate: %lu\n", clk_get_rate(data->clk_dmic)); + +out: + return result; +} + +int vts_acquire_sram(struct platform_device *pdev, int vts) +{ +#ifdef CONFIG_SOC_EXYNOS8895 + struct vts_data *data = platform_get_drvdata(pdev); + int previous; + + if (IS_ENABLED(CONFIG_SOC_EXYNOS8895)) { + dev_info(&pdev->dev, "%s(%d)\n", __func__, vts); + + if (!vts) { + while(pm_runtime_active(&pdev->dev)) { + dev_warn(&pdev->dev, "%s Clear existing active states\n", __func__); + pm_runtime_put_sync(&pdev->dev); + } + } + previous = test_and_set_bit(0, &data->sram_acquired); + if (previous) { + dev_err(&pdev->dev, "vts sram acquisition failed\n"); + return -EBUSY; + } + + if (!vts) { + pm_runtime_get_sync(&pdev->dev); + data->voicecall_enabled = true; + data->vts_state = VTS_STATE_VOICECALL; + } + + writel((vts ? 0 : 1) << VTS_MEM_SEL_OFFSET, data->sfr_base + VTS_SHARED_MEM_CTRL); + } +#endif + + return 0; +} +EXPORT_SYMBOL(vts_acquire_sram); + +int vts_release_sram(struct platform_device *pdev, int vts) +{ +#ifdef CONFIG_SOC_EXYNOS8895 + struct vts_data *data = platform_get_drvdata(pdev); + + dev_info(&pdev->dev, "%s(%d)\n", __func__, vts); + + if (IS_ENABLED(CONFIG_SOC_EXYNOS8895)) { + if (test_bit(0, &data->sram_acquired) && + (data->voicecall_enabled || vts)) { + writel(0 << VTS_MEM_SEL_OFFSET, + data->sfr_base + VTS_SHARED_MEM_CTRL); + clear_bit(0, &data->sram_acquired); + + if (!vts) { + pm_runtime_put_sync(&pdev->dev); + data->voicecall_enabled = false; + } + dev_info(&pdev->dev, "%s(%d) completed\n", + __func__, vts); + } else + dev_warn(&pdev->dev, "%s(%d) already released\n", + __func__, vts); + } + +#endif + return 0; +} +EXPORT_SYMBOL(vts_release_sram); + +int vts_clear_sram(struct platform_device *pdev) +{ + struct vts_data *data = platform_get_drvdata(pdev); + + pr_info("%s\n", __func__); + + memset(data->sram_base, 0, data->sram_size); + + return 0; +} +EXPORT_SYMBOL(vts_clear_sram); + +volatile bool vts_is_on(void) +{ + return p_vts_data && p_vts_data->enabled; +} +EXPORT_SYMBOL(vts_is_on); + +volatile bool vts_is_recognitionrunning(void) +{ + return p_vts_data && p_vts_data->running; +} +EXPORT_SYMBOL(vts_is_recognitionrunning); + +static struct snd_soc_dai_driver vts_dai[] = { + { + .name = "vts-tri", + .capture = { + .stream_name = "VTS Trigger Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16, + .sig_bits = 16, + }, + }, + { + .name = "vts-rec", + .capture = { + .stream_name = "VTS Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16, + .sig_bits = 16, + }, + }, +}; + +static const char *vts_hpf_sel_texts[] = {"120Hz", "40Hz"}; +static SOC_ENUM_SINGLE_DECL(vts_hpf_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_HPF_SEL_OFFSET, vts_hpf_sel_texts); + +static const char *vts_cps_sel_texts[] = {"normal", "absolute"}; +static SOC_ENUM_SINGLE_DECL(vts_cps_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_CPS_SEL_OFFSET, vts_cps_sel_texts); + +static const DECLARE_TLV_DB_SCALE(vts_gain_tlv_array, 0, 6, 0); + +static const char *vts_sys_sel_texts[] = {"512kHz", "768kHz", "384kHz", "2048kHz"}; +static SOC_ENUM_SINGLE_DECL(vts_sys_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_SYS_SEL_OFFSET, vts_sys_sel_texts); + +static int vts_sys_sel_put_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct device *dev = component->dev; + unsigned int *item = ucontrol->value.enumerated.item; + struct vts_data *data = p_vts_data; + + dev_dbg(dev, "%s(%u)\n", __func__, item[0]); + + data->syssel_rate = item[0]; + + return snd_soc_put_enum_double(kcontrol, ucontrol); +} + +static const char *vts_polarity_clk_texts[] = {"rising edge of clock", "falling edge of clock"}; +static SOC_ENUM_SINGLE_DECL(vts_polarity_clk, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_POLARITY_CLK_OFFSET, vts_polarity_clk_texts); + +static const char *vts_polarity_output_texts[] = {"right first", "left first"}; +static SOC_ENUM_SINGLE_DECL(vts_polarity_output, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_POLARITY_OUTPUT_OFFSET, vts_polarity_output_texts); + +static const char *vts_polarity_input_texts[] = {"left PDM on CLK high", "left PDM on CLK low"}; +static SOC_ENUM_SINGLE_DECL(vts_polarity_input, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_POLARITY_INPUT_OFFSET, vts_polarity_input_texts); + +static const char *vts_ovfw_ctrl_texts[] = {"limit", "reset"}; +static SOC_ENUM_SINGLE_DECL(vts_ovfw_ctrl, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_OVFW_CTRL_OFFSET, vts_ovfw_ctrl_texts); + +static const char *vts_cic_sel_texts[] = {"Off", "On"}; +static SOC_ENUM_SINGLE_DECL(vts_cic_sel, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_CIC_SEL_OFFSET, vts_cic_sel_texts); + +static const char * const vtsvcrecog_mode_text[] = { + "OFF", "VOICE_TRIGGER_MODE_ON", "SOUND_DETECT_MODE_ON", + "VT_ALWAYS_ON_MODE_ON", "GOOGLE_TRIGGER_MODE_ON", + "SENSORY_TRIGGER_MODE_ON", "VOICE_TRIGGER_MODE_OFF", + "SOUND_DETECT_MODE_OFF", "VT_ALWAYS_ON_MODE_OFF", + "GOOGLE_TRIGGER_MODE_OFF", "SENSORY_TRIGGER_MODE_OFF" +}; +/* Keyphrases svoice: "Hi Galaxy", Google:"Okay Google" Sensory: "Hi Blue Genie" */ +static const char * const vtsactive_phrase_text[] = { + "SVOICE", "GOOGLE", "SENSORY" +}; +static const char * const vtsforce_reset_text[] = { + "NONE", "RESET" +}; +static SOC_ENUM_SINGLE_EXT_DECL(vtsvcrecog_mode_enum, vtsvcrecog_mode_text); +static SOC_ENUM_SINGLE_EXT_DECL(vtsactive_phrase_enum, vtsactive_phrase_text); +static SOC_ENUM_SINGLE_EXT_DECL(vtsforce_reset_enum, vtsforce_reset_text); + +static int vts_start_recognization(struct device *dev, int start) +{ + struct vts_data *data = dev_get_drvdata(dev); + int active_trigger = data->active_trigger; + int result; + u32 values[3]; + + dev_info(dev, "%s for %s\n", __func__, vtsactive_phrase_text[active_trigger]); + + start = !!start; + if (start) { + dev_info(dev, "%s for %s G-loaded:%d s-loaded: %d\n", __func__, + vtsactive_phrase_text[active_trigger], + data->google_info.loaded, + data->svoice_info.loaded); + dev_info(dev, "%s exec_mode %d active_trig :%d\n", __func__, + data->exec_mode, active_trigger); + if (!(data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE))) { + if (active_trigger == TRIGGER_SVOICE && + data->svoice_info.loaded) { + /* + * load svoice model.bin @ offset 0x2A800 + * file before starting recognition + */ + memcpy(data->sram_base + 0x2A800, data->svoice_info.data, + data->svoice_info.actual_sz); + dev_info(dev, "svoice.bin Binary uploaded size=%zu\n", + data->svoice_info.actual_sz); + + } else if (active_trigger == TRIGGER_GOOGLE && + data->google_info.loaded) { + /* + * load google model.bin @ offset 0x32B00 + * file before starting recognition + */ + memcpy(data->sram_base + 0x32B00, data->google_info.data, + data->google_info.actual_sz); + dev_info(dev, "google.bin Binary uploaded size=%zu\n", + data->google_info.actual_sz); + } else { + dev_err(dev, "%s Model Binary File not Loaded\n", __func__); + return -EINVAL; + } + } + + result = vts_set_dmicctrl(data->pdev, + ((active_trigger == TRIGGER_SVOICE) ? + VTS_MICCONF_FOR_TRIGGER + : VTS_MICCONF_FOR_GOOGLE), true); + if (result < 0) { + dev_err(dev, "%s: MIC control failed\n", + __func__); + return result; + } + + if (data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE)) { + int ctrl_dmicif; + /* set VTS Gain for LPSD mode */ + ctrl_dmicif = readl(data->dmic_base + + VTS_DMIC_CONTROL_DMIC_IF); + ctrl_dmicif &= ~(0x7 << VTS_DMIC_GAIN_OFFSET); + writel((ctrl_dmicif | + (data->lpsdgain << VTS_DMIC_GAIN_OFFSET)), + data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF); + } + + /* Send Start recognition IPC command to VTS */ + values[0] = 1 << active_trigger; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_START_RECOGNITION, + &values, 0, 1); + if (result < 0) { + dev_err(dev, "vts ipc VTS_IRQ_AP_START_RECOGNITION failed: %d\n", result); + return result; + } + + data->vts_state = VTS_STATE_RECOG_STARTED; + dev_info(dev, "%s start=%d, active_trigger=%d\n", __func__, start, active_trigger); + + } else if (!start) { + values[0] = 1 << active_trigger; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_STOP_RECOGNITION, + &values, 0, 1); + if (result < 0) { + dev_err(dev, "vts ipc VTS_IRQ_AP_STOP_RECOGNITION failed: %d\n", result); + return result; + } + + data->vts_state = VTS_STATE_RECOG_STOPPED; + dev_info(dev, "%s start=%d, active_trigger=%d\n", __func__, start, active_trigger); + + result = vts_set_dmicctrl(data->pdev, + ((active_trigger == TRIGGER_SVOICE) ? + VTS_MICCONF_FOR_TRIGGER + : VTS_MICCONF_FOR_GOOGLE), false); + if (result < 0) { + dev_err(dev, "%s: MIC control failed\n", + __func__); + return result; + } + } + + + return 0; +} + +static int get_vtsvoicerecognize_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + + ucontrol->value.integer.value[0] = data->exec_mode; + + dev_dbg(component->dev, "GET VTS Execution mode: %d\n", + data->exec_mode); + + return 0; +} + +static int set_vtsvoicerecognize_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + struct device *dev = component->dev; + u32 values[3]; + int result = 0; + int vcrecognize_mode = 0; + int vcrecognize_start = 0; + + u32 keyword_type = 1; + char env[100] = {0,}; + char *envp[2] = {env, NULL}; + int loopcnt = 10; + + pm_runtime_barrier(component->dev); + + while (data->voicecall_enabled) { + dev_warn(dev, "%s voicecall (%d)\n", __func__, data->voicecall_enabled); + + if (loopcnt <= 0) { + dev_warn(dev, "%s VTS SRAM is Used for CP call\n", __func__); + + keyword_type = -EBUSY; + snprintf(env, sizeof(env), + "VOICE_WAKEUP_WORD_ID=%x", + keyword_type); + + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + return -EBUSY; + } + + loopcnt--; + usleep_range(10000, 10000); + } + + dev_warn(dev, "%s voicecall (%d) (End)\n", __func__, data->voicecall_enabled); + + vcrecognize_mode = ucontrol->value.integer.value[0]; + + if (vcrecognize_mode < VTS_VOICE_TRIGGER_MODE || + vcrecognize_mode >= VTS_MODE_COUNT) { + dev_err(dev, + "Invalid voice control mode =%d", vcrecognize_mode); + return 0; + } else { + dev_info(dev, "%s Current: %d requested %s\n", + __func__, data->exec_mode, + vtsvcrecog_mode_text[vcrecognize_mode]); + if ((vcrecognize_mode < VTS_VOICE_TRIGGER_MODE_OFF && + data->exec_mode & (0x1 << vcrecognize_mode)) || + (vcrecognize_mode >= VTS_VOICE_TRIGGER_MODE_OFF && + !(data->exec_mode & (0x1 << (vcrecognize_mode - + VTS_SENSORY_TRIGGER_MODE))))) { + dev_err(dev, + "Requested Recognition mode=%d already completed", + vcrecognize_mode); + return 0; + } + + if (vcrecognize_mode <= VTS_SENSORY_TRIGGER_MODE) { + pm_runtime_get_sync(dev); + vts_clk_set_rate(dev, data->syssel_rate); + vcrecognize_start = true; + } else + vcrecognize_start = false; + + if (!pm_runtime_active(dev)) { + dev_warn(dev, "%s wrong state %d req: %d\n", + __func__, data->exec_mode, + vcrecognize_mode); + return 0; + } + + values[0] = vcrecognize_mode; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, + data, VTS_IRQ_AP_SET_MODE, + &values, 0, 1); + if (result < 0) { + dev_err(dev, "%s IPC transaction Failed\n", + vtsvcrecog_mode_text[vcrecognize_mode]); + goto err_ipcmode; + } + + if (vcrecognize_start) + data->exec_mode |= (0x1 << vcrecognize_mode); + else + data->exec_mode &= ~(0x1 << (vcrecognize_mode - + VTS_SENSORY_TRIGGER_MODE)); + + /* Start/stop the request Voice recognization mode */ + result = vts_start_recognization(dev, + vcrecognize_start); + if (result < 0) { + dev_err(dev, "Start Recognization Failed: %d\n", + result); + goto err_ipcmode; + } + + if (vcrecognize_start) + data->voicerecog_start |= (0x1 << data->active_trigger); + else + data->voicerecog_start &= ~(0x1 << data->active_trigger); + + dev_info(dev, "%s Configured: [%d] %s started\n", + __func__, data->exec_mode, + vtsvcrecog_mode_text[vcrecognize_mode]); + + if (!vcrecognize_start && + pm_runtime_active(dev)) { + pm_runtime_put_sync(dev); + } + } + + return 0; + +err_ipcmode: + if (pm_runtime_active(dev) && (vcrecognize_start || + data->exec_mode & (0x1 << vcrecognize_mode) || + data->voicerecog_start & (0x1 << data->active_trigger))) { + pm_runtime_put_sync(dev); + } + + if (!vcrecognize_start) { + data->exec_mode &= ~(0x1 << (vcrecognize_mode - + VTS_SENSORY_TRIGGER_MODE)); + data->voicerecog_start &= ~(0x1 << data->active_trigger); + } + + return result; +} + +#ifdef CONFIG_SOC_EXYNOS8895 +static int set_vtsexec_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + u32 values[3]; + int result = 0; + int vtsexecution_mode; + + u32 keyword_type = 1; + char env[100] = {0,}; + char *envp[2] = {env, NULL}; + struct device *dev = &data->pdev->dev; + int loopcnt = 10; + + pm_runtime_barrier(component->dev); + + while (data->voicecall_enabled) { + dev_warn(component->dev, "%s voicecall (%d)\n", __func__, data->voicecall_enabled); + + if (loopcnt <= 0) { + dev_warn(component->dev, "%s VTS SRAM is Used for CP call\n", __func__); + + keyword_type = -EBUSY; + snprintf(env, sizeof(env), + "VOICE_WAKEUP_WORD_ID=%x", + keyword_type); + + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + return -EBUSY; + } + + loopcnt--; + usleep_range(10000, 10000); + } + + dev_warn(component->dev, "%s voicecall (%d) (End)\n", __func__, data->voicecall_enabled); + + vtsexecution_mode = ucontrol->value.integer.value[0]; + + if (vtsexecution_mode >= VTS_MODE_COUNT) { + dev_err(component->dev, + "Invalid voice control mode =%d", vtsexecution_mode); + return 0; + } else { + dev_info(component->dev, "%s Current: %d requested %s\n", + __func__, data->exec_mode, + vtsexec_mode_text[vtsexecution_mode]); + if (data->exec_mode == VTS_OFF_MODE && + vtsexecution_mode != VTS_OFF_MODE) { + pm_runtime_get_sync(component->dev); + vts_clk_set_rate(component->dev, data->syssel_rate); + } + + if (pm_runtime_active(component->dev)) { + values[0] = vtsexecution_mode; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(component->dev, + data, VTS_IRQ_AP_SET_MODE, + &values, 0, 1); + if (result < 0) { + dev_err(component->dev, "%s SET_MODE IPC transaction Failed\n", + vtsexec_mode_text[vtsexecution_mode]); + if (data->exec_mode == VTS_OFF_MODE && + vtsexecution_mode != VTS_OFF_MODE) + pm_runtime_put_sync(component->dev); + return result; + } + } + if (vtsexecution_mode <= VTS_SENSORY_TRIGGER_MODE) + data->exec_mode |= (0x1 << vtsexecution_mode); + else + data->exec_mode &= ~(0x1 << (vtsexecution_mode - + VTS_SENSORY_TRIGGER_MODE)); + dev_info(component->dev, "%s Configured: [%d] %s\n", + __func__, data->exec_mode, + vtsexec_mode_text[vtsexecution_mode]); + + if (data->exec_mode == VTS_OFF_MODE && + pm_runtime_active(component->dev)) { + pm_runtime_put_sync(component->dev); + } + } + + return 0; +} +#endif + +static int get_vtsactive_phrase(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + + ucontrol->value.integer.value[0] = data->active_trigger; + + dev_dbg(component->dev, "GET VTS Active Phrase: %s \n", + vtsactive_phrase_text[data->active_trigger]); + + return 0; +} + +static int set_vtsactive_phrase(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + int vtsactive_phrase; + + vtsactive_phrase = ucontrol->value.integer.value[0]; + + if (vtsactive_phrase > 2) { + dev_err(component->dev, + "Invalid VTS Trigger Key phrase =%d", vtsactive_phrase); + return 0; + } else { + data->active_trigger = vtsactive_phrase; + dev_info(component->dev, "VTS Active phrase: %s \n", + vtsactive_phrase_text[vtsactive_phrase]); + } + + return 0; +} + +static int get_voicetrigger_value(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + + ucontrol->value.integer.value[0] = data->target_size; + + dev_info(component->dev, "GET Voice Trigger Value: %d \n", + data->target_size); + + return 0; +} + +static int set_voicetrigger_value(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + int active_trigger = data->active_trigger; + u32 values[3]; + int result = 0; + int trig_ms; + + pm_runtime_barrier(component->dev); + + if (data->voicecall_enabled) { + u32 keyword_type = 1; + char env[100] = {0,}; + char *envp[2] = {env, NULL}; + struct device *dev = &data->pdev->dev; + + dev_warn(component->dev, "%s VTS SRAM is Used for CP call\n", __func__); + keyword_type = -EBUSY; + snprintf(env, sizeof(env), + "VOICE_WAKEUP_WORD_ID=%x", + keyword_type); + + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + return -EBUSY; + } + + trig_ms = ucontrol->value.integer.value[0]; + + if (trig_ms > 2000 || trig_ms < 0) { + dev_err(component->dev, + "Invalid Voice Trigger Value = %d (valid range 0~2000ms)", trig_ms); + return 0; + } else { + /* Configure VTS target size */ + values[0] = trig_ms * 32; /* 1ms requires (16KHz,16bit,Mono) = 16samples * 2 bytes = 32 bytes*/ + values[1] = 1 << active_trigger; + values[2] = 0; + result = vts_start_ipc_transaction(component->dev, data, VTS_IRQ_AP_TARGET_SIZE, &values, 0, 1); + if (result < 0) { + dev_err(component->dev, "Voice Trigger Value setting IPC Transaction Failed: %d\n", result); + return result; + } + + data->target_size = trig_ms; + dev_info(component->dev, "SET Voice Trigger Value: %dms\n", + data->target_size); + } + + return 0; +} + +static int get_vtsforce_reset(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct vts_data *data = p_vts_data; + + ucontrol->value.integer.value[0] = data->running; + + dev_dbg(component->dev, "GET VTS Force Reset: %s\n", + (data->running ? "VTS Running" : + "VTS Not Running")); + + return 0; +} + +static int set_vtsforce_reset(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct device *dev = component->dev; + struct vts_data *data = p_vts_data; + + dev_dbg(dev, "VTS RESET: %s\n", __func__); + + while (data->running && pm_runtime_active(dev)) { + dev_warn(dev, "%s Clear active models\n", __func__); + pm_runtime_put_sync(dev); + } + + return 0; +} + +static const struct snd_kcontrol_new vts_controls[] = { + SOC_SINGLE("PERIOD DATA2REQ", VTS_DMIC_ENABLE_DMIC_IF, VTS_DMIC_PERIOD_DATA2REQ_OFFSET, 3, 0), + SOC_SINGLE("HPF EN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_HPF_EN_OFFSET, 1, 0), + SOC_ENUM("HPF SEL", vts_hpf_sel), + SOC_ENUM("CPS SEL", vts_cps_sel), + SOC_SINGLE_TLV("GAIN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_GAIN_OFFSET, 4, 0, vts_gain_tlv_array), + SOC_ENUM_EXT("SYS SEL", vts_sys_sel, snd_soc_get_enum_double, vts_sys_sel_put_enum), + SOC_ENUM("POLARITY CLK", vts_polarity_clk), + SOC_ENUM("POLARITY OUTPUT", vts_polarity_output), + SOC_ENUM("POLARITY INPUT", vts_polarity_input), + SOC_ENUM("OVFW CTRL", vts_ovfw_ctrl), + SOC_ENUM("CIC SEL", vts_cic_sel), + SOC_ENUM_EXT("VoiceRecognization Mode", vtsvcrecog_mode_enum, + get_vtsvoicerecognize_mode, set_vtsvoicerecognize_mode), + SOC_ENUM_EXT("Active Keyphrase", vtsactive_phrase_enum, + get_vtsactive_phrase, set_vtsactive_phrase), + SOC_SINGLE_EXT("VoiceTrigger Value", + SND_SOC_NOPM, + 0, 2000, 0, + get_voicetrigger_value, set_voicetrigger_value), + SOC_ENUM_EXT("Force Reset", vtsforce_reset_enum, + get_vtsforce_reset, set_vtsforce_reset), +}; + +static const char *dmic_sel_texts[] = {"DPDM", "APDM"}; +static SOC_ENUM_SINGLE_DECL(dmic_sel_enum, VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_DMIC_SEL_OFFSET, dmic_sel_texts); +static const struct snd_kcontrol_new dmic_sel_controls[] = { + SOC_DAPM_ENUM("MUX", dmic_sel_enum), +}; + +static const struct snd_kcontrol_new dmic_if_controls[] = { + SOC_DAPM_SINGLE("RCH EN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_RCH_EN_OFFSET, 1, 0), + SOC_DAPM_SINGLE("LCH EN", VTS_DMIC_CONTROL_DMIC_IF, VTS_DMIC_LCH_EN_OFFSET, 1, 0), +}; + +static const struct snd_soc_dapm_widget vts_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("PAD APDM"), + SND_SOC_DAPM_INPUT("PAD DPDM"), + SND_SOC_DAPM_MUX("DMIC SEL", SND_SOC_NOPM, 0, 0, dmic_sel_controls), + SOC_MIXER_ARRAY("DMIC IF", SND_SOC_NOPM, 0, 0, dmic_if_controls), +}; + +static const struct snd_soc_dapm_route vts_dapm_routes[] = { + // sink, control, source + {"DMIC SEL", "APDM", "PAD APDM"}, + {"DMIC SEL", "DPDM", "PAD DPDM"}, + {"DMIC IF", "RCH EN", "DMIC SEL"}, + {"DMIC IF", "LCH EN", "DMIC SEL"}, + {"VTS Capture", NULL, "DMIC IF"}, +}; + + +static int vts_component_probe(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + struct vts_data *data = dev_get_drvdata(dev); + + dev_info(dev, "%s\n", __func__); + + data->cmpnt = component; + vts_clk_set_rate(component->dev, 0); + + return 0; +} + +static const struct snd_soc_component_driver vts_component = { + .probe = vts_component_probe, + .controls = vts_controls, + .num_controls = ARRAY_SIZE(vts_controls), + .dapm_widgets = vts_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(vts_dapm_widgets), + .dapm_routes = vts_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(vts_dapm_routes), +}; + +int vts_set_dmicctrl(struct platform_device *pdev, int micconf_type, bool enable) +{ + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + int dmic_clkctrl = 0; + int ctrl_dmicif = 0; + int select_dmicclk = 0; + + dev_dbg(dev, "%s-- flag: %d mictype: %d micusagecnt: %d\n", + __func__, enable, micconf_type, data->micclk_init_cnt); + if (!data->vts_ready) { + dev_warn(dev, "%s: VTS Firmware Not running\n", __func__); + return -EINVAL; + } + + if (enable) { + if (!data->micclk_init_cnt) { + ctrl_dmicif = readl(data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF); + + if (ctrl_dmicif & (0x1 << VTS_DMIC_DMIC_SEL_OFFSET)) { + vts_cfg_gpio(dev, "amic_default"); + select_dmicclk = ((0x1 << VTS_ENABLE_CLK_GEN_OFFSET) | + (0x1 << VTS_SEL_EXT_DMIC_CLK_OFFSET) | + (0x1 << VTS_ENABLE_CLK_CLK_GEN_OFFSET)); + writel(select_dmicclk, data->sfr_base + VTS_DMIC_CLK_CON); + /* Set AMIC VTS Gain */ + writel((ctrl_dmicif | + (data->amicgain << VTS_DMIC_GAIN_OFFSET)), + data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF); + + } else { + vts_cfg_gpio(dev, "dmic_default"); + select_dmicclk = ((0x0 << VTS_ENABLE_CLK_GEN_OFFSET) | + (0x0 << VTS_SEL_EXT_DMIC_CLK_OFFSET) | + (0x0 << VTS_ENABLE_CLK_CLK_GEN_OFFSET)); + writel(select_dmicclk, data->sfr_base + VTS_DMIC_CLK_CON); + /* Set DMIC VTS Gain */ + writel((ctrl_dmicif | + (data->dmicgain << VTS_DMIC_GAIN_OFFSET)), + data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF); + } + + dmic_clkctrl = readl(data->sfr_base + VTS_DMIC_CLK_CTRL); + writel(dmic_clkctrl | (0x1 << VTS_CLK_ENABLE_OFFSET), + data->sfr_base + VTS_DMIC_CLK_CTRL); + dev_info(dev, "%s Micclk setting ENABLED\n", __func__); + } + + /* check whether Mic is already configure or not based on VTS + option type for MIC configuration book keeping */ + if ((!(data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER)) || + !(data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE))) && + (micconf_type == VTS_MICCONF_FOR_TRIGGER || + micconf_type == VTS_MICCONF_FOR_GOOGLE)) { + data->micclk_init_cnt++; + data->mic_ready |= (0x1 << micconf_type); + dev_info(dev, "%s Micclk ENABLED for TRIGGER ++ %d\n", + __func__, data->mic_ready); + } else if (!(data->mic_ready & (0x1 << VTS_MICCONF_FOR_RECORD)) && + micconf_type == VTS_MICCONF_FOR_RECORD) { + data->micclk_init_cnt++; + data->mic_ready |= (0x1 << micconf_type); + dev_info(dev, "%s Micclk ENABLED for RECORD ++ %d\n", + __func__, data->mic_ready); + } + } else { + if (data->micclk_init_cnt) + data->micclk_init_cnt--; + if (!data->micclk_init_cnt) { + vts_cfg_gpio(dev, "idle"); + + dmic_clkctrl = readl(data->sfr_base + VTS_DMIC_CLK_CTRL); + writel(dmic_clkctrl & ~(0x1 << VTS_CLK_ENABLE_OFFSET), + data->sfr_base + VTS_DMIC_CLK_CTRL); + writel(0x0, data->sfr_base + VTS_DMIC_CLK_CON); + /* reset VTS Gain to default */ + writel((ctrl_dmicif & (~(0x7 << VTS_DMIC_GAIN_OFFSET))), + data->dmic_base + VTS_DMIC_CONTROL_DMIC_IF); + dev_info(dev, "%s Micclk setting DISABLED\n", __func__); + } + + /* MIC configuration book keeping */ + if (((data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER)) || + (data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE))) && + (micconf_type == VTS_MICCONF_FOR_TRIGGER || + micconf_type == VTS_MICCONF_FOR_GOOGLE)) { + data->mic_ready &= ~(0x1 << micconf_type); + dev_info(dev, "%s Micclk DISABLED for TRIGGER -- %d\n", + __func__, data->mic_ready); + } else if ((data->mic_ready & (0x1 << VTS_MICCONF_FOR_RECORD)) && + micconf_type == VTS_MICCONF_FOR_RECORD) { + data->mic_ready &= ~(0x1 << micconf_type); + dev_info(dev, "%s Micclk DISABLED for RECORD -- %d\n", + __func__, data->mic_ready); + } + } + + return 0; +} + +static irqreturn_t vts_error_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + u32 error_code; + + mailbox_read_shared_register(data->pdev_mailbox, &error_code, 3, 1); + vts_ipc_ack(data, 1); + + dev_err(dev, "Error occurred on VTS: 0x%x\n", (int)error_code); + vts_reset_cpu(); + + return IRQ_HANDLED; +} + +static irqreturn_t vts_boot_completed_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + + data->vts_ready = 1; + + vts_ipc_ack(data, 1); + wake_up(&data->ipc_wait_queue); + + dev_info(dev, "VTS boot completed\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t vts_ipc_received_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + u32 result; + + mailbox_read_shared_register(data->pdev_mailbox, &result, 3, 1); + dev_info(dev, "VTS received IPC: 0x%x\n", result); + + switch (data->ipc_state_ap) { + case SEND_MSG: + if (result == (0x1 << data->running_ipc)) { + dev_dbg(dev, "IPC transaction completed\n"); + data->ipc_state_ap = SEND_MSG_OK; + } else { + dev_err(dev, "IPC transaction error\n"); + data->ipc_state_ap = SEND_MSG_FAIL; + } + break; + default: + dev_warn(dev, "State fault: %d Ack_value:0x%x\n", + data->ipc_state_ap, result); + break; + } + + return IRQ_HANDLED; +} + +static irqreturn_t vts_voice_triggered_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + u32 id, score, frame_count; + u32 keyword_type = 1; + char env[100] = {0,}; + char *envp[2] = {env, NULL}; + + if (data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER) || + data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE)) { + mailbox_read_shared_register(data->pdev_mailbox, &id, + 3, 1); + vts_ipc_ack(data, 1); + + frame_count = (u32)(id & GENMASK(15, 0)); + score = (u32)((id & GENMASK(27, 16)) >> 16); + id >>= 28; + + dev_info(dev, "VTS triggered: id = %u, score = %u\n", + id, score); + dev_info(dev, "VTS triggered: frame_count = %u\n", + frame_count); + + if (!(data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE))) { + keyword_type = id; + snprintf(env, sizeof(env), + "VOICE_WAKEUP_WORD_ID=%x", + keyword_type); + } else if (data->exec_mode & (0x1 << VTS_SOUND_DETECT_MODE)) { + snprintf(env, sizeof(env), + "VOICE_WAKEUP_WORD_ID=LPSD"); + } else { + dev_warn(dev, "Unknown VTS Execution Mode!!\n"); + } + + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + wake_lock_timeout(&data->wake_lock, + VTS_TRIGGERED_TIMEOUT_MS); + data->vts_state = VTS_STATE_RECOG_TRIGGERED; + } + + return IRQ_HANDLED; +} + +static irqreturn_t vts_trigger_period_elapsed_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + struct vts_platform_data *platform_data = platform_get_drvdata(data->pdev_vtsdma[0]); + u32 pointer; + + if (data->mic_ready & (0x1 << VTS_MICCONF_FOR_TRIGGER) || + data->mic_ready & (0x1 << VTS_MICCONF_FOR_GOOGLE)) { + mailbox_read_shared_register(data->pdev_mailbox, + &pointer, 2, 1); + dev_dbg(dev, "%s:[%s] Base: %08x pointer:%08x\n", + __func__, (platform_data->id ? "VTS-RECORD" : + "VTS-TRIGGER"), data->dma_area_vts, pointer); + + if (pointer) + platform_data->pointer = (pointer - + data->dma_area_vts); + vts_ipc_ack(data, 1); + + + snd_pcm_period_elapsed(platform_data->substream); + } + + return IRQ_HANDLED; +} + +static irqreturn_t vts_record_period_elapsed_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + struct vts_platform_data *platform_data = platform_get_drvdata(data->pdev_vtsdma[1]); + u32 pointer; + + if (data->mic_ready & (0x1 << VTS_MICCONF_FOR_RECORD)) { + mailbox_read_shared_register(data->pdev_mailbox, + &pointer, 1, 1); + dev_dbg(dev, "%s:[%s] Base: %08x pointer:%08x\n", + __func__, + (platform_data->id ? "VTS-RECORD" : "VTS-TRIGGER"), + (data->dma_area_vts + BUFFER_BYTES_MAX/2), pointer); + + if (pointer) + platform_data->pointer = (pointer - + (data->dma_area_vts + BUFFER_BYTES_MAX/2)); + vts_ipc_ack(data, 1); + + snd_pcm_period_elapsed(platform_data->substream); + } + + return IRQ_HANDLED; +} + +static irqreturn_t vts_debuglog_bufzero_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + + dev_dbg(dev, "%s LogBuffer Index: %d\n", __func__, 0); + + /* schedule log dump */ + vts_log_schedule_flush(dev, 0); + + vts_ipc_ack(data, 1); + + return IRQ_HANDLED; +} + +static irqreturn_t vts_debuglog_bufone_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + + dev_dbg(dev, "%s LogBuffer Index: %d\n", __func__, 1); + + /* schedule log dump */ + vts_log_schedule_flush(dev, 1); + + vts_ipc_ack(data, 1); + + return IRQ_HANDLED; +} + +static irqreturn_t vts_audiodump_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + + dev_info(dev, "%s\n", __func__); + + if (data->vts_ready && data->audiodump_enabled) { + u32 ackvalues[3] = {0, 0, 0}; + + mailbox_read_shared_register(data->pdev_mailbox, + ackvalues, 0, 2); + dev_info(dev, "%sDump offset: 0x%x size:0x%x\n", + __func__, ackvalues[0], ackvalues[1]); + /* register audio dump offset & size */ + vts_dump_addr_register(dev, ackvalues[0], + ackvalues[1], VTS_AUDIO_DUMP); + /* schedule pcm dump */ + vts_audiodump_schedule_flush(dev); + /* vts_ipc_ack should be sent once dump is completed */ + } else { + vts_ipc_ack(data, 1); + } + + return IRQ_HANDLED; +} + +static irqreturn_t vts_logdump_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + + dev_info(dev, "%s\n", __func__); + + if (data->vts_ready && data->logdump_enabled) { + /* schedule pcm dump */ + vts_logdump_schedule_flush(dev); + /* vts_ipc_ack should be sent once dump is completed */ + } else { + vts_ipc_ack(data, 1); + } + + return IRQ_HANDLED; +} + +void vts_register_dma(struct platform_device *pdev_vts, + struct platform_device *pdev_vts_dma, unsigned int id) +{ + struct vts_data *data = platform_get_drvdata(pdev_vts); + + if (id < ARRAY_SIZE(data->pdev_vtsdma)) { + data->pdev_vtsdma[id] = pdev_vts_dma; + if (id > data->vtsdma_count) { + data->vtsdma_count = id + 1; + } + dev_info(&data->pdev->dev, "%s: VTS-DMA id(%u)Registered \n", __func__, id); + } else { + dev_err(&data->pdev->dev, "%s: invalid id(%u)\n", __func__, id); + } +} + +static int vts_suspend(struct device *dev) +{ + struct vts_data *data = dev_get_drvdata(dev); + u32 values[3] = {0,0,0}; + int result = 0; + + if (data->vts_ready) { + if (data->running && + data->vts_state == VTS_STATE_RECOG_TRIGGERED) { + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_RESTART_RECOGNITION, + &values, 0, 1); + if (result < 0) { + dev_err(dev, "%s restarted trigger failed\n", + __func__); + goto error_ipc; + } + data->vts_state = VTS_STATE_RECOG_STARTED; + } + + /* enable vts wakeup source interrupts */ + enable_irq_wake(data->irq[VTS_IRQ_VTS_VOICE_TRIGGERED]); + enable_irq_wake(data->irq[VTS_IRQ_VTS_ERROR]); + if (data->audiodump_enabled) + enable_irq_wake(data->irq[VTS_IRQ_VTS_AUDIO_DUMP]); + if (data->logdump_enabled) + enable_irq_wake(data->irq[VTS_IRQ_VTS_LOG_DUMP]); + + dev_info(dev, "%s: Enable VTS Wakeup source irqs\n", __func__); + } + +error_ipc: + return result; +} + +static int vts_resume(struct device *dev) +{ + struct vts_data *data = dev_get_drvdata(dev); + + if (data->vts_ready) { + /* disable vts wakeup source interrupts */ + disable_irq_wake(data->irq[VTS_IRQ_VTS_VOICE_TRIGGERED]); + disable_irq_wake(data->irq[VTS_IRQ_VTS_ERROR]); + if (data->audiodump_enabled) + disable_irq_wake(data->irq[VTS_IRQ_VTS_AUDIO_DUMP]); + if (data->logdump_enabled) + disable_irq_wake(data->irq[VTS_IRQ_VTS_LOG_DUMP]); + + dev_info(dev, "%s: Disable VTS Wakeup source irqs\n", __func__); + } + return 0; +} + +static void vts_irq_enable(struct platform_device *pdev, bool enable) +{ + struct vts_data *data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int irqidx; + + dev_info(dev, "%s IRQ Enable: [%s]\n", __func__, + (enable ? "TRUE" : "FALSE")); + + for (irqidx = 0; irqidx < VTS_IRQ_COUNT; irqidx++) { + if (enable) + enable_irq(data->irq[irqidx]); + else + disable_irq(data->irq[irqidx]); + } +} + +static void vts_save_register(struct vts_data *data) +{ + regcache_cache_only(data->regmap_dmic, true); + regcache_mark_dirty(data->regmap_dmic); +} + +static void vts_restore_register(struct vts_data *data) +{ + regcache_cache_only(data->regmap_dmic, false); + regcache_sync(data->regmap_dmic); +} + +void vts_dbg_print_gpr(struct device *dev, struct vts_data *data) +{ + int i; + char buf[10]; + unsigned int version = data->vtsfw_version; + + buf[0] = (((version >> 24) & 0xFF) + '0'); + buf[1] = '.'; + buf[2] = (((version >> 16) & 0xFF) + '0'); + buf[3] = '.'; + buf[4] = (((version >> 8) & 0xFF) + '0'); + buf[5] = '.'; + buf[6] = ((version & 0xFF) + '0'); + buf[7] = '\0'; + + dev_info(dev, "========================================\n"); + dev_info(dev, "VTS CM4 register dump (%s)\n", buf); + dev_info(dev, "----------------------------------------\n"); + for (i = 0; i <= 14; i++) { + dev_info(dev, "CM4_R%02d : %08x\n", i, + readl(data->gpr_base + VTS_CM4_R(i))); + } + dev_info(dev, "CM4_PC : %08x\n", + readl(data->gpr_base + VTS_CM4_PC)); + dev_info(dev, "========================================\n"); +} + +void vts_dbg_dump_log_gpr( + struct device *dev, + struct vts_data *data, + unsigned int dbg_type, + const char *reason) +{ + struct vts_dbg_dump *p_dump = NULL; + int i; + + dev_dbg(dev, "%s\n", __func__); + + if (!vts_is_on() || !data->running) { + dev_info(dev, "%s is skipped due to no power\n", __func__); + return; + } + + if (dbg_type == RUNTIME_SUSPEND_DUMP) + p_dump = (struct vts_dbg_dump *)data->dmab_log.area; + else + p_dump = (struct vts_dbg_dump *)(data->dmab_log.area + + (LOG_BUFFER_BYTES_MAX/2)); + + /* Get VTS firmware log msgs */ + memcpy_fromio(p_dump->sram, data->sramlog_baseaddr, SZ_2K); + + p_dump->time = sched_clock(); + strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1); + for (i = 0; i <= 14; i++) + p_dump->gpr[i] = readl(data->gpr_base + VTS_CM4_R(i)); + + p_dump->gpr[i++] = readl(data->gpr_base + VTS_CM4_PC); +} + +static void exynos_vts_panic_handler(void) +{ + static bool has_run; + struct vts_data *data = p_vts_data; + struct device *dev = data ? (data->pdev ? &data->pdev->dev : NULL) : NULL; + + dev_dbg(dev, "%s\n", __func__); + + if (vts_is_on() && dev) { + if (has_run) { + dev_info(dev, "already dumped\n"); + return; + } + has_run = true; + + /* Dump VTS GPR register & Log messages */ + vts_dbg_dump_log_gpr(dev, data, KERNEL_PANIC_DUMP, + "panic"); + } else { + dev_info(dev, "%s: dump is skipped due to no power\n", __func__); + } +} + +static int vts_panic_handler(struct notifier_block *nb, + unsigned long action, void *data) +{ + exynos_vts_panic_handler(); + return NOTIFY_OK; +} + +static struct notifier_block vts_panic_notifier = { + .notifier_call = vts_panic_handler, + .next = NULL, + .priority = 0 /* priority: INT_MAX >= x >= 0 */ +}; + +static int vts_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vts_data *data = dev_get_drvdata(dev); + int i = 1000; + unsigned int status = 0; + u32 values[3] = {0,0,0}; + int result = 0; + + dev_info(dev, "%s \n", __func__); + + vts_save_register(data); + + if (data->running) { + if (data->audiodump_enabled) { + values[0] = VTS_DISABLE_AUDIODUMP; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_TEST_COMMAND, + &values, 0, 1); + if (result < 0) + dev_warn(dev, "Disable_AudioDump ipc failed\n"); + /* reset audio dump offset & size */ + vts_dump_addr_register(dev, 0, 0, VTS_AUDIO_DUMP); + } + + if (data->logdump_enabled) { + values[0] = VTS_DISABLE_LOGDUMP; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_TEST_COMMAND, + &values, 0, 1); + if (result < 0) + dev_warn(dev, "Disable_LogDump ipc failed\n"); + /* reset audio dump offset & size */ + vts_dump_addr_register(dev, 0, 0, VTS_LOG_DUMP); + } + + if (data->vtslog_enabled) { + values[0] = VTS_DISABLE_DEBUGLOG; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_TEST_COMMAND, + &values, 0, 1); + if (result < 0) + dev_warn(dev, "Disable_debuglog ipc transaction failed\n"); + /* reset VTS SRAM debug log buffer */ + vts_register_log_buffer(dev, 0, 0); + } + + values[0] = 0; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_POWER_DOWN, &values, 0, 1); + if (result < 0) { + dev_warn(dev, "POWER_DOWN IPC transaction Failed\n"); + } + + /* Dump VTS GPR register & Log messages */ + vts_dbg_print_gpr(dev, data); + vts_dbg_dump_log_gpr(dev, data, RUNTIME_SUSPEND_DUMP, + "runtime_suspend"); + + /* wait for VTS STANDBYWFI in STATUS SFR */ + do { + exynos_pmu_read(VTS_CPU_STANDBY, &status); + dev_dbg(dev, "%s Read VTS_CPU_STANDBY for STANDBYWFI status\n", __func__); + } while (i-- && !(status & VTS_CPU_STANDBY_STANDBYWFI_MASK)); + + if (!i) { + dev_warn(dev, "VTS IP entering WFI time out\n"); + } + + if (data->irq_state) { + vts_irq_enable(pdev, false); + data->irq_state = false; + } + clk_disable(data->clk_dmic); + vts_cpu_enable(false); + vts_cpu_power(false); + data->running = false; + } + + data->enabled = false; + data->exec_mode = VTS_OFF_MODE; + data->active_trigger = TRIGGER_SVOICE; + data->voicerecog_start = 0; + data->target_size = 0; + /* reset micbias setting count */ + data->micclk_init_cnt = 0; + data->mic_ready = 0; + data->vts_ready = 0; + data->vts_state = VTS_STATE_NONE; + dev_info(dev, "%s Exit \n", __func__); + return 0; +} + +static int vts_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vts_data *data = dev_get_drvdata(dev); + u32 values[3]; + int result; + + dev_info(dev, "%s \n", __func__); + + data->enabled = true; + + vts_restore_register(data); + + vts_cfg_gpio(dev, "dmic_default"); + vts_cfg_gpio(dev, "idle"); + vts_pad_retention(false); + + if (!data->irq_state) { + vts_irq_enable(pdev, true); + data->irq_state = true; + } + + result = clk_enable(data->clk_dmic); + if (result < 0) { + dev_err(dev, "Failed to enable the clock\n"); + goto error_clk; + } + dev_info(dev, "dmic clock rate:%lu\n", clk_get_rate(data->clk_dmic)); + + vts_cpu_power(true); + + result = vts_download_firmware(pdev); + if (result < 0) { + dev_err(dev, "Failed to download firmware\n"); + goto error_firmware; + } + + vts_cpu_enable(true); + + vts_wait_for_fw_ready(dev); + + /* Configure select sys clock rate */ + vts_clk_set_rate(dev, data->syssel_rate); + +#if 1 + data->dma_area_vts= vts_set_baaw(data->baaw_base, + data->dmab.addr, BUFFER_BYTES_MAX); +#endif + values[0] = data->dma_area_vts; + values[1] = 0x140; + values[2] = 0x800; + result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_SET_DRAM_BUFFER, &values, 0, 1); + if (result < 0) { + dev_err(dev, "DRAM_BUFFER Setting IPC transaction Failed\n"); + goto error_firmware; + } + + values[0] = VTS_OFF_MODE; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_SET_MODE, &values, 0, 1); + if (result < 0) { + dev_err(dev, "SET_MODE to OFF IPC transaction Failed\n"); + goto error_firmware; + } + data->exec_mode = VTS_OFF_MODE; + + values[0] = VTS_ENABLE_SRAM_LOG; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1); + if (result < 0) { + dev_err(dev, "Enable_SRAM_log ipc transaction failed\n"); + goto error_firmware; + } + + if (data->vtslog_enabled) { + values[0] = VTS_ENABLE_DEBUGLOG; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1); + if (result < 0) { + dev_err(dev, "Enable_debuglog ipc transaction failed\n"); + goto error_firmware; + } + } + + /* Enable Audio data Dump */ + if (data->audiodump_enabled) { + values[0] = VTS_ENABLE_AUDIODUMP; + values[1] = (VTS_ADUIODUMP_AFTER_MINS * 60); + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_TEST_COMMAND, &values, + 0, 2); + if (result < 0) { + dev_err(dev, "Enable_AudioDump ipc failed\n"); + goto error_firmware; + } + } + + /* Enable VTS FW log Dump */ + if (data->logdump_enabled) { + values[0] = VTS_ENABLE_LOGDUMP; + values[1] = (VTS_LOGDUMP_AFTER_MINS * 60); + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_TEST_COMMAND, &values, + 0, 2); + if (result < 0) { + dev_err(dev, "Enable_LogDump ipc failed\n"); + goto error_firmware; + } + } + + /* Enable CM4 GPR Dump */ + writel(0x1, data->gpr_base); + + dev_dbg(dev, "%s DRAM-setting and VTS-Mode is completed \n", __func__); + dev_info(dev, "%s Exit \n", __func__); + + data->running = true; + data->vts_state = VTS_STATE_IDLE; + return 0; + +error_firmware: + vts_cpu_power(false); + clk_disable(data->clk_dmic); +error_clk: + if (data->irq_state) { + vts_irq_enable(pdev, false); + data->irq_state = false; + } + data->running = false; + return 0; +} + +static const struct dev_pm_ops samsung_vts_pm = { + SET_SYSTEM_SLEEP_PM_OPS(vts_suspend, vts_resume) + SET_RUNTIME_PM_OPS(vts_runtime_suspend, vts_runtime_resume, NULL) +}; + +static const struct of_device_id exynos_vts_of_match[] = { + { + .compatible = "samsung,vts", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_vts_of_match); + +static ssize_t vts_google_model_read(struct file *file, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buffer, + loff_t ppos, size_t count) +{ + dev_info(kobj_to_dev(kobj), "%s\n", __func__); + + return 0; +} + +static ssize_t vts_google_model_write(struct file *file, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buffer, loff_t ppos, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct vts_data *data = dev_get_drvdata(dev); + char *to; + ssize_t available; + + dev_info(dev, "%s\n", __func__); + + if (!data->google_info.data) { + dev_info(dev, "%s Google binary Buffer not allocated\n", __func__); + return -EINVAL; + } + + to = data->google_info.data; + available = data->google_info.max_sz; + + dev_dbg(dev, "%s available %zu Cur-pos %lld size-to-copy %zu\n", __func__, + available, ppos, count); + if (ppos < 0) { + dev_info(dev, "%s Error wrong current position\n", __func__); + return -EINVAL; + } + if (ppos >= available || !count) { + dev_info(dev, "%s Error copysize[%lld] greater than available buffer\n", + __func__, ppos); + return 0; + } + if (count > available - ppos) + count = available - ppos; + + memcpy(to + ppos, buffer, count); + + to = (to + ppos); + data->google_info.actual_sz = ppos + count; + dev_info(dev, "%s updated Size %zu\n", __func__, + data->google_info.actual_sz); + data->google_info.loaded = true; + + return count; +} + +static ssize_t vts_svoice_model_read(struct file *file, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buffer, + loff_t ppos, size_t count) +{ + dev_info(kobj_to_dev(kobj), "%s\n", __func__); + + return 0; +} + +static ssize_t vts_svoice_model_write(struct file *file, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buffer, loff_t ppos, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct vts_data *data = dev_get_drvdata(dev); + char *to; + ssize_t available; + + dev_info(dev, "%s\n", __func__); + + if (!data->svoice_info.data) { + dev_info(dev, "%s Grammar binary Buffer not allocated\n", __func__); + return -EINVAL; + } + + to = data->svoice_info.data; + available = data->svoice_info.max_sz; + + dev_dbg(dev, "%s available %zu Cur-pos %lld size-to-copy %zu\n", __func__, + available, ppos, count); + if (ppos < 0) { + dev_info(dev, "%s Error wrong current position\n", __func__); + return -EINVAL; + } + if (ppos >= available || !count) { + dev_info(dev, "%s Error copysize[%lld] greater than available buffer\n", + __func__, ppos); + return 0; + } + if (count > available - ppos) + count = available - ppos; + + memcpy(to + ppos, buffer, count); + + to = (to + ppos); + data->svoice_info.actual_sz = ppos + count; + dev_info(dev, "%s updated Size %zu\n", __func__, + data->svoice_info.actual_sz); + data->svoice_info.loaded = true; + + return count; +} + +static const BIN_ATTR_RW(vts_google_model, VTS_MODEL_GOOGLE_BIN_MAXSZ); +static const BIN_ATTR_RW(vts_svoice_model, VTS_MODEL_SVOICE_BIN_MAXSZ); + +static ssize_t vtsfw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vts_data *data = dev_get_drvdata(dev); + unsigned int version = data->vtsfw_version; + + buf[0] = (((version >> 24) & 0xFF) + '0'); + buf[1] = '.'; + buf[2] = (((version >> 16) & 0xFF) + '0'); + buf[3] = '.'; + buf[4] = (((version >> 8) & 0xFF) + '0'); + buf[5] = '.'; + buf[6] = ((version & 0xFF) + '0'); + buf[7] = '\n'; + buf[8] = '\0'; + + return 9; +} + +static ssize_t vtsdetectlib_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vts_data *data = dev_get_drvdata(dev); + unsigned int version = data->vtsdetectlib_version; + + buf[0] = (((version >> 24) & 0xFF) + '0'); + buf[1] = '.'; + buf[2] = (((version >> 16) & 0xFF) + '0'); + buf[3] = '.'; + buf[4] = ((version & 0xFF) + '0'); + buf[5] = '\n'; + buf[6] = '\0'; + + return 7; +} + +static ssize_t vts_audiodump_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct vts_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->audiodump_enabled); +} + +static ssize_t vts_audiodump_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vts_data *data = dev_get_drvdata(dev); + u32 val = 0; + u32 values[3] = {0, 0, 0}; + int result; + int err = kstrtouint(buf, 0, &val); + + if (err < 0) + return err; + data->audiodump_enabled = (val ? true : false); + + if (data->vts_ready) { + if (data->audiodump_enabled) { + values[0] = VTS_ENABLE_AUDIODUMP; + values[1] = (VTS_ADUIODUMP_AFTER_MINS * 60); + } else { + values[0] = VTS_DISABLE_AUDIODUMP; + values[1] = 0; + } + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_TEST_COMMAND, &values, + 0, 2); + if (result < 0) { + dev_err(dev, "AudioDump[%d] ipc failed\n", + data->audiodump_enabled); + } + } + + dev_info(dev, "%s: Audio dump %sabled\n", + __func__, (val ? "en" : "dis")); + return count; +} + +static ssize_t vts_logdump_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct vts_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", data->logdump_enabled); +} + +static ssize_t vts_logdump_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vts_data *data = dev_get_drvdata(dev); + u32 val = 0; + u32 values[3] = {0, 0, 0}; + int result; + int err = kstrtouint(buf, 0, &val); + + if (err < 0) + return err; + + data->logdump_enabled = (val ? true : false); + + if (data->vts_ready) { + if (data->logdump_enabled) { + values[0] = VTS_ENABLE_LOGDUMP; + values[1] = (VTS_LOGDUMP_AFTER_MINS * 60); + } else { + values[0] = VTS_DISABLE_LOGDUMP; + values[1] = 0; + } + values[2] = 0; + result = vts_start_ipc_transaction(dev, data, + VTS_IRQ_AP_TEST_COMMAND, &values, + 0, 2); + if (result < 0) { + dev_err(dev, "LogDump[%d] ipc failed\n", + data->logdump_enabled); + } + } + + dev_info(dev, "%s: Log dump %sabled\n", + __func__, (val ? "en" : "dis")); + return count; +} + +static DEVICE_ATTR_RO(vtsfw_version); +static DEVICE_ATTR_RO(vtsdetectlib_version); +static DEVICE_ATTR_RW(vts_audiodump); +static DEVICE_ATTR_RW(vts_logdump); + +static void vts_complete_firmware_request(const struct firmware *fw, void *context) +{ + struct platform_device *pdev = context; + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + unsigned int *pversion = NULL; + + if (!fw) { + dev_err(dev, "Failed to request firmware\n"); + return; + } + + data->firmware = fw; + pversion = (unsigned int*) (fw->data + VTSFW_VERSION_OFFSET); + data->vtsfw_version = *pversion; + pversion = (unsigned int*) (fw->data + DETLIB_VERSION_OFFSET); + data->vtsdetectlib_version = *pversion; + + dev_info(dev, "Firmware loaded at %p (%zu)\n", fw->data, fw->size); + dev_info(dev, "Firmware version: 0x%x Detection library version: 0x%x\n", data->vtsfw_version, data->vtsdetectlib_version); +} + +static void __iomem *samsung_vts_devm_request_and_map(struct platform_device *pdev, const char *name, size_t *size) +{ + struct resource *res; + void __iomem *result; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to get %s\n", name); + return ERR_PTR(-EINVAL); + } + + if (size) { + *size = resource_size(res); + } + + res = devm_request_mem_region(&pdev->dev, res->start, resource_size(res), name); + if (IS_ERR_OR_NULL(res)) { + dev_err(&pdev->dev, "Failed to request %s\n", name); + return ERR_PTR(-EFAULT); + } + + result = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(result)) { + dev_err(&pdev->dev, "Failed to map %s\n", name); + return ERR_PTR(-EFAULT); + } + + dev_info(&pdev->dev, "%s: %s(%p) is mapped on %p with size of %zu", + __func__, name, (void *)res->start, result, (size_t)resource_size(res)); + + return result; +} + +static int samsung_vts_devm_request_threaded_irq( + struct platform_device *pdev, const char *irq_name, + unsigned int hw_irq, irq_handler_t thread_fn) +{ + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + int result; + + data->irq[hw_irq] = platform_get_irq_byname(pdev, irq_name); + if (data->irq[hw_irq] < 0) { + dev_err(dev, "Failed to get irq %s: %d\n", irq_name, data->irq[hw_irq]); + return data->irq[hw_irq]; + } + + result = devm_request_threaded_irq(dev, data->irq[hw_irq], + NULL, thread_fn, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev->init_name, + pdev); + + if (result < 0) { + dev_err(dev, "Unable to request irq %s: %d\n", irq_name, result); + } + + return result; +} + +static struct clk *devm_clk_get_and_prepare(struct device *dev, const char *name) +{ + struct clk *clk; + int result; + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + dev_err(dev, "Failed to get clock %s\n", name); + goto error; + } + + result = clk_prepare(clk); + if (result < 0) { + dev_err(dev, "Failed to prepare clock %s\n", name); + goto error; + } + +error: + return clk; +} + +static const struct reg_default vts_dmic_reg_defaults[] = { + {0x0000, 0x00030000}, + {0x0004, 0x00000000}, +}; + +static const struct regmap_config vts_component_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = VTS_DMIC_CONTROL_DMIC_IF, + .reg_defaults = vts_dmic_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(vts_dmic_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .fast_io = true, +}; + +static int samsung_vts_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct vts_data *data; + int result; + int dmic_clkctrl = 0; + + dev_info(dev, "%s \n", __func__); + data = devm_kzalloc(dev, sizeof(struct vts_data), GFP_KERNEL); + if (!data) { + dev_err(dev, "Failed to allocate memory\n"); + result = -ENOMEM; + goto error; + } + + /* Model binary memory allocation */ + data->google_info.max_sz = VTS_MODEL_GOOGLE_BIN_MAXSZ; + data->google_info.actual_sz = 0; + data->google_info.loaded = false; + data->google_info.data = vmalloc(VTS_MODEL_GOOGLE_BIN_MAXSZ); + if (!data->google_info.data) { + dev_err(dev, "%s Failed to allocate Grammar Bin memory\n", __func__); + result = -ENOMEM; + goto error; + } + + data->svoice_info.max_sz = VTS_MODEL_SVOICE_BIN_MAXSZ; + data->svoice_info.actual_sz = 0; + data->svoice_info.loaded = false; + data->svoice_info.data = vmalloc(VTS_MODEL_SVOICE_BIN_MAXSZ); + if (!data->svoice_info.data) { + dev_err(dev, "%s Failed to allocate Net Bin memory\n", __func__); + result = -ENOMEM; + goto error; + } + + /* initialize device structure members */ + data->active_trigger = TRIGGER_SVOICE; + + /* initialize micbias setting count */ + data->micclk_init_cnt = 0; + data->mic_ready = 0; + data->vts_state = VTS_STATE_NONE; + + platform_set_drvdata(pdev, data); + data->pdev = pdev; + p_vts_data = data; + + init_waitqueue_head(&data->ipc_wait_queue); + spin_lock_init(&data->ipc_spinlock); + mutex_init(&data->ipc_mutex); + wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "vts"); + + data->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(data->pinctrl)) { + dev_err(dev, "Couldn't get pins (%li)\n", + PTR_ERR(data->pinctrl)); + return PTR_ERR(data->pinctrl); + } + + data->sfr_base = samsung_vts_devm_request_and_map(pdev, "sfr", NULL); + if (IS_ERR(data->sfr_base)) { + result = PTR_ERR(data->sfr_base); + goto error; + } + + data->baaw_base = samsung_vts_devm_request_and_map(pdev, "baaw", NULL); + if (IS_ERR(data->baaw_base)) { + result = PTR_ERR(data->baaw_base); + goto error; + } + + data->sram_base = samsung_vts_devm_request_and_map(pdev, "sram", &data->sram_size); + if (IS_ERR(data->sram_base)) { + result = PTR_ERR(data->sram_base); + goto error; + } + + data->dmic_base = samsung_vts_devm_request_and_map(pdev, "dmic", NULL); + if (IS_ERR(data->dmic_base)) { + result = PTR_ERR(data->dmic_base); + goto error; + } + + data->gpr_base = samsung_vts_devm_request_and_map(pdev, "gpr", NULL); + if (IS_ERR(data->gpr_base)) { + result = PTR_ERR(data->gpr_base); + goto error; + } + + data->lpsdgain = 0; + data->dmicgain = 0; + data->amicgain = 0; + + /* read tunned VTS gain values */ + of_property_read_u32(np, "lpsd-gain", &data->lpsdgain); + of_property_read_u32(np, "dmic-gain", &data->dmicgain); + of_property_read_u32(np, "amic-gain", &data->amicgain); + + dev_info(dev, "VTS Tunned Gain value LPSD: %d DMIC: %d AMIC: %d\n", + data->lpsdgain, data->dmicgain, data->amicgain); + + data->dmab.area = dmam_alloc_coherent(dev, BUFFER_BYTES_MAX, &data->dmab.addr, GFP_KERNEL); + if (data->dmab.area == NULL) { + result = -ENOMEM; + goto error; + } + data->dmab.bytes = BUFFER_BYTES_MAX/2; + data->dmab.dev.dev = dev; + data->dmab.dev.type = SNDRV_DMA_TYPE_DEV; + + data->dmab_rec.area = (data->dmab.area + BUFFER_BYTES_MAX/2); + data->dmab_rec.addr = (data->dmab.addr + BUFFER_BYTES_MAX/2); + data->dmab_rec.bytes = BUFFER_BYTES_MAX/2; + data->dmab_rec.dev.dev = dev; + data->dmab_rec.dev.type = SNDRV_DMA_TYPE_DEV; + + data->dmab_log.area = dmam_alloc_coherent(dev, LOG_BUFFER_BYTES_MAX, + &data->dmab_log.addr, GFP_KERNEL); + if (data->dmab_log.area == NULL) { + result = -ENOMEM; + goto error; + } + data->dmab.bytes = LOG_BUFFER_BYTES_MAX; + data->dmab.dev.dev = dev; + data->dmab.dev.type = SNDRV_DMA_TYPE_DEV; + +#ifdef CONFIG_SOC_EXYNOS8895 + data->clk_rco = devm_clk_get_and_prepare(dev, "rco"); + if (IS_ERR(data->clk_rco)) { + result = PTR_ERR(data->clk_rco); + goto error; + } + + result = clk_enable(data->clk_rco); + if (result < 0) { + dev_err(dev, "Failed to enable the rco\n"); + goto error; + } +#endif + data->clk_dmic = devm_clk_get_and_prepare(dev, "dmic"); + if (IS_ERR(data->clk_dmic)) { + result = PTR_ERR(data->clk_dmic); + goto error; + } + + data->clk_dmic_if= devm_clk_get_and_prepare(dev, "dmic_if"); + if (IS_ERR(data->clk_dmic_if)) { + result = PTR_ERR(data->clk_dmic_if); + goto error; + } + + data->clk_dmic_sync = devm_clk_get_and_prepare(dev, "dmic_sync"); + if (IS_ERR(data->clk_dmic_sync)) { + result = PTR_ERR(data->clk_dmic_sync); + goto error; + } + + result = samsung_vts_devm_request_threaded_irq(pdev, "error", + VTS_IRQ_VTS_ERROR, vts_error_handler); + if (result < 0) { + goto error; + } + + result = samsung_vts_devm_request_threaded_irq(pdev, "boot_completed", + VTS_IRQ_VTS_BOOT_COMPLETED, vts_boot_completed_handler); + if (result < 0) { + goto error; + } + + result = samsung_vts_devm_request_threaded_irq(pdev, "ipc_received", + VTS_IRQ_VTS_IPC_RECEIVED, vts_ipc_received_handler); + if (result < 0) { + goto error; + } + + result = samsung_vts_devm_request_threaded_irq(pdev, "voice_triggered", + VTS_IRQ_VTS_VOICE_TRIGGERED, vts_voice_triggered_handler); + if (result < 0) { + goto error; + } + + result = samsung_vts_devm_request_threaded_irq(pdev, "trigger_period_elapsed", + VTS_IRQ_VTS_PERIOD_ELAPSED, vts_trigger_period_elapsed_handler); + if (result < 0) { + goto error; + } + + result = samsung_vts_devm_request_threaded_irq(pdev, "record_period_elapsed", + VTS_IRQ_VTS_REC_PERIOD_ELAPSED, vts_record_period_elapsed_handler); + if (result < 0) { + goto error; + } + + result = samsung_vts_devm_request_threaded_irq(pdev, "debuglog_bufzero", + VTS_IRQ_VTS_DBGLOG_BUFZERO, vts_debuglog_bufzero_handler); + if (result < 0) + goto error; + + result = samsung_vts_devm_request_threaded_irq(pdev, "debuglog_bufone", + VTS_IRQ_VTS_DBGLOG_BUFONE, vts_debuglog_bufone_handler); + if (result < 0) + goto error; + + result = samsung_vts_devm_request_threaded_irq(pdev, "audio_dump", + VTS_IRQ_VTS_AUDIO_DUMP, vts_audiodump_handler); + if (result < 0) + goto error; + + result = samsung_vts_devm_request_threaded_irq(pdev, "log_dump", + VTS_IRQ_VTS_LOG_DUMP, vts_logdump_handler); + if (result < 0) + goto error; + + data->irq_state = true; + + data->pdev_mailbox = of_find_device_by_node(of_parse_phandle(np, "mailbox", 0)); + if (!data->pdev_mailbox) { + dev_err(dev, "Failed to get mailbox\n"); + result = -EPROBE_DEFER; + goto error; + } + + result = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, + "vts.bin", + dev, + GFP_KERNEL, + pdev, + vts_complete_firmware_request); + if (result < 0) { + dev_err(dev, "Failed to request firmware\n"); + goto error; + } + + result = device_create_bin_file(dev, &bin_attr_vts_google_model); + if (result < 0) { + dev_err(dev, "Failed to create attribute %s\n", "vts_google_model"); + goto error; + } + + result = device_create_bin_file(dev, &bin_attr_vts_svoice_model); + if (result < 0) { + dev_err(dev, "Failed to create attribute %s\n", "vts_svoice_model"); + goto error; + } + + data->regmap_dmic = devm_regmap_init_mmio_clk(dev, + NULL, + data->dmic_base, + &vts_component_regmap_config); + + result = snd_soc_register_component(dev, &vts_component, vts_dai, ARRAY_SIZE(vts_dai)); + if (result < 0) { + dev_err(dev, "Failed to register ASoC component\n"); + goto error; + } + +#ifdef EMULATOR + pmu_alive = ioremap(0x16480000, 0x10000); +#endif + printk("come hear %d\n", __LINE__); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + vts_cfg_gpio(dev, "idle"); + + data->voicecall_enabled = false; + data->voicerecog_start = 0; + data->syssel_rate = 0; + data->target_size = 0; + data->vtsfw_version = 0x0; + data->vtsdetectlib_version = 0x0; + data->vtslog_enabled = 0; + data->audiodump_enabled = false; + data->logdump_enabled = false; + + /* set VTS BAAW config */ + writel(0x40300, data->baaw_base); + writel(0x40600, data->baaw_base + 0x4); + writel(0x14100, data->baaw_base + 0x8); + writel(0x80000003, data->baaw_base + 0xC); + + dmic_clkctrl = readl(data->sfr_base + VTS_DMIC_CLK_CTRL); + writel(dmic_clkctrl & ~(0x1 << VTS_CLK_ENABLE_OFFSET), + data->sfr_base + VTS_DMIC_CLK_CTRL); + dev_dbg(dev, "DMIC_CLK_CTRL: Before 0x%x After 0x%x \n", dmic_clkctrl, + readl(data->sfr_base + VTS_DMIC_CLK_CTRL)); + + result = device_create_file(dev, &dev_attr_vtsfw_version); + if (result < 0) + dev_warn(dev, "Failed to create file: %s\n", "vtsfw_version"); + + result = device_create_file(dev, &dev_attr_vtsdetectlib_version); + if (result < 0) + dev_warn(dev, "Failed to create file: %s\n", "vtsdetectlib_version"); + + result = device_create_file(dev, &dev_attr_vts_audiodump); + if (result < 0) + dev_warn(dev, "Failed to create file: %s\n", "vts_audiodump"); + + result = device_create_file(dev, &dev_attr_vts_logdump); + if (result < 0) + dev_warn(dev, "Failed to create file: %s\n", "vts_logdump"); + + data->sramlog_baseaddr = (char *)(data->sram_base + VTS_SRAMLOG_MSGS_OFFSET); + + atomic_notifier_chain_register(&panic_notifier_list, &vts_panic_notifier); + + /* initialize log buffer offset as non */ + vts_register_log_buffer(dev, 0, 0); + + device_init_wakeup(dev, true); + dev_info(dev, "Probed successfully\n"); + +error: + return result; +} + +static int samsung_vts_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vts_data *data = platform_get_drvdata(pdev); + + pm_runtime_disable(dev); + clk_unprepare(data->clk_dmic); +#ifndef CONFIG_PM + vts_runtime_suspend(dev); +#endif + release_firmware(data->firmware); + if (data->google_info.data) + vfree(data->google_info.data); + if (data->svoice_info.data) + vfree(data->svoice_info.data); + snd_soc_unregister_component(dev); +#ifdef EMULATOR + iounmap(pmu_alive); +#endif + return 0; +} + +static struct platform_driver samsung_vts_driver = { + .probe = samsung_vts_probe, + .remove = samsung_vts_remove, + .driver = { + .name = "samsung-vts", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(exynos_vts_of_match), + .pm = &samsung_vts_pm, + }, +}; + +module_platform_driver(samsung_vts_driver); + +static int __init samsung_vts_late_initcall(void) +{ + pr_info("%s\n", __func__); + + if (p_vts_data && p_vts_data->pdev) { + pm_runtime_put_sync(&p_vts_data->pdev->dev); + } else { + pr_err("%s: p_vts_data or p_vts_data->pdev is null", __func__); + } + return 0; +} +late_initcall(samsung_vts_late_initcall); + +/* Module information */ +MODULE_AUTHOR("Gyeongtaek Lee, "); +MODULE_AUTHOR("Palli Satish Kumar Reddy, "); +MODULE_DESCRIPTION("Samsung Voice Trigger System"); +MODULE_ALIAS("platform:samsung-vts"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/vts/vts.h b/sound/soc/samsung/vts/vts.h new file mode 100644 index 000000000000..249092b1f2cb --- /dev/null +++ b/sound/soc/samsung/vts/vts.h @@ -0,0 +1,325 @@ +/* sound/soc/samsung/vts/vts.h + * + * ALSA SoC - Samsung VTS driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_VTS_H +#define __SND_SOC_VTS_H + +#include +#include + +/* SYSREG_VTS */ +#define VTS_DEBUG (0x0404) +#define VTS_DMIC_CLK_CTRL (0x0408) +#define VTS_HWACG_CM4_CLKREQ (0x0428) +#define VTS_DMIC_CLK_CON (0x0434) +#define VTS_SYSPOWER_CTRL (0x0500) +#define VTS_SYSPOWER_STATUS (0x0504) + +/* VTS_DEBUG */ +#define VTS_NMI_EN_BY_WDT_OFFSET (0) +#define VTS_NMI_EN_BY_WDT_SIZE (1) +/* VTS_DMIC_CLK_CTRL */ +#define VTS_CG_STATUS_OFFSET (5) +#define VTS_CG_STATUS_SIZE (1) +#define VTS_CLK_ENABLE_OFFSET (4) +#define VTS_CLK_ENABLE_SIZE (1) +#define VTS_CLK_SEL_OFFSET (0) +#define VTS_CLK_SEL_SIZE (1) +/* VTS_HWACG_CM4_CLKREQ */ +#define VTS_MASK_OFFSET (0) +#define VTS_MASK_SIZE (32) +/* VTS_DMIC_CLK_CON */ +#define VTS_ENABLE_CLK_GEN_OFFSET (0) +#define VTS_ENABLE_CLK_GEN_SIZE (1) +#define VTS_SEL_EXT_DMIC_CLK_OFFSET (1) +#define VTS_SEL_EXT_DMIC_CLK_SIZE (1) +#define VTS_ENABLE_CLK_CLK_GEN_OFFSET (14) +#define VTS_ENABLE_CLK_CLK_GEN_SIZE (1) + +/* VTS_SYSPOWER_CTRL */ +#define VTS_SYSPOWER_CTRL_OFFSET (0) +#define VTS_SYSPOWER_CTRL_SIZE (1) +/* VTS_SYSPOWER_STATUS */ +#define VTS_SYSPOWER_STATUS_OFFSET (0) +#define VTS_SYSPOWER_STATUS_SIZE (1) + + +#define VTS_DMIC_ENABLE_DMIC_IF (0x0000) +#define VTS_DMIC_CONTROL_DMIC_IF (0x0004) +/* VTS_DMIC_ENABLE_DMIC_IF */ +#define VTS_DMIC_ENABLE_DMIC_IF_OFFSET (31) +#define VTS_DMIC_ENABLE_DMIC_IF_SIZE (1) +#define VTS_DMIC_PERIOD_DATA2REQ_OFFSET (16) +#define VTS_DMIC_PERIOD_DATA2REQ_SIZE (2) +/* VTS_DMIC_CONTROL_DMIC_IF */ +#define VTS_DMIC_HPF_EN_OFFSET (31) +#define VTS_DMIC_HPF_EN_SIZE (1) +#define VTS_DMIC_HPF_SEL_OFFSET (28) +#define VTS_DMIC_HPF_SEL_SIZE (1) +#define VTS_DMIC_CPS_SEL_OFFSET (27) +#define VTS_DMIC_CPS_SEL_SIZE (1) +#define VTS_DMIC_GAIN_OFFSET (24) +#define VTS_DMIC_GAIN_SIZE (3) +#define VTS_DMIC_DMIC_SEL_OFFSET (18) +#define VTS_DMIC_DMIC_SEL_SIZE (1) +#define VTS_DMIC_RCH_EN_OFFSET (17) +#define VTS_DMIC_RCH_EN_SIZE (1) +#define VTS_DMIC_LCH_EN_OFFSET (16) +#define VTS_DMIC_LCH_EN_SIZE (1) +#define VTS_DMIC_SYS_SEL_OFFSET (12) +#define VTS_DMIC_SYS_SEL_SIZE (2) +#define VTS_DMIC_POLARITY_CLK_OFFSET (10) +#define VTS_DMIC_POLARITY_CLK_SIZE (1) +#define VTS_DMIC_POLARITY_OUTPUT_OFFSET (9) +#define VTS_DMIC_POLARITY_OUTPUT_SIZE (1) +#define VTS_DMIC_POLARITY_INPUT_OFFSET (8) +#define VTS_DMIC_POLARITY_INPUT_SIZE (1) +#define VTS_DMIC_OVFW_CTRL_OFFSET (4) +#define VTS_DMIC_OVFW_CTRL_SIZE (1) +#define VTS_DMIC_CIC_SEL_OFFSET (0) +#define VTS_DMIC_CIC_SEL_SIZE (1) + +/* CM4 */ +#define VTS_CM4_R(x) (0x0010 + (x * 0x4)) +#define VTS_CM4_PC (0x0004) + +#define VTS_IRQ_VTS_ERROR (16) +#define VTS_IRQ_VTS_BOOT_COMPLETED (17) +#define VTS_IRQ_VTS_IPC_RECEIVED (18) +#define VTS_IRQ_VTS_VOICE_TRIGGERED (19) +#define VTS_IRQ_VTS_PERIOD_ELAPSED (20) +#define VTS_IRQ_VTS_REC_PERIOD_ELAPSED (21) +#define VTS_IRQ_VTS_DBGLOG_BUFZERO (22) +#define VTS_IRQ_VTS_DBGLOG_BUFONE (23) +#define VTS_IRQ_VTS_AUDIO_DUMP (24) +#define VTS_IRQ_VTS_LOG_DUMP (25) +#define VTS_IRQ_COUNT (26) + +#define VTS_IRQ_AP_IPC_RECEIVED (0) +#define VTS_IRQ_AP_SET_DRAM_BUFFER (1) +#define VTS_IRQ_AP_START_RECOGNITION (2) +#define VTS_IRQ_AP_STOP_RECOGNITION (3) +#define VTS_IRQ_AP_START_COPY (4) +#define VTS_IRQ_AP_STOP_COPY (5) +#define VTS_IRQ_AP_SET_MODE (6) +#define VTS_IRQ_AP_POWER_DOWN (7) +#define VTS_IRQ_AP_TARGET_SIZE (8) +#define VTS_IRQ_AP_SET_REC_BUFFER (9) +#define VTS_IRQ_AP_START_REC (10) +#define VTS_IRQ_AP_STOP_REC (11) +#define VTS_IRQ_AP_RESTART_RECOGNITION (13) +#define VTS_IRQ_AP_TEST_COMMAND (15) + +#define VTS_IRQ_LIMIT (32) + +#define VTS_BAAW_BASE (0x60000000) +#define VTS_BAAW_SRC_START_ADDRESS (0x10000) +#define VTS_BAAW_SRC_END_ADDRESS (0x10004) +#define VTS_BAAW_REMAPPED_ADDRESS (0x10008) +#define VTS_BAAW_INIT_DONE (0x1000C) + +#define BUFFER_BYTES_MAX (0xa0000) +#define PERIOD_BYTES_MIN (SZ_4) +#define PERIOD_BYTES_MAX (BUFFER_BYTES_MAX / 2) + +#define SOUND_MODEL_SIZE_MAX (SZ_32K) +#define SOUND_MODEL_COUNT (3) + +/* DRAM for copying VTS firmware logs */ +#define LOG_BUFFER_BYTES_MAX (0x2000) +#define VTS_SRAMLOG_MSGS_OFFSET (0x59000) + +/* VTS firmware version information offset */ +#define VTSFW_VERSION_OFFSET (0x7c) +#define DETLIB_VERSION_OFFSET (0x78) + +/* VTS Model Binary Max buffer sizes */ +#define VTS_MODEL_SVOICE_BIN_MAXSZ (SZ_64K) +#define VTS_MODEL_GOOGLE_BIN_MAXSZ (SZ_64K) + +enum ipc_state { + IDLE, + SEND_MSG, + SEND_MSG_OK, + SEND_MSG_FAIL, +}; + +enum trigger { + TRIGGER_NONE = -1, + TRIGGER_SVOICE = 0, + TRIGGER_GOOGLE = 1, + TRIGGER_SENSORY = 2, + TRIGGER_COUNT, +}; + +enum vts_platform_type { + PLATFORM_VTS_NORMAL_RECORD, + PLATFORM_VTS_TRIGGER_RECORD, +}; + +enum executionmode { + //default is off + VTS_OFF_MODE = 0, + //voice-trig-mode:Both LPSD & Trigger are enabled + VTS_VOICE_TRIGGER_MODE = 1, + //sound-detect-mode: Low Power sound Detect + VTS_SOUND_DETECT_MODE = 2, + //vt-always-mode: key phrase Detection only(Trigger) + VTS_VT_ALWAYS_ON_MODE = 3, + //google-trigger: key phrase Detection only(Trigger) + VTS_GOOGLE_TRIGGER_MODE = 4, + //sensory-trigger: key phrase Detection only(Trigger) + VTS_SENSORY_TRIGGER_MODE = 5, + //off:voice-trig-mode:Both LPSD & Trigger are enabled + VTS_VOICE_TRIGGER_MODE_OFF = 6, + //off:sound-detect-mode: Low Power sound Detect + VTS_SOUND_DETECT_MODE_OFF = 7, + //off:vt-always-mode: key phrase Detection only(Trigger) + VTS_VT_ALWAYS_ON_MODE_OFF = 8, + //off:google-trigger: key phrase Detection only(Trigger) + VTS_GOOGLE_TRIGGER_MODE_OFF = 9, + //off:sensory-trigger: key phrase Detection only(Trigger) + VTS_SENSORY_TRIGGER_MODE_OFF = 10, + VTS_MODE_COUNT, +}; + +enum vts_dump_type { + KERNEL_PANIC_DUMP = 0, + RUNTIME_SUSPEND_DUMP = 1, +}; + +enum vts_test_command { + VTS_DISABLE_LOGDUMP = 0x01000000, + VTS_ENABLE_LOGDUMP = 0x02000000, + VTS_DISABLE_AUDIODUMP = 0x04000000, + VTS_ENABLE_AUDIODUMP = 0x08000000, + VTS_DISABLE_DEBUGLOG = 0x10000000, + VTS_ENABLE_DEBUGLOG = 0x20000000, + VTS_ENABLE_SRAM_LOG = 0x80000000, +}; + +struct vts_ipc_msg { + int msg; + u32 values[3]; +}; + +enum vts_micconf_type { + VTS_MICCONF_FOR_RECORD = 0, + VTS_MICCONF_FOR_TRIGGER = 1, + VTS_MICCONF_FOR_GOOGLE = 2, +}; + +enum vts_state_machine { + VTS_STATE_NONE = 0, //runtime_suspended state + VTS_STATE_VOICECALL = 1, //sram L2Cache voicecall state + VTS_STATE_IDLE = 2, //runtime_resume state + VTS_STATE_RECOG_STARTED = 3, //Voice Recognization started + VTS_STATE_RECOG_TRIGGERED = 4, //Voice Recognize triggered + VTS_STATE_SEAMLESS_REC_STARTED = 5, //seamless record started + VTS_STATE_SEAMLESS_REC_STOPPED = 6, //seamless record started + VTS_STATE_RECOG_STOPPED = 7, //Voice Recognization stopped +}; + +struct vts_model_bin_info { + unsigned char *data; + size_t actual_sz; + size_t max_sz; + bool loaded; +}; + +struct vts_data { + struct platform_device *pdev; + struct snd_soc_component *cmpnt; + void __iomem *sfr_base; + void __iomem *baaw_base; + void __iomem *sram_base; + void __iomem *dmic_base; + void __iomem *gpr_base; + size_t sram_size; + struct regmap *regmap_dmic; + struct clk *clk_rco; + struct clk *clk_dmic; + struct clk *clk_dmic_if; + struct clk *clk_dmic_sync; + struct pinctrl *pinctrl; + unsigned int vtsfw_version; + unsigned int vtsdetectlib_version; + const struct firmware *firmware; + unsigned int vtsdma_count; + unsigned long syssel_rate; + struct platform_device *pdev_mailbox; + struct platform_device *pdev_vtsdma[2]; + struct proc_dir_entry *proc_dir_entry; + int irq[VTS_IRQ_LIMIT]; + volatile enum ipc_state ipc_state_ap; + wait_queue_head_t ipc_wait_queue; + spinlock_t ipc_spinlock; + struct mutex ipc_mutex; + u32 dma_area_vts; + struct snd_dma_buffer dmab; + struct snd_dma_buffer dmab_rec; + struct snd_dma_buffer dmab_log; + u32 target_size; + volatile enum trigger active_trigger; + u32 voicerecog_start; + enum executionmode exec_mode; + bool vts_ready; + volatile unsigned long sram_acquired; + volatile bool enabled; + volatile bool running; + bool voicecall_enabled; + struct snd_soc_card *card; + int micclk_init_cnt; + unsigned int mic_ready; + bool irq_state; + u32 lpsdgain; + u32 dmicgain; + u32 amicgain; + char *sramlog_baseaddr; + u32 running_ipc; + struct wake_lock wake_lock; + unsigned int vts_state; + u32 vtslog_enabled; + bool audiodump_enabled; + bool logdump_enabled; + struct vts_model_bin_info svoice_info; + struct vts_model_bin_info google_info; +}; + +struct vts_platform_data { + unsigned int id; + struct platform_device *pdev_vts; + struct vts_data *vts_data; + struct snd_pcm_substream *substream; + enum vts_platform_type type; + volatile unsigned int pointer; +}; + +struct vts_dbg_dump { + char sram[SZ_2K]; + unsigned int gpr[17]; + long long time; + char reason[SZ_32]; +}; + +struct vts_log_buffer { + char *addr; + unsigned int size; +}; + +extern int vts_start_ipc_transaction(struct device *dev, struct vts_data *data, + int msg, u32 (*values)[3], int atomic, int sync); + +extern int vts_send_ipc_ack(struct vts_data *data, u32 result); +extern void vts_register_dma(struct platform_device *pdev_vts, + struct platform_device *pdev_vts_dma, unsigned int id); +extern int vts_set_dmicctrl(struct platform_device *pdev, int micconf_type, bool enable); +#endif /* __SND_SOC_VTS_H */ diff --git a/sound/soc/samsung/vts/vts_dma.c b/sound/soc/samsung/vts/vts_dma.c new file mode 100644 index 000000000000..6d77ab793936 --- /dev/null +++ b/sound/soc/samsung/vts/vts_dma.c @@ -0,0 +1,355 @@ +/* sound/soc/samsung/vts/vts-plat.c + * + * ALSA SoC - Samsung VTS platfrom driver + * + * Copyright (c) 2016 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +//#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "vts.h" + +static const struct snd_pcm_hardware vts_platform_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_16000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = PERIOD_BYTES_MAX, + .periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, +}; + +static int vts_platform_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_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct vts_platform_data *data = dev_get_drvdata(dev); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (data->type == PLATFORM_VTS_TRIGGER_RECORD) { + snd_pcm_set_runtime_buffer(substream, &data->vts_data->dmab); + } else { + snd_pcm_set_runtime_buffer(substream, &data->vts_data->dmab_rec); + } + dev_info(dev, "%s:%s:DmaAddr=%pad Total=%zu PrdSz=%u(%u) #Prds=%u dma_area=%p\n", + __func__, snd_pcm_stream_str(substream), &runtime->dma_addr, + runtime->dma_bytes, params_period_size(params), + params_period_bytes(params), params_periods(params), + runtime->dma_area); + + data->pointer = 0; + + return 0; +} + +static int vts_platform_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + + dev_dbg(dev, "%s\n", __func__); + + return 0; +} + +static int vts_platform_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + + dev_info(dev, "%s\n", __func__); + + return 0; +} + +static int vts_platform_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct vts_platform_data *data = dev_get_drvdata(dev); + u32 values[3] = {0,0,0}; + int result = 0; + + dev_info(dev, "%s ++ CMD: %d\n", __func__, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (data->type == PLATFORM_VTS_TRIGGER_RECORD) { + dev_dbg(dev, "%s VTS_IRQ_AP_START_COPY\n", __func__); + result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_START_COPY, &values, 1, 1); + } else { + dev_dbg(dev, "%s VTS_IRQ_AP_START_REC\n", __func__); + result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_START_REC, &values, 1, 1); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (data->type == PLATFORM_VTS_TRIGGER_RECORD) { + dev_dbg(dev, "%s VTS_IRQ_AP_STOP_COPY\n", __func__); + result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_STOP_COPY, &values, 1, 1); + } else { + dev_dbg(dev, "%s VTS_IRQ_AP_STOP_REC\n", __func__); + result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_STOP_REC, &values, 1, 1); + } + break; + default: + result = -EINVAL; + break; + } + + dev_info(dev, "%s -- CMD: %d\n", __func__, cmd); + return result; +} + +static snd_pcm_uframes_t vts_platform_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct vts_platform_data *data = dev_get_drvdata(dev); + struct snd_pcm_runtime *runtime = substream->runtime; + + dev_dbg(dev, "%s: pointer=%08x\n", __func__, data->pointer); + + return bytes_to_frames(runtime, data->pointer); +} + +static int vts_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct vts_platform_data *data = dev_get_drvdata(dev); + int result = 0; + + dev_info(dev, "%s\n", __func__); + + if (data->vts_data->voicecall_enabled) { + dev_warn(dev, "%s VTS SRAM is Used for CP call\n", + __func__); + return -EBUSY; + } + + pm_runtime_get_sync(dev); + snd_soc_set_runtime_hwparams(substream, &vts_platform_hardware); + if (data->type == PLATFORM_VTS_NORMAL_RECORD) { + dev_info(dev, "%s open --\n", __func__); + result = vts_set_dmicctrl(data->vts_data->pdev, + VTS_MICCONF_FOR_RECORD, true); + if (result < 0) { + dev_err(dev, "%s: MIC control configuration failed\n", __func__); + pm_runtime_put_sync(dev); + } + } + + return result; +} + +static int vts_platform_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct vts_platform_data *data = dev_get_drvdata(dev); + int result = 0; + + dev_info(dev, "%s\n", __func__); + + if (data->vts_data->voicecall_enabled) { + dev_warn(dev, "%s VTS SRAM is Used for CP call\n", + __func__); + return -EBUSY; + } + + if (data->type == PLATFORM_VTS_NORMAL_RECORD) { + dev_info(dev, "%s close --\n", __func__); + result = vts_set_dmicctrl(data->vts_data->pdev, + VTS_MICCONF_FOR_RECORD, false); + if (result < 0) + dev_warn(dev, "%s: MIC control configuration failed\n", __func__); + } + + pm_runtime_put_sync(dev); + return result; +} + +static int vts_platform_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct device *dev = platform->dev; + struct snd_pcm_runtime *runtime = substream->runtime; + + dev_info(dev, "%s\n", __func__); + + return dma_mmap_writecombine(dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops vts_platform_ops = { + .open = vts_platform_open, + .close = vts_platform_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = vts_platform_hw_params, + .hw_free = vts_platform_hw_free, + .prepare = vts_platform_prepare, + .trigger = vts_platform_trigger, + .pointer = vts_platform_pointer, + .mmap = vts_platform_mmap, +}; + +static int vts_platform_new(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_platform *platform = runtime->platform; + struct device *dev = platform->dev; + struct vts_platform_data *data = dev_get_drvdata(dev); + struct snd_pcm_substream *substream = runtime->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + + dev_info(dev, "%s \n", __func__); + data->substream = substream; + dev_info(dev, "%s Update Soc Card from runtime!!\n", __func__); + data->vts_data->card = runtime->card; + + return 0; +} + +static void vts_platform_free(struct snd_pcm *pcm) +{ + return; +} + +static const struct snd_soc_platform_driver vts_dma = { + .ops = &vts_platform_ops, + .pcm_new = vts_platform_new, + .pcm_free = vts_platform_free, +}; + +static int samsung_vts_dma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *np_vts; + struct vts_platform_data *data; + int result; + const char *type; + + dev_info(dev, "%s \n", __func__); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, data); + + np_vts = of_parse_phandle(np, "vts", 0); + if (!np_vts) { + dev_err(dev, "Failed to get vts device node\n"); + return -EPROBE_DEFER; + } + data->pdev_vts = of_find_device_by_node(np_vts); + if (!data->pdev_vts) { + dev_err(dev, "Failed to get vts platform device\n"); + return -EPROBE_DEFER; + } + data->vts_data = platform_get_drvdata(data->pdev_vts); + + result = of_property_read_u32_index(np, "id", 0, &data->id); + if (result < 0) { + dev_err(dev, "id property reading fail\n"); + return result; + } + + result = of_property_read_string(np, "type", &type); + if (result < 0) { + dev_err(dev, "type property reading fail\n"); + return result; + } + + if (!strncmp(type, "vts-record", sizeof("vts-record"))) { + data->type = PLATFORM_VTS_NORMAL_RECORD; + dev_info(dev, "%s - vts-record Probed \n", __func__); + } else { + data->type = PLATFORM_VTS_TRIGGER_RECORD; + dev_info(dev, "%s - vts-trigger-record Probed \n", __func__); + } + + vts_register_dma(data->vts_data->pdev, pdev, data->id); + + return snd_soc_register_platform(&pdev->dev, &vts_dma); +} + +static int samsung_vts_dma_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static const struct of_device_id samsung_vts_dma_match[] = { + { + .compatible = "samsung,vts-dma", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_vts_dma_match); + +static struct platform_driver samsung_vts_dma_driver = { + .probe = samsung_vts_dma_probe, + .remove = samsung_vts_dma_remove, + .driver = { + .name = "samsung-vts-dma", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(samsung_vts_dma_match), + }, +}; + +module_platform_driver(samsung_vts_dma_driver); + +/* Module information */ +MODULE_AUTHOR("Palli Satish Kumar Reddy, "); +MODULE_DESCRIPTION("Samsung VTS DMA"); +MODULE_ALIAS("platform:samsung-vts-dma"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/vts/vts_dump.c b/sound/soc/samsung/vts/vts_dump.c new file mode 100644 index 000000000000..b9815647dca2 --- /dev/null +++ b/sound/soc/samsung/vts/vts_dump.c @@ -0,0 +1,183 @@ +/* sound/soc/samsung/vts/vts_dump.c + * + * ALSA SoC - Samsung VTS dump driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +//#define DEBUG +#include +#include +#include +#include + +#include "vts_dump.h" + +#define S_IRWUG (S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP) + +struct vts_dump_info { + struct device *dev; + struct mutex audiolock; + struct mutex loglock; + u32 audioaddr_offset; + u32 audiodump_sz; + u32 logaddr_offset; + u32 logdump_sz; +}; + +static struct vts_dump_info gdump_info; + +static void vts_audiodump_flush_work_func(struct work_struct *work) +{ + struct device *dev = gdump_info.dev; + struct vts_dump_info *dump_info = &gdump_info; + struct vts_data *data = dev_get_drvdata(dev); + char filename[SZ_64]; + struct file *filp; + + dev_dbg(dev, "%s: SRAM Offset: 0x%x Size: %d\n", __func__, + dump_info->audioaddr_offset, dump_info->audiodump_sz); + + mutex_lock(&gdump_info.audiolock); + sprintf(filename, "/data/vts_audiodump.raw"); + + filp = filp_open(filename, O_RDWR | O_APPEND | + O_CREAT, S_IRUSR | S_IWUSR); + dev_info(dev, "AudioDump appended mode\n"); + if (!IS_ERR_OR_NULL(filp)) { + void *area = (void *)(data->sram_base + + dump_info->audioaddr_offset); + size_t bytes = (size_t)dump_info->audiodump_sz; + + /* dev_dbg(dev, " %p, %zx\n", area, bytes); */ + kernel_write(filp, area, bytes, filp->f_pos); + dev_dbg(dev, "kernel_write %p, %zx\n", area, bytes); + + vfs_fsync(filp, 1); + filp_close(filp, NULL); + } else { + dev_err(dev, "VTS Audiodump file [%s] open error: %ld\n", + filename, PTR_ERR(filp)); + } + + vts_send_ipc_ack(data, 1); + mutex_unlock(&gdump_info.audiolock); +} + +static void vts_logdump_flush_work_func(struct work_struct *work) +{ + struct device *dev = gdump_info.dev; + struct vts_dump_info *dump_info = &gdump_info; + struct vts_data *data = dev_get_drvdata(dev); + char filename[SZ_64]; + struct file *filp; + + dev_dbg(dev, "%s: SRAM Offset: 0x%x Size: %d\n", __func__, + dump_info->logaddr_offset, dump_info->logdump_sz); + + mutex_lock(&gdump_info.loglock); + sprintf(filename, "/data/vts_logdump.txt"); + + filp = filp_open(filename, O_RDWR | O_APPEND | + O_CREAT, S_IRUSR | S_IWUSR); + dev_info(dev, "LogDump appended mode\n"); + if (!IS_ERR_OR_NULL(filp)) { + void *area = (void *)(data->sram_base + + dump_info->logaddr_offset); + size_t bytes = (size_t)dump_info->logdump_sz; + + /* dev_dbg(dev, " %p, %zx\n", area, bytes); */ + kernel_write(filp, area, bytes, filp->f_pos); + dev_dbg(dev, "kernel_write %p, %zx\n", area, bytes); + + vfs_fsync(filp, 1); + filp_close(filp, NULL); + } else { + dev_err(dev, "VTS Logdump file [%s] open error: %ld\n", + filename, PTR_ERR(filp)); + } + + vts_send_ipc_ack(data, 1); + mutex_unlock(&gdump_info.loglock); +} + +static DECLARE_WORK(vts_audiodump_work, vts_audiodump_flush_work_func); +static DECLARE_WORK(vts_logdump_work, vts_logdump_flush_work_func); +void vts_audiodump_schedule_flush(struct device *dev) +{ + struct vts_data *data = dev_get_drvdata(dev); + + if (gdump_info.audioaddr_offset) { + schedule_work(&vts_audiodump_work); + dev_dbg(dev, "%s: AudioDump Scheduled\n", __func__); + } else { + dev_warn(dev, "%s: AudioDump address not registered\n", + __func__); + /* send ipc ack to unblock vts firmware */ + vts_send_ipc_ack(data, 1); + } +} +EXPORT_SYMBOL(vts_audiodump_schedule_flush); + +void vts_logdump_schedule_flush(struct device *dev) +{ + struct vts_data *data = dev_get_drvdata(dev); + + if (gdump_info.logaddr_offset) { + schedule_work(&vts_logdump_work); + dev_dbg(dev, "%s: LogDump Scheduled\n", __func__); + } else { + dev_warn(dev, "%s: LogDump address not registered\n", + __func__); + /* send ipc ack to unblock vts firmware */ + vts_send_ipc_ack(data, 1); + } +} +EXPORT_SYMBOL(vts_logdump_schedule_flush); + +void vts_dump_addr_register( + struct device *dev, + u32 addroffset, + u32 dumpsz, + u32 dump_mode) +{ + struct vts_data *data = dev_get_drvdata(dev); + + gdump_info.dev = dev; + if ((addroffset + dumpsz) > data->sram_size) { + dev_warn(dev, "%s: wrong offset[0x%x] or size[0x%x]\n", + __func__, addroffset, dumpsz); + return; + } + + if (dump_mode == VTS_AUDIO_DUMP) { + gdump_info.audioaddr_offset = addroffset; + gdump_info.audiodump_sz = dumpsz; + } else if (dump_mode == VTS_LOG_DUMP) { + gdump_info.logaddr_offset = addroffset; + gdump_info.logdump_sz = dumpsz; + } else + dev_warn(dev, "%s: Unknown dump mode\n", __func__); + + dev_info(dev, "%s: %sDump offset[0x%x] size [%d]Scheduled\n", + __func__, (dump_mode ? "Log" : "Audio"), + addroffset, dumpsz); +} +EXPORT_SYMBOL(vts_dump_addr_register); + +static int __init samsung_vts_dump_late_initcall(void) +{ + pr_info("%s\n", __func__); + mutex_init(&gdump_info.audiolock); + mutex_init(&gdump_info.loglock); + gdump_info.audioaddr_offset = 0; + gdump_info.audiodump_sz = 0; + gdump_info.logaddr_offset = 0; + gdump_info.logdump_sz = 0; + + return 0; +} +late_initcall(samsung_vts_dump_late_initcall); diff --git a/sound/soc/samsung/vts/vts_dump.h b/sound/soc/samsung/vts/vts_dump.h new file mode 100644 index 000000000000..ba9367cd87e5 --- /dev/null +++ b/sound/soc/samsung/vts/vts_dump.h @@ -0,0 +1,40 @@ +/* sound/soc/samsung/vts/vts_dump.h + * + * ALSA SoC - Samsung vts dump driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_VTS_DUMP_H +#define __SND_SOC_VTS_DUMP_H + +#include +#include "vts.h" + +#define VTS_ADUIODUMP_AFTER_MINS 2 // VTS will dump 4.4 sec data after every 2 minutes +#define VTS_LOGDUMP_AFTER_MINS 1 // VTS will dump available log after every 1 minute + +enum vts_dump_mode { + VTS_AUDIO_DUMP = 0, + VTS_LOG_DUMP = 1, +}; + +/** + * Schedule pcm dump from sram memory to file + * @param[in] dev pointer to vts device + * @param[in] addroffset SRAM offset for PCM dta + * @param[in] size size of pcm data + */ +extern void vts_audiodump_schedule_flush(struct device *dev); +extern void vts_logdump_schedule_flush(struct device *dev); +extern void vts_dump_addr_register( + struct device *dev, + u32 addroffset, + u32 dumpsz, + u32 dump_mode); + +#endif /* __SND_SOC_VTS_DUMP_H */ diff --git a/sound/soc/samsung/vts/vts_log.c b/sound/soc/samsung/vts/vts_log.c new file mode 100644 index 000000000000..25c4a2f692d7 --- /dev/null +++ b/sound/soc/samsung/vts/vts_log.c @@ -0,0 +1,302 @@ +/* sound/soc/samsung/vts/vts_log.c + * + * ALSA SoC - Samsung VTS Log driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +//#define DEBUG +#include +#include +#include +#include +#include + +#include "vts.h" +#include "vts_log.h" + +#define VTS_LOG_BUFFER_SIZE (SZ_1M) +#define S_IRWUG (S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP) + +struct vts_kernel_log_buffer { + char *buffer; + unsigned int index; + bool wrap; + bool updated; + wait_queue_head_t wq; +}; + +struct vts_log_buffer_info { + struct device *dev; + struct mutex lock; + struct vts_log_buffer log_buffer; + struct vts_kernel_log_buffer kernel_buffer; + bool registered; + u32 logbuf_index; +}; + +static struct dentry *vts_dbg_root_dir __read_mostly; +static struct vts_log_buffer_info glogbuf_info; + +struct dentry *vts_dbg_get_root_dir(void) +{ + pr_debug("%s\n", __func__); + + if (vts_dbg_root_dir == NULL) + vts_dbg_root_dir = debugfs_create_dir("vts", NULL); + + return vts_dbg_root_dir; +} + +static ssize_t vts_log_file_index; + +static int vts_log_file_open(struct inode *inode, struct file *file) +{ + struct vts_log_buffer_info *info = inode->i_private; + struct vts_data *data = dev_get_drvdata(info->dev); + u32 values[3] = {0, 0, 0}; + int result = 0; + + dev_dbg(info->dev, "%s\n", __func__); + + file->private_data = inode->i_private; + vts_log_file_index = -1; + + if (data->vts_ready) { + values[0] = VTS_ENABLE_DEBUGLOG; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(info->dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1); + if (result < 0) + dev_err(info->dev, "%s Enable_debuglog ipc transaction failed\n", __func__); + } + + data->vtslog_enabled = 1; + return 0; +} + +static int vts_log_file_close(struct inode *inode, struct file *file) +{ + struct vts_log_buffer_info *info = inode->i_private; + struct vts_data *data = dev_get_drvdata(info->dev); + u32 values[3] = {0, 0, 0}; + int result = 0; + + dev_dbg(info->dev, "%s\n", __func__); + + vts_log_file_index = -1; + + if (data->vts_ready) { + values[0] = VTS_DISABLE_DEBUGLOG; + values[1] = 0; + values[2] = 0; + result = vts_start_ipc_transaction(info->dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1); + if (result < 0) + dev_err(info->dev, "%s Disable_debuglog ipc transaction failed\n", __func__); + /* reset VTS SRAM debug log buffer */ + vts_register_log_buffer(info->dev, 0, 0); + } + + data->vtslog_enabled = 0; + return 0; +} + +static ssize_t vts_log_file_read( + struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + struct vts_log_buffer_info *info = file->private_data; + struct vts_kernel_log_buffer *kernel_buffer = &info->kernel_buffer; + size_t end, size; + bool first = (vts_log_file_index < 0); + int result; + + dev_dbg(info->dev, "%s(%zu, %lld)\n", __func__, count, *ppos); + + mutex_lock(&info->lock); + + if (vts_log_file_index < 0) + vts_log_file_index = likely(kernel_buffer->wrap) ? + kernel_buffer->index : 0; + + do { + end = ((vts_log_file_index < kernel_buffer->index) || + ((vts_log_file_index == kernel_buffer->index) + && !first)) ? kernel_buffer->index + : VTS_LOG_BUFFER_SIZE; + size = min(end - vts_log_file_index, count); + if (size == 0) { + mutex_unlock(&info->lock); + if (file->f_flags & O_NONBLOCK) { + dev_dbg(info->dev, "non block\n"); + return -EAGAIN; + } + kernel_buffer->updated = false; + + result = wait_event_interruptible(kernel_buffer->wq, + kernel_buffer->updated); + + if (result != 0) { + dev_dbg(info->dev, "interrupted\n"); + return result; + } + mutex_lock(&info->lock); + } +#ifdef VERBOSE_LOG + dev_dbg(info->dev, "loop %zu, %zu, %zd, %zu\n", size, end, vts_log_file_index, count); +#endif + } while (size == 0); + + dev_dbg(info->dev, "start=%zd, end=%zd size=%zd\n", vts_log_file_index, end, size); + if (copy_to_user(buf, kernel_buffer->buffer + vts_log_file_index, size)) + return -EFAULT; + + vts_log_file_index += size; + if (vts_log_file_index >= VTS_LOG_BUFFER_SIZE) + vts_log_file_index = 0; + + mutex_unlock(&info->lock); + + dev_dbg(info->dev, "%s: size = %zd\n", __func__, size); + + return size; +} + +static unsigned int vts_log_file_poll(struct file *file, poll_table *wait) +{ + struct vts_log_buffer_info *info = file->private_data; + struct vts_kernel_log_buffer *kernel_buffer = &info->kernel_buffer; + + dev_dbg(info->dev, "%s\n", __func__); + + poll_wait(file, &kernel_buffer->wq, wait); + return POLLIN | POLLRDNORM; +} + +static const struct file_operations vts_log_fops = { + .open = vts_log_file_open, + .release = vts_log_file_close, + .read = vts_log_file_read, + .poll = vts_log_file_poll, + .llseek = generic_file_llseek, + .owner = THIS_MODULE, +}; + +static void vts_log_memcpy(struct device *dev, + char *src, size_t size) +{ + struct vts_kernel_log_buffer *kernel_buffer = NULL; + size_t left_size = 0; + + kernel_buffer = &glogbuf_info.kernel_buffer; + left_size = VTS_LOG_BUFFER_SIZE - kernel_buffer->index; + + dev_dbg(dev, "%s(%zu)\n", __func__, size); + + if (left_size < size) { +#ifdef VERBOSE_LOG + dev_dbg(dev, "0: %s\n", src); +#endif + memcpy_fromio(kernel_buffer->buffer + + kernel_buffer->index, src, left_size); + src += left_size; + size -= left_size; + kernel_buffer->index = 0; + kernel_buffer->wrap = true; + } +#ifdef VERBOSE_LOG + dev_dbg(dev, "1: %s\n", src); +#endif + memcpy_fromio(kernel_buffer->buffer + kernel_buffer->index, src, size); + kernel_buffer->index += size; +} + +static void vts_log_flush_work_func(struct work_struct *work) +{ + struct device *dev = glogbuf_info.dev; + struct vts_log_buffer *log_buffer = &glogbuf_info.log_buffer; + struct vts_kernel_log_buffer *kernel_buffer = NULL; + int logbuf_index = glogbuf_info.logbuf_index; + + kernel_buffer = &glogbuf_info.kernel_buffer; + + dev_dbg(dev, "%s: LogBuffer Index: %d\n", __func__, + logbuf_index); + + mutex_lock(&glogbuf_info.lock); + + vts_log_memcpy(dev, (log_buffer->addr + + log_buffer->size * logbuf_index), log_buffer->size); + /* memory barrier */ + wmb(); + mutex_unlock(&glogbuf_info.lock); + + kernel_buffer->updated = true; + wake_up_interruptible(&kernel_buffer->wq); +} + +static DECLARE_WORK(vts_log_work, vts_log_flush_work_func); +void vts_log_schedule_flush(struct device *dev, u32 index) +{ + if (glogbuf_info.registered && + glogbuf_info.log_buffer.size) { + glogbuf_info.logbuf_index = index; + schedule_work(&vts_log_work); + dev_dbg(dev, "%s: VTS Log Buffer[%d] Scheduled\n", + __func__, index); + } else + dev_warn(dev, "%s: VTS Debugging buffer not registered\n", + __func__); +} +EXPORT_SYMBOL(vts_log_schedule_flush); + +int vts_register_log_buffer( + struct device *dev, + u32 addroffset, + u32 logsz) +{ + struct vts_data *data = dev_get_drvdata(dev); + + dev_dbg(dev, "%s(offset 0x%x)\n", __func__, addroffset); + + if ((addroffset + logsz) > data->sram_size) { + dev_warn(dev, "%s: wrong offset[0x%x] or size[0x%x]\n", + __func__, addroffset, logsz); + return -EINVAL; + } + + if (!glogbuf_info.registered) { + glogbuf_info.dev = dev; + mutex_init(&glogbuf_info.lock); + glogbuf_info.kernel_buffer.buffer = + vzalloc(VTS_LOG_BUFFER_SIZE); + glogbuf_info.kernel_buffer.index = 0; + glogbuf_info.kernel_buffer.wrap = false; + init_waitqueue_head(&glogbuf_info.kernel_buffer.wq); + + debugfs_create_file("vts-log", S_IRWUG, + vts_dbg_get_root_dir(), + &glogbuf_info, &vts_log_fops); + glogbuf_info.registered = true; + } + + /* Update logging buffer address and size info */ + glogbuf_info.log_buffer.addr = data->sram_base + addroffset; + glogbuf_info.log_buffer.size = logsz; + + return 0; +} +EXPORT_SYMBOL(vts_register_log_buffer); + +static int __init samsung_vts_log_late_initcall(void) +{ + pr_info("%s\n", __func__); + + return 0; +} +late_initcall(samsung_vts_log_late_initcall); diff --git a/sound/soc/samsung/vts/vts_log.h b/sound/soc/samsung/vts/vts_log.h new file mode 100644 index 000000000000..b5a9b8bd8f8d --- /dev/null +++ b/sound/soc/samsung/vts/vts_log.h @@ -0,0 +1,36 @@ +/* sound/soc/samsung/vts/vts_log.h + * + * ALSA SoC - Samsung vts Log driver + * + * Copyright (c) 2017 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_VTS_LOG_H +#define __SND_SOC_VTS_LOG_H + +#include +#include "vts.h" + +/** + * Schedule log flush sram memory to kernel memory + * @param[in] dev pointer to vts device + */ +extern void vts_log_schedule_flush(struct device *dev, u32 index); + +/** + * Register abox log buffer + * @param[in] dev pointer to abox device + * @param[in] addroffset Sram log buffer offset + * @param[in] logsz log buffer size + * @return error code if any + */ +extern int vts_register_log_buffer( + struct device *dev, + u32 addroffset, + u32 logsz); + +#endif /* __SND_SOC_VTS_LOG_H */ diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 2cb8d3b55fbc..30fcbbcaf43a 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -155,7 +155,9 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_lock_nested(&fe->pcm_mutex, fe->pcm_subclass); snd_soc_runtime_activate(fe, stream); + mutex_unlock(&fe->pcm_mutex); mutex_unlock(&fe->card->mutex); @@ -278,7 +280,9 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) else stream = SNDRV_PCM_STREAM_CAPTURE; + mutex_lock_nested(&fe->pcm_mutex, fe->pcm_subclass); snd_soc_runtime_deactivate(fe, stream); + mutex_unlock(&fe->pcm_mutex); fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; @@ -354,6 +358,7 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) struct snd_soc_pcm_runtime *fe = cstream->private_data; struct snd_soc_platform *platform = fe->platform; struct snd_soc_dai *cpu_dai = fe->cpu_dai; + enum snd_soc_dpcm_trigger trigger; int ret = 0, stream; if (cmd == SND_COMPR_TRIGGER_PARTIAL_DRAIN || @@ -370,6 +375,37 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) else stream = SNDRV_PCM_STREAM_CAPTURE; + trigger = fe->dai_link->trigger[stream]; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + switch (trigger) { + case SND_SOC_DPCM_TRIGGER_PRE_POST: + trigger = SND_SOC_DPCM_TRIGGER_PRE; + break; + case SND_SOC_DPCM_TRIGGER_POST_PRE: + trigger = SND_SOC_DPCM_TRIGGER_POST; + break; + default: + break; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + switch (trigger) { + case SND_SOC_DPCM_TRIGGER_PRE_POST: + trigger = SND_SOC_DPCM_TRIGGER_POST; + break; + case SND_SOC_DPCM_TRIGGER_POST_PRE: + trigger = SND_SOC_DPCM_TRIGGER_PRE; + break; + default: + break; + } + break; + } mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); @@ -379,10 +415,12 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) goto out; } - if (platform->driver->compr_ops && platform->driver->compr_ops->trigger) { - ret = platform->driver->compr_ops->trigger(cstream, cmd); - if (ret < 0) - goto out; + if (trigger != SND_SOC_DPCM_TRIGGER_POST) { + if (platform->driver->compr_ops && platform->driver->compr_ops->trigger) { + ret = platform->driver->compr_ops->trigger(cstream, cmd); + if (ret < 0) + goto out; + } } fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; @@ -404,6 +442,13 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) break; } + if (trigger == SND_SOC_DPCM_TRIGGER_POST) { + if (platform->driver->compr_ops && platform->driver->compr_ops->trigger) { + ret = platform->driver->compr_ops->trigger(cstream, cmd); + if (ret < 0) + goto out; + } + } out: fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; mutex_unlock(&fe->card->mutex); @@ -507,6 +552,13 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream, memset(&fe->dpcm[fe_substream->stream].hw_params, 0, sizeof(struct snd_pcm_hw_params)); + if (platform->driver->compr_ops && platform->driver->compr_ops->get_hw_params) { + ret = platform->driver->compr_ops->get_hw_params(cstream, + &fe->dpcm[fe_substream->stream].hw_params); + if (ret < 0) + goto out; + } + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; ret = dpcm_be_dai_hw_params(fe, stream); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index dcef67a9bd48..d981328c6964 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1194,6 +1194,18 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, is_connected_input_ep, custom_stop_condition); } +int snd_soc_dapm_connected_output_ep(struct snd_soc_dapm_widget *widget, + struct list_head *list) +{ + return is_connected_output_ep(widget, list, NULL); +} + +int snd_soc_dapm_connected_input_ep(struct snd_soc_dapm_widget *widget, + struct list_head *list) +{ + return is_connected_input_ep(widget, list, NULL); +} + /** * snd_soc_dapm_get_connected_widgets - query audio path and it's widgets. * @dai: the soc DAI. diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 94b88b897c3b..2d43492a5677 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -461,15 +461,14 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) const char *codec_dai_name = "multicodec"; int i, ret = 0; - pinctrl_pm_select_default_state(cpu_dai->dev); - for (i = 0; i < rtd->num_codecs; i++) - pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev); - for_each_rtdcom(rtd, rtdcom) { component = rtdcom->component; pm_runtime_get_sync(component->dev); } + pinctrl_pm_select_default_state(cpu_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev); mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -607,19 +606,18 @@ platform_err: out: mutex_unlock(&rtd->pcm_mutex); - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - pm_runtime_mark_last_busy(component->dev); - pm_runtime_put_autosuspend(component->dev); - } - for (i = 0; i < rtd->num_codecs; i++) { if (!rtd->codec_dais[i]->active) pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev); + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + pm_runtime_mark_last_busy(component->dev); + pm_runtime_put_autosuspend(component->dev); + } return ret; } @@ -719,19 +717,18 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) mutex_unlock(&rtd->pcm_mutex); - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - pm_runtime_mark_last_busy(component->dev); - pm_runtime_put_autosuspend(component->dev); - } - for (i = 0; i < rtd->num_codecs; i++) { if (!rtd->codec_dais[i]->active) pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev); + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + pm_runtime_mark_last_busy(component->dev); + pm_runtime_put_autosuspend(component->dev); + } return 0; } @@ -909,6 +906,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, codec_dai->channels = params_channels(&codec_params); codec_dai->sample_bits = snd_pcm_format_physical_width( params_format(&codec_params)); + codec_dai->sample_width = params_width(&codec_params); } ret = soc_dai_hw_params(substream, params, cpu_dai); @@ -929,6 +927,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, cpu_dai->channels = params_channels(params); cpu_dai->sample_bits = snd_pcm_format_physical_width(params_format(params)); + cpu_dai->sample_width = params_width(params); out: mutex_unlock(&rtd->pcm_mutex); @@ -975,6 +974,7 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) cpu_dai->rate = 0; cpu_dai->channels = 0; cpu_dai->sample_bits = 0; + cpu_dai->sample_width = 0; } for (i = 0; i < rtd->num_codecs; i++) { @@ -983,6 +983,7 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) codec_dai->rate = 0; codec_dai->channels = 0; codec_dai->sample_bits = 0; + codec_dai->sample_width = 0; } } @@ -1146,9 +1147,11 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, stream ? "<-" : "->", be->dai_link->name); #ifdef CONFIG_DEBUG_FS +#ifndef CONFIG_SND_SOC_SAMSUNG_ABOX if (fe->debugfs_dpcm_root) dpcm->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644, fe->debugfs_dpcm_root, &dpcm->state); +#endif #endif return 1; } @@ -1202,7 +1205,9 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) dpcm_be_reparent(fe, dpcm->be, stream); #ifdef CONFIG_DEBUG_FS +#ifndef CONFIG_SND_SOC_SAMSUNG_ABOX debugfs_remove(dpcm->debugfs_state); +#endif #endif list_del(&dpcm->list_be); list_del(&dpcm->list_fe); @@ -1908,7 +1913,7 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) /* perform any hw_params fixups */ if (be->dai_link->be_hw_params_fixup) { ret = be->dai_link->be_hw_params_fixup(be, - &dpcm->hw_params); + &dpcm->hw_params, stream); if (ret < 0) { dev_err(be->dev, "ASoC: hw_params BE fixup failed %d\n", @@ -2035,7 +2040,8 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, switch (cmd) { case SNDRV_PCM_TRIGGER_START: if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) continue; ret = dpcm_do_trigger(dpcm, be_substream, cmd); @@ -2065,7 +2071,8 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; break; case SNDRV_PCM_TRIGGER_STOP: - if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) continue; if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) @@ -2118,6 +2125,37 @@ static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd) fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + switch (trigger) { + case SND_SOC_DPCM_TRIGGER_PRE_POST: + trigger = SND_SOC_DPCM_TRIGGER_PRE; + break; + case SND_SOC_DPCM_TRIGGER_POST_PRE: + trigger = SND_SOC_DPCM_TRIGGER_POST; + break; + default: + break; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + switch (trigger) { + case SND_SOC_DPCM_TRIGGER_PRE_POST: + trigger = SND_SOC_DPCM_TRIGGER_POST; + break; + case SND_SOC_DPCM_TRIGGER_POST_PRE: + trigger = SND_SOC_DPCM_TRIGGER_PRE; + break; + default: + break; + } + break; + } + switch (trigger) { case SND_SOC_DPCM_TRIGGER_PRE: /* call trigger on the frontend before the backend. */