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 {
#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";
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 */
};
};
+ 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>;
#size-cells = <1>;
ranges;
- domain-clients = <>;
+ domain-clients = <&abox>;
};
iommu-domain_isp {
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
# 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
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
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);
};
/**
--- /dev/null
+/*
+ * 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 <linux/device.h>
+#include <linux/irqreturn.h>
+#include <sound/soc.h>
+#include <sound/samsung/abox_ipc.h>
+
+/**
+ * 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 */
--- /dev/null
+/* 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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 <linux/platform_device.h>
+
+#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 */
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;
unsigned int rate;
unsigned int channels;
unsigned int sample_bits;
+ unsigned int sample_width;
/* parent platform/codec */
struct snd_soc_codec *codec;
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, \
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 */
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 */
};
/*
/* 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;
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
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
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
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
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
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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/regmap.h>
+#include <linux/exynos_iovmm.h>
+#include <linux/workqueue.h>
+#include <linux/smc.h>
+#include <linux/delay.h>
+#include <linux/suspend.h>
+#include <linux/sched/clock.h>
+#include <linux/shm_ipc.h>
+#include <linux/modem_notifier.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/samsung/abox.h>
+#include <sound/samsung/vts.h>
+//#include <linux/exynos_iovmm.h>
+
+#include <soc/samsung/exynos-pmu.h>
+//#include <soc/samsung/exynos-itmon.h>
+#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 <misc/exynos_ima.h>
+
+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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box Driver");
+MODULE_ALIAS("platform:samsung-abox");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 <linux/pm_wakeup.h>
+#include <sound/samsung/abox.h>
+
+#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 */
--- /dev/null
+/* 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 <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/shm_ipc.h>
+#include <linux/io.h>
+#include <linux/iommu.h>
+
+#include <scsc/api/bt_audio.h>
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box SCSC Bluetooth Driver");
+MODULE_ALIAS("platform:samsung-abox-bt");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 <linux/device.h>
+
+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 */
--- /dev/null
+/* 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 <linux/io.h>
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/iommu.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched/clock.h>
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box Debug Driver");
+MODULE_ALIAS("platform:samsung-abox-debug");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/spinlock.h>
+#include <linux/vmalloc.h>
+#include <linux/pm_runtime.h>
+#include <sound/samsung/abox.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box Internal Buffer Dumping Driver");
+MODULE_ALIAS("platform:samsung-abox-dump");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 <linux/device.h>
+#include <sound/samsung/abox.h>
+
+/**
+ * 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 */
--- /dev/null
+/* 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 <sound/soc.h>
+#include <sound/tlv.h>
+
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <sound/samsung/abox.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box Effect Driver");
+MODULE_ALIAS("platform:samsung-abox-effect");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <linux/vmalloc.h>
+#include <linux/pm_runtime.h>
+#include <sound/samsung/abox.h>
+
+#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);
+}
--- /dev/null
+/* 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 <linux/device.h>
+#include <sound/samsung/abox.h>
+
+/**
+ * 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 */
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/smc.h>
+#include <linux/irqchip/arm-gic.h>
+#include <linux/delay.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box GIC Driver");
+MODULE_ALIAS("platform:samsung-abox-gic");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <sound/pcm_params.h>
+#include <sound/samsung/abox.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box UAIF/DSIF Driver");
+MODULE_ALIAS("platform:samsung-abox-if");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 <linux/debugfs.h>
+#include <linux/mutex.h>
+#include <linux/vmalloc.h>
+#include <sound/samsung/abox.h>
+
+#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);
--- /dev/null
+/* 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 <linux/device.h>
+#include <sound/samsung/abox.h>
+
+/**
+ * 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 */
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/regmap.h>
+#include <linux/iommu.h>
+#include <linux/delay.h>
+#include <linux/memblock.h>
+#include <linux/sched/clock.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "../../../../drivers/iommu/exynos-iommu.h"
+#include <sound/samsung/abox.h>
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box RDMA Driver");
+MODULE_ALIAS("platform:samsung-abox-rdma");
+MODULE_LICENSE("GPL");
--- /dev/null
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <sound/pcm.h>
+
+#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
--- /dev/null
+/* 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 <sound/pcm.h>
+
+/**
+ * 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 */
--- /dev/null
+/* 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/memblock.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box Virtual DMA Driver");
+MODULE_ALIAS("platform:samsung-abox-vdma");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/shm_ipc.h>
+#include <linux/io.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box VSS Driver");
+MODULE_ALIAS("platform:samsung-abox-vss");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/regmap.h>
+#include <linux/iommu.h>
+#include <linux/delay.h>
+#include <linux/memblock.h>
+#include <linux/sched/clock.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "../../../../drivers/iommu/exynos-iommu.h"
+#include <sound/samsung/abox.h>
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box WDMA Driver");
+MODULE_ALIAS("platform:samsung-abox-wdma");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * 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 <linux/clk.h>
+#include <linux/debugfs.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dpcm.h>
+#include <linux/mfd/madera/core.h>
+#include <linux/extcon/extcon-madera.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+
+#include <soc/samsung/exynos-pmu.h>
+#include <sound/samsung/abox.h>
+
+#if IS_ENABLED(CONFIG_SND_SOC_MADERA)
+#include <linux/mfd/madera/core.h>
+#include <linux/extcon/extcon-madera.h>
+#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 <ckeepax@opensource.wolfsonmicro.com>");
+MODULE_AUTHOR("Gyeongtaek Lee <gt82.lee@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:exynos9610-madera");
--- /dev/null
+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.
--- /dev/null
+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
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <sound/samsung/vts.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_DESCRIPTION("Samsung Mailbox");
+MODULE_ALIAS("platform:samsung-mailbox");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pm_runtime.h>
+#include <linux/firmware.h>
+#include <linux/dma-mapping.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/wakelock.h>
+
+#include <asm-generic/delay.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include <sound/samsung/mailbox.h>
+#include <sound/samsung/vts.h>
+#include <soc/samsung/exynos-pmu.h>
+
+#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, <gt82.lee@samsung.com>");
+MODULE_AUTHOR("Palli Satish Kumar Reddy, <palli.satish@samsung.com>");
+MODULE_DESCRIPTION("Samsung Voice Trigger System");
+MODULE_ALIAS("platform:samsung-vts");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 <sound/memalloc.h>
+#include <linux/wakelock.h>
+
+/* 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 */
--- /dev/null
+/* 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pm_runtime.h>
+#include <linux/firmware.h>
+#include <linux/dma-mapping.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/wakelock.h>
+
+#include <asm-generic/delay.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include <sound/samsung/mailbox.h>
+#include <sound/samsung/vts.h>
+#include <soc/samsung/exynos-pmu.h>
+
+#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, <palli.satish@samsung.com>");
+MODULE_DESCRIPTION("Samsung VTS DMA");
+MODULE_ALIAS("platform:samsung-vts-dma");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* 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 <linux/debugfs.h>
+#include <linux/mutex.h>
+#include <linux/vmalloc.h>
+#include <sound/soc.h>
+
+#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);
--- /dev/null
+/* 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 <linux/device.h>
+#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 */
--- /dev/null
+/* 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 <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/mutex.h>
+#include <linux/vmalloc.h>
+#include <sound/soc.h>
+
+#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);
--- /dev/null
+/* 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 <linux/device.h>
+#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 */
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);
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;
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 ||
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);
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;
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);
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);
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.
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);
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;
}
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;
}
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);
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);
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++) {
codec_dai->rate = 0;
codec_dai->channels = 0;
codec_dai->sample_bits = 0;
+ codec_dai->sample_width = 0;
}
}
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;
}
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);
/* 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",
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:
switch (cmd) {