diff options
author | Linaro CI <ci_notify@linaro.org> | 2020-12-13 22:54:35 +0000 |
---|---|---|
committer | Linaro CI <ci_notify@linaro.org> | 2020-12-13 22:54:35 +0000 |
commit | 4b8bf7dc24f12ad055199fb3ec35420235faed98 (patch) | |
tree | fe0f22b424061434211dd958697d7890ca7a2131 | |
parent | 83bcafa17e28c2b1efe0215e8febc3ce5687c6b3 (diff) | |
parent | a196f371b5435d42667a435e191362b1218b84a0 (diff) |
Merge remote-tracking branch 'audio/tracking-qcomlt-audio' into integration-linux-qcomlt
29 files changed, 5882 insertions, 5 deletions
diff --git a/Documentation/devicetree/bindings/clock/qcom,aoncc-sm8250.yaml b/Documentation/devicetree/bindings/clock/qcom,aoncc-sm8250.yaml new file mode 100644 index 000000000000..c40a74b5d672 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,aoncc-sm8250.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/qcom,aoncc-sm8250.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Clock bindings for LPASS Always ON Clock Controller on SM8250 SoCs + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: | + The clock consumer should specify the desired clock by having the clock + ID in its "clocks" phandle cell. + See include/dt-bindings/clock/qcom,sm8250-lpass-aoncc.h for the full list + of Audio Clock controller clock IDs. + +properties: + compatible: + const: qcom,sm8250-lpass-aon + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + clocks: + items: + - description: LPASS Core voting clock + - description: Glitch Free Mux register clock + + clock-names: + items: + - const: core + - const: bus + +required: + - compatible + - reg + - '#clock-cells' + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,sm8250-lpass-aoncc.h> + #include <dt-bindings/sound/qcom,q6afe.h> + clock-controller@3800000 { + #clock-cells = <1>; + compatible = "qcom,sm8250-lpass-aon"; + reg = <0x03380000 0x40000>; + clocks = <&q6afecc LPASS_HW_MACRO_VOTE LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6afecc LPASS_CLK_ID_TX_CORE_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "core", "bus"; + }; diff --git a/Documentation/devicetree/bindings/clock/qcom,audiocc-sm8250.yaml b/Documentation/devicetree/bindings/clock/qcom,audiocc-sm8250.yaml new file mode 100644 index 000000000000..915d76206ad0 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,audiocc-sm8250.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/qcom,audiocc-sm8250.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Clock bindings for LPASS Audio Clock Controller on SM8250 SoCs + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: | + The clock consumer should specify the desired clock by having the clock + ID in its "clocks" phandle cell. + See include/dt-bindings/clock/qcom,sm8250-lpass-audiocc.h for the full list + of Audio Clock controller clock IDs. + +properties: + compatible: + const: qcom,sm8250-lpass-audiocc + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + clocks: + items: + - description: LPASS Core voting clock + - description: Glitch Free Mux register clock + + clock-names: + items: + - const: core + - const: bus + +required: + - compatible + - reg + - '#clock-cells' + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,sm8250-lpass-audiocc.h> + #include <dt-bindings/sound/qcom,q6afe.h> + clock-controller@3300000 { + #clock-cells = <1>; + compatible = "qcom,sm8250-lpass-audiocc"; + reg = <0x03300000 0x30000>; + clocks = <&q6afecc LPASS_HW_MACRO_VOTE LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6afecc LPASS_CLK_ID_TX_CORE_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "core", "bus"; + }; diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,lpass-lpi-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/qcom,lpass-lpi-pinctrl.yaml new file mode 100644 index 000000000000..0e0c98ba9376 --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/qcom,lpass-lpi-pinctrl.yaml @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pinctrl/qcom,lpass-lpi-pinctrl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. Low Power Audio SubSystem (LPASS) + Low Power Island (LPI) TLMM block + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: | + This binding describes the Top Level Mode Multiplexer block found in the + LPASS LPI IP on most Qualcomm SoCs + +properties: + compatible: + const: qcom,sm8250-lpass-lpi-pinctrl + + reg: + minItems: 2 + maxItems: 2 + + clocks: + items: + - description: LPASS Core voting clock + - description: LPASS Audio voting clock + + clock-names: + items: + - const: core + - const: audio + + gpio-controller: true + + '#gpio-cells': + description: Specifying the pin number and flags, as defined in + include/dt-bindings/gpio/gpio.h + const: 2 + + gpio-ranges: + maxItems: 1 + +#PIN CONFIGURATION NODES +patternProperties: + '^.*$': + if: + type: object + then: + properties: + pins: + description: + List of gpio pins affected by the properties specified in this + subnode. + items: + oneOf: + - pattern: "^gpio([0-9]|[1-9][0-9])$" + minItems: 1 + maxItems: 14 + + function: + enum: [ gpio, func1, func2, func3, func4, func5 ] + description: + Specify the alternative function to be configured for the specified + pins. + + drive-strength: + enum: [2, 4, 6, 8, 10, 12, 14, 16] + default: 2 + description: + Selects the drive strength for the specified pins, in mA. + + slew-rate: + enum: [0, 1, 2, 3] + default: 0 + description: | + 0: No adjustments + 1: Higher Slew rate (faster edges) + 2: Lower Slew rate (slower edges) + 3: Reserved (No adjustments) + + bias-pull-down: true + + bias-pull-up: true + + bias-disable: true + + output-high: true + + output-low: true + + required: + - pins + - function + + additionalProperties: false + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - gpio-controller + - '#gpio-cells' + - gpio-ranges + +additionalProperties: false + +examples: + - | + #include <dt-bindings/sound/qcom,q6afe.h> + lpi_tlmm: pinctrl@33c0000 { + compatible = "qcom,sm8250-lpass-lpi-pinctrl"; + reg = <0 0x33c0000 0x0 0x20000>, + <0 0x355a000 0x0 0x1000>; + clocks = <&q6afecc LPASS_HW_MACRO_VOTE LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6afecc LPASS_HW_DCODEC_VOTE LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "core", "audio"; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&lpi_tlmm 0 0 14>; + }; diff --git a/Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt b/Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt index e94a2ad3a710..1c615b622d07 100644 --- a/Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt +++ b/Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt @@ -14,6 +14,7 @@ Please refer to slimbus/bus.txt for details of the common SLIMBus bindings. must be one of the following. "qcom,slim-ngd-v1.5.0" for MSM8996 "qcom,slim-ngd-v2.1.0" for SDM845 + "qcom,slim-ngd-v2.2.0" for SM8250 - reg: Usage: required diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml b/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml new file mode 100644 index 000000000000..214b4b117d39 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,lpass-va-macro.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LPASS(Low Power Audio Subsystem) VA Macro audio codec DT bindings + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +properties: + compatible: + const: qcom,sm8250-lpass-va-macro + + reg: + maxItems: 1 + + "#sound-dai-cells": + const: 1 + + '#clock-cells': + const: 0 + + clocks: + maxItems: 3 + + clock-names: + items: + - const: mclk + - const: core + - const: dcodec + + clock-output-names: + items: + - const: fsgen + + qcom,dmic-sample-rate: + description: dmic sample rate + $ref: /schemas/types.yaml#/definitions/uint32 + + vdd-micb-supply: + description: phandle to voltage regulator of MIC Bias + +required: + - compatible + - reg + - "#sound-dai-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,sm8250-lpass-aoncc.h> + #include <dt-bindings/sound/qcom,q6afe.h> + codec@3370000 { + compatible = "qcom,sm8250-lpass-va-macro"; + reg = <0x3370000 0x1000>; + #sound-dai-cells = <1>; + #clock-cells = <0>; + clocks = <&aoncc LPASS_CDC_VA_MCLK>, + <&q6afecc LPASS_HW_MACRO_VOTE LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6afecc LPASS_HW_DCODEC_VOTE LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk", "core", "dcodec"; + clock-output-names = "fsgen"; + qcom,dmic-sample-rate = <600000>; + vdd-micb-supply = <&vreg_s4a_1p8>; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,sc8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sc8250.yaml new file mode 100644 index 000000000000..101da93e2e85 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,sc8250.yaml @@ -0,0 +1,157 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,sc8250.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies Inc. SC8250 ASoC sound card driver + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: + This bindings describes SC8250 SoC based sound cards + which uses LPASS internal codec for audio. + +properties: + compatible: + const: qcom,qrb5165-rb5 + + audio-routing: + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + description: + A list of the connections between audio components. Each entry is a + pair of strings, the first being the connection's sink, the second + being the connection's source. Valid names could be power supplies, + MicBias of codec and the jacks on the board. + + model: + $ref: /schemas/types.yaml#/definitions/string + description: User visible long sound card name + +patternProperties: + ".*-dai-link$": + description: + Each subnode represents a dai link. Subnodes of each dai links would be + cpu/codec dais. + + type: object + + properties: + link-name: + description: Indicates dai-link name and PCM stream name. + $ref: /schemas/types.yaml#/definitions/string + maxItems: 1 + + cpu: + description: Holds subnode which indicates cpu dai. + type: object + properties: + sound-dai: true + + platform: + description: Holds subnode which indicates platform dai. + type: object + properties: + sound-dai: true + + codec: + description: Holds subnode which indicates codec dai. + type: object + properties: + sound-dai: true + + required: + - link-name + - cpu + + additionalProperties: false + +required: + - compatible + - model + +additionalProperties: false + +examples: + + - | + #include <dt-bindings/sound/qcom,q6afe.h> + #include <dt-bindings/sound/qcom,q6asm.h> + sound { + compatible = "qcom,qrb5165-rb5"; + model = "Qualcomm-qrb5165-RB5-WSA8815-Speakers-DMIC0"; + audio-routing = "SpkrLeft IN", "WSA_SPK1 OUT", + "SpkrRight IN", "WSA_SPK2 OUT", + "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "MM_DL1", "MultiMedia1 Playback", + "MM_DL2", "MultiMedia2 Playback", + "MultiMedia3 Capture", "MM_UL3"; + + mm1-dai-link { + link-name = "MultiMedia0"; + cpu { + sound-dai = <&q6asmdai MSM_FRONTEND_DAI_MULTIMEDIA1>; + }; + }; + + mm2-dai-link { + link-name = "MultiMedia2"; + cpu { + sound-dai = <&q6asmdai MSM_FRONTEND_DAI_MULTIMEDIA2>; + }; + }; + + mm3-dai-link { + link-name = "MultiMedia3"; + cpu { + sound-dai = <&q6asmdai MSM_FRONTEND_DAI_MULTIMEDIA3>; + }; + }; + + hdmi-dai-link { + link-name = "HDMI Playback"; + cpu { + sound-dai = <&q6afedai TERTIARY_MI2S_RX>; + }; + + platform { + sound-dai = <&q6routing>; + }; + + codec { + sound-dai = <<9611_codec 0>; + }; + }; + + wsa-dai-link { + link-name = "WSA Playback"; + cpu { + sound-dai = <&q6afedai WSA_CODEC_DMA_RX_0>; + }; + + platform { + sound-dai = <&q6routing>; + }; + + codec { + sound-dai = <&left_spkr>, <&right_spkr>, <&swr0 0>, <&wsamacro>; + }; + }; + + va-dai-link { + link-name = "VA Capture"; + cpu { + sound-dai = <&q6afedai VA_CODEC_DMA_TX_0>; + }; + + platform { + sound-dai = <&q6routing>; + }; + + codec { + sound-dai = <&vamacro 0>; + }; + }; + }; diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 3a965bd326d5..b619d861cb2e 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -502,4 +502,10 @@ config KRAITCC Support for the Krait CPU clocks on Qualcomm devices. Say Y if you want to support CPU frequency scaling. +config CLK_GFM_LPASS_SM8250 + tristate "SM8250 GFM LPASS Clocks" + help + Support for the Glitch Free Mux (GFM) Low power audio + subsystem (LPASS) clocks found on SM8250 SoCs. + endif diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 11ae86febe87..6f4f25ba9139 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -19,6 +19,7 @@ clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o # Keep alphabetically sorted by config obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o obj-$(CONFIG_APQ_MMCC_8084) += mmcc-apq8084.o +obj-$(CONFIG_CLK_GFM_LPASS_SM8250) += lpass-gfm-sm8250.o obj-$(CONFIG_IPQ_APSS_PLL) += apss-ipq-pll.o obj-$(CONFIG_IPQ_APSS_6018) += apss-ipq6018.o obj-$(CONFIG_IPQ_GCC_4019) += gcc-ipq4019.o diff --git a/drivers/clk/qcom/lpass-gfm-sm8250.c b/drivers/clk/qcom/lpass-gfm-sm8250.c new file mode 100644 index 000000000000..d366c7c2abc7 --- /dev/null +++ b/drivers/clk/qcom/lpass-gfm-sm8250.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LPASS Audio CC and Always ON CC Glitch Free Mux clock driver + * + * Copyright (c) 2020 Linaro Ltd. + * Author: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/pm_clock.h> +#include <linux/pm_runtime.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <dt-bindings/clock/qcom,sm8250-lpass-audiocc.h> +#include <dt-bindings/clock/qcom,sm8250-lpass-aoncc.h> + +struct lpass_gfm { + struct device *dev; + void __iomem *base; +}; + +struct clk_gfm { + unsigned int mux_reg; + unsigned int mux_mask; + struct clk_hw hw; + struct lpass_gfm *priv; + void __iomem *gfm_mux; +}; + +#define GFM_MASK BIT(1) +#define to_clk_gfm(_hw) container_of(_hw, struct clk_gfm, hw) + +static u8 clk_gfm_get_parent(struct clk_hw *hw) +{ + struct clk_gfm *clk = to_clk_gfm(hw); + + return readl(clk->gfm_mux) & GFM_MASK; +} + +static int clk_gfm_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_gfm *clk = to_clk_gfm(hw); + unsigned int val; + + val = readl(clk->gfm_mux); + + if (index) + val |= GFM_MASK; + else + val &= ~GFM_MASK; + + writel(val, clk->gfm_mux); + + return 0; +} + +static const struct clk_ops clk_gfm_ops = { + .get_parent = clk_gfm_get_parent, + .set_parent = clk_gfm_set_parent, + .determine_rate = __clk_mux_determine_rate, +}; + +static struct clk_gfm lpass_gfm_va_mclk = { + .mux_reg = 0x20000, + .mux_mask = BIT(0), + .hw.init = &(struct clk_init_data) { + .name = "VA_MCLK", + .ops = &clk_gfm_ops, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, + .num_parents = 2, + .parent_data = (const struct clk_parent_data[]){ + { + .index = 0, + .fw_name = "LPASS_CLK_ID_TX_CORE_MCLK", + }, { + .index = 1, + .fw_name = "LPASS_CLK_ID_VA_CORE_MCLK", + }, + }, + }, +}; + +static struct clk_gfm lpass_gfm_tx_npl = { + .mux_reg = 0x20000, + .mux_mask = BIT(0), + .hw.init = &(struct clk_init_data) { + .name = "TX_NPL", + .ops = &clk_gfm_ops, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, + .parent_data = (const struct clk_parent_data[]){ + { + .index = 0, + .fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK", + }, { + .index = 1, + .fw_name = "LPASS_CLK_ID_VA_CORE_2X_MCLK", + }, + }, + .num_parents = 2, + }, +}; + +static struct clk_gfm lpass_gfm_wsa_mclk = { + .mux_reg = 0x220d8, + .mux_mask = BIT(0), + .hw.init = &(struct clk_init_data) { + .name = "WSA_MCLK", + .ops = &clk_gfm_ops, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, + .parent_data = (const struct clk_parent_data[]){ + { + .index = 0, + .fw_name = "LPASS_CLK_ID_TX_CORE_MCLK", + }, { + .index = 1, + .fw_name = "LPASS_CLK_ID_WSA_CORE_MCLK", + }, + }, + .num_parents = 2, + }, +}; + +static struct clk_gfm lpass_gfm_wsa_npl = { + .mux_reg = 0x220d8, + .mux_mask = BIT(0), + .hw.init = &(struct clk_init_data) { + .name = "WSA_NPL", + .ops = &clk_gfm_ops, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, + .parent_data = (const struct clk_parent_data[]){ + { + .index = 0, + .fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK", + }, { + .index = 1, + .fw_name = "LPASS_CLK_ID_WSA_CORE_NPL_MCLK", + }, + }, + .num_parents = 2, + }, +}; + +static struct clk_gfm lpass_gfm_rx_mclk_mclk2 = { + .mux_reg = 0x240d8, + .mux_mask = BIT(0), + .hw.init = &(struct clk_init_data) { + .name = "RX_MCLK_MCLK2", + .ops = &clk_gfm_ops, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, + .parent_data = (const struct clk_parent_data[]){ + { + .index = 0, + .fw_name = "LPASS_CLK_ID_TX_CORE_MCLK", + }, { + .index = 1, + .fw_name = "LPASS_CLK_ID_RX_CORE_MCLK", + }, + }, + .num_parents = 2, + }, +}; + +static struct clk_gfm lpass_gfm_rx_npl = { + .mux_reg = 0x240d8, + .mux_mask = BIT(0), + .hw.init = &(struct clk_init_data) { + .name = "RX_NPL", + .ops = &clk_gfm_ops, + .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, + .parent_data = (const struct clk_parent_data[]){ + { + .index = 0, + .fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK", + }, { + .index = 1, + .fw_name = "LPASS_CLK_ID_RX_CORE_NPL_MCLK", + }, + }, + .num_parents = 2, + }, +}; + +static struct clk_gfm *aoncc_gfm_clks[] = { + [LPASS_CDC_VA_MCLK] = &lpass_gfm_va_mclk, + [LPASS_CDC_TX_NPL] = &lpass_gfm_tx_npl, +}; + +static struct clk_hw_onecell_data aoncc_hw_onecell_data = { + .hws = { + [LPASS_CDC_VA_MCLK] = &lpass_gfm_va_mclk.hw, + [LPASS_CDC_TX_NPL] = &lpass_gfm_tx_npl.hw, + }, + .num = ARRAY_SIZE(aoncc_gfm_clks), +}; + +static struct clk_gfm *audiocc_gfm_clks[] = { + [LPASS_CDC_WSA_NPL] = &lpass_gfm_wsa_npl, + [LPASS_CDC_WSA_MCLK] = &lpass_gfm_wsa_mclk, + [LPASS_CDC_RX_NPL] = &lpass_gfm_rx_npl, + [LPASS_CDC_RX_MCLK_MCLK2] = &lpass_gfm_rx_mclk_mclk2, +}; + +static struct clk_hw_onecell_data audiocc_hw_onecell_data = { + .hws = { + [LPASS_CDC_WSA_NPL] = &lpass_gfm_wsa_npl.hw, + [LPASS_CDC_WSA_MCLK] = &lpass_gfm_wsa_mclk.hw, + [LPASS_CDC_RX_NPL] = &lpass_gfm_rx_npl.hw, + [LPASS_CDC_RX_MCLK_MCLK2] = &lpass_gfm_rx_mclk_mclk2.hw, + }, + .num = ARRAY_SIZE(audiocc_gfm_clks), +}; + +struct lpass_gfm_data { + struct clk_hw_onecell_data *onecell_data; + struct clk_gfm **gfm_clks; +}; + +static struct lpass_gfm_data audiocc_data = { + .onecell_data = &audiocc_hw_onecell_data, + .gfm_clks = audiocc_gfm_clks, +}; + +static struct lpass_gfm_data aoncc_data = { + .onecell_data = &aoncc_hw_onecell_data, + .gfm_clks = aoncc_gfm_clks, +}; + +static int lpass_gfm_clk_driver_probe(struct platform_device *pdev) +{ + const struct lpass_gfm_data *data; + struct device *dev = &pdev->dev; + struct clk_gfm *gfm; + struct lpass_gfm *cc; + int err, i; + + data = of_device_get_match_data(dev); + if (!data) + return -EINVAL; + + cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); + if (!cc) + return -ENOMEM; + + cc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(cc->base)) + return PTR_ERR(cc->base); + + pm_runtime_enable(dev); + err = pm_clk_create(dev); + if (err) + goto pm_clk_err; + + err = of_pm_clk_add_clks(dev); + if (err < 0) { + dev_dbg(dev, "Failed to get lpass core voting clocks\n"); + goto clk_reg_err; + } + + for (i = 0; i < data->onecell_data->num; i++) { + if (!data->gfm_clks[i]) + continue; + + gfm = data->gfm_clks[i]; + gfm->priv = cc; + gfm->gfm_mux = cc->base; + gfm->gfm_mux = gfm->gfm_mux + data->gfm_clks[i]->mux_reg; + + err = devm_clk_hw_register(dev, &data->gfm_clks[i]->hw); + if (err) + goto clk_reg_err; + + } + + err = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, + data->onecell_data); + if (err) + goto clk_reg_err; + + return 0; + +clk_reg_err: + pm_clk_destroy(dev); +pm_clk_err: + pm_runtime_disable(dev); + return err; +} + +static const struct of_device_id lpass_gfm_clk_match_table[] = { + { + .compatible = "qcom,sm8250-lpass-aoncc", + .data = &aoncc_data, + }, + { + .compatible = "qcom,sm8250-lpass-audiocc", + .data = &audiocc_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, lpass_gfm_clk_match_table); + +static const struct dev_pm_ops lpass_gfm_pm_ops = { + SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL) +}; + +static struct platform_driver lpass_gfm_clk_driver = { + .probe = lpass_gfm_clk_driver_probe, + .driver = { + .name = "lpass-gfm-clk", + .of_match_table = lpass_gfm_clk_match_table, + .pm = &lpass_gfm_pm_ops, + }, +}; +module_platform_driver(lpass_gfm_clk_driver); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index fc00aaccb5f7..3e845be895ac 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -359,6 +359,7 @@ static int mfd_remove_devices_fn(struct device *dev, void *data) struct platform_device *pdev; const struct mfd_cell *cell; int *level = data; + struct mfd_of_node_entry *of_entry, *tmp; if (dev->type != &mfd_dev_type) return 0; @@ -372,6 +373,12 @@ static int mfd_remove_devices_fn(struct device *dev, void *data) regulator_bulk_unregister_supply_alias(dev, cell->parent_supplies, cell->num_parent_supplies); + list_for_each_entry_safe(of_entry, tmp, &mfd_of_node_list, list) + if (of_entry->dev == &pdev->dev) { + list_del(&of_entry->list); + kfree(of_entry); + } + platform_device_unregister(pdev); return 0; } diff --git a/drivers/mfd/wcd934x.c b/drivers/mfd/wcd934x.c index c274d733b656..b5252053a624 100644 --- a/drivers/mfd/wcd934x.c +++ b/drivers/mfd/wcd934x.c @@ -194,10 +194,14 @@ static int wcd934x_slim_status_up(struct slim_device *sdev) static int wcd934x_slim_status(struct slim_device *sdev, enum slim_device_status status) { + struct wcd934x_ddata *ddata; + switch (status) { case SLIM_DEVICE_STATUS_UP: return wcd934x_slim_status_up(sdev); case SLIM_DEVICE_STATUS_DOWN: + ddata = dev_get_drvdata(&sdev->dev); + regcache_cache_only(ddata->regmap, true); mfd_remove_devices(&sdev->dev); break; default: diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig index 5fe7b8aaf69d..af26f4c51f77 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -236,4 +236,12 @@ config PINCTRL_SM8250 Qualcomm Technologies Inc TLMM block found on the Qualcomm Technologies Inc SM8250 platform. +config PINCTRL_LPASS_LPI + tristate "Qualcomm Technologies Inc LPASS LPI pin controller driver" + depends on GPIOLIB && OF + help + This is the pinctrl, pinmux, pinconf and gpiolib driver for the + Qualcomm Technologies Inc LPASS (Low Power Audio SubSystem) LPI + (Low Power Island) found on the Qualcomm Technologies Inc SoCs. + endif diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index 9e3d9c91a444..c8520155fb1b 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_PINCTRL_SDM660) += pinctrl-sdm660.o obj-$(CONFIG_PINCTRL_SDM845) += pinctrl-sdm845.o obj-$(CONFIG_PINCTRL_SM8150) += pinctrl-sm8150.o obj-$(CONFIG_PINCTRL_SM8250) += pinctrl-sm8250.o +obj-$(CONFIG_PINCTRL_LPASS_LPI) += pinctrl-lpass-lpi.o diff --git a/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c new file mode 100644 index 000000000000..9b79230952e0 --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + */ + +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pinctrl/pinconf-generic.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/clk.h> +#include <linux/bitops.h> + +#include "../core.h" +#include "../pinctrl-utils.h" + +#define LPI_GPIO_REG_VAL_CTL 0x00 +#define LPI_GPIO_REG_DIR_CTL 0x04 +#define LPI_SLEW_REG_VAL_CTL 0x00 +#define LPI_SLEW_RATE_MAX 0x03 +#define LPI_SLEW_BITS_SIZE 0x02 + +#define LPI_GPIO_REG_PULL_SHIFT 0x0 +#define LPI_GPIO_REG_PULL_MASK GENMASK(1, 0) + +#define LPI_GPIO_REG_FUNCTION_SHIFT 0x2 +#define LPI_GPIO_REG_FUNCTION_MASK GENMASK(5, 2) + +#define LPI_GPIO_REG_OUT_STRENGTH_SHIFT 0x6 +#define LPI_GPIO_REG_OUT_STRENGTH_MASK GENMASK(8, 6) + +#define LPI_GPIO_REG_OE_SHIFT 0x9 +#define LPI_GPIO_REG_OE_MASK BIT(9) + +#define LPI_GPIO_REG_DIR_SHIFT 0x1 +#define LPI_GPIO_REG_DIR_MASK 0x2 + +#define LPI_GPIO_BIAS_DISABLE 0x0 +#define LPI_GPIO_PULL_DOWN 0x1 +#define LPI_GPIO_KEEPER 0x2 +#define LPI_GPIO_PULL_UP 0x3 + +#define LPI_GPIO_FUNC_GPIO "gpio" +#define LPI_GPIO_FUNC_FUNC1 "func1" +#define LPI_GPIO_FUNC_FUNC2 "func2" +#define LPI_GPIO_FUNC_FUNC3 "func3" +#define LPI_GPIO_FUNC_FUNC4 "func4" +#define LPI_GPIO_FUNC_FUNC5 "func5" + +/* The index of each function in lpi_gpio_functions[] array */ +enum lpi_gpio_func_index { + LPI_GPIO_FUNC_INDEX_GPIO = 0x00, + LPI_GPIO_FUNC_INDEX_FUNC1, + LPI_GPIO_FUNC_INDEX_FUNC2, + LPI_GPIO_FUNC_INDEX_FUNC3, + LPI_GPIO_FUNC_INDEX_FUNC4, + LPI_GPIO_FUNC_INDEX_FUNC5, +}; + +struct lpi_pinctrl_variant_data { + int tlmm_reg_offset; + const struct pinctrl_pin_desc *pins; + int npins; + const char *const *groups; + int ngroups; + int *slew_reg_pin_offsets; +}; + +struct lpi_pinctrl { + struct device *dev; + struct pinctrl_dev *ctrl; + struct gpio_chip chip; + struct pinctrl_desc desc; + char __iomem *tlmm_base; + char __iomem *slew_base; + struct clk *core_vote; + struct clk *audio_vote; + struct mutex slew_access_lock; + const struct lpi_pinctrl_variant_data *data; +}; + +/* sm8250 variant specific data */ +#define SM8250_LPASS_PINS 14 + +static const char *const sm8250_gpio_groups[SM8250_LPASS_PINS] = { + "gpio0", "gpio1", "gpio2", "gpio3", "gpio4", + "gpio5", "gpio6", "gpio7", "gpio8", "gpio9", + "gpio10", "gpio11", "gpio12", "gpio13" +}; + +static const struct pinctrl_pin_desc sm8250_lpi_pins[] = { + PINCTRL_PIN(0, "gpio0"), + PINCTRL_PIN(1, "gpio1"), + PINCTRL_PIN(2, "gpio2"), + PINCTRL_PIN(3, "gpio3"), + PINCTRL_PIN(4, "gpio4"), + PINCTRL_PIN(5, "gpio5"), + PINCTRL_PIN(6, "gpio6"), + PINCTRL_PIN(7, "gpio7"), + PINCTRL_PIN(8, "gpio8"), + PINCTRL_PIN(9, "gpio9"), + PINCTRL_PIN(10, "gpio10"), + PINCTRL_PIN(11, "gpio11"), + PINCTRL_PIN(12, "gpio12"), + PINCTRL_PIN(13, "gpio13"), +}; + +static int sm8250_slew_reg_offsets[] = { + 0x0, 0x2, 0x4, 0x8, 0xa, + 0xc, 0x0, 0x0, 0x0, 0x0, + 0x10, 0x12, 0x0, 0x0, +}; + +static struct lpi_pinctrl_variant_data sm8250_lpi_data = { + .tlmm_reg_offset = 0x1000, + .pins = sm8250_lpi_pins, + .npins = ARRAY_SIZE(sm8250_lpi_pins), + .slew_reg_pin_offsets = sm8250_slew_reg_offsets, + .groups = sm8250_gpio_groups, + .ngroups = ARRAY_SIZE(sm8250_gpio_groups), +}; + +static const char *const lpi_gpio_functions[] = { + [LPI_GPIO_FUNC_INDEX_GPIO] = LPI_GPIO_FUNC_GPIO, + [LPI_GPIO_FUNC_INDEX_FUNC1] = LPI_GPIO_FUNC_FUNC1, + [LPI_GPIO_FUNC_INDEX_FUNC2] = LPI_GPIO_FUNC_FUNC2, + [LPI_GPIO_FUNC_INDEX_FUNC3] = LPI_GPIO_FUNC_FUNC3, + [LPI_GPIO_FUNC_INDEX_FUNC4] = LPI_GPIO_FUNC_FUNC4, + [LPI_GPIO_FUNC_INDEX_FUNC5] = LPI_GPIO_FUNC_FUNC5, +}; + +static int lpi_gpio_read(struct lpi_pinctrl *state, unsigned int pin, + unsigned int addr) +{ + return ioread32(state->tlmm_base + + state->data->tlmm_reg_offset * pin + addr); +} + +static int lpi_gpio_write(struct lpi_pinctrl *state, unsigned int pin, + unsigned int addr, unsigned int val) +{ + iowrite32(val, state->tlmm_base + + state->data->tlmm_reg_offset * pin + addr); + pr_err("DEBUG: %s: %x-> %x \n", __func__, + state->data->tlmm_reg_offset * pin, val); + + return 0; +} + +static int lpi_gpio_get_groups_count(struct pinctrl_dev *pctldev) +{ + /* Every PIN is a group */ + return pctldev->desc->npins; +} + +static const char *lpi_gpio_get_group_name(struct pinctrl_dev *pctldev, + unsigned int pin) +{ + struct lpi_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return pctrl->data->groups[pin]; +} + +static int lpi_gpio_get_group_pins(struct pinctrl_dev *pctldev, + unsigned int pin, + const unsigned int **pins, + unsigned int *num_pins) +{ + *pins = &pctldev->desc->pins[pin].number; + *num_pins = 1; + + return 0; +} + +static const struct pinctrl_ops lpi_gpio_pinctrl_ops = { + .get_groups_count = lpi_gpio_get_groups_count, + .get_group_name = lpi_gpio_get_group_name, + .get_group_pins = lpi_gpio_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_group, + .dt_free_map = pinctrl_utils_free_map, +}; + +static int lpi_gpio_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(lpi_gpio_functions); +} + +static const char *lpi_gpio_get_function_name(struct pinctrl_dev *pctldev, + unsigned int function) +{ + return lpi_gpio_functions[function]; +} + +static int lpi_gpio_get_function_groups(struct pinctrl_dev *pctldev, + unsigned int function, + const char *const **groups, + unsigned *const num_qgroups) +{ + struct lpi_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + *groups = pctrl->data->groups; + *num_qgroups = pctrl->data->ngroups; + + return 0; +} + +static int lpi_gpio_set_mux(struct pinctrl_dev *pctldev, unsigned int function, + unsigned int pin) +{ + struct lpi_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + unsigned int val; + + val = lpi_gpio_read(pctrl, pin, LPI_GPIO_REG_VAL_CTL); + val &= ~(LPI_GPIO_REG_FUNCTION_MASK); + val |= function << LPI_GPIO_REG_FUNCTION_SHIFT; + lpi_gpio_write(pctrl, pin, LPI_GPIO_REG_VAL_CTL, val); + + return 0; +} + +static const struct pinmux_ops lpi_gpio_pinmux_ops = { + .get_functions_count = lpi_gpio_get_functions_count, + .get_function_name = lpi_gpio_get_function_name, + .get_function_groups = lpi_gpio_get_function_groups, + .set_mux = lpi_gpio_set_mux, +}; + +static int lpi_config_get(struct pinctrl_dev *pctldev, + unsigned int pin, unsigned long *config) +{ + unsigned int param = pinconf_to_config_param(*config); + struct lpi_pinctrl *state = dev_get_drvdata(pctldev->dev); + unsigned int arg = 0; + int is_out; + int pull; + u32 ctl_reg; + + ctl_reg = lpi_gpio_read(state, pin, LPI_GPIO_REG_DIR_CTL); + + is_out = (ctl_reg & LPI_GPIO_REG_DIR_MASK) >> LPI_GPIO_REG_DIR_SHIFT; + + ctl_reg = lpi_gpio_read(state, pin, LPI_GPIO_REG_VAL_CTL); + + pull = (ctl_reg & LPI_GPIO_REG_PULL_MASK) >> LPI_GPIO_REG_PULL_SHIFT; + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + if (pull == LPI_GPIO_BIAS_DISABLE) + arg = 1; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + if (pull == LPI_GPIO_PULL_DOWN) + arg = 1; + break; + case PIN_CONFIG_BIAS_BUS_HOLD: + if (pull == LPI_GPIO_KEEPER) + arg = 1; + break; + case PIN_CONFIG_BIAS_PULL_UP: + if (pull == LPI_GPIO_PULL_UP) + arg = 1; + break; + case PIN_CONFIG_INPUT_ENABLE: + case PIN_CONFIG_OUTPUT: + if (is_out) + arg = 1; + break; + default: + return -EINVAL; + } + + *config = pinconf_to_config_packed(param, arg); + return 0; +} + +static unsigned int lpi_drive_to_regval(u32 arg) +{ + return (arg/2 - 1); +} + +static int lpi_config_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *configs, unsigned int nconfs) +{ + unsigned int param, arg; + int i, ret = 0; + volatile unsigned long val; + struct lpi_pinctrl *state = dev_get_drvdata(pctldev->dev); + bool output_enabled; + unsigned int pullup; + unsigned int strength; + unsigned int offset; + bool value; + + for (i = 0; i < nconfs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + pullup = LPI_GPIO_BIAS_DISABLE; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + pullup = LPI_GPIO_PULL_DOWN; + break; + case PIN_CONFIG_BIAS_BUS_HOLD: + pullup = LPI_GPIO_KEEPER; + break; + case PIN_CONFIG_BIAS_PULL_UP: + pullup = LPI_GPIO_PULL_UP; + break; + case PIN_CONFIG_INPUT_ENABLE: + output_enabled = false; + break; + case PIN_CONFIG_OUTPUT: + output_enabled = true; + lpi_gpio_write(state, pin, LPI_GPIO_REG_DIR_CTL, + output_enabled << LPI_GPIO_REG_DIR_SHIFT); + value = arg; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + strength = arg; + break; + case PIN_CONFIG_SLEW_RATE: + if (arg > LPI_SLEW_RATE_MAX) { + dev_err(pctldev->dev, "%s: invalid slew rate %u for pin: %d\n", + __func__, arg, pin); + goto set_gpio; + } + + mutex_lock(&state->slew_access_lock); + + val = ioread32(state->slew_base + LPI_SLEW_REG_VAL_CTL); + + offset = state->data->slew_reg_pin_offsets[pin]; + for (i = 0; i < LPI_SLEW_BITS_SIZE; i++) { + if (arg & 0x01) + set_bit(offset, &val); + else + clear_bit(offset, &val); + offset++; + arg = arg >> 1; + } + + iowrite32(val, state->slew_base + LPI_SLEW_REG_VAL_CTL); + + mutex_unlock(&state->slew_access_lock); + break; + default: + ret = -EINVAL; + goto done; + } + } + +set_gpio: + + val = lpi_gpio_read(state, pin, LPI_GPIO_REG_VAL_CTL); + val &= ~(LPI_GPIO_REG_PULL_MASK | LPI_GPIO_REG_OUT_STRENGTH_MASK | + LPI_GPIO_REG_OE_MASK); + val |= pullup << LPI_GPIO_REG_PULL_SHIFT; + val |= lpi_drive_to_regval(strength) << LPI_GPIO_REG_OUT_STRENGTH_SHIFT; + if (output_enabled) + val |= value << LPI_GPIO_REG_OE_SHIFT; + + lpi_gpio_write(state, pin, LPI_GPIO_REG_VAL_CTL, val); + lpi_gpio_write(state, pin, LPI_GPIO_REG_DIR_CTL, + output_enabled << LPI_GPIO_REG_DIR_SHIFT); +done: + return ret; +} + +static const struct pinconf_ops lpi_gpio_pinconf_ops = { + .is_generic = true, + .pin_config_group_get = lpi_config_get, + .pin_config_group_set = lpi_config_set, +}; + +static int lpi_gpio_direction_input(struct gpio_chip *chip, unsigned int pin) +{ + struct lpi_pinctrl *state = gpiochip_get_data(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1); + + return lpi_config_set(state->ctrl, pin, &config, 1); +} + +static int lpi_gpio_direction_output(struct gpio_chip *chip, + unsigned int pin, int val) +{ + struct lpi_pinctrl *state = gpiochip_get_data(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val); + + return lpi_config_set(state->ctrl, pin, &config, 1); +} + +static int lpi_gpio_get(struct gpio_chip *chip, unsigned int pin) +{ + struct lpi_pinctrl *state = gpiochip_get_data(chip); + int value; + + value = lpi_gpio_read(state, pin, LPI_GPIO_REG_VAL_CTL); + return value; +} + +static void lpi_gpio_set(struct gpio_chip *chip, unsigned int pin, int value) +{ + struct lpi_pinctrl *state = gpiochip_get_data(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value); + + lpi_config_set(state->ctrl, pin, &config, 1); +} +#ifdef CONFIG_DEBUG_FS +#include <linux/seq_file.h> + +static unsigned int lpi_regval_to_drive(u32 val) +{ + return (val + 1) * 2; +} + +static void lpi_gpio_dbg_show_one(struct seq_file *s, + struct pinctrl_dev *pctldev, + struct gpio_chip *chip, + unsigned int offset, + unsigned int gpio) +{ + struct lpi_pinctrl *state = gpiochip_get_data(chip); + struct pinctrl_pin_desc pindesc; + unsigned int func; + int is_out; + int drive; + int pull; + u32 ctl_reg; + + static const char * const pulls[] = { + "no pull", + "pull down", + "keeper", + "pull up" + }; + + pctldev = pctldev ? : state->ctrl; + pindesc = pctldev->desc->pins[offset]; + ctl_reg = lpi_gpio_read(state, offset, LPI_GPIO_REG_DIR_CTL); + is_out = (ctl_reg & LPI_GPIO_REG_DIR_MASK) >> LPI_GPIO_REG_DIR_SHIFT; + ctl_reg = lpi_gpio_read(state, offset, LPI_GPIO_REG_VAL_CTL); + + func = (ctl_reg & LPI_GPIO_REG_FUNCTION_MASK) >> + LPI_GPIO_REG_FUNCTION_SHIFT; + drive = (ctl_reg & LPI_GPIO_REG_OUT_STRENGTH_MASK) >> + LPI_GPIO_REG_OUT_STRENGTH_SHIFT; + pull = (ctl_reg & LPI_GPIO_REG_PULL_MASK) >> LPI_GPIO_REG_PULL_SHIFT; + + seq_printf(s, " %-8s: %-3s %d", + pindesc.name, is_out ? "out" : "in", func); + seq_printf(s, " %dmA", lpi_regval_to_drive(drive)); + seq_printf(s, " %s", pulls[pull]); +} + +static void lpi_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + unsigned int gpio = chip->base; + unsigned int i; + + for (i = 0; i < chip->ngpio; i++, gpio++) { + lpi_gpio_dbg_show_one(s, NULL, chip, i, gpio); + seq_puts(s, "\n"); + } +} + +#else +#define lpi_gpio_dbg_show NULL +#endif + +static const struct gpio_chip lpi_gpio_template = { + .direction_input = lpi_gpio_direction_input, + .direction_output = lpi_gpio_direction_output, + .get = lpi_gpio_get, + .set = lpi_gpio_set, + .request = gpiochip_generic_request, + .free = gpiochip_generic_free, + .dbg_show = lpi_gpio_dbg_show, +}; + +static int lpi_pinctrl_probe(struct platform_device *pdev) +{ + int ret, npins; + struct clk *core_vote = NULL; + struct clk *audio_vote = NULL; + + struct lpi_pinctrl *pctrl; + const struct lpi_pinctrl_variant_data *data; + struct device *dev = &pdev->dev; + struct resource *res; + + pctrl = devm_kzalloc(dev, sizeof(*pctrl), GFP_KERNEL); + if (!pctrl) + return -ENOMEM; + + platform_set_drvdata(pdev, pctrl); + + data = of_device_get_match_data(dev); + pctrl->data = data; + pctrl->dev = &pdev->dev; + npins = data->npins; + + /* Register LPASS core hw vote */ + core_vote = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(core_vote)) { + dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n", + __func__, "core_vote", ret); + return PTR_ERR(core_vote); + } + pctrl->core_vote = core_vote; + + /* Register LPASS audio hw vote */ + audio_vote = devm_clk_get(&pdev->dev, "audio"); + if (IS_ERR(audio_vote)) { + dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n", + __func__, "audio_vote", ret); + return PTR_ERR(audio_vote); + } + + pctrl->audio_vote = audio_vote; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pctrl->tlmm_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pctrl->tlmm_base)) + return PTR_ERR(pctrl->tlmm_base); + + clk_prepare_enable(pctrl->core_vote); + clk_prepare_enable(pctrl->audio_vote); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + pctrl->slew_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pctrl->slew_base)) + return PTR_ERR(pctrl->slew_base); + + pctrl->desc.pctlops = &lpi_gpio_pinctrl_ops; + pctrl->desc.pmxops = &lpi_gpio_pinmux_ops; + pctrl->desc.confops = &lpi_gpio_pinconf_ops; + + pctrl->desc.owner = THIS_MODULE; + pctrl->desc.name = dev_name(dev); + pctrl->desc.pins = data->pins; + pctrl->desc.npins = data->npins; + + pctrl->chip = lpi_gpio_template; + pctrl->chip.parent = dev; + pctrl->chip.base = -1; + pctrl->chip.ngpio = npins; + pctrl->chip.label = dev_name(dev); + pctrl->chip.of_gpio_n_cells = 2; + pctrl->chip.can_sleep = false; + + mutex_init(&pctrl->slew_access_lock); + + pctrl->ctrl = devm_pinctrl_register(dev, &pctrl->desc, pctrl); + if (IS_ERR(pctrl->ctrl)) + return PTR_ERR(pctrl->ctrl); + + ret = gpiochip_add_data(&pctrl->chip, pctrl); + if (ret) { + dev_err(pctrl->dev, "can't add gpio chip\n"); + goto err_chip; + } + + ret = gpiochip_add_pin_range(&pctrl->chip, dev_name(dev), 0, 0, npins); + if (ret) { + dev_err(dev, "failed to add pin range\n"); + goto err_range; + } + + return 0; + +err_range: + gpiochip_remove(&pctrl->chip); +err_chip: + mutex_destroy(&pctrl->slew_access_lock); + return ret; +} + +static int lpi_pinctrl_remove(struct platform_device *pdev) +{ + struct lpi_pinctrl *state = platform_get_drvdata(pdev); + + gpiochip_remove(&state->chip); + mutex_destroy(&state->slew_access_lock); + + return 0; +} + +static const struct of_device_id lpi_pinctrl_of_match[] = { + { + .compatible = "qcom,sm8250-lpass-lpi-pinctrl", + .data = &sm8250_lpi_data, + }, + { }, +}; + +MODULE_DEVICE_TABLE(of, lpi_pinctrl_of_match); + +static struct platform_driver lpi_pinctrl_driver = { + .driver = { + .name = "qcom-lpass-lpi-pinctrl", + .of_match_table = lpi_pinctrl_of_match, + }, + .probe = lpi_pinctrl_probe, + .remove = lpi_pinctrl_remove, +}; + +module_platform_driver(lpi_pinctrl_driver); + +MODULE_DESCRIPTION("QTI LPI GPIO pin control driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig index 8cd595148d17..060a2c65978a 100644 --- a/drivers/slimbus/Kconfig +++ b/drivers/slimbus/Kconfig @@ -25,6 +25,8 @@ config SLIM_QCOM_NGD_CTRL depends on HAS_IOMEM && DMA_ENGINE && NET depends on ARCH_QCOM || COMPILE_TEST select QCOM_QMI_HELPERS + select QCOM_RPROC_COMMON + select QCOM_PDR_HELPERS help Select driver if Qualcomm's SLIMbus Satellite Non-Generic Device Component is programmed using Linux kernel. diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 218aefc3531c..8c08074fef30 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -13,9 +13,13 @@ #include <linux/slimbus.h> #include <linux/delay.h> #include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/remoteproc/qcom_rproc.h> #include <linux/of.h> #include <linux/io.h> #include <linux/soc/qcom/qmi.h> +#include <linux/soc/qcom/pdr.h> #include <net/sock.h> #include "slimbus.h" @@ -155,8 +159,15 @@ struct qcom_slim_ngd_ctrl { struct qcom_slim_ngd_dma_desc txdesc[QCOM_SLIM_NGD_DESC_NUM]; struct completion reconf; struct work_struct m_work; + struct work_struct ngd_up_work; struct workqueue_struct *mwq; + struct completion qmi_up; spinlock_t tx_buf_lock; + struct mutex tx_lock; + struct mutex ssr_lock; + struct notifier_block nb; + void *notifier; + struct pdr_handle *pdr; enum qcom_slim_ngd_state state; dma_addr_t rx_phys_base; dma_addr_t tx_phys_base; @@ -868,14 +879,18 @@ static int qcom_slim_ngd_xfer_msg(struct slim_controller *sctrl, if (txn->msg && txn->msg->wbuf) memcpy(puc, txn->msg->wbuf, txn->msg->num_bytes); + mutex_lock(&ctrl->tx_lock); ret = qcom_slim_ngd_tx_msg_post(ctrl, pbuf, txn->rl); - if (ret) + if (ret) { + mutex_unlock(&ctrl->tx_lock); return ret; + } timeout = wait_for_completion_timeout(&tx_sent, HZ); if (!timeout) { dev_err(sctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, txn->mt); + mutex_unlock(&ctrl->tx_lock); return -ETIMEDOUT; } @@ -884,10 +899,12 @@ static int qcom_slim_ngd_xfer_msg(struct slim_controller *sctrl, if (!timeout) { dev_err(sctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, txn->mt); + mutex_unlock(&ctrl->tx_lock); return -ETIMEDOUT; } } + mutex_unlock(&ctrl->tx_lock); return 0; } @@ -1200,6 +1217,13 @@ capability_retry: } } +static int qcom_slim_ngd_update_device_status(struct device *dev, void *null) +{ + slim_report_absent(to_slim_device(dev)); + + return 0; +} + static int qcom_slim_ngd_runtime_resume(struct device *dev) { struct qcom_slim_ngd_ctrl *ctrl = dev_get_drvdata(dev); @@ -1267,7 +1291,7 @@ static int qcom_slim_ngd_qmi_new_server(struct qmi_handle *hdl, qmi->svc_info.sq_node = service->node; qmi->svc_info.sq_port = service->port; - qcom_slim_ngd_enable(ctrl, true); + complete(&ctrl->qmi_up); return 0; } @@ -1280,10 +1304,9 @@ static void qcom_slim_ngd_qmi_del_server(struct qmi_handle *hdl, struct qcom_slim_ngd_ctrl *ctrl = container_of(qmi, struct qcom_slim_ngd_ctrl, qmi); + reinit_completion(&ctrl->qmi_up); qmi->svc_info.sq_node = 0; qmi->svc_info.sq_port = 0; - - qcom_slim_ngd_enable(ctrl, false); } static struct qmi_ops qcom_slim_ngd_qmi_svc_event_ops = { @@ -1327,12 +1350,81 @@ static const struct of_device_id qcom_slim_ngd_dt_match[] = { },{ .compatible = "qcom,slim-ngd-v2.1.0", .data = &ngd_v1_5_offset_info, + },{ + .compatible = "qcom,slim-ngd-v2.2.0", + .data = &ngd_v1_5_offset_info, }, {} }; MODULE_DEVICE_TABLE(of, qcom_slim_ngd_dt_match); +static void qcom_slim_ngd_down(struct qcom_slim_ngd_ctrl *ctrl) +{ + mutex_lock(&ctrl->ssr_lock); + device_for_each_child(ctrl->ctrl.dev, NULL, + qcom_slim_ngd_update_device_status); + qcom_slim_ngd_enable(ctrl, false); + mutex_unlock(&ctrl->ssr_lock); +} + +static void qcom_slim_ngd_up_worker(struct work_struct *work) +{ + struct qcom_slim_ngd_ctrl *ctrl; + + ctrl = container_of(work, struct qcom_slim_ngd_ctrl, ngd_up_work); + + /* Make sure qmi service is up before continuing */ + wait_for_completion_interruptible(&ctrl->qmi_up); + + mutex_lock(&ctrl->ssr_lock); + qcom_slim_ngd_enable(ctrl, true); + mutex_unlock(&ctrl->ssr_lock); +} + +static int qcom_slim_ngd_ssr_pdr_notify(struct qcom_slim_ngd_ctrl *ctrl, + unsigned long action) +{ + switch (action) { + case QCOM_SSR_BEFORE_SHUTDOWN: + case SERVREG_SERVICE_STATE_DOWN: + /* Make sure the last dma xfer is finished */ + mutex_lock(&ctrl->tx_lock); + if (ctrl->state != QCOM_SLIM_NGD_CTRL_DOWN) { + pm_runtime_get_noresume(ctrl->dev); + ctrl->state = QCOM_SLIM_NGD_CTRL_DOWN; + qcom_slim_ngd_down(ctrl); + qcom_slim_ngd_exit_dma(ctrl); + } + mutex_unlock(&ctrl->tx_lock); + break; + case QCOM_SSR_AFTER_POWERUP: + case SERVREG_SERVICE_STATE_UP: + schedule_work(&ctrl->ngd_up_work); + break; + default: + break; + } + + return NOTIFY_OK; +} + +static int qcom_slim_ngd_ssr_notify(struct notifier_block *nb, + unsigned long action, + void *data) +{ + struct qcom_slim_ngd_ctrl *ctrl = container_of(nb, + struct qcom_slim_ngd_ctrl, nb); + + return qcom_slim_ngd_ssr_pdr_notify(ctrl, action); +} + +static void slim_pd_status(int state, char *svc_path, void *priv) +{ + struct qcom_slim_ngd_ctrl *ctrl = (struct qcom_slim_ngd_ctrl *)priv; + + qcom_slim_ngd_ssr_pdr_notify(ctrl, state); +} static int of_qcom_slim_ngd_register(struct device *parent, struct qcom_slim_ngd_ctrl *ctrl) { @@ -1397,6 +1489,7 @@ static int qcom_slim_ngd_probe(struct platform_device *pdev) } INIT_WORK(&ctrl->m_work, qcom_slim_ngd_master_worker); + INIT_WORK(&ctrl->ngd_up_work, qcom_slim_ngd_up_worker); ctrl->mwq = create_singlethread_workqueue("ngd_master"); if (!ctrl->mwq) { dev_err(&pdev->dev, "Failed to start master worker\n"); @@ -1419,6 +1512,7 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) struct qcom_slim_ngd_ctrl *ctrl; struct resource *res; int ret; + struct pdr_service *pds; ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); if (!ctrl) @@ -1444,6 +1538,11 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) return ret; } + ctrl->nb.notifier_call = qcom_slim_ngd_ssr_notify; + ctrl->notifier = qcom_register_ssr_notifier("lpass", &ctrl->nb); + if (IS_ERR(ctrl->notifier)) + return PTR_ERR(ctrl->notifier); + ctrl->dev = dev; ctrl->framer.rootfreq = SLIM_ROOT_FREQ >> 3; ctrl->framer.superfreq = @@ -1457,9 +1556,24 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) ctrl->ctrl.wakeup = NULL; ctrl->state = QCOM_SLIM_NGD_CTRL_DOWN; + mutex_init(&ctrl->tx_lock); + mutex_init(&ctrl->ssr_lock); spin_lock_init(&ctrl->tx_buf_lock); init_completion(&ctrl->reconf); init_completion(&ctrl->qmi.qmi_comp); + init_completion(&ctrl->qmi_up); + + ctrl->pdr = pdr_handle_alloc(slim_pd_status, ctrl); + if (IS_ERR(ctrl->pdr)) { + dev_err(dev, "Failed to init PDR handle\n"); + return PTR_ERR(ctrl->pdr); + } + + pds = pdr_add_lookup(ctrl->pdr, "avs/audio", "msm/adsp/audio_pd"); + if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) { + dev_err(dev, "pdr add lookup failed: %d\n", ret); + return PTR_ERR(pds); + } platform_driver_register(&qcom_slim_ngd_driver); return of_qcom_slim_ngd_register(dev, ctrl); @@ -1477,6 +1591,8 @@ static int qcom_slim_ngd_remove(struct platform_device *pdev) struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); + pdr_handle_release(ctrl->pdr); + qcom_unregister_ssr_notifier(ctrl->notifier, &ctrl->nb); qcom_slim_ngd_enable(ctrl, false); qcom_slim_ngd_exit_dma(ctrl); qcom_slim_ngd_qmi_svc_event_deinit(&ctrl->qmi); diff --git a/include/dt-bindings/clock/qcom,sm8250-lpass-aoncc.h b/include/dt-bindings/clock/qcom,sm8250-lpass-aoncc.h new file mode 100644 index 000000000000..f5a1cfac8612 --- /dev/null +++ b/include/dt-bindings/clock/qcom,sm8250-lpass-aoncc.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _DT_BINDINGS_CLK_LPASS_AONCC_SM8250_H +#define _DT_BINDINGS_CLK_LPASS_AONCC_SM8250_H + +/* from AOCC */ +#define LPASS_CDC_VA_MCLK 0 +#define LPASS_CDC_TX_NPL 1 +#define LPASS_CDC_TX_MCLK 2 + +#endif /* _DT_BINDINGS_CLK_LPASS_AONCC_SM8250_H */ diff --git a/include/dt-bindings/clock/qcom,sm8250-lpass-audiocc.h b/include/dt-bindings/clock/qcom,sm8250-lpass-audiocc.h new file mode 100644 index 000000000000..a1aa6cb5d840 --- /dev/null +++ b/include/dt-bindings/clock/qcom,sm8250-lpass-audiocc.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _DT_BINDINGS_CLK_LPASS_AUDIOCC_SM8250_H +#define _DT_BINDINGS_CLK_LPASS_AUDIOCC_SM8250_H + +/* From AudioCC */ +#define LPASS_CDC_WSA_NPL 0 +#define LPASS_CDC_WSA_MCLK 1 +#define LPASS_CDC_RX_MCLK 2 +#define LPASS_CDC_RX_NPL 3 +#define LPASS_CDC_RX_MCLK_MCLK2 4 + +#endif /* _DT_BINDINGS_CLK_LPASS_AUDIOCC_SM8250_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 34c6dd04b85a..4e45fb5e9817 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1784,4 +1784,12 @@ config SND_SOC_TPA6130A2 tristate "Texas Instruments TPA6130A2 headphone amplifier" depends on I2C +config SND_SOC_LPASS_WSA_MACRO + depends on COMMON_CLK + tristate "Qualcomm WSA Macro in LPASS(Low Power Audio SubSystem)" + +config SND_SOC_LPASS_VA_MACRO + depends on COMMON_CLK + tristate "Qualcomm VA Macro in LPASS(Low Power Audio SubSystem)" + endmenu diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 11ce98c25d6c..5d68da2b1182 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -103,6 +103,8 @@ snd-soc-l3-objs := l3.o snd-soc-lm4857-objs := lm4857.o snd-soc-lm49453-objs := lm49453.o snd-soc-lochnagar-sc-objs := lochnagar-sc.o +snd-soc-lpass-wsa-macro-objs := lpass-wsa-macro.o +snd-soc-lpass-va-macro-objs := lpass-va-macro.o snd-soc-madera-objs := madera.o snd-soc-max9759-objs := max9759.o snd-soc-max9768-objs := max9768.o @@ -613,3 +615,5 @@ obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_SIMPLE_AMPLIFIER) += snd-soc-simple-amplifier.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o +obj-$(CONFIG_SND_SOC_LPASS_WSA_MACRO) += snd-soc-lpass-wsa-macro.o +obj-$(CONFIG_SND_SOC_LPASS_VA_MACRO) += snd-soc-lpass-va-macro.o diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c new file mode 100644 index 000000000000..35ce4f136d38 --- /dev/null +++ b/sound/soc/codecs/lpass-va-macro.c @@ -0,0 +1,1501 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_clk.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +/* VA macro registers */ +#define CDC_VA_CLK_RST_CTRL_MCLK_CONTROL (0x0000) +#define CDC_VA_MCLK_CONTROL_EN BIT(0) +#define CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL (0x0004) +#define CDC_VA_FS_CONTROL_EN BIT(0) +#define CDC_VA_CLK_RST_CTRL_SWR_CONTROL (0x0008) +#define CDC_VA_TOP_CSR_TOP_CFG0 (0x0080) +#define CDC_VA_FS_BROADCAST_EN BIT(1) +#define CDC_VA_TOP_CSR_DMIC0_CTL (0x0084) +#define CDC_VA_TOP_CSR_DMIC1_CTL (0x0088) +#define CDC_VA_TOP_CSR_DMIC2_CTL (0x008C) +#define CDC_VA_TOP_CSR_DMIC3_CTL (0x0090) +#define CDC_VA_DMIC_CLK_SEL_MASK GENMASK(3, 1) +#define CDC_VA_DMIC_CLK_SEL_SHFT 1 +#define CDC_VA_DMIC_CLK_SEL_DIV0 0x0 +#define CDC_VA_DMIC_CLK_SEL_DIV1 0x2 +#define CDC_VA_DMIC_CLK_SEL_DIV2 0x4 +#define CDC_VA_DMIC_CLK_SEL_DIV3 0x6 +#define CDC_VA_DMIC_CLK_SEL_DIV4 0x8 +#define CDC_VA_DMIC_CLK_SEL_DIV5 0xa +#define CDC_VA_TOP_CSR_DMIC_CFG (0x0094) +#define CDC_VA_RESET_ALL_DMICS_MASK BIT(7) +#define CDC_VA_RESET_ALL_DMICS_RESET BIT(7) +#define CDC_VA_RESET_ALL_DMICS_DISABLE 0 +#define CDC_VA_DMIC3_FREQ_CHANGE_MASK BIT(3) +#define CDC_VA_DMIC3_FREQ_CHANGE_EN BIT(3) +#define CDC_VA_DMIC2_FREQ_CHANGE_MASK BIT(2) +#define CDC_VA_DMIC2_FREQ_CHANGE_EN BIT(2) +#define CDC_VA_DMIC1_FREQ_CHANGE_MASK BIT(1) +#define CDC_VA_DMIC1_FREQ_CHANGE_EN BIT(1) +#define CDC_VA_DMIC0_FREQ_CHANGE_MASK BIT(0) +#define CDC_VA_DMIC0_FREQ_CHANGE_EN BIT(0) +#define CDC_VA_DMIC_FREQ_CHANGE_DISABLE 0 +#define CDC_VA_TOP_CSR_DEBUG_BUS (0x009C) +#define CDC_VA_TOP_CSR_DEBUG_EN (0x00A0) +#define CDC_VA_TOP_CSR_TX_I2S_CTL (0x00A4) +#define CDC_VA_TOP_CSR_I2S_CLK (0x00A8) +#define CDC_VA_TOP_CSR_I2S_RESET (0x00AC) +#define CDC_VA_TOP_CSR_CORE_ID_0 (0x00C0) +#define CDC_VA_TOP_CSR_CORE_ID_1 (0x00C4) +#define CDC_VA_TOP_CSR_CORE_ID_2 (0x00C8) +#define CDC_VA_TOP_CSR_CORE_ID_3 (0x00CC) +#define CDC_VA_TOP_CSR_SWR_MIC_CTL0 (0x00D0) +#define CDC_VA_TOP_CSR_SWR_MIC_CTL1 (0x00D4) +#define CDC_VA_TOP_CSR_SWR_MIC_CTL2 (0x00D8) +#define CDC_VA_TOP_CSR_SWR_CTRL (0x00DC) +#define CDC_VA_INP_MUX_ADC_MUX0_CFG0 (0x0100) +#define CDC_VA_INP_MUX_ADC_MUX0_CFG1 (0x0104) +#define CDC_VA_INP_MUX_ADC_MUX1_CFG0 (0x0108) +#define CDC_VA_INP_MUX_ADC_MUX1_CFG1 (0x010C) +#define CDC_VA_INP_MUX_ADC_MUX2_CFG0 (0x0110) +#define CDC_VA_INP_MUX_ADC_MUX2_CFG1 (0x0114) +#define CDC_VA_INP_MUX_ADC_MUX3_CFG0 (0x0118) +#define CDC_VA_INP_MUX_ADC_MUX3_CFG1 (0x011C) +#define CDC_VA_TX0_TX_PATH_CTL (0x0400) +#define CDC_VA_TX_PATH_CLK_EN_MASK BIT(5) +#define CDC_VA_TX_PATH_CLK_EN BIT(5) +#define CDC_VA_TX_PATH_CLK_DISABLE 0 +#define CDC_VA_TX_PATH_PGA_MUTE_EN_MASK BIT(4) +#define CDC_VA_TX_PATH_PGA_MUTE_EN BIT(4) +#define CDC_VA_TX_PATH_PGA_MUTE_DISABLE 0 +#define CDC_VA_TX0_TX_PATH_CFG0 (0x0404) +#define CDC_VA_ADC_MODE_MASK GENMASK(2, 1) +#define CDC_VA_ADC_MODE_SHIFT 1 +#define TX_HPF_CUT_OFF_FREQ_MASK GENMASK(6, 5) +#define CF_MIN_3DB_4HZ 0x0 +#define CF_MIN_3DB_75HZ 0x1 +#define CF_MIN_3DB_150HZ 0x2 +#define CDC_VA_TX0_TX_PATH_CFG1 (0x0408) +#define CDC_VA_TX0_TX_VOL_CTL (0x040C) +#define CDC_VA_TX0_TX_PATH_SEC0 (0x0410) +#define CDC_VA_TX0_TX_PATH_SEC1 (0x0414) +#define CDC_VA_TX0_TX_PATH_SEC2 (0x0418) +#define CDC_VA_TX_HPF_CUTOFF_FREQ_CHANGE_MASK BIT(1) +#define CDC_VA_TX_HPF_CUTOFF_FREQ_CHANGE_REQ BIT(1) +#define CDC_VA_TX_HPF_ZERO_GATE_MASK BIT(0) +#define CDC_VA_TX_HPF_ZERO_NO_GATE BIT(0) +#define CDC_VA_TX_HPF_ZERO_GATE 0 +#define CDC_VA_TX0_TX_PATH_SEC3 (0x041C) +#define CDC_VA_TX0_TX_PATH_SEC4 (0x0420) +#define CDC_VA_TX0_TX_PATH_SEC5 (0x0424) +#define CDC_VA_TX0_TX_PATH_SEC6 (0x0428) +#define CDC_VA_TX0_TX_PATH_SEC7 (0x042C) +#define CDC_VA_TX1_TX_PATH_CTL (0x0480) +#define CDC_VA_TX1_TX_PATH_CFG0 (0x0484) +#define CDC_VA_TX1_TX_PATH_CFG1 (0x0488) +#define CDC_VA_TX1_TX_VOL_CTL (0x048C) +#define CDC_VA_TX1_TX_PATH_SEC0 (0x0490) +#define CDC_VA_TX1_TX_PATH_SEC1 (0x0494) +#define CDC_VA_TX1_TX_PATH_SEC2 (0x0498) +#define CDC_VA_TX1_TX_PATH_SEC3 (0x049C) +#define CDC_VA_TX1_TX_PATH_SEC4 (0x04A0) +#define CDC_VA_TX1_TX_PATH_SEC5 (0x04A4) +#define CDC_VA_TX1_TX_PATH_SEC6 (0x04A8) +#define CDC_VA_TX2_TX_PATH_CTL (0x0500) +#define CDC_VA_TX2_TX_PATH_CFG0 (0x0504) +#define CDC_VA_TX2_TX_PATH_CFG1 (0x0508) +#define CDC_VA_TX2_TX_VOL_CTL (0x050C) +#define CDC_VA_TX2_TX_PATH_SEC0 (0x0510) +#define CDC_VA_TX2_TX_PATH_SEC1 (0x0514) +#define CDC_VA_TX2_TX_PATH_SEC2 (0x0518) +#define CDC_VA_TX2_TX_PATH_SEC3 (0x051C) +#define CDC_VA_TX2_TX_PATH_SEC4 (0x0520) +#define CDC_VA_TX2_TX_PATH_SEC5 (0x0524) +#define CDC_VA_TX2_TX_PATH_SEC6 (0x0528) +#define CDC_VA_TX3_TX_PATH_CTL (0x0580) +#define CDC_VA_TX3_TX_PATH_CFG0 (0x0584) +#define CDC_VA_TX_PATH_ADC_DMIC_SEL_MASK BIT(7) +#define CDC_VA_TX_PATH_ADC_DMIC_SEL_DMIC BIT(7) +#define CDC_VA_TX_PATH_ADC_DMIC_SEL_ADC 0 +#define CDC_VA_TX3_TX_PATH_CFG1 (0x0588) +#define CDC_VA_TX3_TX_VOL_CTL (0x058C) +#define CDC_VA_TX3_TX_PATH_SEC0 (0x0590) +#define CDC_VA_TX3_TX_PATH_SEC1 (0x0594) +#define CDC_VA_TX3_TX_PATH_SEC2 (0x0598) +#define CDC_VA_TX3_TX_PATH_SEC3 (0x059C) +#define CDC_VA_TX3_TX_PATH_SEC4 (0x05A0) +#define CDC_VA_TX3_TX_PATH_SEC5 (0x05A4) +#define CDC_VA_TX3_TX_PATH_SEC6 (0x05A8) + +#define VA_MAX_OFFSET (0x07A8) + +#define VA_MACRO_NUM_DECIMATORS 4 +#define VA_MACRO_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) +#define VA_MACRO_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S24_3LE) + +#define VA_MACRO_MCLK_FREQ 9600000 +#define VA_MACRO_TX_PATH_OFFSET 0x80 +#define VA_MACRO_SWR_MIC_MUX_SEL_MASK 0xF +#define VA_MACRO_ADC_MUX_CFG_OFFSET 0x8 + +static const DECLARE_TLV_DB_SCALE(digital_gain, -8400, 100, -8400); + +enum { + VA_MACRO_AIF_INVALID = 0, + VA_MACRO_AIF1_CAP, + VA_MACRO_AIF2_CAP, + VA_MACRO_AIF3_CAP, + VA_MACRO_MAX_DAIS, +}; + +enum { + VA_MACRO_DEC0, + VA_MACRO_DEC1, + VA_MACRO_DEC2, + VA_MACRO_DEC3, + VA_MACRO_DEC4, + VA_MACRO_DEC5, + VA_MACRO_DEC6, + VA_MACRO_DEC7, + VA_MACRO_DEC_MAX, +}; + +enum { + VA_MACRO_CLK_DIV_2, + VA_MACRO_CLK_DIV_3, + VA_MACRO_CLK_DIV_4, + VA_MACRO_CLK_DIV_6, + VA_MACRO_CLK_DIV_8, + VA_MACRO_CLK_DIV_16, +}; + +#define VA_NUM_CLKS_MAX 3 + +struct va_macro { + struct device *dev; + unsigned long active_ch_mask[VA_MACRO_MAX_DAIS]; + unsigned long active_ch_cnt[VA_MACRO_MAX_DAIS]; + unsigned long active_decimator[VA_MACRO_MAX_DAIS]; + u16 dmic_clk_div; + + int dec_mode[VA_MACRO_NUM_DECIMATORS]; + struct regmap *regmap; + struct clk_bulk_data clks[VA_NUM_CLKS_MAX]; + struct clk_hw hw; + + s32 dmic_0_1_clk_cnt; + s32 dmic_2_3_clk_cnt; + s32 dmic_4_5_clk_cnt; + s32 dmic_6_7_clk_cnt; + u8 dmic_0_1_clk_div; + u8 dmic_2_3_clk_div; + u8 dmic_4_5_clk_div; + u8 dmic_6_7_clk_div; +}; + +#define to_va_macro(_hw) container_of(_hw, struct va_macro, hw) + +static bool va_is_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CDC_VA_TOP_CSR_CORE_ID_0: + case CDC_VA_TOP_CSR_CORE_ID_1: + case CDC_VA_TOP_CSR_CORE_ID_2: + case CDC_VA_TOP_CSR_CORE_ID_3: + case CDC_VA_TOP_CSR_DMIC0_CTL: + case CDC_VA_TOP_CSR_DMIC1_CTL: + case CDC_VA_TOP_CSR_DMIC2_CTL: + case CDC_VA_TOP_CSR_DMIC3_CTL: + return true; + } + return false; +} + +static const struct reg_default va_defaults[] = { + /* VA macro */ + { CDC_VA_CLK_RST_CTRL_MCLK_CONTROL, 0x00}, + { CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL, 0x00}, + { CDC_VA_CLK_RST_CTRL_SWR_CONTROL, 0x00}, + { CDC_VA_TOP_CSR_TOP_CFG0, 0x00}, + { CDC_VA_TOP_CSR_DMIC0_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC1_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC2_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC3_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC_CFG, 0x80}, + { CDC_VA_TOP_CSR_DEBUG_BUS, 0x00}, + { CDC_VA_TOP_CSR_DEBUG_EN, 0x00}, + { CDC_VA_TOP_CSR_TX_I2S_CTL, 0x0C}, + { CDC_VA_TOP_CSR_I2S_CLK, 0x00}, + { CDC_VA_TOP_CSR_I2S_RESET, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_0, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_1, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_2, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_3, 0x00}, + { CDC_VA_TOP_CSR_SWR_MIC_CTL0, 0xEE}, + { CDC_VA_TOP_CSR_SWR_MIC_CTL1, 0xEE}, + { CDC_VA_TOP_CSR_SWR_MIC_CTL2, 0xEE}, + { CDC_VA_TOP_CSR_SWR_CTRL, 0x06}, + + /* VA core */ + { CDC_VA_INP_MUX_ADC_MUX0_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX0_CFG1, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX1_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX1_CFG1, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX2_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX2_CFG1, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX3_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX3_CFG1, 0x00}, + { CDC_VA_TX0_TX_PATH_CTL, 0x04}, + { CDC_VA_TX0_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX0_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX0_TX_VOL_CTL, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX0_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX0_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX0_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC6, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC7, 0x25}, + { CDC_VA_TX1_TX_PATH_CTL, 0x04}, + { CDC_VA_TX1_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX1_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX1_TX_VOL_CTL, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX1_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX1_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX1_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC6, 0x00}, + { CDC_VA_TX2_TX_PATH_CTL, 0x04}, + { CDC_VA_TX2_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX2_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX2_TX_VOL_CTL, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX2_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX2_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX2_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC6, 0x00}, + { CDC_VA_TX3_TX_PATH_CTL, 0x04}, + { CDC_VA_TX3_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX3_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX3_TX_VOL_CTL, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX3_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX3_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX3_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC6, 0x00}, +}; + +static bool va_is_rw_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CDC_VA_CLK_RST_CTRL_MCLK_CONTROL: + case CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL: + case CDC_VA_CLK_RST_CTRL_SWR_CONTROL: + case CDC_VA_TOP_CSR_TOP_CFG0: + case CDC_VA_TOP_CSR_DMIC0_CTL: + case CDC_VA_TOP_CSR_DMIC1_CTL: + case CDC_VA_TOP_CSR_DMIC2_CTL: + case CDC_VA_TOP_CSR_DMIC3_CTL: + case CDC_VA_TOP_CSR_DMIC_CFG: + case CDC_VA_TOP_CSR_DEBUG_BUS: + case CDC_VA_TOP_CSR_DEBUG_EN: + case CDC_VA_TOP_CSR_TX_I2S_CTL: + case CDC_VA_TOP_CSR_I2S_CLK: + case CDC_VA_TOP_CSR_I2S_RESET: + case CDC_VA_INP_MUX_ADC_MUX0_CFG0: + case CDC_VA_INP_MUX_ADC_MUX0_CFG1: + case CDC_VA_INP_MUX_ADC_MUX1_CFG0: + case CDC_VA_INP_MUX_ADC_MUX1_CFG1: + case CDC_VA_INP_MUX_ADC_MUX2_CFG0: + case CDC_VA_INP_MUX_ADC_MUX2_CFG1: + case CDC_VA_INP_MUX_ADC_MUX3_CFG0: + case CDC_VA_INP_MUX_ADC_MUX3_CFG1: + case CDC_VA_TX0_TX_PATH_CTL: + case CDC_VA_TX0_TX_PATH_CFG0: + case CDC_VA_TX0_TX_PATH_CFG1: + case CDC_VA_TX0_TX_VOL_CTL: + case CDC_VA_TX0_TX_PATH_SEC0: + case CDC_VA_TX0_TX_PATH_SEC1: + case CDC_VA_TX0_TX_PATH_SEC2: + case CDC_VA_TX0_TX_PATH_SEC3: + case CDC_VA_TX0_TX_PATH_SEC4: + case CDC_VA_TX0_TX_PATH_SEC5: + case CDC_VA_TX0_TX_PATH_SEC6: + case CDC_VA_TX0_TX_PATH_SEC7: + case CDC_VA_TX1_TX_PATH_CTL: + case CDC_VA_TX1_TX_PATH_CFG0: + case CDC_VA_TX1_TX_PATH_CFG1: + case CDC_VA_TX1_TX_VOL_CTL: + case CDC_VA_TX1_TX_PATH_SEC0: + case CDC_VA_TX1_TX_PATH_SEC1: + case CDC_VA_TX1_TX_PATH_SEC2: + case CDC_VA_TX1_TX_PATH_SEC3: + case CDC_VA_TX1_TX_PATH_SEC4: + case CDC_VA_TX1_TX_PATH_SEC5: + case CDC_VA_TX1_TX_PATH_SEC6: + case CDC_VA_TX2_TX_PATH_CTL: + case CDC_VA_TX2_TX_PATH_CFG0: + case CDC_VA_TX2_TX_PATH_CFG1: + case CDC_VA_TX2_TX_VOL_CTL: + case CDC_VA_TX2_TX_PATH_SEC0: + case CDC_VA_TX2_TX_PATH_SEC1: + case CDC_VA_TX2_TX_PATH_SEC2: + case CDC_VA_TX2_TX_PATH_SEC3: + case CDC_VA_TX2_TX_PATH_SEC4: + case CDC_VA_TX2_TX_PATH_SEC5: + case CDC_VA_TX2_TX_PATH_SEC6: + case CDC_VA_TX3_TX_PATH_CTL: + case CDC_VA_TX3_TX_PATH_CFG0: + case CDC_VA_TX3_TX_PATH_CFG1: + case CDC_VA_TX3_TX_VOL_CTL: + case CDC_VA_TX3_TX_PATH_SEC0: + case CDC_VA_TX3_TX_PATH_SEC1: + case CDC_VA_TX3_TX_PATH_SEC2: + case CDC_VA_TX3_TX_PATH_SEC3: + case CDC_VA_TX3_TX_PATH_SEC4: + case CDC_VA_TX3_TX_PATH_SEC5: + case CDC_VA_TX3_TX_PATH_SEC6: + return true; + } + + return false; +} + +static bool va_is_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CDC_VA_TOP_CSR_CORE_ID_0: + case CDC_VA_TOP_CSR_CORE_ID_1: + case CDC_VA_TOP_CSR_CORE_ID_2: + case CDC_VA_TOP_CSR_CORE_ID_3: + return true; + } + + return va_is_rw_register(dev, reg); +} + +static const struct regmap_config va_regmap_config = { + .name = "va_macro", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .cache_type = REGCACHE_FLAT, + .reg_defaults = va_defaults, + .num_reg_defaults = ARRAY_SIZE(va_defaults), + .max_register = VA_MAX_OFFSET, + .volatile_reg = va_is_volatile_register, + .readable_reg = va_is_readable_register, + .writeable_reg = va_is_rw_register, +}; + +static int va_clk_rsc_fs_gen_request(struct va_macro *va, bool enable) +{ + struct regmap *regmap = va->regmap; + + if (enable) { + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_MCLK_CONTROL, + CDC_VA_MCLK_CONTROL_EN, + CDC_VA_MCLK_CONTROL_EN); + + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL, + CDC_VA_FS_CONTROL_EN, + CDC_VA_FS_CONTROL_EN); + + regmap_update_bits(regmap, CDC_VA_TOP_CSR_TOP_CFG0, + CDC_VA_FS_BROADCAST_EN, + CDC_VA_FS_BROADCAST_EN); + } else { + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_MCLK_CONTROL, + CDC_VA_MCLK_CONTROL_EN, 0x0); + + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL, + CDC_VA_FS_CONTROL_EN, 0x0); + + regmap_update_bits(regmap, CDC_VA_TOP_CSR_TOP_CFG0, + CDC_VA_FS_BROADCAST_EN, 0x0); + } + + return 0; +} + +static int va_macro_mclk_enable(struct va_macro *va, bool mclk_enable) +{ + struct regmap *regmap = va->regmap; + + if (mclk_enable) { + va_clk_rsc_fs_gen_request(va, true); + regcache_mark_dirty(regmap); + regcache_sync_region(regmap, 0x0, VA_MAX_OFFSET); + } else { + va_clk_rsc_fs_gen_request(va, false); + } + + return 0; +} + +static int va_macro_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm); + struct va_macro *va = snd_soc_component_get_drvdata(comp); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return va_macro_mclk_enable(va, true); + case SND_SOC_DAPM_POST_PMD: + return va_macro_mclk_enable(va, false); + } + + return 0; +} + +static int va_macro_put_dec_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = + snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *component = + snd_soc_dapm_to_component(widget->dapm); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val; + u16 mic_sel_reg; + + val = ucontrol->value.enumerated.item[0]; + + switch (e->reg) { + case CDC_VA_INP_MUX_ADC_MUX0_CFG0: + mic_sel_reg = CDC_VA_TX0_TX_PATH_CFG0; + break; + case CDC_VA_INP_MUX_ADC_MUX1_CFG0: + mic_sel_reg = CDC_VA_TX1_TX_PATH_CFG0; + break; + case CDC_VA_INP_MUX_ADC_MUX2_CFG0: + mic_sel_reg = CDC_VA_TX2_TX_PATH_CFG0; + break; + case CDC_VA_INP_MUX_ADC_MUX3_CFG0: + mic_sel_reg = CDC_VA_TX3_TX_PATH_CFG0; + break; + default: + dev_err(component->dev, "%s: e->reg: 0x%x not expected\n", + __func__, e->reg); + return -EINVAL; + } + + if (val != 0) + snd_soc_component_update_bits(component, mic_sel_reg, + CDC_VA_TX_PATH_ADC_DMIC_SEL_MASK, + CDC_VA_TX_PATH_ADC_DMIC_SEL_DMIC); + + return snd_soc_dapm_put_enum_double(kcontrol, ucontrol); +} + +static int va_macro_tx_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = + snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *component = + snd_soc_dapm_to_component(widget->dapm); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 dai_id = widget->shift; + u32 dec_id = mc->shift; + struct va_macro *va = snd_soc_component_get_drvdata(component); + + if (test_bit(dec_id, &va->active_ch_mask[dai_id])) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int va_macro_tx_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = + snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *component = + snd_soc_dapm_to_component(widget->dapm); + struct snd_soc_dapm_update *update = NULL; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 dai_id = widget->shift; + u32 dec_id = mc->shift; + u32 enable = ucontrol->value.integer.value[0]; + struct va_macro *va = snd_soc_component_get_drvdata(component); + + if (enable) { + set_bit(dec_id, &va->active_ch_mask[dai_id]); + va->active_ch_cnt[dai_id]++; + va->active_decimator[dai_id] = dec_id; + } else { + clear_bit(dec_id, &va->active_ch_mask[dai_id]); + va->active_ch_cnt[dai_id]--; + va->active_decimator[dai_id] = -1; + } + + snd_soc_dapm_mixer_update_power(widget->dapm, kcontrol, enable, update); + + return 0; +} + +static int va_dmic_clk_enable(struct snd_soc_component *component, + u32 dmic, bool enable) +{ + struct va_macro *va = snd_soc_component_get_drvdata(component); + u8 dmic_clk_en = 0x01; + u16 dmic_clk_reg; + s32 *dmic_clk_cnt; + u8 *dmic_clk_div; + u8 freq_change_mask; + u8 clk_div = 0; + + switch (dmic) { + case 0: + case 1: + dmic_clk_cnt = &(va->dmic_0_1_clk_cnt); + dmic_clk_div = &(va->dmic_0_1_clk_div); + dmic_clk_reg = CDC_VA_TOP_CSR_DMIC0_CTL; + freq_change_mask = CDC_VA_DMIC0_FREQ_CHANGE_MASK; + break; + case 2: + case 3: + dmic_clk_cnt = &(va->dmic_2_3_clk_cnt); + dmic_clk_div = &(va->dmic_2_3_clk_div); + dmic_clk_reg = CDC_VA_TOP_CSR_DMIC1_CTL; + freq_change_mask = CDC_VA_DMIC1_FREQ_CHANGE_MASK; + break; + case 4: + case 5: + dmic_clk_cnt = &(va->dmic_4_5_clk_cnt); + dmic_clk_div = &(va->dmic_4_5_clk_div); + dmic_clk_reg = CDC_VA_TOP_CSR_DMIC2_CTL; + freq_change_mask = CDC_VA_DMIC2_FREQ_CHANGE_MASK; + break; + case 6: + case 7: + dmic_clk_cnt = &(va->dmic_6_7_clk_cnt); + dmic_clk_div = &(va->dmic_6_7_clk_div); + dmic_clk_reg = CDC_VA_TOP_CSR_DMIC3_CTL; + freq_change_mask = CDC_VA_DMIC3_FREQ_CHANGE_MASK; + break; + default: + dev_err(component->dev, "%s: Invalid DMIC Selection\n", + __func__); + return -EINVAL; + } + + if (enable) { + clk_div = va->dmic_clk_div; + (*dmic_clk_cnt)++; + if (*dmic_clk_cnt == 1) { + snd_soc_component_update_bits(component, + CDC_VA_TOP_CSR_DMIC_CFG, + CDC_VA_RESET_ALL_DMICS_MASK, + CDC_VA_RESET_ALL_DMICS_DISABLE); + snd_soc_component_update_bits(component, dmic_clk_reg, + CDC_VA_DMIC_CLK_SEL_MASK, + clk_div << CDC_VA_DMIC_CLK_SEL_SHFT); + snd_soc_component_update_bits(component, dmic_clk_reg, + dmic_clk_en, dmic_clk_en); + } else { + if (*dmic_clk_div > clk_div) { + snd_soc_component_update_bits(component, + CDC_VA_TOP_CSR_DMIC_CFG, + freq_change_mask, + freq_change_mask); + snd_soc_component_update_bits(component, dmic_clk_reg, + CDC_VA_DMIC_CLK_SEL_MASK, + clk_div << CDC_VA_DMIC_CLK_SEL_SHFT); + snd_soc_component_update_bits(component, + CDC_VA_TOP_CSR_DMIC_CFG, + freq_change_mask, + CDC_VA_DMIC_FREQ_CHANGE_DISABLE); + } else { + clk_div = *dmic_clk_div; + } + } + *dmic_clk_div = clk_div; + } else { + (*dmic_clk_cnt)--; + if (*dmic_clk_cnt == 0) { + snd_soc_component_update_bits(component, dmic_clk_reg, + dmic_clk_en, 0); + clk_div = 0; + snd_soc_component_update_bits(component, dmic_clk_reg, + CDC_VA_DMIC_CLK_SEL_MASK, + clk_div << CDC_VA_DMIC_CLK_SEL_SHFT); + } else { + clk_div = va->dmic_clk_div; + if (*dmic_clk_div > clk_div) { + clk_div = va->dmic_clk_div; + snd_soc_component_update_bits(component, + CDC_VA_TOP_CSR_DMIC_CFG, + freq_change_mask, + freq_change_mask); + snd_soc_component_update_bits(component, dmic_clk_reg, + CDC_VA_DMIC_CLK_SEL_MASK, + clk_div << CDC_VA_DMIC_CLK_SEL_SHFT); + snd_soc_component_update_bits(component, + CDC_VA_TOP_CSR_DMIC_CFG, + freq_change_mask, + CDC_VA_DMIC_FREQ_CHANGE_DISABLE); + } else { + clk_div = *dmic_clk_div; + } + } + *dmic_clk_div = clk_div; + } + + return 0; +} + +static int va_macro_enable_dmic(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm); + unsigned int dmic = w->shift; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + va_dmic_clk_enable(comp, dmic, true); + break; + case SND_SOC_DAPM_POST_PMD: + va_dmic_clk_enable(comp, dmic, false); + break; + } + + return 0; +} + +static int va_macro_enable_dec(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm); + unsigned int decimator; + u16 tx_vol_ctl_reg, dec_cfg_reg, hpf_gate_reg; + u16 tx_gain_ctl_reg; + u8 hpf_cut_off_freq; + + struct va_macro *va = snd_soc_component_get_drvdata(comp); + + decimator = w->shift; + + tx_vol_ctl_reg = CDC_VA_TX0_TX_PATH_CTL + + VA_MACRO_TX_PATH_OFFSET * decimator; + hpf_gate_reg = CDC_VA_TX0_TX_PATH_SEC2 + + VA_MACRO_TX_PATH_OFFSET * decimator; + dec_cfg_reg = CDC_VA_TX0_TX_PATH_CFG0 + + VA_MACRO_TX_PATH_OFFSET * decimator; + tx_gain_ctl_reg = CDC_VA_TX0_TX_VOL_CTL + + VA_MACRO_TX_PATH_OFFSET * decimator; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(comp, + dec_cfg_reg, CDC_VA_ADC_MODE_MASK, + va->dec_mode[decimator] << CDC_VA_ADC_MODE_SHIFT); + /* Enable TX PGA Mute */ + break; + case SND_SOC_DAPM_POST_PMU: + /* Enable TX CLK */ + snd_soc_component_update_bits(comp, tx_vol_ctl_reg, + CDC_VA_TX_PATH_CLK_EN_MASK, + CDC_VA_TX_PATH_CLK_EN); + snd_soc_component_update_bits(comp, hpf_gate_reg, + CDC_VA_TX_HPF_ZERO_GATE_MASK, + CDC_VA_TX_HPF_ZERO_GATE); + + usleep_range(1000, 1010); + hpf_cut_off_freq = (snd_soc_component_read(comp, dec_cfg_reg) & + TX_HPF_CUT_OFF_FREQ_MASK) >> 5; + + if (hpf_cut_off_freq != CF_MIN_3DB_150HZ) { + snd_soc_component_update_bits(comp, dec_cfg_reg, + TX_HPF_CUT_OFF_FREQ_MASK, + CF_MIN_3DB_150HZ << 5); + + snd_soc_component_update_bits(comp, hpf_gate_reg, + CDC_VA_TX_HPF_CUTOFF_FREQ_CHANGE_MASK, + CDC_VA_TX_HPF_CUTOFF_FREQ_CHANGE_REQ); + + /* + * Minimum 1 clk cycle delay is required as per HW spec + */ + usleep_range(1000, 1010); + + snd_soc_component_update_bits(comp, + hpf_gate_reg, + CDC_VA_TX_HPF_CUTOFF_FREQ_CHANGE_MASK, + 0x0); + } + + + usleep_range(1000, 1010); + snd_soc_component_update_bits(comp, hpf_gate_reg, + CDC_VA_TX_HPF_ZERO_GATE_MASK, + CDC_VA_TX_HPF_ZERO_NO_GATE); + /* + * 6ms delay is required as per HW spec + */ + usleep_range(6000, 6010); + /* apply gain after decimator is enabled */ + snd_soc_component_write(comp, tx_gain_ctl_reg, + snd_soc_component_read(comp, tx_gain_ctl_reg)); + break; + case SND_SOC_DAPM_POST_PMD: + /* Disable TX CLK */ + snd_soc_component_update_bits(comp, tx_vol_ctl_reg, + CDC_VA_TX_PATH_CLK_EN_MASK, + CDC_VA_TX_PATH_CLK_DISABLE); + break; + } + return 0; +} + +static int va_macro_dec_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct va_macro *va = snd_soc_component_get_drvdata(comp); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int path = e->shift_l; + + ucontrol->value.integer.value[0] = va->dec_mode[path]; + + return 0; +} + +static int va_macro_dec_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + int value = ucontrol->value.integer.value[0]; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int path = e->shift_l; + struct va_macro *va = snd_soc_component_get_drvdata(comp); + + va->dec_mode[path] = value; + + return 0; +} + +static int va_macro_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int tx_fs_rate = -EINVAL; + struct snd_soc_component *component = dai->component; + u32 decimator, sample_rate; + u16 tx_fs_reg = 0; + struct device *va_dev = component->dev; + struct va_macro *va = snd_soc_component_get_drvdata(component); + + sample_rate = params_rate(params); + switch (sample_rate) { + case 8000: + tx_fs_rate = 0; + break; + case 16000: + tx_fs_rate = 1; + break; + case 32000: + tx_fs_rate = 3; + break; + case 48000: + tx_fs_rate = 4; + break; + case 96000: + tx_fs_rate = 5; + break; + case 192000: + tx_fs_rate = 6; + break; + case 384000: + tx_fs_rate = 7; + break; + default: + dev_err(va_dev, "%s: Invalid TX sample rate: %d\n", + __func__, params_rate(params)); + return -EINVAL; + } + + for_each_set_bit(decimator, &va->active_ch_mask[dai->id], + VA_MACRO_DEC_MAX) { + if (decimator >= 0) { + tx_fs_reg = CDC_VA_TX0_TX_PATH_CTL + + VA_MACRO_TX_PATH_OFFSET * decimator; + snd_soc_component_update_bits(component, tx_fs_reg, + 0x0F, tx_fs_rate); + } else { + dev_err(va_dev, + "%s: ERROR: Invalid decimator: %d\n", + __func__, decimator); + return -EINVAL; + } + } + return 0; +} + +static int va_macro_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) +{ + struct snd_soc_component *component = dai->component; + struct device *va_dev = component->dev; + struct va_macro *va = snd_soc_component_get_drvdata(component); + + switch (dai->id) { + case VA_MACRO_AIF1_CAP: + case VA_MACRO_AIF2_CAP: + case VA_MACRO_AIF3_CAP: + *tx_slot = va->active_ch_mask[dai->id]; + *tx_num = va->active_ch_cnt[dai->id]; + break; + default: + dev_err(va_dev, "%s: Invalid AIF\n", __func__); + break; + } + return 0; +} + +static int va_macro_digital_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_component *component = dai->component; + struct va_macro *va = snd_soc_component_get_drvdata(component); + u16 tx_vol_ctl_reg, decimator; + + decimator = va->active_decimator[dai->id]; + + tx_vol_ctl_reg = CDC_VA_TX0_TX_PATH_CTL + + VA_MACRO_TX_PATH_OFFSET * decimator; + if (mute) + snd_soc_component_update_bits(component, tx_vol_ctl_reg, + CDC_VA_TX_PATH_PGA_MUTE_EN_MASK, + CDC_VA_TX_PATH_PGA_MUTE_EN); + else + snd_soc_component_update_bits(component, tx_vol_ctl_reg, + CDC_VA_TX_PATH_PGA_MUTE_EN_MASK, + CDC_VA_TX_PATH_PGA_MUTE_DISABLE); + + return 0; +} + +static struct snd_soc_dai_ops va_macro_dai_ops = { + .hw_params = va_macro_hw_params, + .get_channel_map = va_macro_get_channel_map, + .mute_stream = va_macro_digital_mute, +}; + +static struct snd_soc_dai_driver va_macro_dais[] = { + { + .name = "va_macro_tx1", + .id = VA_MACRO_AIF1_CAP, + .capture = { + .stream_name = "VA_AIF1 Capture", + .rates = VA_MACRO_RATES, + .formats = VA_MACRO_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &va_macro_dai_ops, + }, + { + .name = "va_macro_tx2", + .id = VA_MACRO_AIF2_CAP, + .capture = { + .stream_name = "VA_AIF2 Capture", + .rates = VA_MACRO_RATES, + .formats = VA_MACRO_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &va_macro_dai_ops, + }, + { + .name = "va_macro_tx3", + .id = VA_MACRO_AIF3_CAP, + .capture = { + .stream_name = "VA_AIF3 Capture", + .rates = VA_MACRO_RATES, + .formats = VA_MACRO_FORMATS, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &va_macro_dai_ops, + }, +}; + +static const char * const adc_mux_text[] = { + "VA_DMIC", "SWR_MIC" +}; + +static SOC_ENUM_SINGLE_DECL(va_dec0_enum, CDC_VA_INP_MUX_ADC_MUX0_CFG1, + 0, adc_mux_text); +static SOC_ENUM_SINGLE_DECL(va_dec1_enum, CDC_VA_INP_MUX_ADC_MUX1_CFG1, + 0, adc_mux_text); +static SOC_ENUM_SINGLE_DECL(va_dec2_enum, CDC_VA_INP_MUX_ADC_MUX2_CFG1, + 0, adc_mux_text); +static SOC_ENUM_SINGLE_DECL(va_dec3_enum, CDC_VA_INP_MUX_ADC_MUX3_CFG1, + 0, adc_mux_text); + +static const struct snd_kcontrol_new va_dec0_mux = SOC_DAPM_ENUM("va_dec0", + va_dec0_enum); +static const struct snd_kcontrol_new va_dec1_mux = SOC_DAPM_ENUM("va_dec1", + va_dec1_enum); +static const struct snd_kcontrol_new va_dec2_mux = SOC_DAPM_ENUM("va_dec2", + va_dec2_enum); +static const struct snd_kcontrol_new va_dec3_mux = SOC_DAPM_ENUM("va_dec3", + va_dec3_enum); + +static const char * const dmic_mux_text[] = { + "ZERO", "DMIC0", "DMIC1", "DMIC2", "DMIC3", + "DMIC4", "DMIC5", "DMIC6", "DMIC7" +}; + +static SOC_ENUM_SINGLE_DECL(va_dmic0_enum, CDC_VA_INP_MUX_ADC_MUX0_CFG0, + 4, dmic_mux_text); + +static SOC_ENUM_SINGLE_DECL(va_dmic1_enum, CDC_VA_INP_MUX_ADC_MUX1_CFG0, + 4, dmic_mux_text); + +static SOC_ENUM_SINGLE_DECL(va_dmic2_enum, CDC_VA_INP_MUX_ADC_MUX2_CFG0, + 4, dmic_mux_text); + +static SOC_ENUM_SINGLE_DECL(va_dmic3_enum, CDC_VA_INP_MUX_ADC_MUX3_CFG0, + 4, dmic_mux_text); + +static const struct snd_kcontrol_new va_dmic0_mux = SOC_DAPM_ENUM_EXT("va_dmic0", + va_dmic0_enum, snd_soc_dapm_get_enum_double, + va_macro_put_dec_enum); + +static const struct snd_kcontrol_new va_dmic1_mux = SOC_DAPM_ENUM_EXT("va_dmic1", + va_dmic1_enum, snd_soc_dapm_get_enum_double, + va_macro_put_dec_enum); + +static const struct snd_kcontrol_new va_dmic2_mux = SOC_DAPM_ENUM_EXT("va_dmic2", + va_dmic2_enum, snd_soc_dapm_get_enum_double, + va_macro_put_dec_enum); + +static const struct snd_kcontrol_new va_dmic3_mux = SOC_DAPM_ENUM_EXT("va_dmic3", + va_dmic3_enum, snd_soc_dapm_get_enum_double, + va_macro_put_dec_enum); + +static const struct snd_kcontrol_new va_aif1_cap_mixer[] = { + SOC_SINGLE_EXT("DEC0", SND_SOC_NOPM, VA_MACRO_DEC0, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC1", SND_SOC_NOPM, VA_MACRO_DEC1, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC2", SND_SOC_NOPM, VA_MACRO_DEC2, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC3", SND_SOC_NOPM, VA_MACRO_DEC3, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC4", SND_SOC_NOPM, VA_MACRO_DEC4, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC5", SND_SOC_NOPM, VA_MACRO_DEC5, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC6", SND_SOC_NOPM, VA_MACRO_DEC6, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC7", SND_SOC_NOPM, VA_MACRO_DEC7, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), +}; + +static const struct snd_kcontrol_new va_aif2_cap_mixer[] = { + SOC_SINGLE_EXT("DEC0", SND_SOC_NOPM, VA_MACRO_DEC0, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC1", SND_SOC_NOPM, VA_MACRO_DEC1, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC2", SND_SOC_NOPM, VA_MACRO_DEC2, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC3", SND_SOC_NOPM, VA_MACRO_DEC3, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC4", SND_SOC_NOPM, VA_MACRO_DEC4, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC5", SND_SOC_NOPM, VA_MACRO_DEC5, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC6", SND_SOC_NOPM, VA_MACRO_DEC6, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC7", SND_SOC_NOPM, VA_MACRO_DEC7, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), +}; + +static const struct snd_kcontrol_new va_aif3_cap_mixer[] = { + SOC_SINGLE_EXT("DEC0", SND_SOC_NOPM, VA_MACRO_DEC0, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC1", SND_SOC_NOPM, VA_MACRO_DEC1, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC2", SND_SOC_NOPM, VA_MACRO_DEC2, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC3", SND_SOC_NOPM, VA_MACRO_DEC3, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC4", SND_SOC_NOPM, VA_MACRO_DEC4, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC5", SND_SOC_NOPM, VA_MACRO_DEC5, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC6", SND_SOC_NOPM, VA_MACRO_DEC6, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), + SOC_SINGLE_EXT("DEC7", SND_SOC_NOPM, VA_MACRO_DEC7, 1, 0, + va_macro_tx_mixer_get, va_macro_tx_mixer_put), +}; + +static const struct snd_soc_dapm_widget va_macro_dapm_widgets[] = { + SND_SOC_DAPM_AIF_OUT("VA_AIF1 CAP", "VA_AIF1 Capture", 0, + SND_SOC_NOPM, VA_MACRO_AIF1_CAP, 0), + + SND_SOC_DAPM_AIF_OUT("VA_AIF2 CAP", "VA_AIF2 Capture", 0, + SND_SOC_NOPM, VA_MACRO_AIF2_CAP, 0), + + SND_SOC_DAPM_AIF_OUT("VA_AIF3 CAP", "VA_AIF3 Capture", 0, + SND_SOC_NOPM, VA_MACRO_AIF3_CAP, 0), + + SND_SOC_DAPM_MIXER("VA_AIF1_CAP Mixer", SND_SOC_NOPM, + VA_MACRO_AIF1_CAP, 0, + va_aif1_cap_mixer, ARRAY_SIZE(va_aif1_cap_mixer)), + + SND_SOC_DAPM_MIXER("VA_AIF2_CAP Mixer", SND_SOC_NOPM, + VA_MACRO_AIF2_CAP, 0, + va_aif2_cap_mixer, ARRAY_SIZE(va_aif2_cap_mixer)), + + SND_SOC_DAPM_MIXER("VA_AIF3_CAP Mixer", SND_SOC_NOPM, + VA_MACRO_AIF3_CAP, 0, + va_aif3_cap_mixer, ARRAY_SIZE(va_aif3_cap_mixer)), + + SND_SOC_DAPM_MUX("VA DMIC MUX0", SND_SOC_NOPM, 0, 0, &va_dmic0_mux), + SND_SOC_DAPM_MUX("VA DMIC MUX1", SND_SOC_NOPM, 0, 0, &va_dmic1_mux), + SND_SOC_DAPM_MUX("VA DMIC MUX2", SND_SOC_NOPM, 0, 0, &va_dmic2_mux), + SND_SOC_DAPM_MUX("VA DMIC MUX3", SND_SOC_NOPM, 0, 0, &va_dmic3_mux), + + SND_SOC_DAPM_REGULATOR_SUPPLY("vdd-micb", 0, 0), + SND_SOC_DAPM_INPUT("DMIC0 Pin"), + SND_SOC_DAPM_INPUT("DMIC1 Pin"), + SND_SOC_DAPM_INPUT("DMIC2 Pin"), + SND_SOC_DAPM_INPUT("DMIC3 Pin"), + SND_SOC_DAPM_INPUT("DMIC4 Pin"), + SND_SOC_DAPM_INPUT("DMIC5 Pin"), + SND_SOC_DAPM_INPUT("DMIC6 Pin"), + SND_SOC_DAPM_INPUT("DMIC7 Pin"), + + SND_SOC_DAPM_ADC_E("VA DMIC0", NULL, SND_SOC_NOPM, 0, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("VA DMIC1", NULL, SND_SOC_NOPM, 1, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("VA DMIC2", NULL, SND_SOC_NOPM, 2, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("VA DMIC3", NULL, SND_SOC_NOPM, 3, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("VA DMIC4", NULL, SND_SOC_NOPM, 4, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("VA DMIC5", NULL, SND_SOC_NOPM, 5, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("VA DMIC6", NULL, SND_SOC_NOPM, 6, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC_E("VA DMIC7", NULL, SND_SOC_NOPM, 7, 0, + va_macro_enable_dmic, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("VA SWR_ADC0"), + SND_SOC_DAPM_INPUT("VA SWR_ADC1"), + SND_SOC_DAPM_INPUT("VA SWR_ADC2"), + SND_SOC_DAPM_INPUT("VA SWR_ADC3"), + SND_SOC_DAPM_INPUT("VA SWR_MIC0"), + SND_SOC_DAPM_INPUT("VA SWR_MIC1"), + SND_SOC_DAPM_INPUT("VA SWR_MIC2"), + SND_SOC_DAPM_INPUT("VA SWR_MIC3"), + SND_SOC_DAPM_INPUT("VA SWR_MIC4"), + SND_SOC_DAPM_INPUT("VA SWR_MIC5"), + SND_SOC_DAPM_INPUT("VA SWR_MIC6"), + SND_SOC_DAPM_INPUT("VA SWR_MIC7"), + + SND_SOC_DAPM_MUX_E("VA DEC0 MUX", SND_SOC_NOPM, VA_MACRO_DEC0, 0, + &va_dec0_mux, va_macro_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("VA DEC1 MUX", SND_SOC_NOPM, VA_MACRO_DEC1, 0, + &va_dec1_mux, va_macro_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("VA DEC2 MUX", SND_SOC_NOPM, VA_MACRO_DEC2, 0, + &va_dec2_mux, va_macro_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("VA DEC3 MUX", SND_SOC_NOPM, VA_MACRO_DEC3, 0, + &va_dec3_mux, va_macro_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY_S("VA_MCLK", -1, SND_SOC_NOPM, 0, 0, + va_macro_mclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route va_audio_map[] = { + {"VA_AIF1 CAP", NULL, "VA_MCLK"}, + {"VA_AIF2 CAP", NULL, "VA_MCLK"}, + {"VA_AIF3 CAP", NULL, "VA_MCLK"}, + + {"VA_AIF1 CAP", NULL, "VA_AIF1_CAP Mixer"}, + {"VA_AIF2 CAP", NULL, "VA_AIF2_CAP Mixer"}, + {"VA_AIF3 CAP", NULL, "VA_AIF3_CAP Mixer"}, + + {"VA_AIF1_CAP Mixer", "DEC0", "VA DEC0 MUX"}, + {"VA_AIF1_CAP Mixer", "DEC1", "VA DEC1 MUX"}, + {"VA_AIF1_CAP Mixer", "DEC2", "VA DEC2 MUX"}, + {"VA_AIF1_CAP Mixer", "DEC3", "VA DEC3 MUX"}, + + {"VA_AIF2_CAP Mixer", "DEC0", "VA DEC0 MUX"}, + {"VA_AIF2_CAP Mixer", "DEC1", "VA DEC1 MUX"}, + {"VA_AIF2_CAP Mixer", "DEC2", "VA DEC2 MUX"}, + {"VA_AIF2_CAP Mixer", "DEC3", "VA DEC3 MUX"}, + + {"VA_AIF3_CAP Mixer", "DEC0", "VA DEC0 MUX"}, + {"VA_AIF3_CAP Mixer", "DEC1", "VA DEC1 MUX"}, + {"VA_AIF3_CAP Mixer", "DEC2", "VA DEC2 MUX"}, + {"VA_AIF3_CAP Mixer", "DEC3", "VA DEC3 MUX"}, + + {"VA DEC0 MUX", "VA_DMIC", "VA DMIC MUX0"}, + {"VA DMIC MUX0", "DMIC0", "VA DMIC0"}, + {"VA DMIC MUX0", "DMIC1", "VA DMIC1"}, + {"VA DMIC MUX0", "DMIC2", "VA DMIC2"}, + {"VA DMIC MUX0", "DMIC3", "VA DMIC3"}, + {"VA DMIC MUX0", "DMIC4", "VA DMIC4"}, + {"VA DMIC MUX0", "DMIC5", "VA DMIC5"}, + {"VA DMIC MUX0", "DMIC6", "VA DMIC6"}, + {"VA DMIC MUX0", "DMIC7", "VA DMIC7"}, + + {"VA DEC1 MUX", "VA_DMIC", "VA DMIC MUX1"}, + {"VA DMIC MUX1", "DMIC0", "VA DMIC0"}, + {"VA DMIC MUX1", "DMIC1", "VA DMIC1"}, + {"VA DMIC MUX1", "DMIC2", "VA DMIC2"}, + {"VA DMIC MUX1", "DMIC3", "VA DMIC3"}, + {"VA DMIC MUX1", "DMIC4", "VA DMIC4"}, + {"VA DMIC MUX1", "DMIC5", "VA DMIC5"}, + {"VA DMIC MUX1", "DMIC6", "VA DMIC6"}, + {"VA DMIC MUX1", "DMIC7", "VA DMIC7"}, + + {"VA DEC2 MUX", "VA_DMIC", "VA DMIC MUX2"}, + {"VA DMIC MUX2", "DMIC0", "VA DMIC0"}, + {"VA DMIC MUX2", "DMIC1", "VA DMIC1"}, + {"VA DMIC MUX2", "DMIC2", "VA DMIC2"}, + {"VA DMIC MUX2", "DMIC3", "VA DMIC3"}, + {"VA DMIC MUX2", "DMIC4", "VA DMIC4"}, + {"VA DMIC MUX2", "DMIC5", "VA DMIC5"}, + {"VA DMIC MUX2", "DMIC6", "VA DMIC6"}, + {"VA DMIC MUX2", "DMIC7", "VA DMIC7"}, + + {"VA DEC3 MUX", "VA_DMIC", "VA DMIC MUX3"}, + {"VA DMIC MUX3", "DMIC0", "VA DMIC0"}, + {"VA DMIC MUX3", "DMIC1", "VA DMIC1"}, + {"VA DMIC MUX3", "DMIC2", "VA DMIC2"}, + {"VA DMIC MUX3", "DMIC3", "VA DMIC3"}, + {"VA DMIC MUX3", "DMIC4", "VA DMIC4"}, + {"VA DMIC MUX3", "DMIC5", "VA DMIC5"}, + {"VA DMIC MUX3", "DMIC6", "VA DMIC6"}, + {"VA DMIC MUX3", "DMIC7", "VA DMIC7"}, + + { "VA DMIC0", NULL, "DMIC0 Pin" }, + { "VA DMIC1", NULL, "DMIC1 Pin" }, + { "VA DMIC2", NULL, "DMIC2 Pin" }, + { "VA DMIC3", NULL, "DMIC3 Pin" }, + { "VA DMIC4", NULL, "DMIC4 Pin" }, + { "VA DMIC5", NULL, "DMIC5 Pin" }, + { "VA DMIC6", NULL, "DMIC6 Pin" }, + { "VA DMIC7", NULL, "DMIC7 Pin" }, +}; + +static const char * const dec_mode_mux_text[] = { + "ADC_DEFAULT", "ADC_LOW_PWR", "ADC_HIGH_PERF", +}; + +static const struct soc_enum dec_mode_mux_enum[] = { + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(dec_mode_mux_text), + dec_mode_mux_text), + SOC_ENUM_SINGLE(SND_SOC_NOPM, 1, ARRAY_SIZE(dec_mode_mux_text), + dec_mode_mux_text), + SOC_ENUM_SINGLE(SND_SOC_NOPM, 2, ARRAY_SIZE(dec_mode_mux_text), + dec_mode_mux_text), + SOC_ENUM_SINGLE(SND_SOC_NOPM, 3, ARRAY_SIZE(dec_mode_mux_text), + dec_mode_mux_text), +}; + +static const struct snd_kcontrol_new va_macro_snd_controls[] = { + SOC_SINGLE_S8_TLV("VA_DEC0 Volume", CDC_VA_TX0_TX_VOL_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("VA_DEC1 Volume", CDC_VA_TX1_TX_VOL_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("VA_DEC2 Volume", CDC_VA_TX2_TX_VOL_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("VA_DEC3 Volume", CDC_VA_TX3_TX_VOL_CTL, + -84, 40, digital_gain), + + SOC_ENUM_EXT("VA_DEC0 MODE", dec_mode_mux_enum[0], + va_macro_dec_mode_get, va_macro_dec_mode_put), + SOC_ENUM_EXT("VA_DEC1 MODE", dec_mode_mux_enum[1], + va_macro_dec_mode_get, va_macro_dec_mode_put), + SOC_ENUM_EXT("VA_DEC2 MODE", dec_mode_mux_enum[2], + va_macro_dec_mode_get, va_macro_dec_mode_put), + SOC_ENUM_EXT("VA_DEC3 MODE", dec_mode_mux_enum[3], + va_macro_dec_mode_get, va_macro_dec_mode_put), +}; + +static int va_macro_component_probe(struct snd_soc_component *component) +{ + struct va_macro *va = snd_soc_component_get_drvdata(component); + + snd_soc_component_init_regmap(component, va->regmap); + + return 0; +} + +static const struct snd_soc_component_driver va_macro_component_drv = { + .name = "VA MACRO", + .probe = va_macro_component_probe, + .controls = va_macro_snd_controls, + .num_controls = ARRAY_SIZE(va_macro_snd_controls), + .dapm_widgets = va_macro_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(va_macro_dapm_widgets), + .dapm_routes = va_audio_map, + .num_dapm_routes = ARRAY_SIZE(va_audio_map), +}; + +static int fsgen_gate_enable(struct clk_hw *hw) +{ + return va_macro_mclk_enable(to_va_macro(hw), true); +} + +static void fsgen_gate_disable(struct clk_hw *hw) +{ + va_macro_mclk_enable(to_va_macro(hw), false); +} + +static int fsgen_gate_is_enabled(struct clk_hw *hw) +{ + struct va_macro *va = to_va_macro(hw); + int val; + + regmap_read(va->regmap, CDC_VA_TOP_CSR_TOP_CFG0, &val); + + return !!(val & CDC_VA_FS_BROADCAST_EN); +} + +static const struct clk_ops fsgen_gate_ops = { + .prepare = fsgen_gate_enable, + .unprepare = fsgen_gate_disable, + .is_enabled = fsgen_gate_is_enabled, +}; + +static int va_macro_register_fsgen_output(struct va_macro *va) +{ + struct clk *parent = va->clks[2].clk; + struct device *dev = va->dev; + struct device_node *np = dev->of_node; + const char *parent_clk_name; + const char *clk_name = "fsgen"; + struct clk_init_data init; + int ret; + + parent_clk_name = __clk_get_name(parent); + + of_property_read_string(np, "clock-output-names", &clk_name); + + init.name = clk_name; + init.ops = &fsgen_gate_ops; + init.flags = 0; + init.parent_names = &parent_clk_name; + init.num_parents = 1; + va->hw.init = &init; + ret = devm_clk_hw_register(va->dev, &va->hw); + if (ret) + return ret; + + return of_clk_add_provider(np, of_clk_src_simple_get, va->hw.clk); +} + +static int va_macro_validate_dmic_sample_rate(u32 dmic_sample_rate, + struct va_macro *va) +{ + u32 div_factor; + u32 mclk_rate = VA_MACRO_MCLK_FREQ; + + if (!dmic_sample_rate || mclk_rate % dmic_sample_rate != 0) + goto undefined_rate; + + div_factor = mclk_rate / dmic_sample_rate; + + switch (div_factor) { + case 2: + va->dmic_clk_div = VA_MACRO_CLK_DIV_2; + break; + case 3: + va->dmic_clk_div = VA_MACRO_CLK_DIV_3; + break; + case 4: + va->dmic_clk_div = VA_MACRO_CLK_DIV_4; + break; + case 6: + va->dmic_clk_div = VA_MACRO_CLK_DIV_6; + break; + case 8: + va->dmic_clk_div = VA_MACRO_CLK_DIV_8; + break; + case 16: + va->dmic_clk_div = VA_MACRO_CLK_DIV_16; + break; + default: + /* Any other DIV factor is invalid */ + goto undefined_rate; + } + + return dmic_sample_rate; + +undefined_rate: + dev_err(va->dev, "%s: Invalid rate %d, for mclk %d\n", + __func__, dmic_sample_rate, mclk_rate); + dmic_sample_rate = 0; + + return dmic_sample_rate; +} + +static int va_macro_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct va_macro *va; + void __iomem *base; + u32 sample_rate = 0; + int ret; + + va = devm_kzalloc(dev, sizeof(*va), GFP_KERNEL); + if (!va) + return -ENOMEM; + + va->dev = dev; + va->clks[0].id = "macro"; + va->clks[1].id = "dcodec"; + va->clks[2].id = "mclk"; + + ret = devm_clk_bulk_get(dev, VA_NUM_CLKS_MAX, va->clks); + if (ret) { + dev_err(dev, "Error getting VA Clocks (%d)\n", ret); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "qcom,dmic-sample-rate", + &sample_rate); + if (ret) { + dev_err(dev, "qcom,dmic-sample-rate dt entry missing\n"); + va->dmic_clk_div = VA_MACRO_CLK_DIV_2; + } else { + ret = va_macro_validate_dmic_sample_rate(sample_rate, va); + if (!ret) + return -EINVAL; + } + + /* mclk rate */ + clk_set_rate(va->clks[1].clk, VA_MACRO_MCLK_FREQ); + ret = clk_bulk_prepare_enable(VA_NUM_CLKS_MAX, va->clks); + if (ret) + return ret; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + goto err; + } + + va->regmap = devm_regmap_init_mmio(dev, base, &va_regmap_config); + if (IS_ERR(va->regmap)) { + ret = -EINVAL; + goto err; + } + + dev_set_drvdata(dev, va); + ret = va_macro_register_fsgen_output(va); + if (ret) + goto err; + + ret = devm_snd_soc_register_component(dev, &va_macro_component_drv, + va_macro_dais, + ARRAY_SIZE(va_macro_dais)); + if (ret) + goto soc_err; + + return ret; + +soc_err: + of_clk_del_provider(pdev->dev.of_node); +err: + clk_bulk_disable_unprepare(VA_NUM_CLKS_MAX, va->clks); + + return ret; +} + +static int va_macro_remove(struct platform_device *pdev) +{ + struct va_macro *va = dev_get_drvdata(&pdev->dev); + + of_clk_del_provider(pdev->dev.of_node); + clk_bulk_disable_unprepare(VA_NUM_CLKS_MAX, va->clks); + + return 0; +} + +static const struct of_device_id va_macro_dt_match[] = { + { .compatible = "qcom,sm8250-lpass-va-macro" }, + {} +}; +MODULE_DEVICE_TABLE(of, va_macro_dt_match); + +static struct platform_driver va_macro_driver = { + .driver = { + .name = "va_macro", + .of_match_table = va_macro_dt_match, + .suppress_bind_attrs = true, + }, + .probe = va_macro_probe, + .remove = va_macro_remove, +}; + +module_platform_driver(va_macro_driver); +MODULE_DESCRIPTION("VA macro driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c new file mode 100644 index 000000000000..d3de35e80cf0 --- /dev/null +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -0,0 +1,2472 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/of_clk.h> +#include <linux/clk-provider.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/of_platform.h> +#include <sound/tlv.h> +#include "lpass-wsa-macro.h" + +#define CDC_WSA_CLK_RST_CTRL_MCLK_CONTROL (0x0000) +#define CDC_WSA_MCLK_EN_MASK BIT(0) +#define CDC_WSA_MCLK_ENABLE BIT(0) +#define CDC_WSA_MCLK_DISABLE 0 +#define CDC_WSA_CLK_RST_CTRL_FS_CNT_CONTROL (0x0004) +#define CDC_WSA_FS_CNT_EN_MASK BIT(0) +#define CDC_WSA_FS_CNT_ENABLE BIT(0) +#define CDC_WSA_FS_CNT_DISABLE 0 +#define CDC_WSA_CLK_RST_CTRL_SWR_CONTROL (0x0008) +#define CDC_WSA_TOP_TOP_CFG0 (0x0080) +#define CDC_WSA_TOP_TOP_CFG1 (0x0084) +#define CDC_WSA_TOP_FREQ_MCLK (0x0088) +#define CDC_WSA_TOP_DEBUG_BUS_SEL (0x008C) +#define CDC_WSA_TOP_DEBUG_EN0 (0x0090) +#define CDC_WSA_TOP_DEBUG_EN1 (0x0094) +#define CDC_WSA_TOP_DEBUG_DSM_LB (0x0098) +#define CDC_WSA_TOP_RX_I2S_CTL (0x009C) +#define CDC_WSA_TOP_TX_I2S_CTL (0x00A0) +#define CDC_WSA_TOP_I2S_CLK (0x00A4) +#define CDC_WSA_TOP_I2S_RESET (0x00A8) +#define CDC_WSA_RX_INP_MUX_RX_INT0_CFG0 (0x0100) +#define CDC_WSA_RX_INTX_1_MIX_INP2_SEL_MASK GENMASK(5, 3) +#define CDC_WSA_RX_INTX_2_SEL_MASK GENMASK(2, 0) +#define CDC_WSA_RX_INP_MUX_RX_INT0_CFG1 (0x0104) +#define CDC_WSA_RX_INP_MUX_RX_INT1_CFG0 (0x0108) +#define CDC_WSA_RX_INP_MUX_RX_INT1_CFG1 (0x010C) +#define CDC_WSA_RX_INP_MUX_RX_MIX_CFG0 (0x0110) +#define CDC_WSA_RX_MIX_TX1_SEL_MASK GENMASK(5, 3) +#define CDC_WSA_RX_MIX_TX1_SEL_SHFT 3 +#define CDC_WSA_RX_MIX_TX0_SEL_MASK GENMASK(2, 0) +#define CDC_WSA_RX_INP_MUX_RX_EC_CFG0 (0x0114) +#define CDC_WSA_RX_INP_MUX_SOFTCLIP_CFG0 (0x0118) +#define CDC_WSA_TX0_SPKR_PROT_PATH_CTL (0x0244) +#define CDC_WSA_TX_SPKR_PROT_RESET_MASK BIT(5) +#define CDC_WSA_TX_SPKR_PROT_RESET BIT(5) +#define CDC_WSA_TX_SPKR_PROT_NO_RESET 0 +#define CDC_WSA_TX_SPKR_PROT_CLK_EN_MASK BIT(4) +#define CDC_WSA_TX_SPKR_PROT_CLK_ENABLE BIT(4) +#define CDC_WSA_TX_SPKR_PROT_CLK_DISABLE 0 +#define CDC_WSA_TX_SPKR_PROT_PCM_RATE_MASK GENMASK(3, 0) +#define CDC_WSA_TX_SPKR_PROT_PCM_RATE_8K 0 +#define CDC_WSA_TX0_SPKR_PROT_PATH_CFG0 (0x0248) +#define CDC_WSA_TX1_SPKR_PROT_PATH_CTL (0x0264) +#define CDC_WSA_TX1_SPKR_PROT_PATH_CFG0 (0x0268) +#define CDC_WSA_TX2_SPKR_PROT_PATH_CTL (0x0284) +#define CDC_WSA_TX2_SPKR_PROT_PATH_CFG0 (0x0288) +#define CDC_WSA_TX3_SPKR_PROT_PATH_CTL (0x02A4) +#define CDC_WSA_TX3_SPKR_PROT_PATH_CFG0 (0x02A8) +#define CDC_WSA_INTR_CTRL_CFG (0x0340) +#define CDC_WSA_INTR_CTRL_CLR_COMMIT (0x0344) +#define CDC_WSA_INTR_CTRL_PIN1_MASK0 (0x0360) +#define CDC_WSA_INTR_CTRL_PIN1_STATUS0 (0x0368) +#define CDC_WSA_INTR_CTRL_PIN1_CLEAR0 (0x0370) +#define CDC_WSA_INTR_CTRL_PIN2_MASK0 (0x0380) +#define CDC_WSA_INTR_CTRL_PIN2_STATUS0 (0x0388) +#define CDC_WSA_INTR_CTRL_PIN2_CLEAR0 (0x0390) +#define CDC_WSA_INTR_CTRL_LEVEL0 (0x03C0) +#define CDC_WSA_INTR_CTRL_BYPASS0 (0x03C8) +#define CDC_WSA_INTR_CTRL_SET0 (0x03D0) +#define CDC_WSA_RX0_RX_PATH_CTL (0x0400) +#define CDC_WSA_RX_PATH_CLK_EN_MASK BIT(5) +#define CDC_WSA_RX_PATH_CLK_ENABLE BIT(5) +#define CDC_WSA_RX_PATH_CLK_DISABLE 0 +#define CDC_WSA_RX_PATH_PGA_MUTE_EN_MASK BIT(4) +#define CDC_WSA_RX_PATH_PGA_MUTE_ENABLE BIT(4) +#define CDC_WSA_RX_PATH_PGA_MUTE_DISABLE 0 +#define CDC_WSA_RX0_RX_PATH_CFG0 (0x0404) +#define CDC_WSA_RX_PATH_COMP_EN_MASK BIT(1) +#define CDC_WSA_RX_PATH_COMP_ENABLE BIT(1) +#define CDC_WSA_RX_PATH_HD2_EN_MASK BIT(2) +#define CDC_WSA_RX_PATH_HD2_ENABLE BIT(2) +#define CDC_WSA_RX_PATH_SPKR_RATE_MASK BIT(3) +#define CDC_WSA_RX_PATH_SPKR_RATE_FS_2P4_3P072 BIT(3) +#define CDC_WSA_RX0_RX_PATH_CFG1 (0x0408) +#define CDC_WSA_RX_PATH_SMART_BST_EN_MASK BIT(0) +#define CDC_WSA_RX_PATH_SMART_BST_ENABLE BIT(0) +#define CDC_WSA_RX_PATH_SMART_BST_DISABLE 0 +#define CDC_WSA_RX0_RX_PATH_CFG2 (0x040C) +#define CDC_WSA_RX0_RX_PATH_CFG3 (0x0410) +#define CDC_WSA_RX_DC_DCOEFF_MASK GENMASK(1, 0) +#define CDC_WSA_RX0_RX_VOL_CTL (0x0414) +#define CDC_WSA_RX0_RX_PATH_MIX_CTL (0x0418) +#define CDC_WSA_RX_PATH_MIX_CLK_EN_MASK BIT(5) +#define CDC_WSA_RX_PATH_MIX_CLK_ENABLE BIT(5) +#define CDC_WSA_RX_PATH_MIX_CLK_DISABLE 0 +#define CDC_WSA_RX0_RX_PATH_MIX_CFG (0x041C) +#define CDC_WSA_RX0_RX_VOL_MIX_CTL (0x0420) +#define CDC_WSA_RX0_RX_PATH_SEC0 (0x0424) +#define CDC_WSA_RX0_RX_PATH_SEC1 (0x0428) +#define CDC_WSA_RX_PGA_HALF_DB_MASK BIT(0) +#define CDC_WSA_RX_PGA_HALF_DB_ENABLE BIT(0) +#define CDC_WSA_RX_PGA_HALF_DB_DISABLE 0 +#define CDC_WSA_RX0_RX_PATH_SEC2 (0x042C) +#define CDC_WSA_RX0_RX_PATH_SEC3 (0x0430) +#define CDC_WSA_RX_PATH_HD2_SCALE_MASK GENMASK(1, 0) +#define CDC_WSA_RX_PATH_HD2_ALPHA_MASK GENMASK(5, 2) +#define CDC_WSA_RX0_RX_PATH_SEC5 (0x0438) +#define CDC_WSA_RX0_RX_PATH_SEC6 (0x043C) +#define CDC_WSA_RX0_RX_PATH_SEC7 (0x0440) +#define CDC_WSA_RX0_RX_PATH_MIX_SEC0 (0x0444) +#define CDC_WSA_RX0_RX_PATH_MIX_SEC1 (0x0448) +#define CDC_WSA_RX0_RX_PATH_DSMDEM_CTL (0x044C) +#define CDC_WSA_RX_DSMDEM_CLK_EN_MASK BIT(0) +#define CDC_WSA_RX_DSMDEM_CLK_ENABLE BIT(0) +#define CDC_WSA_RX1_RX_PATH_CTL (0x0480) +#define CDC_WSA_RX1_RX_PATH_CFG0 (0x0484) +#define CDC_WSA_RX1_RX_PATH_CFG1 (0x0488) +#define CDC_WSA_RX1_RX_PATH_CFG2 (0x048C) +#define CDC_WSA_RX1_RX_PATH_CFG3 (0x0490) +#define CDC_WSA_RX1_RX_VOL_CTL (0x0494) +#define CDC_WSA_RX1_RX_PATH_MIX_CTL (0x0498) +#define CDC_WSA_RX1_RX_PATH_MIX_CFG (0x049C) +#define CDC_WSA_RX1_RX_VOL_MIX_CTL (0x04A0) +#define CDC_WSA_RX1_RX_PATH_SEC0 (0x04A4) +#define CDC_WSA_RX1_RX_PATH_SEC1 (0x04A8) +#define CDC_WSA_RX1_RX_PATH_SEC2 (0x04AC) +#define CDC_WSA_RX1_RX_PATH_SEC3 (0x04B0) +#define CDC_WSA_RX1_RX_PATH_SEC5 (0x04B8) +#define CDC_WSA_RX1_RX_PATH_SEC6 (0x04BC) +#define CDC_WSA_RX1_RX_PATH_SEC7 (0x04C0) +#define CDC_WSA_RX1_RX_PATH_MIX_SEC0 (0x04C4) +#define CDC_WSA_RX1_RX_PATH_MIX_SEC1 (0x04C8) +#define CDC_WSA_RX1_RX_PATH_DSMDEM_CTL (0x04CC) +#define CDC_WSA_BOOST0_BOOST_PATH_CTL (0x0500) +#define CDC_WSA_BOOST_PATH_CLK_EN_MASK BIT(4) +#define CDC_WSA_BOOST_PATH_CLK_ENABLE BIT(4) +#define CDC_WSA_BOOST_PATH_CLK_DISABLE 0 +#define CDC_WSA_BOOST0_BOOST_CTL (0x0504) +#define CDC_WSA_BOOST0_BOOST_CFG1 (0x0508) +#define CDC_WSA_BOOST0_BOOST_CFG2 (0x050C) +#define CDC_WSA_BOOST1_BOOST_PATH_CTL (0x0540) +#define CDC_WSA_BOOST1_BOOST_CTL (0x0544) +#define CDC_WSA_BOOST1_BOOST_CFG1 (0x0548) +#define CDC_WSA_BOOST1_BOOST_CFG2 (0x054C) +#define CDC_WSA_COMPANDER0_CTL0 (0x0580) +#define CDC_WSA_COMPANDER_CLK_EN_MASK BIT(0) +#define CDC_WSA_COMPANDER_CLK_ENABLE BIT(0) +#define CDC_WSA_COMPANDER_SOFT_RST_MASK BIT(1) +#define CDC_WSA_COMPANDER_SOFT_RST_ENABLE BIT(1) +#define CDC_WSA_COMPANDER_HALT_MASK BIT(2) +#define CDC_WSA_COMPANDER_HALT BIT(2) +#define CDC_WSA_COMPANDER0_CTL1 (0x0584) +#define CDC_WSA_COMPANDER0_CTL2 (0x0588) +#define CDC_WSA_COMPANDER0_CTL3 (0x058C) +#define CDC_WSA_COMPANDER0_CTL4 (0x0590) +#define CDC_WSA_COMPANDER0_CTL5 (0x0594) +#define CDC_WSA_COMPANDER0_CTL6 (0x0598) +#define CDC_WSA_COMPANDER0_CTL7 (0x059C) +#define CDC_WSA_COMPANDER1_CTL0 (0x05C0) +#define CDC_WSA_COMPANDER1_CTL1 (0x05C4) +#define CDC_WSA_COMPANDER1_CTL2 (0x05C8) +#define CDC_WSA_COMPANDER1_CTL3 (0x05CC) +#define CDC_WSA_COMPANDER1_CTL4 (0x05D0) +#define CDC_WSA_COMPANDER1_CTL5 (0x05D4) +#define CDC_WSA_COMPANDER1_CTL6 (0x05D8) +#define CDC_WSA_COMPANDER1_CTL7 (0x05DC) +#define CDC_WSA_SOFTCLIP0_CRC (0x0600) +#define CDC_WSA_SOFTCLIP_CLK_EN_MASK BIT(0) +#define CDC_WSA_SOFTCLIP_CLK_ENABLE BIT(0) +#define CDC_WSA_SOFTCLIP0_SOFTCLIP_CTRL (0x0604) +#define CDC_WSA_SOFTCLIP_EN_MASK BIT(0) +#define CDC_WSA_SOFTCLIP_ENABLE BIT(0) +#define CDC_WSA_SOFTCLIP1_CRC (0x0640) +#define CDC_WSA_SOFTCLIP1_SOFTCLIP_CTRL (0x0644) +#define CDC_WSA_EC_HQ0_EC_REF_HQ_PATH_CTL (0x0680) +#define CDC_WSA_EC_HQ_EC_CLK_EN_MASK BIT(0) +#define CDC_WSA_EC_HQ_EC_CLK_ENABLE BIT(0) +#define CDC_WSA_EC_HQ0_EC_REF_HQ_CFG0 (0x0684) +#define CDC_WSA_EC_HQ_EC_REF_PCM_RATE_MASK GENMASK(4, 1) +#define CDC_WSA_EC_HQ_EC_REF_PCM_RATE_48K BIT(3) +#define CDC_WSA_EC_HQ1_EC_REF_HQ_PATH_CTL (0x06C0) +#define CDC_WSA_EC_HQ1_EC_REF_HQ_CFG0 (0x06C4) +#define CDC_WSA_SPLINE_ASRC0_CLK_RST_CTL (0x0700) +#define CDC_WSA_SPLINE_ASRC0_CTL0 (0x0704) +#define CDC_WSA_SPLINE_ASRC0_CTL1 (0x0708) +#define CDC_WSA_SPLINE_ASRC0_FIFO_CTL (0x070C) +#define CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_LSB (0x0710) +#define CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_MSB (0x0714) +#define CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_LSB (0x0718) +#define CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_MSB (0x071C) +#define CDC_WSA_SPLINE_ASRC0_STATUS_FIFO (0x0720) +#define CDC_WSA_SPLINE_ASRC1_CLK_RST_CTL (0x0740) +#define CDC_WSA_SPLINE_ASRC1_CTL0 (0x0744) +#define CDC_WSA_SPLINE_ASRC1_CTL1 (0x0748) +#define CDC_WSA_SPLINE_ASRC1_FIFO_CTL (0x074C) +#define CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_LSB (0x0750) +#define CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_MSB (0x0754) +#define CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_LSB (0x0758) +#define CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_MSB (0x075C) +#define CDC_WSA_SPLINE_ASRC1_STATUS_FIFO (0x0760) +#define WSA_MAX_OFFSET (0x0760) + +#define WSA_MACRO_RX_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) +#define WSA_MACRO_RX_MIX_RATES (SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) +#define WSA_MACRO_RX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +#define WSA_MACRO_ECHO_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_48000) +#define WSA_MACRO_ECHO_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S24_3LE) + +#define NUM_INTERPOLATORS 2 +#define WSA_NUM_CLKS_MAX 5 +#define WSA_MACRO_MCLK_FREQ 19200000 +#define WSA_MACRO_MUX_INP_SHFT 0x3 +#define WSA_MACRO_MUX_INP_MASK1 0x07 +#define WSA_MACRO_MUX_INP_MASK2 0x38 +#define WSA_MACRO_MUX_CFG_OFFSET 0x8 +#define WSA_MACRO_MUX_CFG1_OFFSET 0x4 +#define WSA_MACRO_RX_COMP_OFFSET 0x40 +#define WSA_MACRO_RX_SOFTCLIP_OFFSET 0x40 +#define WSA_MACRO_RX_PATH_OFFSET 0x80 +#define WSA_MACRO_RX_PATH_CFG3_OFFSET 0x10 +#define WSA_MACRO_RX_PATH_DSMDEM_OFFSET 0x4C +#define WSA_MACRO_FS_RATE_MASK 0x0F +#define WSA_MACRO_EC_MIX_TX0_MASK 0x03 +#define WSA_MACRO_EC_MIX_TX1_MASK 0x18 +#define WSA_MACRO_MAX_DMA_CH_PER_PORT 0x2 + +enum { + WSA_MACRO_GAIN_OFFSET_M1P5_DB, + WSA_MACRO_GAIN_OFFSET_0_DB, +}; +enum { + WSA_MACRO_RX0 = 0, + WSA_MACRO_RX1, + WSA_MACRO_RX_MIX, + WSA_MACRO_RX_MIX0 = WSA_MACRO_RX_MIX, + WSA_MACRO_RX_MIX1, + WSA_MACRO_RX_MAX, +}; + +enum { + WSA_MACRO_TX0 = 0, + WSA_MACRO_TX1, + WSA_MACRO_TX_MAX, +}; + +enum { + WSA_MACRO_EC0_MUX = 0, + WSA_MACRO_EC1_MUX, + WSA_MACRO_EC_MUX_MAX, +}; + +enum { + WSA_MACRO_COMP1, /* SPK_L */ + WSA_MACRO_COMP2, /* SPK_R */ + WSA_MACRO_COMP_MAX +}; + +enum { + WSA_MACRO_SOFTCLIP0, /* RX0 */ + WSA_MACRO_SOFTCLIP1, /* RX1 */ + WSA_MACRO_SOFTCLIP_MAX +}; + +enum { + INTn_1_INP_SEL_ZERO = 0, + INTn_1_INP_SEL_RX0, + INTn_1_INP_SEL_RX1, + INTn_1_INP_SEL_RX2, + INTn_1_INP_SEL_RX3, + INTn_1_INP_SEL_DEC0, + INTn_1_INP_SEL_DEC1, +}; + +enum { + INTn_2_INP_SEL_ZERO = 0, + INTn_2_INP_SEL_RX0, + INTn_2_INP_SEL_RX1, + INTn_2_INP_SEL_RX2, + INTn_2_INP_SEL_RX3, +}; + +struct interp_sample_rate { + int sample_rate; + int rate_val; +}; + +static struct interp_sample_rate int_prim_sample_rate_val[] = { + {8000, 0x0}, /* 8K */ + {16000, 0x1}, /* 16K */ + {24000, -EINVAL},/* 24K */ + {32000, 0x3}, /* 32K */ + {48000, 0x4}, /* 48K */ + {96000, 0x5}, /* 96K */ + {192000, 0x6}, /* 192K */ + {384000, 0x7}, /* 384K */ + {44100, 0x8}, /* 44.1K */ +}; + +static struct interp_sample_rate int_mix_sample_rate_val[] = { + {48000, 0x4}, /* 48K */ + {96000, 0x5}, /* 96K */ + {192000, 0x6}, /* 192K */ +}; + +enum { + WSA_MACRO_AIF_INVALID = 0, + WSA_MACRO_AIF1_PB, + WSA_MACRO_AIF_MIX1_PB, + WSA_MACRO_AIF_VI, + WSA_MACRO_AIF_ECHO, + WSA_MACRO_MAX_DAIS, +}; + +struct wsa_macro { + struct device *dev; + int comp_enabled[WSA_MACRO_COMP_MAX]; + int ec_hq[WSA_MACRO_RX1 + 1]; + u16 prim_int_users[WSA_MACRO_RX1 + 1]; + u16 wsa_mclk_users; + bool reset_swr; + unsigned long active_ch_mask[WSA_MACRO_MAX_DAIS]; + unsigned long active_ch_cnt[WSA_MACRO_MAX_DAIS]; + int rx_port_value[WSA_MACRO_RX_MAX]; + int ear_spkr_gain; + int spkr_gain_offset; + int spkr_mode; + int is_softclip_on[WSA_MACRO_SOFTCLIP_MAX]; + int softclip_clk_users[WSA_MACRO_SOFTCLIP_MAX]; + struct regmap *regmap; + struct clk_bulk_data clks[WSA_NUM_CLKS_MAX]; + struct clk_hw hw; +}; +#define to_wsa_macro(_hw) container_of(_hw, struct wsa_macro, hw) + +static const DECLARE_TLV_DB_SCALE(digital_gain, -8400, 100, -8400); + +static const char *const rx_text[] = { + "ZERO", "RX0", "RX1", "RX_MIX0", "RX_MIX1", "DEC0", "DEC1" +}; + +static const char *const rx_mix_text[] = { + "ZERO", "RX0", "RX1", "RX_MIX0", "RX_MIX1" +}; + +static const char *const rx_mix_ec_text[] = { + "ZERO", "RX_MIX_TX0", "RX_MIX_TX1" +}; + +static const char *const rx_mux_text[] = { + "ZERO", "AIF1_PB", "AIF_MIX1_PB" +}; + +static const char *const rx_sidetone_mix_text[] = { + "ZERO", "SRC0" +}; + +static const char * const wsa_macro_ear_spkr_pa_gain_text[] = { + "G_DEFAULT", "G_0_DB", "G_1_DB", "G_2_DB", "G_3_DB", + "G_4_DB", "G_5_DB", "G_6_DB" +}; + +static SOC_ENUM_SINGLE_EXT_DECL(wsa_macro_ear_spkr_pa_gain_enum, + wsa_macro_ear_spkr_pa_gain_text); + +/* RX INT0 */ +static const struct soc_enum rx0_prim_inp0_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT0_CFG0, + 0, 7, rx_text); + +static const struct soc_enum rx0_prim_inp1_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT0_CFG0, + 3, 7, rx_text); + +static const struct soc_enum rx0_prim_inp2_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT0_CFG1, + 3, 7, rx_text); + +static const struct soc_enum rx0_mix_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT0_CFG1, + 0, 5, rx_mix_text); + +static const struct soc_enum rx0_sidetone_mix_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, 2, rx_sidetone_mix_text); + +static const struct snd_kcontrol_new rx0_prim_inp0_mux = + SOC_DAPM_ENUM("WSA_RX0 INP0 Mux", rx0_prim_inp0_chain_enum); + +static const struct snd_kcontrol_new rx0_prim_inp1_mux = + SOC_DAPM_ENUM("WSA_RX0 INP1 Mux", rx0_prim_inp1_chain_enum); + +static const struct snd_kcontrol_new rx0_prim_inp2_mux = + SOC_DAPM_ENUM("WSA_RX0 INP2 Mux", rx0_prim_inp2_chain_enum); + +static const struct snd_kcontrol_new rx0_mix_mux = + SOC_DAPM_ENUM("WSA_RX0 MIX Mux", rx0_mix_chain_enum); + +static const struct snd_kcontrol_new rx0_sidetone_mix_mux = + SOC_DAPM_ENUM("WSA_RX0 SIDETONE MIX Mux", rx0_sidetone_mix_enum); + +/* RX INT1 */ +static const struct soc_enum rx1_prim_inp0_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT1_CFG0, + 0, 7, rx_text); + +static const struct soc_enum rx1_prim_inp1_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT1_CFG0, + 3, 7, rx_text); + +static const struct soc_enum rx1_prim_inp2_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT1_CFG1, + 3, 7, rx_text); + +static const struct soc_enum rx1_mix_chain_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_INT1_CFG1, + 0, 5, rx_mix_text); + +static const struct snd_kcontrol_new rx1_prim_inp0_mux = + SOC_DAPM_ENUM("WSA_RX1 INP0 Mux", rx1_prim_inp0_chain_enum); + +static const struct snd_kcontrol_new rx1_prim_inp1_mux = + SOC_DAPM_ENUM("WSA_RX1 INP1 Mux", rx1_prim_inp1_chain_enum); + +static const struct snd_kcontrol_new rx1_prim_inp2_mux = + SOC_DAPM_ENUM("WSA_RX1 INP2 Mux", rx1_prim_inp2_chain_enum); + +static const struct snd_kcontrol_new rx1_mix_mux = + SOC_DAPM_ENUM("WSA_RX1 MIX Mux", rx1_mix_chain_enum); + +static const struct soc_enum rx_mix_ec0_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_MIX_CFG0, + 0, 3, rx_mix_ec_text); + +static const struct soc_enum rx_mix_ec1_enum = + SOC_ENUM_SINGLE(CDC_WSA_RX_INP_MUX_RX_MIX_CFG0, + 3, 3, rx_mix_ec_text); + +static const struct snd_kcontrol_new rx_mix_ec0_mux = + SOC_DAPM_ENUM("WSA RX_MIX EC0_Mux", rx_mix_ec0_enum); + +static const struct snd_kcontrol_new rx_mix_ec1_mux = + SOC_DAPM_ENUM("WSA RX_MIX EC1_Mux", rx_mix_ec1_enum); + +static const struct reg_default wsa_defaults[] = { + /* WSA Macro */ + { CDC_WSA_CLK_RST_CTRL_MCLK_CONTROL, 0x00}, + { CDC_WSA_CLK_RST_CTRL_FS_CNT_CONTROL, 0x00}, + { CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, 0x00}, + { CDC_WSA_TOP_TOP_CFG0, 0x00}, + { CDC_WSA_TOP_TOP_CFG1, 0x00}, + { CDC_WSA_TOP_FREQ_MCLK, 0x00}, + { CDC_WSA_TOP_DEBUG_BUS_SEL, 0x00}, + { CDC_WSA_TOP_DEBUG_EN0, 0x00}, + { CDC_WSA_TOP_DEBUG_EN1, 0x00}, + { CDC_WSA_TOP_DEBUG_DSM_LB, 0x88}, + { CDC_WSA_TOP_RX_I2S_CTL, 0x0C}, + { CDC_WSA_TOP_TX_I2S_CTL, 0x0C}, + { CDC_WSA_TOP_I2S_CLK, 0x02}, + { CDC_WSA_TOP_I2S_RESET, 0x00}, + { CDC_WSA_RX_INP_MUX_RX_INT0_CFG0, 0x00}, + { CDC_WSA_RX_INP_MUX_RX_INT0_CFG1, 0x00}, + { CDC_WSA_RX_INP_MUX_RX_INT1_CFG0, 0x00}, + { CDC_WSA_RX_INP_MUX_RX_INT1_CFG1, 0x00}, + { CDC_WSA_RX_INP_MUX_RX_MIX_CFG0, 0x00}, + { CDC_WSA_RX_INP_MUX_RX_EC_CFG0, 0x00}, + { CDC_WSA_RX_INP_MUX_SOFTCLIP_CFG0, 0x00}, + { CDC_WSA_TX0_SPKR_PROT_PATH_CTL, 0x02}, + { CDC_WSA_TX0_SPKR_PROT_PATH_CFG0, 0x00}, + { CDC_WSA_TX1_SPKR_PROT_PATH_CTL, 0x02}, + { CDC_WSA_TX1_SPKR_PROT_PATH_CFG0, 0x00}, + { CDC_WSA_TX2_SPKR_PROT_PATH_CTL, 0x02}, + { CDC_WSA_TX2_SPKR_PROT_PATH_CFG0, 0x00}, + { CDC_WSA_TX3_SPKR_PROT_PATH_CTL, 0x02}, + { CDC_WSA_TX3_SPKR_PROT_PATH_CFG0, 0x00}, + { CDC_WSA_INTR_CTRL_CFG, 0x00}, + { CDC_WSA_INTR_CTRL_CLR_COMMIT, 0x00}, + { CDC_WSA_INTR_CTRL_PIN1_MASK0, 0xFF}, + { CDC_WSA_INTR_CTRL_PIN1_STATUS0, 0x00}, + { CDC_WSA_INTR_CTRL_PIN1_CLEAR0, 0x00}, + { CDC_WSA_INTR_CTRL_PIN2_MASK0, 0xFF}, + { CDC_WSA_INTR_CTRL_PIN2_STATUS0, 0x00}, + { CDC_WSA_INTR_CTRL_PIN2_CLEAR0, 0x00}, + { CDC_WSA_INTR_CTRL_LEVEL0, 0x00}, + { CDC_WSA_INTR_CTRL_BYPASS0, 0x00}, + { CDC_WSA_INTR_CTRL_SET0, 0x00}, + { CDC_WSA_RX0_RX_PATH_CTL, 0x04}, + { CDC_WSA_RX0_RX_PATH_CFG0, 0x00}, + { CDC_WSA_RX0_RX_PATH_CFG1, 0x64}, + { CDC_WSA_RX0_RX_PATH_CFG2, 0x8F}, + { CDC_WSA_RX0_RX_PATH_CFG3, 0x00}, + { CDC_WSA_RX0_RX_VOL_CTL, 0x00}, + { CDC_WSA_RX0_RX_PATH_MIX_CTL, 0x04}, + { CDC_WSA_RX0_RX_PATH_MIX_CFG, 0x7E}, + { CDC_WSA_RX0_RX_VOL_MIX_CTL, 0x00}, + { CDC_WSA_RX0_RX_PATH_SEC0, 0x04}, + { CDC_WSA_RX0_RX_PATH_SEC1, 0x08}, + { CDC_WSA_RX0_RX_PATH_SEC2, 0x00}, + { CDC_WSA_RX0_RX_PATH_SEC3, 0x00}, + { CDC_WSA_RX0_RX_PATH_SEC5, 0x00}, + { CDC_WSA_RX0_RX_PATH_SEC6, 0x00}, + { CDC_WSA_RX0_RX_PATH_SEC7, 0x00}, + { CDC_WSA_RX0_RX_PATH_MIX_SEC0, 0x08}, + { CDC_WSA_RX0_RX_PATH_MIX_SEC1, 0x00}, + { CDC_WSA_RX0_RX_PATH_DSMDEM_CTL, 0x00}, + { CDC_WSA_RX1_RX_PATH_CFG0, 0x00}, + { CDC_WSA_RX1_RX_PATH_CFG1, 0x64}, + { CDC_WSA_RX1_RX_PATH_CFG2, 0x8F}, + { CDC_WSA_RX1_RX_PATH_CFG3, 0x00}, + { CDC_WSA_RX1_RX_VOL_CTL, 0x00}, + { CDC_WSA_RX1_RX_PATH_MIX_CTL, 0x04}, + { CDC_WSA_RX1_RX_PATH_MIX_CFG, 0x7E}, + { CDC_WSA_RX1_RX_VOL_MIX_CTL, 0x00}, + { CDC_WSA_RX1_RX_PATH_SEC0, 0x04}, + { CDC_WSA_RX1_RX_PATH_SEC1, 0x08}, + { CDC_WSA_RX1_RX_PATH_SEC2, 0x00}, + { CDC_WSA_RX1_RX_PATH_SEC3, 0x00}, + { CDC_WSA_RX1_RX_PATH_SEC5, 0x00}, + { CDC_WSA_RX1_RX_PATH_SEC6, 0x00}, + { CDC_WSA_RX1_RX_PATH_SEC7, 0x00}, + { CDC_WSA_RX1_RX_PATH_MIX_SEC0, 0x08}, + { CDC_WSA_RX1_RX_PATH_MIX_SEC1, 0x00}, + { CDC_WSA_RX1_RX_PATH_DSMDEM_CTL, 0x00}, + { CDC_WSA_BOOST0_BOOST_PATH_CTL, 0x00}, + { CDC_WSA_BOOST0_BOOST_CTL, 0xD0}, + { CDC_WSA_BOOST0_BOOST_CFG1, 0x89}, + { CDC_WSA_BOOST0_BOOST_CFG2, 0x04}, + { CDC_WSA_BOOST1_BOOST_PATH_CTL, 0x00}, + { CDC_WSA_BOOST1_BOOST_CTL, 0xD0}, + { CDC_WSA_BOOST1_BOOST_CFG1, 0x89}, + { CDC_WSA_BOOST1_BOOST_CFG2, 0x04}, + { CDC_WSA_COMPANDER0_CTL0, 0x60}, + { CDC_WSA_COMPANDER0_CTL1, 0xDB}, + { CDC_WSA_COMPANDER0_CTL2, 0xFF}, + { CDC_WSA_COMPANDER0_CTL3, 0x35}, + { CDC_WSA_COMPANDER0_CTL4, 0xFF}, + { CDC_WSA_COMPANDER0_CTL5, 0x00}, + { CDC_WSA_COMPANDER0_CTL6, 0x01}, + { CDC_WSA_COMPANDER0_CTL7, 0x28}, + { CDC_WSA_COMPANDER1_CTL0, 0x60}, + { CDC_WSA_COMPANDER1_CTL1, 0xDB}, + { CDC_WSA_COMPANDER1_CTL2, 0xFF}, + { CDC_WSA_COMPANDER1_CTL3, 0x35}, + { CDC_WSA_COMPANDER1_CTL4, 0xFF}, + { CDC_WSA_COMPANDER1_CTL5, 0x00}, + { CDC_WSA_COMPANDER1_CTL6, 0x01}, + { CDC_WSA_COMPANDER1_CTL7, 0x28}, + { CDC_WSA_SOFTCLIP0_CRC, 0x00}, + { CDC_WSA_SOFTCLIP0_SOFTCLIP_CTRL, 0x38}, + { CDC_WSA_SOFTCLIP1_CRC, 0x00}, + { CDC_WSA_SOFTCLIP1_SOFTCLIP_CTRL, 0x38}, + { CDC_WSA_EC_HQ0_EC_REF_HQ_PATH_CTL, 0x00}, + { CDC_WSA_EC_HQ0_EC_REF_HQ_CFG0, 0x01}, + { CDC_WSA_EC_HQ1_EC_REF_HQ_PATH_CTL, 0x00}, + { CDC_WSA_EC_HQ1_EC_REF_HQ_CFG0, 0x01}, + { CDC_WSA_SPLINE_ASRC0_CLK_RST_CTL, 0x00}, + { CDC_WSA_SPLINE_ASRC0_CTL0, 0x00}, + { CDC_WSA_SPLINE_ASRC0_CTL1, 0x00}, + { CDC_WSA_SPLINE_ASRC0_FIFO_CTL, 0xA8}, + { CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_LSB, 0x00}, + { CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_MSB, 0x00}, + { CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_LSB, 0x00}, + { CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_MSB, 0x00}, + { CDC_WSA_SPLINE_ASRC0_STATUS_FIFO, 0x00}, + { CDC_WSA_SPLINE_ASRC1_CLK_RST_CTL, 0x00}, + { CDC_WSA_SPLINE_ASRC1_CTL0, 0x00}, + { CDC_WSA_SPLINE_ASRC1_CTL1, 0x00}, + { CDC_WSA_SPLINE_ASRC1_FIFO_CTL, 0xA8}, + { CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_LSB, 0x00}, + { CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_MSB, 0x00}, + { CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_LSB, 0x00}, + { CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_MSB, 0x00}, + { CDC_WSA_SPLINE_ASRC1_STATUS_FIFO, 0x00}, +}; + +static bool wsa_is_wronly_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case CDC_WSA_INTR_CTRL_CLR_COMMIT: + case CDC_WSA_INTR_CTRL_PIN1_CLEAR0: + case CDC_WSA_INTR_CTRL_PIN2_CLEAR0: + return true; + } + + return false; +} + +static bool wsa_is_rw_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CDC_WSA_CLK_RST_CTRL_MCLK_CONTROL: + case CDC_WSA_CLK_RST_CTRL_FS_CNT_CONTROL: + case CDC_WSA_CLK_RST_CTRL_SWR_CONTROL: + case CDC_WSA_TOP_TOP_CFG0: + case CDC_WSA_TOP_TOP_CFG1: + case CDC_WSA_TOP_FREQ_MCLK: + case CDC_WSA_TOP_DEBUG_BUS_SEL: + case CDC_WSA_TOP_DEBUG_EN0: + case CDC_WSA_TOP_DEBUG_EN1: + case CDC_WSA_TOP_DEBUG_DSM_LB: + case CDC_WSA_TOP_RX_I2S_CTL: + case CDC_WSA_TOP_TX_I2S_CTL: + case CDC_WSA_TOP_I2S_CLK: + case CDC_WSA_TOP_I2S_RESET: + case CDC_WSA_RX_INP_MUX_RX_INT0_CFG0: + case CDC_WSA_RX_INP_MUX_RX_INT0_CFG1: + case CDC_WSA_RX_INP_MUX_RX_INT1_CFG0: + case CDC_WSA_RX_INP_MUX_RX_INT1_CFG1: + case CDC_WSA_RX_INP_MUX_RX_MIX_CFG0: + case CDC_WSA_RX_INP_MUX_RX_EC_CFG0: + case CDC_WSA_RX_INP_MUX_SOFTCLIP_CFG0: + case CDC_WSA_TX0_SPKR_PROT_PATH_CTL: + case CDC_WSA_TX0_SPKR_PROT_PATH_CFG0: + case CDC_WSA_TX1_SPKR_PROT_PATH_CTL: + case CDC_WSA_TX1_SPKR_PROT_PATH_CFG0: + case CDC_WSA_TX2_SPKR_PROT_PATH_CTL: + case CDC_WSA_TX2_SPKR_PROT_PATH_CFG0: + case CDC_WSA_TX3_SPKR_PROT_PATH_CTL: + case CDC_WSA_TX3_SPKR_PROT_PATH_CFG0: + case CDC_WSA_INTR_CTRL_CFG: + case CDC_WSA_INTR_CTRL_PIN1_MASK0: + case CDC_WSA_INTR_CTRL_PIN2_MASK0: + case CDC_WSA_INTR_CTRL_LEVEL0: + case CDC_WSA_INTR_CTRL_BYPASS0: + case CDC_WSA_INTR_CTRL_SET0: + case CDC_WSA_RX0_RX_PATH_CTL: + case CDC_WSA_RX0_RX_PATH_CFG0: + case CDC_WSA_RX0_RX_PATH_CFG1: + case CDC_WSA_RX0_RX_PATH_CFG2: + case CDC_WSA_RX0_RX_PATH_CFG3: + case CDC_WSA_RX0_RX_VOL_CTL: + case CDC_WSA_RX0_RX_PATH_MIX_CTL: + case CDC_WSA_RX0_RX_PATH_MIX_CFG: + case CDC_WSA_RX0_RX_VOL_MIX_CTL: + case CDC_WSA_RX0_RX_PATH_SEC0: + case CDC_WSA_RX0_RX_PATH_SEC1: + case CDC_WSA_RX0_RX_PATH_SEC2: + case CDC_WSA_RX0_RX_PATH_SEC3: + case CDC_WSA_RX0_RX_PATH_SEC5: + case CDC_WSA_RX0_RX_PATH_SEC6: + case CDC_WSA_RX0_RX_PATH_SEC7: + case CDC_WSA_RX0_RX_PATH_MIX_SEC0: + case CDC_WSA_RX0_RX_PATH_MIX_SEC1: + case CDC_WSA_RX0_RX_PATH_DSMDEM_CTL: + case CDC_WSA_RX1_RX_PATH_CTL: + case CDC_WSA_RX1_RX_PATH_CFG0: + case CDC_WSA_RX1_RX_PATH_CFG1: + case CDC_WSA_RX1_RX_PATH_CFG2: + case CDC_WSA_RX1_RX_PATH_CFG3: + case CDC_WSA_RX1_RX_VOL_CTL: + case CDC_WSA_RX1_RX_PATH_MIX_CTL: + case CDC_WSA_RX1_RX_PATH_MIX_CFG: + case CDC_WSA_RX1_RX_VOL_MIX_CTL: + case CDC_WSA_RX1_RX_PATH_SEC0: + case CDC_WSA_RX1_RX_PATH_SEC1: + case CDC_WSA_RX1_RX_PATH_SEC2: + case CDC_WSA_RX1_RX_PATH_SEC3: + case CDC_WSA_RX1_RX_PATH_SEC5: + case CDC_WSA_RX1_RX_PATH_SEC6: + case CDC_WSA_RX1_RX_PATH_SEC7: + case CDC_WSA_RX1_RX_PATH_MIX_SEC0: + case CDC_WSA_RX1_RX_PATH_MIX_SEC1: + case CDC_WSA_RX1_RX_PATH_DSMDEM_CTL: + case CDC_WSA_BOOST0_BOOST_PATH_CTL: + case CDC_WSA_BOOST0_BOOST_CTL: + case CDC_WSA_BOOST0_BOOST_CFG1: + case CDC_WSA_BOOST0_BOOST_CFG2: + case CDC_WSA_BOOST1_BOOST_PATH_CTL: + case CDC_WSA_BOOST1_BOOST_CTL: + case CDC_WSA_BOOST1_BOOST_CFG1: + case CDC_WSA_BOOST1_BOOST_CFG2: + case CDC_WSA_COMPANDER0_CTL0: + case CDC_WSA_COMPANDER0_CTL1: + case CDC_WSA_COMPANDER0_CTL2: + case CDC_WSA_COMPANDER0_CTL3: + case CDC_WSA_COMPANDER0_CTL4: + case CDC_WSA_COMPANDER0_CTL5: + case CDC_WSA_COMPANDER0_CTL7: + case CDC_WSA_COMPANDER1_CTL0: + case CDC_WSA_COMPANDER1_CTL1: + case CDC_WSA_COMPANDER1_CTL2: + case CDC_WSA_COMPANDER1_CTL3: + case CDC_WSA_COMPANDER1_CTL4: + case CDC_WSA_COMPANDER1_CTL5: + case CDC_WSA_COMPANDER1_CTL7: + case CDC_WSA_SOFTCLIP0_CRC: + case CDC_WSA_SOFTCLIP0_SOFTCLIP_CTRL: + case CDC_WSA_SOFTCLIP1_CRC: + case CDC_WSA_SOFTCLIP1_SOFTCLIP_CTRL: + case CDC_WSA_EC_HQ0_EC_REF_HQ_PATH_CTL: + case CDC_WSA_EC_HQ0_EC_REF_HQ_CFG0: + case CDC_WSA_EC_HQ1_EC_REF_HQ_PATH_CTL: + case CDC_WSA_EC_HQ1_EC_REF_HQ_CFG0: + case CDC_WSA_SPLINE_ASRC0_CLK_RST_CTL: + case CDC_WSA_SPLINE_ASRC0_CTL0: + case CDC_WSA_SPLINE_ASRC0_CTL1: + case CDC_WSA_SPLINE_ASRC0_FIFO_CTL: + case CDC_WSA_SPLINE_ASRC1_CLK_RST_CTL: + case CDC_WSA_SPLINE_ASRC1_CTL0: + case CDC_WSA_SPLINE_ASRC1_CTL1: + case CDC_WSA_SPLINE_ASRC1_FIFO_CTL: + return true; + } + + return false; +} + +static bool wsa_is_writeable_register(struct device *dev, unsigned int reg) +{ + bool ret; + + ret = wsa_is_rw_register(dev, reg); + if (!ret) + return wsa_is_wronly_register(dev, reg); + + return ret; +} + +static bool wsa_is_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CDC_WSA_INTR_CTRL_CLR_COMMIT: + case CDC_WSA_INTR_CTRL_PIN1_CLEAR0: + case CDC_WSA_INTR_CTRL_PIN2_CLEAR0: + case CDC_WSA_INTR_CTRL_PIN1_STATUS0: + case CDC_WSA_INTR_CTRL_PIN2_STATUS0: + case CDC_WSA_COMPANDER0_CTL6: + case CDC_WSA_COMPANDER1_CTL6: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FIFO: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FIFO: + return true; + } + + return wsa_is_rw_register(dev, reg); +} + +static bool wsa_is_volatile_register(struct device *dev, unsigned int reg) +{ + /* Update volatile list for rx/tx macros */ + switch (reg) { + case CDC_WSA_INTR_CTRL_PIN1_STATUS0: + case CDC_WSA_INTR_CTRL_PIN2_STATUS0: + case CDC_WSA_COMPANDER0_CTL6: + case CDC_WSA_COMPANDER1_CTL6: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMIN_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FMAX_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC0_STATUS_FIFO: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMIN_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_LSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FMAX_CNTR_MSB: + case CDC_WSA_SPLINE_ASRC1_STATUS_FIFO: + return true; + } + return false; +} + +const struct regmap_config wsa_regmap_config = { + .name = "wsa_macro", + .reg_bits = 16, + .val_bits = 32, /* 8 but with 32 bit read/write */ + .reg_stride = 4, + .cache_type = REGCACHE_FLAT, + .reg_defaults = wsa_defaults, + .num_reg_defaults = ARRAY_SIZE(wsa_defaults), + .max_register = WSA_MAX_OFFSET, + .writeable_reg = wsa_is_writeable_register, + .volatile_reg = wsa_is_volatile_register, + .readable_reg = wsa_is_readable_register, +}; + +/** + * wsa_macro_set_spkr_mode - Configures speaker compander and smartboost + * settings based on speaker mode. + * + * @component: codec instance + * @mode: Indicates speaker configuration mode. + * + * Returns 0 on success or -EINVAL on error. + */ +int wsa_macro_set_spkr_mode(struct snd_soc_component *component, int mode) +{ + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + wsa->spkr_mode = mode; + + switch (mode) { + case WSA_MACRO_SPKR_MODE_1: + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, 0x00); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL3, 0x80, 0x00); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, 0x00); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL7, 0x01, 0x00); + snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, 0x44); + snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, 0x44); + break; + default: + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, 0x80); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL3, 0x80, 0x80); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, 0x01); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL7, 0x01, 0x01); + snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, 0x58); + snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, 0x58); + break; + } + return 0; +} +EXPORT_SYMBOL(wsa_macro_set_spkr_mode); + +static int wsa_macro_set_prim_interpolator_rate(struct snd_soc_dai *dai, + u8 int_prim_fs_rate_reg_val, + u32 sample_rate) +{ + u8 int_1_mix1_inp; + u32 j, port = 0; + u16 int_mux_cfg0, int_mux_cfg1; + u16 int_fs_reg; + u8 int_mux_cfg0_val, int_mux_cfg1_val; + u8 inp0_sel, inp1_sel, inp2_sel; + struct snd_soc_component *component = dai->component; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + for_each_set_bit(port, &wsa->active_ch_mask[dai->id], WSA_MACRO_RX_MAX) { + int_1_mix1_inp = port; + if ((int_1_mix1_inp < WSA_MACRO_RX0) || (int_1_mix1_inp > WSA_MACRO_RX_MIX1)) { + dev_err(component->dev, "%s: Invalid RX port, Dai ID is %d\n", + __func__, dai->id); + return -EINVAL; + } + + int_mux_cfg0 = CDC_WSA_RX_INP_MUX_RX_INT0_CFG0; + + /* + * Loop through all interpolator MUX inputs and find out + * to which interpolator input, the cdc_dma rx port + * is connected + */ + for (j = 0; j < NUM_INTERPOLATORS; j++) { + int_mux_cfg1 = int_mux_cfg0 + WSA_MACRO_MUX_CFG1_OFFSET; + + int_mux_cfg0_val = snd_soc_component_read(component, + int_mux_cfg0); + int_mux_cfg1_val = snd_soc_component_read(component, + int_mux_cfg1); + inp0_sel = int_mux_cfg0_val & WSA_MACRO_MUX_INP_MASK1; + inp1_sel = (int_mux_cfg0_val >> + WSA_MACRO_MUX_INP_SHFT) & + WSA_MACRO_MUX_INP_MASK1; + inp2_sel = (int_mux_cfg1_val >> + WSA_MACRO_MUX_INP_SHFT) & + WSA_MACRO_MUX_INP_MASK1; + if ((inp0_sel == int_1_mix1_inp + INTn_1_INP_SEL_RX0) || + (inp1_sel == int_1_mix1_inp + INTn_1_INP_SEL_RX0) || + (inp2_sel == int_1_mix1_inp + INTn_1_INP_SEL_RX0)) { + int_fs_reg = CDC_WSA_RX0_RX_PATH_CTL + + WSA_MACRO_RX_PATH_OFFSET * j; + /* sample_rate is in Hz */ + snd_soc_component_update_bits(component, + int_fs_reg, + WSA_MACRO_FS_RATE_MASK, + int_prim_fs_rate_reg_val); + } + int_mux_cfg0 += WSA_MACRO_MUX_CFG_OFFSET; + } + } + + return 0; +} + +static int wsa_macro_set_mix_interpolator_rate(struct snd_soc_dai *dai, + u8 int_mix_fs_rate_reg_val, + u32 sample_rate) +{ + u8 int_2_inp; + u32 j, port = 0; + u16 int_mux_cfg1, int_fs_reg; + u8 int_mux_cfg1_val; + struct snd_soc_component *component = dai->component; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + for_each_set_bit(port, &wsa->active_ch_mask[dai->id], WSA_MACRO_RX_MAX) { + int_2_inp = port; + if ((int_2_inp < WSA_MACRO_RX0) || (int_2_inp > WSA_MACRO_RX_MIX1)) { + dev_err(component->dev, "%s: Invalid RX port, Dai ID is %d\n", + __func__, dai->id); + return -EINVAL; + } + + int_mux_cfg1 = CDC_WSA_RX_INP_MUX_RX_INT0_CFG1; + for (j = 0; j < NUM_INTERPOLATORS; j++) { + int_mux_cfg1_val = snd_soc_component_read(component, + int_mux_cfg1) & + WSA_MACRO_MUX_INP_MASK1; + if (int_mux_cfg1_val == int_2_inp + INTn_2_INP_SEL_RX0) { + int_fs_reg = CDC_WSA_RX0_RX_PATH_MIX_CTL + + WSA_MACRO_RX_PATH_OFFSET * j; + + snd_soc_component_update_bits(component, + int_fs_reg, + WSA_MACRO_FS_RATE_MASK, + int_mix_fs_rate_reg_val); + } + int_mux_cfg1 += WSA_MACRO_MUX_CFG_OFFSET; + } + } + return 0; +} + +static int wsa_macro_set_interpolator_rate(struct snd_soc_dai *dai, + u32 sample_rate) +{ + int rate_val = 0; + int i, ret; + + /* set mixing path rate */ + for (i = 0; i < ARRAY_SIZE(int_mix_sample_rate_val); i++) { + if (sample_rate == + int_mix_sample_rate_val[i].sample_rate) { + rate_val = + int_mix_sample_rate_val[i].rate_val; + break; + } + } + if ((i == ARRAY_SIZE(int_mix_sample_rate_val)) || + (rate_val < 0)) + goto prim_rate; + ret = wsa_macro_set_mix_interpolator_rate(dai, + (u8) rate_val, sample_rate); +prim_rate: + /* set primary path sample rate */ + for (i = 0; i < ARRAY_SIZE(int_prim_sample_rate_val); i++) { + if (sample_rate == + int_prim_sample_rate_val[i].sample_rate) { + rate_val = + int_prim_sample_rate_val[i].rate_val; + break; + } + } + if ((i == ARRAY_SIZE(int_prim_sample_rate_val)) || + (rate_val < 0)) + return -EINVAL; + ret = wsa_macro_set_prim_interpolator_rate(dai, + (u8) rate_val, sample_rate); + return ret; +} + +static int wsa_macro_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int ret; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + ret = wsa_macro_set_interpolator_rate(dai, params_rate(params)); + if (ret) { + dev_err(component->dev, + "%s: cannot set sample rate: %u\n", + __func__, params_rate(params)); + return ret; + } + break; + default: + break; + } + return 0; +} + +static int wsa_macro_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) +{ + struct snd_soc_component *component = dai->component; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + u16 val, mask = 0, cnt = 0, temp; + + switch (dai->id) { + case WSA_MACRO_AIF_VI: + *tx_slot = wsa->active_ch_mask[dai->id]; + *tx_num = wsa->active_ch_cnt[dai->id]; + break; + case WSA_MACRO_AIF1_PB: + case WSA_MACRO_AIF_MIX1_PB: + for_each_set_bit(temp, &wsa->active_ch_mask[dai->id], + WSA_MACRO_RX_MAX) { + mask |= (1 << temp); + if (++cnt == WSA_MACRO_MAX_DMA_CH_PER_PORT) + break; + } + if (mask & 0x0C) + mask = mask >> 0x2; + *rx_slot = mask; + *rx_num = cnt; + break; + case WSA_MACRO_AIF_ECHO: + val = snd_soc_component_read(component, + CDC_WSA_RX_INP_MUX_RX_MIX_CFG0); + if (val & WSA_MACRO_EC_MIX_TX1_MASK) { + mask |= 0x2; + cnt++; + } + if (val & WSA_MACRO_EC_MIX_TX0_MASK) { + mask |= 0x1; + cnt++; + } + *tx_slot = mask; + *tx_num = cnt; + break; + default: + dev_err(component->dev, "%s: Invalid AIF\n", __func__); + break; + } + return 0; +} + +static struct snd_soc_dai_ops wsa_macro_dai_ops = { + .hw_params = wsa_macro_hw_params, + .get_channel_map = wsa_macro_get_channel_map, +}; + +static struct snd_soc_dai_driver wsa_macro_dai[] = { + { + .name = "wsa_macro_rx1", + .id = WSA_MACRO_AIF1_PB, + .playback = { + .stream_name = "WSA_AIF1 Playback", + .rates = WSA_MACRO_RX_RATES, + .formats = WSA_MACRO_RX_FORMATS, + .rate_max = 384000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &wsa_macro_dai_ops, + }, + { + .name = "wsa_macro_rx_mix", + .id = WSA_MACRO_AIF_MIX1_PB, + .playback = { + .stream_name = "WSA_AIF_MIX1 Playback", + .rates = WSA_MACRO_RX_MIX_RATES, + .formats = WSA_MACRO_RX_FORMATS, + .rate_max = 192000, + .rate_min = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &wsa_macro_dai_ops, + }, + { + .name = "wsa_macro_vifeedback", + .id = WSA_MACRO_AIF_VI, + .capture = { + .stream_name = "WSA_AIF_VI Capture", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_48000, + .formats = WSA_MACRO_RX_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &wsa_macro_dai_ops, + }, + { + .name = "wsa_macro_echo", + .id = WSA_MACRO_AIF_ECHO, + .capture = { + .stream_name = "WSA_AIF_ECHO Capture", + .rates = WSA_MACRO_ECHO_RATES, + .formats = WSA_MACRO_ECHO_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &wsa_macro_dai_ops, + }, +}; + +static void wsa_macro_mclk_enable(struct wsa_macro *wsa, bool mclk_enable) +{ + struct regmap *regmap = wsa->regmap; + + if (mclk_enable) { + if (wsa->wsa_mclk_users == 0) { + regcache_mark_dirty(regmap); + regcache_sync_region(regmap, 0x0, WSA_MAX_OFFSET); + /* 9.6MHz MCLK, set value 0x00 if other frequency */ + regmap_update_bits(regmap, CDC_WSA_TOP_FREQ_MCLK, 0x01, 0x01); + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_MCLK_CONTROL, + CDC_WSA_MCLK_EN_MASK, + CDC_WSA_MCLK_ENABLE); + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_FS_CNT_CONTROL, + CDC_WSA_FS_CNT_EN_MASK, + CDC_WSA_FS_CNT_ENABLE); + } + wsa->wsa_mclk_users++; + } else { + if (wsa->wsa_mclk_users <= 0) { + dev_err(wsa->dev, "clock already disabled\n"); + wsa->wsa_mclk_users = 0; + return; + } + wsa->wsa_mclk_users--; + if (wsa->wsa_mclk_users == 0) { + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_FS_CNT_CONTROL, + CDC_WSA_FS_CNT_EN_MASK, + CDC_WSA_FS_CNT_DISABLE); + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_MCLK_CONTROL, + CDC_WSA_MCLK_EN_MASK, + CDC_WSA_MCLK_DISABLE); + } + } +} + +static int wsa_macro_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + wsa_macro_mclk_enable(wsa, event == SND_SOC_DAPM_PRE_PMU); + return 0; +} + +static int wsa_macro_enable_vi_feedback(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + u32 tx_reg0, tx_reg1; + + if (test_bit(WSA_MACRO_TX0, &wsa->active_ch_mask[WSA_MACRO_AIF_VI])) { + tx_reg0 = CDC_WSA_TX0_SPKR_PROT_PATH_CTL; + tx_reg1 = CDC_WSA_TX1_SPKR_PROT_PATH_CTL; + } else if (test_bit(WSA_MACRO_TX1, &wsa->active_ch_mask[WSA_MACRO_AIF_VI])) { + tx_reg0 = CDC_WSA_TX2_SPKR_PROT_PATH_CTL; + tx_reg1 = CDC_WSA_TX3_SPKR_PROT_PATH_CTL; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Enable V&I sensing */ + snd_soc_component_update_bits(component, tx_reg0, + CDC_WSA_TX_SPKR_PROT_RESET_MASK, + CDC_WSA_TX_SPKR_PROT_RESET); + snd_soc_component_update_bits(component, tx_reg1, + CDC_WSA_TX_SPKR_PROT_RESET_MASK, + CDC_WSA_TX_SPKR_PROT_RESET); + snd_soc_component_update_bits(component, tx_reg0, + CDC_WSA_TX_SPKR_PROT_PCM_RATE_MASK, + CDC_WSA_TX_SPKR_PROT_PCM_RATE_8K); + snd_soc_component_update_bits(component, tx_reg1, + CDC_WSA_TX_SPKR_PROT_PCM_RATE_MASK, + CDC_WSA_TX_SPKR_PROT_PCM_RATE_8K); + snd_soc_component_update_bits(component, tx_reg0, + CDC_WSA_TX_SPKR_PROT_CLK_EN_MASK, + CDC_WSA_TX_SPKR_PROT_CLK_ENABLE); + snd_soc_component_update_bits(component, tx_reg1, + CDC_WSA_TX_SPKR_PROT_CLK_EN_MASK, + CDC_WSA_TX_SPKR_PROT_CLK_ENABLE); + snd_soc_component_update_bits(component, tx_reg0, + CDC_WSA_TX_SPKR_PROT_RESET_MASK, + CDC_WSA_TX_SPKR_PROT_NO_RESET); + snd_soc_component_update_bits(component, tx_reg1, + CDC_WSA_TX_SPKR_PROT_RESET_MASK, + CDC_WSA_TX_SPKR_PROT_NO_RESET); + break; + case SND_SOC_DAPM_POST_PMD: + /* Disable V&I sensing */ + snd_soc_component_update_bits(component, tx_reg0, + CDC_WSA_TX_SPKR_PROT_RESET_MASK, + CDC_WSA_TX_SPKR_PROT_RESET); + snd_soc_component_update_bits(component, tx_reg1, + CDC_WSA_TX_SPKR_PROT_RESET_MASK, + CDC_WSA_TX_SPKR_PROT_RESET); + snd_soc_component_update_bits(component, tx_reg0, + CDC_WSA_TX_SPKR_PROT_CLK_EN_MASK, + CDC_WSA_TX_SPKR_PROT_CLK_DISABLE); + snd_soc_component_update_bits(component, tx_reg1, + CDC_WSA_TX_SPKR_PROT_CLK_EN_MASK, + CDC_WSA_TX_SPKR_PROT_CLK_DISABLE); + break; + } + + return 0; +} + +static int wsa_macro_enable_mix_path(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 gain_reg; + int offset_val = 0; + int val = 0; + + switch (w->reg) { + case CDC_WSA_RX0_RX_PATH_MIX_CTL: + gain_reg = CDC_WSA_RX0_RX_VOL_MIX_CTL; + break; + case CDC_WSA_RX1_RX_PATH_MIX_CTL: + gain_reg = CDC_WSA_RX1_RX_VOL_MIX_CTL; + break; + default: + return 0; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = snd_soc_component_read(component, gain_reg); + val += offset_val; + snd_soc_component_write(component, gain_reg, val); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, w->reg, + CDC_WSA_RX_PATH_MIX_CLK_EN_MASK, + CDC_WSA_RX_PATH_MIX_CLK_DISABLE); + break; + } + + return 0; +} + +static void wsa_macro_hd2_control(struct snd_soc_component *component, + u16 reg, int event) +{ + u16 hd2_scale_reg; + u16 hd2_enable_reg = 0; + + if (reg == CDC_WSA_RX0_RX_PATH_CTL) { + hd2_scale_reg = CDC_WSA_RX0_RX_PATH_SEC3; + hd2_enable_reg = CDC_WSA_RX0_RX_PATH_CFG0; + } + if (reg == CDC_WSA_RX1_RX_PATH_CTL) { + hd2_scale_reg = CDC_WSA_RX1_RX_PATH_SEC3; + hd2_enable_reg = CDC_WSA_RX1_RX_PATH_CFG0; + } + + if (hd2_enable_reg && SND_SOC_DAPM_EVENT_ON(event)) { + snd_soc_component_update_bits(component, hd2_scale_reg, + CDC_WSA_RX_PATH_HD2_ALPHA_MASK, + 0x10); + snd_soc_component_update_bits(component, hd2_scale_reg, + CDC_WSA_RX_PATH_HD2_SCALE_MASK, + 0x1); + snd_soc_component_update_bits(component, hd2_enable_reg, + CDC_WSA_RX_PATH_HD2_EN_MASK, + CDC_WSA_RX_PATH_HD2_ENABLE); + } + + if (hd2_enable_reg && SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, hd2_enable_reg, + CDC_WSA_RX_PATH_HD2_EN_MASK, 0); + snd_soc_component_update_bits(component, hd2_scale_reg, + CDC_WSA_RX_PATH_HD2_SCALE_MASK, + 0); + snd_soc_component_update_bits(component, hd2_scale_reg, + CDC_WSA_RX_PATH_HD2_ALPHA_MASK, + 0); + } +} + +static int wsa_macro_config_compander(struct snd_soc_component *component, + int comp, int event) +{ + u16 comp_ctl0_reg, rx_path_cfg0_reg; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + if (!wsa->comp_enabled[comp]) + return 0; + + comp_ctl0_reg = CDC_WSA_COMPANDER0_CTL0 + + (comp * WSA_MACRO_RX_COMP_OFFSET); + rx_path_cfg0_reg = CDC_WSA_RX0_RX_PATH_CFG0 + + (comp * WSA_MACRO_RX_PATH_OFFSET); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* Enable Compander Clock */ + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_CLK_EN_MASK, + CDC_WSA_COMPANDER_CLK_ENABLE); + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_SOFT_RST_MASK, + CDC_WSA_COMPANDER_SOFT_RST_ENABLE); + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_SOFT_RST_MASK, + 0); + snd_soc_component_update_bits(component, rx_path_cfg0_reg, + CDC_WSA_RX_PATH_COMP_EN_MASK, + CDC_WSA_RX_PATH_COMP_ENABLE); + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_HALT_MASK, + CDC_WSA_COMPANDER_HALT); + snd_soc_component_update_bits(component, rx_path_cfg0_reg, + CDC_WSA_RX_PATH_COMP_EN_MASK, 0); + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_SOFT_RST_MASK, + CDC_WSA_COMPANDER_SOFT_RST_ENABLE); + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_SOFT_RST_MASK, + 0); + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_CLK_EN_MASK, 0); + snd_soc_component_update_bits(component, comp_ctl0_reg, + CDC_WSA_COMPANDER_HALT_MASK, 0); + } + + return 0; +} + +static void wsa_macro_enable_softclip_clk(struct snd_soc_component *component, + struct wsa_macro *wsa, + int path, + bool enable) +{ + u16 softclip_clk_reg = CDC_WSA_SOFTCLIP0_CRC + + (path * WSA_MACRO_RX_SOFTCLIP_OFFSET); + u8 softclip_mux_mask = (1 << path); + u8 softclip_mux_value = (1 << path); + + if (enable) { + if (wsa->softclip_clk_users[path] == 0) { + snd_soc_component_update_bits(component, + softclip_clk_reg, + CDC_WSA_SOFTCLIP_CLK_EN_MASK, + CDC_WSA_SOFTCLIP_CLK_ENABLE); + snd_soc_component_update_bits(component, + CDC_WSA_RX_INP_MUX_SOFTCLIP_CFG0, + softclip_mux_mask, softclip_mux_value); + } + wsa->softclip_clk_users[path]++; + } else { + wsa->softclip_clk_users[path]--; + if (wsa->softclip_clk_users[path] == 0) { + snd_soc_component_update_bits(component, + softclip_clk_reg, + CDC_WSA_SOFTCLIP_CLK_EN_MASK, + 0); + snd_soc_component_update_bits(component, + CDC_WSA_RX_INP_MUX_SOFTCLIP_CFG0, + softclip_mux_mask, 0x00); + } + } +} + +static int wsa_macro_config_softclip(struct snd_soc_component *component, + int path, int event) +{ + u16 softclip_ctrl_reg = 0; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + int softclip_path = 0; + + if (path == WSA_MACRO_COMP1) + softclip_path = WSA_MACRO_SOFTCLIP0; + else if (path == WSA_MACRO_COMP2) + softclip_path = WSA_MACRO_SOFTCLIP1; + + if (!wsa->is_softclip_on[softclip_path]) + return 0; + + softclip_ctrl_reg = CDC_WSA_SOFTCLIP0_SOFTCLIP_CTRL + + (softclip_path * WSA_MACRO_RX_SOFTCLIP_OFFSET); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* Enable Softclip clock and mux */ + wsa_macro_enable_softclip_clk(component, wsa, softclip_path, + true); + /* Enable Softclip control */ + snd_soc_component_update_bits(component, softclip_ctrl_reg, + CDC_WSA_SOFTCLIP_EN_MASK, + CDC_WSA_SOFTCLIP_ENABLE); + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, softclip_ctrl_reg, + CDC_WSA_SOFTCLIP_EN_MASK, 0); + wsa_macro_enable_softclip_clk(component, wsa, softclip_path, + false); + } + + return 0; +} + +static bool wsa_macro_adie_lb(struct snd_soc_component *component, + int interp_idx) +{ + u16 int_mux_cfg0 = 0, int_mux_cfg1 = 0; + u8 int_mux_cfg0_val = 0, int_mux_cfg1_val = 0; + u8 int_n_inp0 = 0, int_n_inp1 = 0, int_n_inp2 = 0; + + int_mux_cfg0 = CDC_WSA_RX_INP_MUX_RX_INT0_CFG0 + interp_idx * 8; + int_mux_cfg1 = int_mux_cfg0 + 4; + int_mux_cfg0_val = snd_soc_component_read(component, int_mux_cfg0); + int_mux_cfg1_val = snd_soc_component_read(component, int_mux_cfg1); + + int_n_inp0 = int_mux_cfg0_val & 0x0F; + if (int_n_inp0 == INTn_1_INP_SEL_DEC0 || + int_n_inp0 == INTn_1_INP_SEL_DEC1) + return true; + + int_n_inp1 = int_mux_cfg0_val >> 4; + if (int_n_inp1 == INTn_1_INP_SEL_DEC0 || + int_n_inp1 == INTn_1_INP_SEL_DEC1) + return true; + + int_n_inp2 = int_mux_cfg1_val >> 4; + if (int_n_inp2 == INTn_1_INP_SEL_DEC0 || + int_n_inp2 == INTn_1_INP_SEL_DEC1) + return true; + + return false; +} + +static int wsa_macro_enable_main_path(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 reg = 0; + + reg = CDC_WSA_RX0_RX_PATH_CTL + WSA_MACRO_RX_PATH_OFFSET * w->shift; + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (wsa_macro_adie_lb(component, w->shift)) { + snd_soc_component_update_bits(component, reg, + CDC_WSA_RX_PATH_CLK_EN_MASK, + CDC_WSA_RX_PATH_CLK_ENABLE); + } + break; + default: + break; + } + return 0; +} + +static int wsa_macro_interp_get_primary_reg(u16 reg, u16 *ind) +{ + u16 prim_int_reg = 0; + + switch (reg) { + case CDC_WSA_RX0_RX_PATH_CTL: + case CDC_WSA_RX0_RX_PATH_MIX_CTL: + prim_int_reg = CDC_WSA_RX0_RX_PATH_CTL; + *ind = 0; + break; + case CDC_WSA_RX1_RX_PATH_CTL: + case CDC_WSA_RX1_RX_PATH_MIX_CTL: + prim_int_reg = CDC_WSA_RX1_RX_PATH_CTL; + *ind = 1; + break; + } + + return prim_int_reg; +} + +static int wsa_macro_enable_prim_interpolator(struct snd_soc_component *component, + u16 reg, int event) +{ + u16 prim_int_reg; + u16 ind = 0; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + prim_int_reg = wsa_macro_interp_get_primary_reg(reg, &ind); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wsa->prim_int_users[ind]++; + if (wsa->prim_int_users[ind] == 1) { + snd_soc_component_update_bits(component, + prim_int_reg + WSA_MACRO_RX_PATH_CFG3_OFFSET, + CDC_WSA_RX_DC_DCOEFF_MASK, + 0x3); + snd_soc_component_update_bits(component, prim_int_reg, + CDC_WSA_RX_PATH_PGA_MUTE_EN_MASK, + CDC_WSA_RX_PATH_PGA_MUTE_ENABLE); + wsa_macro_hd2_control(component, prim_int_reg, event); + snd_soc_component_update_bits(component, + prim_int_reg + WSA_MACRO_RX_PATH_DSMDEM_OFFSET, + CDC_WSA_RX_DSMDEM_CLK_EN_MASK, + CDC_WSA_RX_DSMDEM_CLK_ENABLE); + } + if ((reg != prim_int_reg) && + ((snd_soc_component_read( + component, prim_int_reg)) & 0x10)) + snd_soc_component_update_bits(component, reg, + 0x10, 0x10); + break; + case SND_SOC_DAPM_POST_PMD: + wsa->prim_int_users[ind]--; + if (wsa->prim_int_users[ind] == 0) { + snd_soc_component_update_bits(component, + prim_int_reg + WSA_MACRO_RX_PATH_DSMDEM_OFFSET, + CDC_WSA_RX_DSMDEM_CLK_EN_MASK, 0); + wsa_macro_hd2_control(component, prim_int_reg, event); + } + break; + } + + return 0; +} + +static int wsa_macro_config_ear_spkr_gain(struct snd_soc_component *component, + struct wsa_macro *wsa, + int event, int gain_reg) +{ + int comp_gain_offset, val; + + switch (wsa->spkr_mode) { + /* Compander gain in WSA_MACRO_SPKR_MODE1 case is 12 dB */ + case WSA_MACRO_SPKR_MODE_1: + comp_gain_offset = -12; + break; + /* Default case compander gain is 15 dB */ + default: + comp_gain_offset = -15; + break; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Apply ear spkr gain only if compander is enabled */ + if (wsa->comp_enabled[WSA_MACRO_COMP1] && + (gain_reg == CDC_WSA_RX0_RX_VOL_CTL) && + (wsa->ear_spkr_gain != 0)) { + /* For example, val is -8(-12+5-1) for 4dB of gain */ + val = comp_gain_offset + wsa->ear_spkr_gain - 1; + snd_soc_component_write(component, gain_reg, val); + } + break; + case SND_SOC_DAPM_POST_PMD: + /* + * Reset RX0 volume to 0 dB if compander is enabled and + * ear_spkr_gain is non-zero. + */ + if (wsa->comp_enabled[WSA_MACRO_COMP1] && + (gain_reg == CDC_WSA_RX0_RX_VOL_CTL) && + (wsa->ear_spkr_gain != 0)) { + snd_soc_component_write(component, gain_reg, 0x0); + } + break; + } + + return 0; +} + +static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 gain_reg; + u16 reg; + int val; + int offset_val = 0; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + if (w->shift == WSA_MACRO_COMP1) { + reg = CDC_WSA_RX0_RX_PATH_CTL; + gain_reg = CDC_WSA_RX0_RX_VOL_CTL; + } else if (w->shift == WSA_MACRO_COMP2) { + reg = CDC_WSA_RX1_RX_PATH_CTL; + gain_reg = CDC_WSA_RX1_RX_VOL_CTL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Reset if needed */ + wsa_macro_enable_prim_interpolator(component, reg, event); + break; + case SND_SOC_DAPM_POST_PMU: + wsa_macro_config_compander(component, w->shift, event); + wsa_macro_config_softclip(component, w->shift, event); + /* apply gain after int clk is enabled */ + if ((wsa->spkr_gain_offset == WSA_MACRO_GAIN_OFFSET_M1P5_DB) && + (wsa->comp_enabled[WSA_MACRO_COMP1] || + wsa->comp_enabled[WSA_MACRO_COMP2])) { + snd_soc_component_update_bits(component, + CDC_WSA_RX0_RX_PATH_SEC1, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + snd_soc_component_update_bits(component, + CDC_WSA_RX0_RX_PATH_MIX_SEC0, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + snd_soc_component_update_bits(component, + CDC_WSA_RX1_RX_PATH_SEC1, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + snd_soc_component_update_bits(component, + CDC_WSA_RX1_RX_PATH_MIX_SEC0, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + offset_val = -2; + } + val = snd_soc_component_read(component, gain_reg); + val += offset_val; + snd_soc_component_write(component, gain_reg, val); + wsa_macro_config_ear_spkr_gain(component, wsa, + event, gain_reg); + break; + case SND_SOC_DAPM_POST_PMD: + wsa_macro_config_compander(component, w->shift, event); + wsa_macro_config_softclip(component, w->shift, event); + wsa_macro_enable_prim_interpolator(component, reg, event); + if ((wsa->spkr_gain_offset == WSA_MACRO_GAIN_OFFSET_M1P5_DB) && + (wsa->comp_enabled[WSA_MACRO_COMP1] || + wsa->comp_enabled[WSA_MACRO_COMP2])) { + snd_soc_component_update_bits(component, + CDC_WSA_RX0_RX_PATH_SEC1, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_DISABLE); + snd_soc_component_update_bits(component, + CDC_WSA_RX0_RX_PATH_MIX_SEC0, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_DISABLE); + snd_soc_component_update_bits(component, + CDC_WSA_RX1_RX_PATH_SEC1, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_DISABLE); + snd_soc_component_update_bits(component, + CDC_WSA_RX1_RX_PATH_MIX_SEC0, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_DISABLE); + offset_val = 2; + val = snd_soc_component_read(component, gain_reg); + val += offset_val; + snd_soc_component_write(component, gain_reg, val); + } + wsa_macro_config_ear_spkr_gain(component, wsa, + event, gain_reg); + break; + } + + return 0; +} + +static int wsa_macro_spk_boost_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 boost_path_ctl, boost_path_cfg1; + u16 reg, reg_mix; + + if (!strcmp(w->name, "WSA_RX INT0 CHAIN")) { + boost_path_ctl = CDC_WSA_BOOST0_BOOST_PATH_CTL; + boost_path_cfg1 = CDC_WSA_RX0_RX_PATH_CFG1; + reg = CDC_WSA_RX0_RX_PATH_CTL; + reg_mix = CDC_WSA_RX0_RX_PATH_MIX_CTL; + } else if (!strcmp(w->name, "WSA_RX INT1 CHAIN")) { + boost_path_ctl = CDC_WSA_BOOST1_BOOST_PATH_CTL; + boost_path_cfg1 = CDC_WSA_RX1_RX_PATH_CFG1; + reg = CDC_WSA_RX1_RX_PATH_CTL; + reg_mix = CDC_WSA_RX1_RX_PATH_MIX_CTL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, boost_path_cfg1, + CDC_WSA_RX_PATH_SMART_BST_EN_MASK, + CDC_WSA_RX_PATH_SMART_BST_ENABLE); + snd_soc_component_update_bits(component, boost_path_ctl, + CDC_WSA_BOOST_PATH_CLK_EN_MASK, + CDC_WSA_BOOST_PATH_CLK_ENABLE); + if ((snd_soc_component_read(component, reg_mix)) & 0x10) + snd_soc_component_update_bits(component, reg_mix, + 0x10, 0x00); + break; + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, reg, 0x10, 0x00); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, boost_path_ctl, + CDC_WSA_BOOST_PATH_CLK_EN_MASK, + CDC_WSA_BOOST_PATH_CLK_DISABLE); + snd_soc_component_update_bits(component, boost_path_cfg1, + CDC_WSA_RX_PATH_SMART_BST_EN_MASK, + CDC_WSA_RX_PATH_SMART_BST_DISABLE); + break; + } + + return 0; +} + +static int wsa_macro_enable_echo(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + u16 val, ec_tx = 0, ec_hq_reg; + + val = snd_soc_component_read(component, CDC_WSA_RX_INP_MUX_RX_MIX_CFG0); + + switch (w->shift) { + case WSA_MACRO_EC0_MUX: + val = val & CDC_WSA_RX_MIX_TX0_SEL_MASK; + ec_tx = val - 1; + break; + case WSA_MACRO_EC1_MUX: + val = val & CDC_WSA_RX_MIX_TX1_SEL_MASK; + ec_tx = (val >> CDC_WSA_RX_MIX_TX1_SEL_SHFT) - 1; + break; + } + + if (wsa->ec_hq[ec_tx]) { + ec_hq_reg = CDC_WSA_EC_HQ0_EC_REF_HQ_PATH_CTL + 0x40 * ec_tx; + snd_soc_component_update_bits(component, ec_hq_reg, + CDC_WSA_EC_HQ_EC_CLK_EN_MASK, + CDC_WSA_EC_HQ_EC_CLK_ENABLE); + ec_hq_reg = CDC_WSA_EC_HQ0_EC_REF_HQ_CFG0 + 0x40 * ec_tx; + /* default set to 48k */ + snd_soc_component_update_bits(component, ec_hq_reg, + CDC_WSA_EC_HQ_EC_REF_PCM_RATE_MASK, + CDC_WSA_EC_HQ_EC_REF_PCM_RATE_48K); + } + + return 0; +} + +static int wsa_macro_get_ec_hq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + int ec_tx = ((struct soc_mixer_control *) + kcontrol->private_value)->shift; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wsa->ec_hq[ec_tx]; + return 0; +} + +static int wsa_macro_set_ec_hq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + int ec_tx = ((struct soc_mixer_control *) + kcontrol->private_value)->shift; + int value = ucontrol->value.integer.value[0]; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + wsa->ec_hq[ec_tx] = value; + + return 0; +} + +static int wsa_macro_get_compander(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int comp = ((struct soc_mixer_control *) kcontrol->private_value)->shift; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wsa->comp_enabled[comp]; + return 0; +} + +static int wsa_macro_set_compander(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int comp = ((struct soc_mixer_control *) + kcontrol->private_value)->shift; + int value = ucontrol->value.integer.value[0]; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + wsa->comp_enabled[comp] = value; + + return 0; +} + +static int wsa_macro_ear_spkr_pa_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wsa->ear_spkr_gain; + + return 0; +} + +static int wsa_macro_ear_spkr_pa_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + wsa->ear_spkr_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int wsa_macro_rx_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = + snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *component = + snd_soc_dapm_to_component(widget->dapm); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = + wsa->rx_port_value[widget->shift]; + return 0; +} + +static int wsa_macro_rx_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = + snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *component = + snd_soc_dapm_to_component(widget->dapm); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_dapm_update *update = NULL; + u32 rx_port_value = ucontrol->value.integer.value[0]; + u32 bit_input = 0; + u32 aif_rst; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + + aif_rst = wsa->rx_port_value[widget->shift]; + if (!rx_port_value) { + if (aif_rst == 0) { + dev_err(component->dev, "%s: AIF reset already\n", __func__); + return 0; + } + if (aif_rst >= WSA_MACRO_RX_MAX) { + dev_err(component->dev, "%s: Invalid AIF reset\n", __func__); + return 0; + } + } + wsa->rx_port_value[widget->shift] = rx_port_value; + + bit_input = widget->shift; + + switch (rx_port_value) { + case 0: + if (wsa->active_ch_cnt[aif_rst]) { + clear_bit(bit_input, + &wsa->active_ch_mask[aif_rst]); + wsa->active_ch_cnt[aif_rst]--; + } + break; + case 1: + case 2: + set_bit(bit_input, + &wsa->active_ch_mask[rx_port_value]); + wsa->active_ch_cnt[rx_port_value]++; + break; + default: + dev_err(component->dev, + "%s: Invalid AIF_ID for WSA RX MUX %d\n", + __func__, rx_port_value); + return -EINVAL; + } + + snd_soc_dapm_mux_update_power(widget->dapm, kcontrol, + rx_port_value, e, update); + return 0; +} + +static int wsa_macro_soft_clip_enable_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + int path = ((struct soc_mixer_control *)kcontrol->private_value)->shift; + + ucontrol->value.integer.value[0] = wsa->is_softclip_on[path]; + + return 0; +} + +static int wsa_macro_soft_clip_enable_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + int path = ((struct soc_mixer_control *) kcontrol->private_value)->shift; + + wsa->is_softclip_on[path] = ucontrol->value.integer.value[0]; + + return 0; +} + +static const struct snd_kcontrol_new wsa_macro_snd_controls[] = { + SOC_ENUM_EXT("EAR SPKR PA Gain", wsa_macro_ear_spkr_pa_gain_enum, + wsa_macro_ear_spkr_pa_gain_get, + wsa_macro_ear_spkr_pa_gain_put), + SOC_SINGLE_EXT("WSA_Softclip0 Enable", SND_SOC_NOPM, + WSA_MACRO_SOFTCLIP0, 1, 0, + wsa_macro_soft_clip_enable_get, + wsa_macro_soft_clip_enable_put), + SOC_SINGLE_EXT("WSA_Softclip1 Enable", SND_SOC_NOPM, + WSA_MACRO_SOFTCLIP1, 1, 0, + wsa_macro_soft_clip_enable_get, + wsa_macro_soft_clip_enable_put), + + SOC_SINGLE_S8_TLV("WSA_RX0 Digital Volume", CDC_WSA_RX0_RX_VOL_CTL, + -84, 40, digital_gain), + SOC_SINGLE_S8_TLV("WSA_RX1 Digital Volume", CDC_WSA_RX1_RX_VOL_CTL, + -84, 40, digital_gain), + + SOC_SINGLE("WSA_RX0 Digital Mute", CDC_WSA_RX0_RX_PATH_CTL, 4, 1, 0), + SOC_SINGLE("WSA_RX1 Digital Mute", CDC_WSA_RX1_RX_PATH_CTL, 4, 1, 0), + SOC_SINGLE("WSA_RX0_MIX Digital Mute", CDC_WSA_RX0_RX_PATH_MIX_CTL, 4, + 1, 0), + SOC_SINGLE("WSA_RX1_MIX Digital Mute", CDC_WSA_RX1_RX_PATH_MIX_CTL, 4, + 1, 0), + SOC_SINGLE_EXT("WSA_COMP1 Switch", SND_SOC_NOPM, WSA_MACRO_COMP1, 1, 0, + wsa_macro_get_compander, wsa_macro_set_compander), + SOC_SINGLE_EXT("WSA_COMP2 Switch", SND_SOC_NOPM, WSA_MACRO_COMP2, 1, 0, + wsa_macro_get_compander, wsa_macro_set_compander), + SOC_SINGLE_EXT("WSA_RX0 EC_HQ Switch", SND_SOC_NOPM, WSA_MACRO_RX0, 1, 0, + wsa_macro_get_ec_hq, wsa_macro_set_ec_hq), + SOC_SINGLE_EXT("WSA_RX1 EC_HQ Switch", SND_SOC_NOPM, WSA_MACRO_RX1, 1, 0, + wsa_macro_get_ec_hq, wsa_macro_set_ec_hq), +}; + +static const struct soc_enum rx_mux_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(rx_mux_text), rx_mux_text); + +static const struct snd_kcontrol_new rx_mux[WSA_MACRO_RX_MAX] = { + SOC_DAPM_ENUM_EXT("WSA RX0 Mux", rx_mux_enum, + wsa_macro_rx_mux_get, wsa_macro_rx_mux_put), + SOC_DAPM_ENUM_EXT("WSA RX1 Mux", rx_mux_enum, + wsa_macro_rx_mux_get, wsa_macro_rx_mux_put), + SOC_DAPM_ENUM_EXT("WSA RX_MIX0 Mux", rx_mux_enum, + wsa_macro_rx_mux_get, wsa_macro_rx_mux_put), + SOC_DAPM_ENUM_EXT("WSA RX_MIX1 Mux", rx_mux_enum, + wsa_macro_rx_mux_get, wsa_macro_rx_mux_put), +}; + +static int wsa_macro_vi_feed_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(widget->dapm); + struct soc_mixer_control *mixer = (struct soc_mixer_control *)kcontrol->private_value; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + u32 spk_tx_id = mixer->shift; + u32 dai_id = widget->shift; + + if (test_bit(spk_tx_id, &wsa->active_ch_mask[dai_id])) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int wsa_macro_vi_feed_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(widget->dapm); + struct soc_mixer_control *mixer = (struct soc_mixer_control *)kcontrol->private_value; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + u32 enable = ucontrol->value.integer.value[0]; + u32 spk_tx_id = mixer->shift; + + if (enable) { + if (spk_tx_id == WSA_MACRO_TX0 && + !test_bit(WSA_MACRO_TX0, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI])) { + set_bit(WSA_MACRO_TX0, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI]); + wsa->active_ch_cnt[WSA_MACRO_AIF_VI]++; + } + if (spk_tx_id == WSA_MACRO_TX1 && + !test_bit(WSA_MACRO_TX1, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI])) { + set_bit(WSA_MACRO_TX1, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI]); + wsa->active_ch_cnt[WSA_MACRO_AIF_VI]++; + } + } else { + if (spk_tx_id == WSA_MACRO_TX0 && + test_bit(WSA_MACRO_TX0, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI])) { + clear_bit(WSA_MACRO_TX0, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI]); + wsa->active_ch_cnt[WSA_MACRO_AIF_VI]--; + } + if (spk_tx_id == WSA_MACRO_TX1 && + test_bit(WSA_MACRO_TX1, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI])) { + clear_bit(WSA_MACRO_TX1, + &wsa->active_ch_mask[WSA_MACRO_AIF_VI]); + wsa->active_ch_cnt[WSA_MACRO_AIF_VI]--; + } + } + snd_soc_dapm_mixer_update_power(widget->dapm, kcontrol, enable, NULL); + + return 0; +} + +static const struct snd_kcontrol_new aif_vi_mixer[] = { + SOC_SINGLE_EXT("WSA_SPKR_VI_1", SND_SOC_NOPM, WSA_MACRO_TX0, 1, 0, + wsa_macro_vi_feed_mixer_get, + wsa_macro_vi_feed_mixer_put), + SOC_SINGLE_EXT("WSA_SPKR_VI_2", SND_SOC_NOPM, WSA_MACRO_TX1, 1, 0, + wsa_macro_vi_feed_mixer_get, + wsa_macro_vi_feed_mixer_put), +}; + +static const struct snd_soc_dapm_widget wsa_macro_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("WSA AIF1 PB", "WSA_AIF1 Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("WSA AIF_MIX1 PB", "WSA_AIF_MIX1 Playback", 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT_E("WSA AIF_VI", "WSA_AIF_VI Capture", 0, + SND_SOC_NOPM, WSA_MACRO_AIF_VI, 0, + wsa_macro_enable_vi_feedback, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("WSA AIF_ECHO", "WSA_AIF_ECHO Capture", 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MIXER("WSA_AIF_VI Mixer", SND_SOC_NOPM, WSA_MACRO_AIF_VI, + 0, aif_vi_mixer, ARRAY_SIZE(aif_vi_mixer)), + SND_SOC_DAPM_MUX_E("WSA RX_MIX EC0_MUX", SND_SOC_NOPM, + WSA_MACRO_EC0_MUX, 0, + &rx_mix_ec0_mux, wsa_macro_enable_echo, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX_E("WSA RX_MIX EC1_MUX", SND_SOC_NOPM, + WSA_MACRO_EC1_MUX, 0, + &rx_mix_ec1_mux, wsa_macro_enable_echo, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("WSA RX0 MUX", SND_SOC_NOPM, WSA_MACRO_RX0, 0, + &rx_mux[WSA_MACRO_RX0]), + SND_SOC_DAPM_MUX("WSA RX1 MUX", SND_SOC_NOPM, WSA_MACRO_RX1, 0, + &rx_mux[WSA_MACRO_RX1]), + SND_SOC_DAPM_MUX("WSA RX_MIX0 MUX", SND_SOC_NOPM, WSA_MACRO_RX_MIX0, 0, + &rx_mux[WSA_MACRO_RX_MIX0]), + SND_SOC_DAPM_MUX("WSA RX_MIX1 MUX", SND_SOC_NOPM, WSA_MACRO_RX_MIX1, 0, + &rx_mux[WSA_MACRO_RX_MIX1]), + + SND_SOC_DAPM_MIXER("WSA RX0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("WSA RX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("WSA RX_MIX0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("WSA RX_MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("WSA_RX0 INP0", SND_SOC_NOPM, 0, 0, &rx0_prim_inp0_mux), + SND_SOC_DAPM_MUX("WSA_RX0 INP1", SND_SOC_NOPM, 0, 0, &rx0_prim_inp1_mux), + SND_SOC_DAPM_MUX("WSA_RX0 INP2", SND_SOC_NOPM, 0, 0, &rx0_prim_inp2_mux), + SND_SOC_DAPM_MUX_E("WSA_RX0 MIX INP", CDC_WSA_RX0_RX_PATH_MIX_CTL, + 0, 0, &rx0_mix_mux, wsa_macro_enable_mix_path, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("WSA_RX1 INP0", SND_SOC_NOPM, 0, 0, &rx1_prim_inp0_mux), + SND_SOC_DAPM_MUX("WSA_RX1 INP1", SND_SOC_NOPM, 0, 0, &rx1_prim_inp1_mux), + SND_SOC_DAPM_MUX("WSA_RX1 INP2", SND_SOC_NOPM, 0, 0, &rx1_prim_inp2_mux), + SND_SOC_DAPM_MUX_E("WSA_RX1 MIX INP", CDC_WSA_RX1_RX_PATH_MIX_CTL, + 0, 0, &rx1_mix_mux, wsa_macro_enable_mix_path, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("WSA_RX INT0 MIX", SND_SOC_NOPM, 0, 0, NULL, 0, + wsa_macro_enable_main_path, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER_E("WSA_RX INT1 MIX", SND_SOC_NOPM, 1, 0, NULL, 0, + wsa_macro_enable_main_path, SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_MIXER("WSA_RX INT0 SEC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("WSA_RX INT1 SEC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("WSA_RX0 INT0 SIDETONE MIX", CDC_WSA_RX0_RX_PATH_CFG1, + 4, 0, &rx0_sidetone_mix_mux), + + SND_SOC_DAPM_INPUT("WSA SRC0_INP"), + SND_SOC_DAPM_INPUT("WSA_TX DEC0_INP"), + SND_SOC_DAPM_INPUT("WSA_TX DEC1_INP"), + + SND_SOC_DAPM_MIXER_E("WSA_RX INT0 INTERP", SND_SOC_NOPM, + WSA_MACRO_COMP1, 0, NULL, 0, + wsa_macro_enable_interpolator, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("WSA_RX INT1 INTERP", SND_SOC_NOPM, + WSA_MACRO_COMP2, 0, NULL, 0, + wsa_macro_enable_interpolator, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("WSA_RX INT0 CHAIN", SND_SOC_NOPM, 0, 0, + NULL, 0, wsa_macro_spk_boost_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER_E("WSA_RX INT1 CHAIN", SND_SOC_NOPM, 0, 0, + NULL, 0, wsa_macro_spk_boost_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("VIINPUT_WSA"), + SND_SOC_DAPM_OUTPUT("WSA_SPK1 OUT"), + SND_SOC_DAPM_OUTPUT("WSA_SPK2 OUT"), + + SND_SOC_DAPM_SUPPLY("WSA_RX0_CLK", CDC_WSA_RX0_RX_PATH_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("WSA_RX1_CLK", CDC_WSA_RX1_RX_PATH_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("WSA_RX_MIX0_CLK", CDC_WSA_RX0_RX_PATH_MIX_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("WSA_RX_MIX1_CLK", CDC_WSA_RX1_RX_PATH_MIX_CTL, 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("WSA_MCLK", 0, SND_SOC_NOPM, 0, 0, + wsa_macro_mclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route wsa_audio_map[] = { + /* VI Feedback */ + {"WSA_AIF_VI Mixer", "WSA_SPKR_VI_1", "VIINPUT_WSA"}, + {"WSA_AIF_VI Mixer", "WSA_SPKR_VI_2", "VIINPUT_WSA"}, + {"WSA AIF_VI", NULL, "WSA_AIF_VI Mixer"}, + {"WSA AIF_VI", NULL, "WSA_MCLK"}, + + {"WSA RX_MIX EC0_MUX", "RX_MIX_TX0", "WSA_RX INT0 SEC MIX"}, + {"WSA RX_MIX EC1_MUX", "RX_MIX_TX0", "WSA_RX INT0 SEC MIX"}, + {"WSA RX_MIX EC0_MUX", "RX_MIX_TX1", "WSA_RX INT1 SEC MIX"}, + {"WSA RX_MIX EC1_MUX", "RX_MIX_TX1", "WSA_RX INT1 SEC MIX"}, + {"WSA AIF_ECHO", NULL, "WSA RX_MIX EC0_MUX"}, + {"WSA AIF_ECHO", NULL, "WSA RX_MIX EC1_MUX"}, + {"WSA AIF_ECHO", NULL, "WSA_MCLK"}, + + {"WSA AIF1 PB", NULL, "WSA_MCLK"}, + {"WSA AIF_MIX1 PB", NULL, "WSA_MCLK"}, + + {"WSA RX0 MUX", "AIF1_PB", "WSA AIF1 PB"}, + {"WSA RX1 MUX", "AIF1_PB", "WSA AIF1 PB"}, + {"WSA RX_MIX0 MUX", "AIF1_PB", "WSA AIF1 PB"}, + {"WSA RX_MIX1 MUX", "AIF1_PB", "WSA AIF1 PB"}, + + {"WSA RX0 MUX", "AIF_MIX1_PB", "WSA AIF_MIX1 PB"}, + {"WSA RX1 MUX", "AIF_MIX1_PB", "WSA AIF_MIX1 PB"}, + {"WSA RX_MIX0 MUX", "AIF_MIX1_PB", "WSA AIF_MIX1 PB"}, + {"WSA RX_MIX1 MUX", "AIF_MIX1_PB", "WSA AIF_MIX1 PB"}, + + {"WSA RX0", NULL, "WSA RX0 MUX"}, + {"WSA RX1", NULL, "WSA RX1 MUX"}, + {"WSA RX_MIX0", NULL, "WSA RX_MIX0 MUX"}, + {"WSA RX_MIX1", NULL, "WSA RX_MIX1 MUX"}, + + {"WSA RX0", NULL, "WSA_RX0_CLK"}, + {"WSA RX1", NULL, "WSA_RX1_CLK"}, + {"WSA RX_MIX0", NULL, "WSA_RX_MIX0_CLK"}, + {"WSA RX_MIX1", NULL, "WSA_RX_MIX1_CLK"}, + + {"WSA_RX0 INP0", "RX0", "WSA RX0"}, + {"WSA_RX0 INP0", "RX1", "WSA RX1"}, + {"WSA_RX0 INP0", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX0 INP0", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX0 INP0", "DEC0", "WSA_TX DEC0_INP"}, + {"WSA_RX0 INP0", "DEC1", "WSA_TX DEC1_INP"}, + {"WSA_RX INT0 MIX", NULL, "WSA_RX0 INP0"}, + + {"WSA_RX0 INP1", "RX0", "WSA RX0"}, + {"WSA_RX0 INP1", "RX1", "WSA RX1"}, + {"WSA_RX0 INP1", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX0 INP1", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX0 INP1", "DEC0", "WSA_TX DEC0_INP"}, + {"WSA_RX0 INP1", "DEC1", "WSA_TX DEC1_INP"}, + {"WSA_RX INT0 MIX", NULL, "WSA_RX0 INP1"}, + + {"WSA_RX0 INP2", "RX0", "WSA RX0"}, + {"WSA_RX0 INP2", "RX1", "WSA RX1"}, + {"WSA_RX0 INP2", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX0 INP2", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX0 INP2", "DEC0", "WSA_TX DEC0_INP"}, + {"WSA_RX0 INP2", "DEC1", "WSA_TX DEC1_INP"}, + {"WSA_RX INT0 MIX", NULL, "WSA_RX0 INP2"}, + + {"WSA_RX0 MIX INP", "RX0", "WSA RX0"}, + {"WSA_RX0 MIX INP", "RX1", "WSA RX1"}, + {"WSA_RX0 MIX INP", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX0 MIX INP", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX INT0 SEC MIX", NULL, "WSA_RX0 MIX INP"}, + + {"WSA_RX INT0 SEC MIX", NULL, "WSA_RX INT0 MIX"}, + {"WSA_RX INT0 INTERP", NULL, "WSA_RX INT0 SEC MIX"}, + {"WSA_RX0 INT0 SIDETONE MIX", "SRC0", "WSA SRC0_INP"}, + {"WSA_RX INT0 INTERP", NULL, "WSA_RX0 INT0 SIDETONE MIX"}, + {"WSA_RX INT0 CHAIN", NULL, "WSA_RX INT0 INTERP"}, + + {"WSA_SPK1 OUT", NULL, "WSA_RX INT0 CHAIN"}, + {"WSA_SPK1 OUT", NULL, "WSA_MCLK"}, + + {"WSA_RX1 INP0", "RX0", "WSA RX0"}, + {"WSA_RX1 INP0", "RX1", "WSA RX1"}, + {"WSA_RX1 INP0", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX1 INP0", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX1 INP0", "DEC0", "WSA_TX DEC0_INP"}, + {"WSA_RX1 INP0", "DEC1", "WSA_TX DEC1_INP"}, + {"WSA_RX INT1 MIX", NULL, "WSA_RX1 INP0"}, + + {"WSA_RX1 INP1", "RX0", "WSA RX0"}, + {"WSA_RX1 INP1", "RX1", "WSA RX1"}, + {"WSA_RX1 INP1", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX1 INP1", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX1 INP1", "DEC0", "WSA_TX DEC0_INP"}, + {"WSA_RX1 INP1", "DEC1", "WSA_TX DEC1_INP"}, + {"WSA_RX INT1 MIX", NULL, "WSA_RX1 INP1"}, + + {"WSA_RX1 INP2", "RX0", "WSA RX0"}, + {"WSA_RX1 INP2", "RX1", "WSA RX1"}, + {"WSA_RX1 INP2", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX1 INP2", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX1 INP2", "DEC0", "WSA_TX DEC0_INP"}, + {"WSA_RX1 INP2", "DEC1", "WSA_TX DEC1_INP"}, + {"WSA_RX INT1 MIX", NULL, "WSA_RX1 INP2"}, + + {"WSA_RX1 MIX INP", "RX0", "WSA RX0"}, + {"WSA_RX1 MIX INP", "RX1", "WSA RX1"}, + {"WSA_RX1 MIX INP", "RX_MIX0", "WSA RX_MIX0"}, + {"WSA_RX1 MIX INP", "RX_MIX1", "WSA RX_MIX1"}, + {"WSA_RX INT1 SEC MIX", NULL, "WSA_RX1 MIX INP"}, + + {"WSA_RX INT1 SEC MIX", NULL, "WSA_RX INT1 MIX"}, + {"WSA_RX INT1 INTERP", NULL, "WSA_RX INT1 SEC MIX"}, + + {"WSA_RX INT1 CHAIN", NULL, "WSA_RX INT1 INTERP"}, + {"WSA_SPK2 OUT", NULL, "WSA_RX INT1 CHAIN"}, + {"WSA_SPK2 OUT", NULL, "WSA_MCLK"}, +}; + +static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) +{ + struct regmap *regmap = wsa->regmap; + + if (enable) { + wsa_macro_mclk_enable(wsa, true); + if (wsa->reset_swr) + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + 0x02, 0x02); + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + 0x01, 0x01); + + if (wsa->reset_swr) + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + 0x02, 0x00); + wsa->reset_swr = false; + } else { + regmap_update_bits(regmap, + CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + 0x01, 0x00); + wsa_macro_mclk_enable(wsa, false); + } + + return 0; +} + +static int wsa_macro_component_probe(struct snd_soc_component *comp) +{ + struct wsa_macro *wsa = snd_soc_component_get_drvdata(comp); + + snd_soc_component_init_regmap(comp, wsa->regmap); + + wsa->spkr_gain_offset = WSA_MACRO_GAIN_OFFSET_M1P5_DB; + + /* set SPKR rate to FS_2P4_3P072 */ + snd_soc_component_update_bits(comp, CDC_WSA_RX0_RX_PATH_CFG1, + CDC_WSA_RX_PATH_SPKR_RATE_MASK, + CDC_WSA_RX_PATH_SPKR_RATE_FS_2P4_3P072); + + snd_soc_component_update_bits(comp, CDC_WSA_RX1_RX_PATH_CFG1, + CDC_WSA_RX_PATH_SPKR_RATE_MASK, + CDC_WSA_RX_PATH_SPKR_RATE_FS_2P4_3P072); + + wsa_macro_set_spkr_mode(comp, WSA_MACRO_SPKR_MODE_1); + + return 0; +} + +static int swclk_gate_enable(struct clk_hw *hw) +{ + return wsa_swrm_clock(to_wsa_macro(hw), true); +} + +static void swclk_gate_disable(struct clk_hw *hw) +{ + wsa_swrm_clock(to_wsa_macro(hw), false); +} + +static int swclk_gate_is_enabled(struct clk_hw *hw) +{ + struct wsa_macro *wsa = to_wsa_macro(hw); + int ret, val; + + regmap_read(wsa->regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, &val); + ret = val & BIT(0); + + return ret; +} + +static unsigned long swclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return parent_rate / 2; +} + +static const struct clk_ops swclk_gate_ops = { + .prepare = swclk_gate_enable, + .unprepare = swclk_gate_disable, + .is_enabled = swclk_gate_is_enabled, + .recalc_rate = swclk_recalc_rate, + +}; + +static struct clk *wsa_macro_register_mclk_output(struct wsa_macro *wsa) +{ + struct device *dev = wsa->dev; + struct device_node *np = dev->of_node; + const char *parent_clk_name; + const char *clk_name = "mclk"; + struct clk_hw *hw; + struct clk_init_data init; + int ret; + + parent_clk_name = __clk_get_name(wsa->clks[2].clk); + + init.name = clk_name; + init.ops = &swclk_gate_ops; + init.flags = 0; + init.parent_names = &parent_clk_name; + init.num_parents = 1; + wsa->hw.init = &init; + hw = &wsa->hw; + ret = clk_hw_register(wsa->dev, hw); + if (ret) + return ERR_PTR(ret); + + of_clk_add_provider(np, of_clk_src_simple_get, hw->clk); + + return NULL; +} + +static const struct snd_soc_component_driver wsa_macro_component_drv = { + .name = "WSA MACRO", + .probe = wsa_macro_component_probe, + .controls = wsa_macro_snd_controls, + .num_controls = ARRAY_SIZE(wsa_macro_snd_controls), + .dapm_widgets = wsa_macro_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wsa_macro_dapm_widgets), + .dapm_routes = wsa_audio_map, + .num_dapm_routes = ARRAY_SIZE(wsa_audio_map), +}; + +static int wsa_macro_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct wsa_macro *wsa; + void __iomem *base; + int ret; + + wsa = devm_kzalloc(dev, sizeof(*wsa), GFP_KERNEL); + if (!wsa) + return -ENOMEM; + + wsa->clks[0].id = "macro"; + wsa->clks[1].id = "dcodec"; + wsa->clks[2].id = "mclk"; + wsa->clks[3].id = "npl"; + wsa->clks[4].id = "fsgen"; + + ret = devm_clk_bulk_get(dev, WSA_NUM_CLKS_MAX, wsa->clks); + if (ret) { + dev_err(dev, "Error getting WSA Clocks (%d)\n", ret); + return ret; + } + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + wsa->regmap = devm_regmap_init_mmio(dev, base, &wsa_regmap_config); + + dev_set_drvdata(dev, wsa); + + wsa->reset_swr = true; + wsa->dev = dev; + + /* set MCLK and NPL rates */ + clk_set_rate(wsa->clks[2].clk, WSA_MACRO_MCLK_FREQ); + clk_set_rate(wsa->clks[3].clk, WSA_MACRO_MCLK_FREQ); + + ret = clk_bulk_prepare_enable(WSA_NUM_CLKS_MAX, wsa->clks); + if (ret) + return ret; + + wsa_macro_register_mclk_output(wsa); + + ret = devm_snd_soc_register_component(dev, &wsa_macro_component_drv, + wsa_macro_dai, + ARRAY_SIZE(wsa_macro_dai)); + if (ret) + goto err; + + return ret; +err: + clk_bulk_disable_unprepare(WSA_NUM_CLKS_MAX, wsa->clks); + + return ret; + +} + +static int wsa_macro_remove(struct platform_device *pdev) +{ + struct wsa_macro *wsa = dev_get_drvdata(&pdev->dev); + + of_clk_del_provider(pdev->dev.of_node); + + clk_bulk_disable_unprepare(WSA_NUM_CLKS_MAX, wsa->clks); + + return 0; +} + +static const struct of_device_id wsa_macro_dt_match[] = { + {.compatible = "qcom,sm8250-lpass-wsa-macro"}, + {} +}; +MODULE_DEVICE_TABLE(of, wsa_macro_dt_match); + +static struct platform_driver wsa_macro_driver = { + .driver = { + .name = "wsa_macro", + .of_match_table = wsa_macro_dt_match, + }, + .probe = wsa_macro_probe, + .remove = wsa_macro_remove, +}; + +module_platform_driver(wsa_macro_driver); +MODULE_DESCRIPTION("WSA macro driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/lpass-wsa-macro.h b/sound/soc/codecs/lpass-wsa-macro.h new file mode 100644 index 000000000000..2372c13b0be6 --- /dev/null +++ b/sound/soc/codecs/lpass-wsa-macro.h @@ -0,0 +1,15 @@ +#ifndef __LPASS_WSA_MACRO_H__ +#define __LPASS_WSA_MACRO_H__ + +/* + * Selects compander and smart boost settings + * for a given speaker mode + */ +enum { + WSA_MACRO_SPKR_MODE_DEFAULT, + WSA_MACRO_SPKR_MODE_1, /* COMP Gain = 12dB, Smartboost Max = 5.5V */ +}; + +int wsa_macro_set_spkr_mode(struct snd_soc_component *component, int mode); + +#endif /* __LPASS_WSA_MACRO_H__ */ diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c index 40f682f5dab8..3197c701cdc5 100644 --- a/sound/soc/codecs/wcd934x.c +++ b/sound/soc/codecs/wcd934x.c @@ -2112,7 +2112,7 @@ static struct clk *wcd934x_register_mclk_output(struct wcd934x_codec *wcd) wcd->hw.init = &init; hw = &wcd->hw; - ret = clk_hw_register(wcd->dev->parent, hw); + ret = devm_clk_hw_register(wcd->dev->parent, hw); if (ret) return ERR_PTR(ret); @@ -5050,6 +5050,13 @@ static int wcd934x_codec_probe(struct platform_device *pdev) wcd934x_slim_dais, ARRAY_SIZE(wcd934x_slim_dais)); } +static int wcd934x_codec_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + + return 0; +} + static const struct platform_device_id wcd934x_driver_id[] = { { @@ -5061,6 +5068,7 @@ MODULE_DEVICE_TABLE(platform, wcd934x_driver_id); static struct platform_driver wcd934x_codec_driver = { .probe = &wcd934x_codec_probe, + .remove = wcd934x_codec_remove, .id_table = wcd934x_driver_id, .driver = { .name = "wcd934x-codec", diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 2696ffcba880..484cad31da25 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -127,4 +127,15 @@ config SND_SOC_SDM845 SDM845 SoC-based systems. Say Y if you want to use audio device on this SoCs. +config SND_SOC_SM8250 + tristate "SoC Machine driver for SM8250 boards" + depends on QCOM_APR && SOUNDWIRE + depends on COMMON_CLK + select SND_SOC_QDSP6 + select SND_SOC_QCOM_COMMON + help + To add support for audio on Qualcomm Technologies Inc. + SM8250 SoC-based systems. + Say Y if you want to use audio device on this SoCs. + endif #SND_SOC_QCOM diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 0bd90d74e3db..effa4b3f58fa 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -19,12 +19,14 @@ snd-soc-storm-objs := storm.o snd-soc-apq8016-sbc-objs := apq8016_sbc.o snd-soc-apq8096-objs := apq8096.o snd-soc-sdm845-objs := sdm845.o +snd-soc-sm8250-objs := sm8250.o snd-soc-qcom-common-objs := common.o obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o +obj-$(CONFIG_SND_SOC_SM8250) += snd-soc-sm8250.o obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o #DSP lib diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c index 1a69baefc5ce..2426f5e0e10d 100644 --- a/sound/soc/qcom/apq8096.c +++ b/sound/soc/qcom/apq8096.c @@ -5,14 +5,21 @@ #include <linux/platform_device.h> #include <linux/of_device.h> #include <sound/soc.h> +#include <sound/jack.h> #include <sound/soc-dapm.h> #include <sound/pcm.h> +#include <uapi/linux/input-event-codes.h> #include "common.h" #define SLIM_MAX_TX_PORTS 16 #define SLIM_MAX_RX_PORTS 16 #define WCD9335_DEFAULT_MCLK_RATE 9600000 +struct apq8096_card_data { + struct snd_soc_jack jack; + bool jack_setup; +}; + static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -67,6 +74,7 @@ static struct snd_soc_ops apq8096_ops = { static int apq8096_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct apq8096_card_data *data = snd_soc_card_get_drvdata(rtd->card); /* * Codec SLIMBUS configuration @@ -79,6 +87,8 @@ static int apq8096_init(struct snd_soc_pcm_runtime *rtd) unsigned int tx_ch[SLIM_MAX_TX_PORTS] = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; + struct snd_soc_card *card = rtd->card; + int rval; snd_soc_dai_set_channel_map(codec_dai, ARRAY_SIZE(tx_ch), tx_ch, ARRAY_SIZE(rx_ch), rx_ch); @@ -86,6 +96,38 @@ static int apq8096_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dai_set_sysclk(codec_dai, 0, WCD9335_DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + if (!data->jack_setup) { + struct snd_jack *jack; + + rval = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_HEADPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | + SND_JACK_BTN_4, + &data->jack, NULL, 0); + + if (rval < 0) { + dev_err(card->dev, "Unable to add Headphone Jack\n"); + return rval; + } + + jack = data->jack.jack; + + snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + data->jack_setup = true; + } + + rval = snd_soc_component_set_jack(codec_dai->component, + &data->jack, NULL); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set jack: %d\n", rval); + return rval; + } + return 0; } @@ -105,6 +147,7 @@ static void apq8096_add_be_ops(struct snd_soc_card *card) static int apq8096_platform_probe(struct platform_device *pdev) { + struct apq8096_card_data *data; struct snd_soc_card *card; struct device *dev = &pdev->dev; int ret; @@ -113,9 +156,14 @@ static int apq8096_platform_probe(struct platform_device *pdev) if (!card) return -ENOMEM; + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + card->dev = dev; card->owner = THIS_MODULE; dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); ret = qcom_snd_parse_of(card); if (ret) return ret; diff --git a/sound/soc/qcom/qdsp6/q6afe-clocks.c b/sound/soc/qcom/qdsp6/q6afe-clocks.c index 2efc2eaa0424..acfc0c698f6a 100644 --- a/sound/soc/qcom/qdsp6/q6afe-clocks.c +++ b/sound/soc/qcom/qdsp6/q6afe-clocks.c @@ -16,6 +16,7 @@ .afe_clk_id = Q6AFE_##id, \ .name = #id, \ .attributes = LPASS_CLK_ATTRIBUTE_COUPLE_NO, \ + .rate = 19200000, \ .hw.init = &(struct clk_init_data) { \ .ops = &clk_q6afe_ops, \ .name = #id, \ diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c new file mode 100644 index 000000000000..314e3e566d6d --- /dev/null +++ b/sound/soc/qcom/sm8250.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <linux/soundwire/sdw.h> +#include "qdsp6/q6afe.h" +#include "common.h" + +#define DRIVER_NAME "sm8250" +#define MI2S_BCLK_RATE 1536000 + +struct sm8250_snd_data { + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + struct sdw_stream_runtime *sruntime[AFE_PORT_MAX]; +}; + +static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static int sm8250_snd_startup(struct snd_pcm_substream *substream) +{ + unsigned int fmt = SND_SOC_DAIFMT_CBS_CFS; + unsigned int codec_dai_fmt = SND_SOC_DAIFMT_CBS_CFS; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + + switch (cpu_dai->id) { + case TERTIARY_MI2S_RX: + codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT, + MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; + default: + break; + } + return 0; +} + +static int sm8250_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sm8250_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); + struct sdw_stream_runtime *sruntime; + int i; + + switch (cpu_dai->id) { + case WSA_CODEC_DMA_RX_0: + for_each_rtd_codec_dais(rtd, i, codec_dai) { + sruntime = snd_soc_dai_get_sdw_stream(codec_dai, + substream->stream); + if (sruntime != ERR_PTR(-ENOTSUPP)) + pdata->sruntime[cpu_dai->id] = sruntime; + } + break; + } + + return 0; + +} + +static int sm8250_snd_wsa_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + int ret; + + if (!sruntime) + return 0; + + if (data->stream_prepared[cpu_dai->id]) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + data->stream_prepared[cpu_dai->id] = false; + } + + ret = sdw_prepare_stream(sruntime); + if (ret) + return ret; + + /** + * NOTE: there is a strict hw requirement about the ordering of port + * enables and actual WSA881x PA enable. PA enable should only happen + * after soundwire ports are enabled if not DC on the line is + * accumulated resulting in Click/Pop Noise + * PA enable/mute are handled as part of codec DAPM and digital mute. + */ + + ret = sdw_enable_stream(sruntime); + if (ret) { + sdw_deprepare_stream(sruntime); + return ret; + } + data->stream_prepared[cpu_dai->id] = true; + + return ret; +} + +static int sm8250_snd_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + + switch (cpu_dai->id) { + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + return sm8250_snd_wsa_dma_prepare(substream); + default: + break; + } + + return 0; +} + +static int sm8250_snd_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + + switch (cpu_dai->id) { + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + if (sruntime && data->stream_prepared[cpu_dai->id]) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + data->stream_prepared[cpu_dai->id] = false; + } + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_ops sm8250_be_ops = { + .startup = sm8250_snd_startup, + .hw_params = sm8250_snd_hw_params, + .hw_free = sm8250_snd_hw_free, + .prepare = sm8250_snd_prepare, +}; + +static void sm8250_add_be_ops(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link; + int i; + + for_each_card_prelinks(card, i, link) { + if (link->no_pcm == 1) { + link->be_hw_params_fixup = sm8250_be_hw_params_fixup; + link->ops = &sm8250_be_ops; + } + } +} + +static int sm8250_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct sm8250_snd_data *data; + struct device *dev = &pdev->dev; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + + card->driver_name = DRIVER_NAME; + sm8250_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); +} + +static const struct of_device_id snd_sm8250_dt_match[] = { + {.compatible = "qcom,sm8250"}, + {.compatible = "qcom,qrb5165-rb5"}, + {} +}; + +MODULE_DEVICE_TABLE(of, snd_sm8250_dt_match); + +static struct platform_driver snd_sm8250_driver = { + .probe = sm8250_platform_probe, + .driver = { + .name = "snd-sm8250", + .of_match_table = snd_sm8250_dt_match, + }, +}; +module_platform_driver(snd_sm8250_driver); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("SM8250 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2"); |