diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2021-11-03 17:49:25 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2021-11-03 17:49:25 +0300 |
commit | ff0700f03609b9f0defacd4ce96d9519d721e0a2 (patch) | |
tree | 4b7ac6cf015e39f82ef0706ce465a224a43dac42 | |
parent | dcd68326d29b62f3039e4f4d23d3e38f24d37360 (diff) | |
parent | df0380b9539b04c1ae8854a984098da06d5f1e67 (diff) | |
download | linux-ff0700f03609b9f0defacd4ce96d9519d721e0a2.tar.xz |
Merge tag 'sound-5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai:
"Lots of code development have been see in ASoC side as usual, while
the continued development on memalloc helper and USB-audio low-
latency support are found in the rest.
Note that a few changes in the unusual places like arch/sh are
included, which are a part of ASoC DAI format cleanups.
ALSA core:
- Continued memalloc helper updates and cleanups, now supporting
non-coherent and non-contiguous pages
- Fixes for races in mixer OSS layer
ASoC:
- A new version of the audio graph card which supports a wider range
of systems
- Several conversions to YAML DT bindings
- Continuing cleanups to the SOF and Intel code
- Move of the Cirrus DSP framework into drivers/firmware to allow for
future use by non-audio DSPs
- An overhaul of the cs42l42 driver, correcting many problems
- DAI format terminology conversions over many drivers for cleanups
- Support for AMD Vangogh and Yelow Cap, Cirrus CS35L41, Maxim
MAX98520 and MAX98360A, Mediatek MT8195, Nuvoton NAU8821, nVidia
Tegra210, NXP i.MX8ULP, Qualcomm AudioReach, Realtek ALC5682I-VS,
RT5682S, and RT9120 and Rockchip RV1126 and RK3568
USB-audio:
- Continued improvements on low-latency playback
- Quirks for Pioneer devices, Line6 HX-Stomp XL, Audient iD14
HD-audio:
- Reduce excessive udelay() calls on Intel platforms; this should
reduce the CPU load with PulseAudio
- Quirks for HP and Clevo laptops
FireWire:
- Support for meter information on MOTU"
* tag 'sound-5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (513 commits)
ALSA: usb-audio: Add quirk for Audient iD14
ALSA: hda/realtek: Add quirk for Clevo PC70HS
ALSA: usb-audio: Line6 HX-Stomp XL USB_ID for 48k-fixed quirk
ALSA: usb-audio: Add registration quirk for JBL Quantum 400
ASoC: rsnd: Fix an error handling path in 'rsnd_node_count()'
ASoC: tlv320aic3x: Make aic3x_remove() return void
ASoC: Intel: soc-acpi: use const for all uses of snd_soc_acpi_codecs
ASoC: Intel: soc-acpi-cht: shrink tables using compatible IDs
ASoC: Intel: soc-acpi-byt: shrink tables using compatible IDs
ASoC: Intel: sof_rt5682: use comp_ids to enumerate rt5682s
ASoC: Intel: sof_rt5682: detect codec variant in probe function
ASoC: soc-acpi: add comp_ids field for machine driver matching
ASoC: mediatek: mt8195: add mt8195-mt6359-rt1011-rt5682 bindings document
ASoC: mediatek: mt8195: add machine driver with mt6359, rt1011 and rt5682
ASoC: Stop dummy from overriding hwparams
ASoC: topology: Change topology device to card device
ASoC: topology: Use correct device for prints
ASoC: topology: Check for dapm widget completeness
ASoC: topology: Add header payload_size verification
ASoC: core: Remove invalid snd_soc_component_set_jack call
...
477 files changed, 48245 insertions, 7992 deletions
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt deleted file mode 100644 index 2e2f6dc351c0..000000000000 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt +++ /dev/null @@ -1,134 +0,0 @@ -Qualcomm APR (Asynchronous Packet Router) binding - -This binding describes the Qualcomm APR. APR is a IPC protocol for -communication between Application processor and QDSP. APR is mainly -used for audio/voice services on the QDSP. - -- compatible: - Usage: required - Value type: <stringlist> - Definition: must be "qcom,apr-v<VERSION-NUMBER>", example "qcom,apr-v2" - -- qcom,apr-domain - Usage: required - Value type: <u32> - Definition: Destination processor ID. - Possible values are : - 1 - APR simulator - 2 - PC - 3 - MODEM - 4 - ADSP - 5 - APPS - 6 - MODEM2 - 7 - APPS2 - -= APR SERVICES -Each subnode of the APR node represents service tied to this apr. The name -of the nodes are not important. The properties of these nodes are defined -by the individual bindings for the specific service -- All APR services MUST contain the following property: - -- reg - Usage: required - Value type: <u32> - Definition: APR Service ID - Possible values are : - 3 - DSP Core Service - 4 - Audio Front End Service. - 5 - Voice Stream Manager Service. - 6 - Voice processing manager. - 7 - Audio Stream Manager Service. - 8 - Audio Device Manager Service. - 9 - Multimode voice manager. - 10 - Core voice stream. - 11 - Core voice processor. - 12 - Ultrasound stream manager. - 13 - Listen stream manager. - -- qcom,protection-domain - Usage: optional - Value type: <stringlist> - Definition: Must list the protection domain service name and path - that the particular apr service has a dependency on. - Possible values are : - "avs/audio", "msm/adsp/audio_pd". - "kernel/elf_loader", "msm/modem/wlan_pd". - "tms/servreg", "msm/adsp/audio_pd". - "tms/servreg", "msm/modem/wlan_pd". - "tms/servreg", "msm/slpi/sensor_pd". - -= EXAMPLE -The following example represents a QDSP based sound card on a MSM8996 device -which uses apr as communication between Apps and QDSP. - - apr { - compatible = "qcom,apr-v2"; - qcom,apr-domain = <APR_DOMAIN_ADSP>; - - apr-service@3 { - compatible = "qcom,q6core"; - reg = <APR_SVC_ADSP_CORE>; - }; - - apr-service@4 { - compatible = "qcom,q6afe"; - reg = <APR_SVC_AFE>; - - dais { - #sound-dai-cells = <1>; - dai@1 { - reg = <HDMI_RX>; - }; - }; - }; - - apr-service@7 { - compatible = "qcom,q6asm"; - reg = <APR_SVC_ASM>; - ... - }; - - apr-service@8 { - compatible = "qcom,q6adm"; - reg = <APR_SVC_ADM>; - ... - }; - }; - -= EXAMPLE 2 -The following example represents a QDSP based sound card with protection domain -dependencies specified. Here some of the apr services are dependent on services -running on protection domain hosted on ADSP/SLPI remote processors while others -have no such dependency. - - apr { - compatible = "qcom,apr-v2"; - qcom,glink-channels = "apr_audio_svc"; - qcom,apr-domain = <APR_DOMAIN_ADSP>; - - apr-service@3 { - compatible = "qcom,q6core"; - reg = <APR_SVC_ADSP_CORE>; - }; - - q6afe: apr-service@4 { - compatible = "qcom,q6afe"; - reg = <APR_SVC_AFE>; - qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; - ... - }; - - q6asm: apr-service@7 { - compatible = "qcom,q6asm"; - reg = <APR_SVC_ASM>; - qcom,protection-domain = "tms/servreg", "msm/slpi/sensor_pd"; - ... - }; - - q6adm: apr-service@8 { - compatible = "qcom,q6adm"; - reg = <APR_SVC_ADM>; - qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; - ... - }; - }; diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.yaml new file mode 100644 index 000000000000..028c5d105adb --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.yaml @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/soc/qcom/qcom,apr.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm APR/GPR (Asynchronous/Generic Packet Router) binding + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: | + This binding describes the Qualcomm APR/GPR, APR/GPR is a IPC protocol for + communication between Application processor and QDSP. APR/GPR is mainly + used for audio/voice services on the QDSP. + +properties: + compatible: + enum: + - qcom,apr-v2 + - qcom,gpr + + qcom,apr-domain: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 3, 4, 5, 6, 7] + description: + Selects the processor domain for apr + 1 = APR simulator + 2 = PC Domain + 3 = Modem Domain + 4 = ADSP Domain + 5 = Application processor Domain + 6 = Modem2 Domain + 7 = Application Processor2 Domain + deprecated: true + + qcom,domain: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 7 + description: + Selects the processor domain for apr + 1 = APR simulator + 2 = PC Domain + 3 = Modem Domain + 4 = ADSP Domain + 5 = Application processor Domain + 6 = Modem2 Domain + 7 = Application Processor2 Domain + Selects the processor domain for gpr + 1 = Modem Domain + 2 = Audio DSP Domain + 3 = Application Processor Domain + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +#APR/GPR Services +patternProperties: + "^service@[1-9a-d]$": + type: object + description: + APR/GPR node's client devices use subnodes for desired static port services. + + properties: + compatible: + enum: + - qcom,q6core + - qcom,q6asm + - qcom,q6afe + - qcom,q6adm + - qcom,q6apm + - qcom,q6prm + + reg: + minimum: 1 + maximum: 13 + description: + APR Service ID + 3 = DSP Core Service + 4 = Audio Front End Service. + 5 = Voice Stream Manager Service. + 6 = Voice processing manager. + 7 = Audio Stream Manager Service. + 8 = Audio Device Manager Service. + 9 = Multimode voice manager. + 10 = Core voice stream. + 11 = Core voice processor. + 12 = Ultrasound stream manager. + 13 = Listen stream manager. + GPR Service ID + 1 = Audio Process Manager Service + 2 = Proxy Resource Manager Service. + 3 = AMDB Service. + 4 = Voice processing manager. + + qcom,protection-domain: + $ref: /schemas/types.yaml#/definitions/string-array + description: protection domain service name and path for apr service + possible values are + "avs/audio", "msm/adsp/audio_pd". + "kernel/elf_loader", "msm/modem/wlan_pd". + "tms/servreg", "msm/adsp/audio_pd". + "tms/servreg", "msm/modem/wlan_pd". + "tms/servreg", "msm/slpi/sensor_pd". + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + patternProperties: + "^.*@[0-9a-f]+$": + type: object + description: + Service based devices like clock controllers or digital audio interfaces. + + additionalProperties: false + +required: + - compatible + - qcom,domain + +additionalProperties: false + +examples: + - | + #include <dt-bindings/soc/qcom,apr.h> + apr { + compatible = "qcom,apr-v2"; + qcom,domain = <APR_DOMAIN_ADSP>; + #address-cells = <1>; + #size-cells = <0>; + + q6core: service@3 { + compatible = "qcom,q6core"; + reg = <APR_SVC_ADSP_CORE>; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + }; + + q6afe: service@4 { + compatible = "qcom,q6afe"; + reg = <APR_SVC_AFE>; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + }; + + q6asm: service@7 { + compatible = "qcom,q6asm"; + reg = <APR_SVC_ASM>; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + }; + + q6adm: service@8 { + compatible = "qcom,q6adm"; + reg = <APR_SVC_ADM>; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + }; + }; + + - | + #include <dt-bindings/soc/qcom,gpr.h> + gpr { + compatible = "qcom,gpr"; + qcom,domain = <GPR_DOMAIN_ID_ADSP>; + #address-cells = <1>; + #size-cells = <0>; + + service@1 { + compatible = "qcom,q6apm"; + reg = <GPR_APM_MODULE_IID>; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml b/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml index c7613ea728d4..db7b04da0b39 100644 --- a/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml +++ b/Documentation/devicetree/bindings/sound/amlogic,t9015.yaml @@ -34,6 +34,10 @@ properties: resets: maxItems: 1 + AVDD-supply: + description: + Analogue power supply. + required: - "#sound-dai-cells" - compatible @@ -41,6 +45,7 @@ required: - clocks - clock-names - resets + - AVDD-supply additionalProperties: false @@ -56,4 +61,5 @@ examples: clocks = <&clkc CLKID_AUDIO_CODEC>; clock-names = "pclk"; resets = <&reset RESET_AUDIO_CODEC>; + AVDD-supply = <&vddao_1v8>; }; diff --git a/Documentation/devicetree/bindings/sound/audio-graph-card2.yaml b/Documentation/devicetree/bindings/sound/audio-graph-card2.yaml new file mode 100644 index 000000000000..f7e94b1e0e4b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/audio-graph-card2.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/audio-graph-card2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Audio Graph Card2 Device Tree Bindings + +maintainers: + - Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + +properties: + compatible: + enum: + - audio-graph-card2 + links: + $ref: /schemas/types.yaml#/definitions/phandle-array + label: + maxItems: 1 + routing: + 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. + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + multi: + description: Multi-CPU/Codec node + dpcm: + description: DPCM node + codec2codec: + description: Codec to Codec node + +required: + - compatible + - links + +additionalProperties: false + +examples: + - | + sound { + compatible = "audio-graph-card2"; + + links = <&cpu_port>; + }; + + cpu { + compatible = "cpu-driver"; + + cpu_port: port { cpu_ep: endpoint { remote-endpoint = <&codec_ep>; }; }; + }; + + codec { + compatible = "codec-driver"; + + port { codec_ep: endpoint { remote-endpoint = <&cpu_ep>; }; }; + }; diff --git a/Documentation/devicetree/bindings/sound/bt-sco.txt b/Documentation/devicetree/bindings/sound/bt-sco.txt deleted file mode 100644 index 641edf75e184..000000000000 --- a/Documentation/devicetree/bindings/sound/bt-sco.txt +++ /dev/null @@ -1,13 +0,0 @@ -Bluetooth-SCO audio CODEC - -This device support generic Bluetooth SCO link. - -Required properties: - - - compatible : "delta,dfbmcs320" or "linux,bt-sco" - -Example: - -codec: bt_sco { - compatible = "delta,dfbmcs320"; -}; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l41.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l41.yaml new file mode 100644 index 000000000000..3235702ce402 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l41.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/cirrus,cs35l41.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cirrus Logic CS35L41 Speaker Amplifier + +maintainers: + - david.rhodes@cirrus.com + +description: | + CS35L41 is a boosted mono Class D amplifier with DSP + speaker protection and equalization + +properties: + compatible: + enum: + - cirrus,cs35l40 + - cirrus,cs35l41 + + reg: + maxItems: 1 + + '#sound-dai-cells': + description: + The first cell indicating the audio interface. + const: 1 + + reset-gpios: + maxItems: 1 + + VA-supply: + description: voltage regulator phandle for the VA supply + + VP-supply: + description: voltage regulator phandle for the VP supply + + cirrus,boost-peak-milliamp: + description: + Boost-converter peak current limit in mA. + Configures the peak current by monitoring the current through the boost FET. + Range starts at 1600 mA and goes to a maximum of 4500 mA with increments + of 50 mA. See section 4.3.6 of the datasheet for details. + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 1600 + maximum: 4500 + default: 4500 + + cirrus,boost-ind-nanohenry: + description: + Boost inductor value, expressed in nH. Valid + values include 1000, 1200, 1500 and 2200. + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 1000 + maximum: 2200 + + cirrus,boost-cap-microfarad: + description: + Total equivalent boost capacitance on the VBST + and VAMP pins, derated at 11 volts DC. The value must be rounded to the + nearest integer and expressed in uF. + $ref: "/schemas/types.yaml#/definitions/uint32" + + cirrus,asp-sdout-hiz: + description: + Audio serial port SDOUT Hi-Z control. Sets the Hi-Z + configuration for SDOUT pin of amplifier. + 0 = Logic 0 during unused slots, and while all transmit channels disabled + 1 = Hi-Z during unused slots but logic 0 while all transmit channels disabled + 2 = (Default) Logic 0 during unused slots, but Hi-Z while all transmit channels disabled + 3 = Hi-Z during unused slots and while all transmit channels disabled + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 3 + default: 2 + + cirrus,gpio1-polarity-invert: + description: + Boolean which specifies whether the GPIO1 + level is inverted. If this property is not present the level is not inverted. + type: boolean + + cirrus,gpio1-output-enable: + description: + Boolean which specifies whether the GPIO1 pin + is configured as an output. If this property is not present the + pin will be configured as an input. + type: boolean + + cirrus,gpio1-src-select: + description: + Configures the function of the GPIO1 pin. + Note that the options are different from the GPIO2 pin + 0 = High Impedance (Default) + 1 = GPIO + 2 = Sync + 3 = MCLK input + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 3 + + cirrus,gpio2-polarity-invert: + description: + Boolean which specifies whether the GPIO2 + level is inverted. If this property is not present the level is not inverted. + type: boolean + + cirrus,gpio2-output-enable: + description: + Boolean which specifies whether the GPIO2 pin + is configured as an output. If this property is not present the + pin will be configured as an input. + type: boolean + + cirrus,gpio2-src-select: + description: + Configures the function of the GPIO2 pin. + Note that the options are different from the GPIO1 pin. + 0 = High Impedance (Default) + 1 = GPIO + 2 = Open Drain INTB + 3 = MCLK input + 4 = Push-pull INTB (active low) + 5 = Push-pull INT (active high) + $ref: "/schemas/types.yaml#/definitions/uint32" + minimum: 0 + maximum: 5 + +required: + - compatible + - reg + - "#sound-dai-cells" + - cirrus,boost-peak-milliamp + - cirrus,boost-ind-nanohenry + - cirrus,boost-cap-microfarad + +additionalProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + cs35l41: cs35l41@2 { + #sound-dai-cells = <1>; + compatible = "cirrus,cs35l41"; + reg = <2>; + VA-supply = <&dummy_vreg>; + VP-supply = <&dummy_vreg>; + reset-gpios = <&gpio 110 0>; + cirrus,boost-peak-milliamp = <4500>; + cirrus,boost-ind-nanohenry = <1000>; + cirrus,boost-cap-microfarad = <15>; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/cs42l42.txt b/Documentation/devicetree/bindings/sound/cs42l42.txt index 5d416fdaf023..3b7705623980 100644 --- a/Documentation/devicetree/bindings/sound/cs42l42.txt +++ b/Documentation/devicetree/bindings/sound/cs42l42.txt @@ -19,13 +19,14 @@ Optional properties: (See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt for further information relating to interrupt properties) - - cirrus,ts-inv : Boolean property. For jacks that invert the tip sense - polarity. Normal jacks will short tip sense pin to HS1 when headphones are - plugged in and leave tip sense floating when not plugged in. Inverting jacks - short tip sense when unplugged and float when plugged in. + - cirrus,ts-inv : Boolean property. Sets the behaviour of the jack plug + detect switch. - 0 = (Default) Non-inverted - 1 = Inverted + 0 = (Default) Shorted to tip when unplugged, open when plugged. + This is "inverted tip sense (ITS)" in the datasheet. + + 1 = Open when unplugged, shorted to tip when plugged. + This is "normal tip sense (TS)" in the datasheet. - cirrus,ts-dbnc-rise : Debounce the rising edge of TIP_SENSE_PLUG. With no debounce, the tip sense pin might be noisy on a plug event. diff --git a/Documentation/devicetree/bindings/sound/linux,bt-sco.yaml b/Documentation/devicetree/bindings/sound/linux,bt-sco.yaml new file mode 100644 index 000000000000..e3a1f485f664 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/linux,bt-sco.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/linux,bt-sco.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Bluetooth SCO Audio Codec Device Tree Bindings + +maintainers: + - Mark Brown <broonie@kernel.org> + +properties: + '#sound-dai-cells': + enum: + - 0 + + # For Wideband PCM + - 1 + + compatible: + enum: + - delta,dfbmcs320 + - linux,bt-sco + +required: + - '#sound-dai-cells' + - compatible + +additionalProperties: false + +examples: + - | + codec { + #sound-dai-cells = <0>; + compatible = "linux,bt-sco"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/linux,spdif-dit.yaml b/Documentation/devicetree/bindings/sound/linux,spdif-dit.yaml new file mode 100644 index 000000000000..c6b070e1d014 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/linux,spdif-dit.yaml @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/linux,spdif-dit.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Dummy SPDIF Transmitter Device Tree Bindings + +maintainers: + - Mark Brown <broonie@kernel.org> + +properties: + compatible: + const: linux,spdif-dit + + "#sound-dai-cells": + const: 0 + +required: + - "#sound-dai-cells" + - compatible + +additionalProperties: false + +examples: + - | + spdif-out { + #sound-dai-cells = <0>; + compatible = "linux,spdif-dit"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/max9892x.txt b/Documentation/devicetree/bindings/sound/max9892x.txt index f6171591ddc6..98cb9ba5b328 100644 --- a/Documentation/devicetree/bindings/sound/max9892x.txt +++ b/Documentation/devicetree/bindings/sound/max9892x.txt @@ -30,6 +30,9 @@ Required properties: - reg : the I2C address of the device for I2C +Optional properties: + - reset-gpios : GPIO to reset the device + Example: codec: max98927@3a { diff --git a/Documentation/devicetree/bindings/sound/maxim,max98520.yaml b/Documentation/devicetree/bindings/sound/maxim,max98520.yaml new file mode 100644 index 000000000000..b6509cb2c8e0 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/maxim,max98520.yaml @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/maxim,max98520.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim Integrated MAX98520 Speaker Amplifier Driver + +maintainers: + - George Song <george.song@maximintegrated.com> + +properties: + compatible: + const: maxim,max98520 + + reg: + maxItems: 1 + description: I2C address of the device. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + max98520: amplifier@38 { + compatible = "maxim,max98520"; + reg = <0x38>; + }; + }; + diff --git a/Documentation/devicetree/bindings/sound/mt8192-afe-pcm.yaml b/Documentation/devicetree/bindings/sound/mt8192-afe-pcm.yaml new file mode 100644 index 000000000000..7a25bc9b8060 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mt8192-afe-pcm.yaml @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/mt8192-afe-pcm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Mediatek AFE PCM controller for mt8192 + +maintainers: + - Jiaxin Yu <jiaxin.yu@mediatek.com> + - Shane Chien <shane.chien@mediatek.com> + +properties: + compatible: + const: mediatek,mt8192-audio + + interrupts: + maxItems: 1 + + resets: + maxItems: 1 + + reset-names: + const: audiosys + + mediatek,apmixedsys: + $ref: "/schemas/types.yaml#/definitions/phandle" + description: The phandle of the mediatek apmixedsys controller + + mediatek,infracfg: + $ref: "/schemas/types.yaml#/definitions/phandle" + description: The phandle of the mediatek infracfg controller + + mediatek,topckgen: + $ref: "/schemas/types.yaml#/definitions/phandle" + description: The phandle of the mediatek topckgen controller + + power-domains: + maxItems: 1 + + clocks: + items: + - description: AFE clock + - description: ADDA DAC clock + - description: ADDA DAC pre-distortion clock + - description: audio infra sys clock + - description: audio infra 26M clock + + clock-names: + items: + - const: aud_afe_clk + - const: aud_dac_clk + - const: aud_dac_predis_clk + - const: aud_infra_clk + - const: aud_infra_26m_clk + +required: + - compatible + - interrupts + - resets + - reset-names + - mediatek,apmixedsys + - mediatek,infracfg + - mediatek,topckgen + - power-domains + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/mt8192-clk.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/power/mt8192-power.h> + #include <dt-bindings/reset/mt8192-resets.h> + + afe: mt8192-afe-pcm { + compatible = "mediatek,mt8192-audio"; + interrupts = <GIC_SPI 202 IRQ_TYPE_LEVEL_HIGH>; + resets = <&watchdog MT8192_TOPRGU_AUDIO_SW_RST>; + reset-names = "audiosys"; + mediatek,apmixedsys = <&apmixedsys>; + mediatek,infracfg = <&infracfg>; + mediatek,topckgen = <&topckgen>; + power-domains = <&scpsys MT8192_POWER_DOMAIN_AUDIO>; + clocks = <&audsys CLK_AUD_AFE>, + <&audsys CLK_AUD_DAC>, + <&audsys CLK_AUD_DAC_PREDIS>, + <&infracfg CLK_INFRA_AUDIO>, + <&infracfg CLK_INFRA_AUDIO_26M_B>; + clock-names = "aud_afe_clk", + "aud_dac_clk", + "aud_dac_predis_clk", + "aud_infra_clk", + "aud_infra_26m_clk"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/mt8195-mt6359-rt1011-rt5682.yaml b/Documentation/devicetree/bindings/sound/mt8195-mt6359-rt1011-rt5682.yaml new file mode 100644 index 000000000000..d354c30d3377 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mt8195-mt6359-rt1011-rt5682.yaml @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/mt8195-mt6359-rt1011-rt5682.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Mediatek MT8195 with MT6359, RT1011 and RT5682 ASoC sound card driver + +maintainers: + - Trevor Wu <trevor.wu@mediatek.com> + +description: + This binding describes the MT8195 sound card with RT1011 and RT5682. + +properties: + compatible: + const: mediatek,mt8195_mt6359_rt1011_rt5682 + + mediatek,platform: + $ref: "/schemas/types.yaml#/definitions/phandle" + description: The phandle of MT8195 ASoC platform. + + mediatek,dptx-codec: + $ref: "/schemas/types.yaml#/definitions/phandle" + description: The phandle of MT8195 Display Port Tx codec node. + + mediatek,hdmi-codec: + $ref: "/schemas/types.yaml#/definitions/phandle" + description: The phandle of MT8195 HDMI codec node. + +additionalProperties: false + +required: + - compatible + - mediatek,platform + +examples: + - | + + sound: mt8195-sound { + compatible = "mediatek,mt8195_mt6359_rt1011_rt5682"; + mediatek,platform = <&afe>; + pinctrl-names = "default"; + pinctrl-0 = <&aud_pins_default>; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/name-prefix.txt b/Documentation/devicetree/bindings/sound/name-prefix.txt deleted file mode 100644 index 645775908657..000000000000 --- a/Documentation/devicetree/bindings/sound/name-prefix.txt +++ /dev/null @@ -1,24 +0,0 @@ -Name prefix: - -Card implementing the routing property define the connection between -audio components as list of string pair. Component using the same -sink/source names may use the name prefix property to prepend the -name of their sinks/sources with the provided string. - -Optional name prefix property: -- sound-name-prefix : string using as prefix for the sink/source names of - the component. - -Example: Two instances of the same component. - -amp0: analog-amplifier@0 { - compatible = "simple-audio-amplifier"; - enable-gpios = <&gpio GPIOH_3 0>; - sound-name-prefix = "FRONT"; -}; - -amp1: analog-amplifier@1 { - compatible = "simple-audio-amplifier"; - enable-gpios = <&gpio GPIOH_4 0>; - sound-name-prefix = "BACK"; -}; diff --git a/Documentation/devicetree/bindings/sound/name-prefix.yaml b/Documentation/devicetree/bindings/sound/name-prefix.yaml new file mode 100644 index 000000000000..2fe57f87ac52 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/name-prefix.yaml @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/name-prefix.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Component sound name prefix + +maintainers: + - Jerome Brunet <jbrunet@baylibre.com> + +properties: + sound-name-prefix: + $ref: /schemas/types.yaml#/definitions/string + description: | + Card implementing the routing property define the connection between + audio components as list of string pair. Component using the same + sink/source names may use this property to prepend the name of their + sinks/sources with the provided string. + +additionalProperties: true diff --git a/Documentation/devicetree/bindings/sound/nau8821.txt b/Documentation/devicetree/bindings/sound/nau8821.txt new file mode 100644 index 000000000000..6c3baf7a5f21 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nau8821.txt @@ -0,0 +1,55 @@ +Nuvoton NAU88L21 audio codec + +This device supports I2C only. + +Required properties: + - compatible : Must be "nuvoton,nau8821" + + - reg : the I2C address of the device. This is either 0x1B (CSB=0) or 0x54 (CSB=1). + +Optional properties: + - nuvoton,jkdet-enable: Enable jack detection via JKDET pin. + - nuvoton,jkdet-pull-enable: Enable JKDET pin pull. If set - pin pull enabled, + otherwise pin in high impedance state. + - nuvoton,jkdet-pull-up: Pull-up JKDET pin. If set then JKDET pin is pull up, otherwise pull down. + - nuvoton,jkdet-polarity: JKDET pin polarity. 0 - active high, 1 - active low. + + - nuvoton,vref-impedance: VREF Impedance selection + 0 - Open + 1 - 25 kOhm + 2 - 125 kOhm + 3 - 2.5 kOhm + + - nuvoton,micbias-voltage: Micbias voltage level. + 0 - VDDA + 1 - VDDA + 2 - VDDA * 1.1 + 3 - VDDA * 1.2 + 4 - VDDA * 1.3 + 5 - VDDA * 1.4 + 6 - VDDA * 1.53 + 7 - VDDA * 1.53 + + - nuvoton,jack-insert-debounce: number from 0 to 7 that sets debounce time to 2^(n+2) ms + - nuvoton,jack-eject-debounce: number from 0 to 7 that sets debounce time to 2^(n+2) ms + + - nuvoton,dmic-clk-threshold: the ADC threshold of DMIC clock. + + +Example: + + headset: nau8821@1b { + compatible = "nuvoton,nau8821"; + reg = <0x1b>; + interrupt-parent = <&gpio>; + interrupts = <23 IRQ_TYPE_LEVEL_LOW>; + nuvoton,jkdet-enable; + nuvoton,jkdet-pull-enable; + nuvoton,jkdet-pull-up; + nuvoton,jkdet-polarity = <GPIO_ACTIVE_LOW>; + nuvoton,vref-impedance = <2>; + nuvoton,micbias-voltage = <6>; + nuvoton,jack-insert-debounce = <7>; + nuvoton,jack-eject-debounce = <7>; + nuvoton,dmic-clk-threshold = 3072000; + }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra186-dspk.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra186-dspk.yaml index 5f6b37c251a8..0912d3e3fd8e 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra186-dspk.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra186-dspk.yaml @@ -17,6 +17,9 @@ maintainers: - Jon Hunter <jonathanh@nvidia.com> - Sameer Pujar <spujar@nvidia.com> +allOf: + - $ref: name-prefix.yaml# + properties: $nodename: pattern: "^dspk@[0-9a-f]*$" @@ -48,12 +51,6 @@ properties: sound-name-prefix: pattern: "^DSPK[1-9]$" - $ref: /schemas/types.yaml#/definitions/string - description: - Used as prefix for sink/source names of the component. Must be a - unique string among multiple instances of the same component. - The name can be "DSPK1" or "DSPKx", where x depends on the maximum - available instances on a Tegra SoC. ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-adx.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-adx.yaml new file mode 100644 index 000000000000..c4ba12ea3611 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-adx.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra210-adx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Tegra210 ADX Device Tree Bindings + +description: | + The Audio Demultiplexer (ADX) block takes an input stream with up to + 16 channels and demultiplexes it into four output streams of up to 16 + channels each. A byte RAM helps to form output frames by any combination + of bytes from the input frame. Its design is identical to that of byte + RAM in the AMX except that the data flow direction is reversed. + +maintainers: + - Jon Hunter <jonathanh@nvidia.com> + - Mohan Kumar <mkumard@nvidia.com> + - Sameer Pujar <spujar@nvidia.com> + +allOf: + - $ref: name-prefix.yaml# + +properties: + $nodename: + pattern: "^adx@[0-9a-f]*$" + + compatible: + oneOf: + - const: nvidia,tegra210-adx + - items: + - enum: + - nvidia,tegra194-adx + - nvidia,tegra186-adx + - const: nvidia,tegra210-adx + + reg: + maxItems: 1 + + sound-name-prefix: + pattern: "^ADX[1-9]$" + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: | + ADX has one input and four outputs. Accordingly ACIF (Audio Client + Interface) port nodes are defined to represent ADX input (port 0) + and outputs (ports 1 to 4). These are connected to corresponding + ports on AHUB (Audio Hub). + properties: + port@0: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: ADX ACIF input port + patternProperties: + '^port@[1-4]': + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: ADX ACIF output ports + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + + adx@702d3800 { + compatible = "nvidia,tegra210-adx"; + reg = <0x702d3800 0x100>; + sound-name-prefix = "ADX1"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-ahub.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-ahub.yaml index 1118a9488345..df81d208184a 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra210-ahub.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-ahub.yaml @@ -85,6 +85,26 @@ patternProperties: type: object $ref: nvidia,tegra186-dspk.yaml# + '^mvc@[0-9a-f]+$': + type: object + $ref: nvidia,tegra210-mvc.yaml# + + '^sfc@[0-9a-f]+$': + type: object + $ref: nvidia,tegra210-sfc.yaml# + + '^amx@[0-9a-f]+$': + type: object + $ref: nvidia,tegra210-amx.yaml# + + '^adx@[0-9a-f]+$': + type: object + $ref: nvidia,tegra210-adx.yaml# + + '^amixer@[0-9a-f]+$': + type: object + $ref: nvidia,tegra210-mixer.yaml# + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-amx.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-amx.yaml new file mode 100644 index 000000000000..bb2111afe5a8 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-amx.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra210-amx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Tegra210 AMX Device Tree Bindings + +description: | + The Audio Multiplexer (AMX) block can multiplex up to four input streams + each of which can have maximum 16 channels and generate an output stream + with maximum 16 channels. A byte RAM helps to form an output frame by + any combination of bytes from the input frames. + +maintainers: + - Jon Hunter <jonathanh@nvidia.com> + - Mohan Kumar <mkumard@nvidia.com> + - Sameer Pujar <spujar@nvidia.com> + +allOf: + - $ref: name-prefix.yaml# + +properties: + $nodename: + pattern: "^amx@[0-9a-f]*$" + + compatible: + oneOf: + - const: nvidia,tegra210-amx + - items: + - const: nvidia,tegra186-amx + - const: nvidia,tegra210-amx + - const: nvidia,tegra194-amx + + reg: + maxItems: 1 + + sound-name-prefix: + pattern: "^AMX[1-9]$" + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: | + AMX has four inputs and one output. Accordingly ACIF (Audio Client + Interfaces) port nodes are defined to represent AMX inputs (port 0 + to 3) and output (port 4). These are connected to corresponding + ports on AHUB (Audio Hub). + + patternProperties: + '^port@[0-3]': + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: AMX ACIF input ports + + properties: + port@4: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: AMX ACIF output port + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + + amx@702d3000 { + compatible = "nvidia,tegra210-amx"; + reg = <0x702d3000 0x100>; + sound-name-prefix = "AMX1"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-dmic.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-dmic.yaml index fd275a575055..62db982bb01d 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra210-dmic.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-dmic.yaml @@ -16,6 +16,9 @@ maintainers: - Jon Hunter <jonathanh@nvidia.com> - Sameer Pujar <spujar@nvidia.com> +allOf: + - $ref: name-prefix.yaml# + properties: $nodename: pattern: "^dmic@[0-9a-f]*$" @@ -49,12 +52,6 @@ properties: sound-name-prefix: pattern: "^DMIC[1-9]$" - $ref: /schemas/types.yaml#/definitions/string - description: - used as prefix for sink/source names of the component. Must be a - unique string among multiple instances of the same component. - The name can be "DMIC1" or "DMIC2" ... "DMICx", where x depends - on the maximum available instances on a Tegra SoC. ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-i2s.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-i2s.yaml index 63370709c768..f954be636697 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra210-i2s.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-i2s.yaml @@ -16,6 +16,9 @@ maintainers: - Jon Hunter <jonathanh@nvidia.com> - Sameer Pujar <spujar@nvidia.com> +allOf: + - $ref: name-prefix.yaml# + properties: $nodename: pattern: "^i2s@[0-9a-f]*$" @@ -65,12 +68,6 @@ properties: sound-name-prefix: pattern: "^I2S[1-9]$" - $ref: /schemas/types.yaml#/definitions/string - description: - Used as prefix for sink/source names of the component. Must be a - unique string among multiple instances of the same component. - The name can be "I2S1" or "I2S2" ... "I2Sx", where x depends - on the maximum available instances on a Tegra SoC. ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-mixer.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-mixer.yaml new file mode 100644 index 000000000000..428f3c851941 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-mixer.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra210-mixer.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Tegra210 Mixer Device Tree Bindings + +description: | + The Mixer supports mixing of up to ten 7.1 audio input streams and + generate five outputs (each of which can be any combination of the + ten input streams). + +maintainers: + - Jon Hunter <jonathanh@nvidia.com> + - Mohan Kumar <mkumard@nvidia.com> + - Sameer Pujar <spujar@nvidia.com> + +allOf: + - $ref: name-prefix.yaml# + +properties: + $nodename: + pattern: "^amixer@[0-9a-f]*$" + + compatible: + oneOf: + - const: nvidia,tegra210-amixer + - items: + - enum: + - nvidia,tegra194-amixer + - nvidia,tegra186-amixer + - const: nvidia,tegra210-amixer + + reg: + maxItems: 1 + + sound-name-prefix: + pattern: "^MIXER[1-9]$" + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: | + Mixer has ten inputs and five outputs. Accordingly ACIF (Audio + Client Interfaces) port nodes are defined to represent Mixer + inputs (port 0 to 9) and outputs (port 10 to 14). These are + connected to corresponding ports on AHUB (Audio Hub). + + patternProperties: + '^port@[0-9]': + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: Mixer ACIF input ports + '^port@[10-14]': + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: Mixer ACIF output ports + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + + amixer@702dbb00 { + compatible = "nvidia,tegra210-amixer"; + reg = <0x702dbb00 0x800>; + sound-name-prefix = "MIXER1"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-mvc.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-mvc.yaml new file mode 100644 index 000000000000..e2f5a8591d8f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-mvc.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra210-mvc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Tegra210 MVC Device Tree Bindings + +description: | + The Master Volume Control (MVC) provides gain or attenuation to a digital + signal path. It can be used in input or output signal path for per-stream + volume control or it can be used as master volume control. The MVC block + has one input and one output. The input digital stream can be mono or + multi-channel (up to 7.1 channels) stream. An independent mute control is + also included in the MVC block. + +maintainers: + - Jon Hunter <jonathanh@nvidia.com> + - Mohan Kumar <mkumard@nvidia.com> + - Sameer Pujar <spujar@nvidia.com> + +allOf: + - $ref: name-prefix.yaml# + +properties: + $nodename: + pattern: "^mvc@[0-9a-f]*$" + + compatible: + oneOf: + - const: nvidia,tegra210-mvc + - items: + - enum: + - nvidia,tegra194-mvc + - nvidia,tegra186-mvc + - const: nvidia,tegra210-mvc + + reg: + maxItems: 1 + + sound-name-prefix: + pattern: "^MVC[1-9]$" + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: | + MVC ACIF (Audio Client Interface) input port. This is connected + to corresponding ACIF output port on AHUB (Audio Hub). + + port@1: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: | + MVC ACIF output port. This is connected to corresponding ACIF + input port on AHUB. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + + mvc@702da000 { + compatible = "nvidia,tegra210-mvc"; + reg = <0x702da000 0x200>; + sound-name-prefix = "MVC1"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra210-sfc.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra210-sfc.yaml new file mode 100644 index 000000000000..41ad65173548 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra210-sfc.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra210-sfc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Tegra210 SFC Device Tree Bindings + +description: | + The Sampling Frequency Converter (SFC) converts the sampling frequency + of the input signal from one frequency to another. It supports sampling + frequency conversions of streams of up to two channels (stereo). + +maintainers: + - Jon Hunter <jonathanh@nvidia.com> + - Mohan Kumar <mkumard@nvidia.com> + - Sameer Pujar <spujar@nvidia.com> + +allOf: + - $ref: name-prefix.yaml# + +properties: + $nodename: + pattern: "^sfc@[0-9a-f]*$" + + compatible: + oneOf: + - const: nvidia,tegra210-sfc + - items: + - enum: + - nvidia,tegra194-sfc + - nvidia,tegra186-sfc + - const: nvidia,tegra210-sfc + + reg: + maxItems: 1 + + sound-name-prefix: + pattern: "^SFC[1-9]$" + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: | + SFC ACIF (Audio Client Interface) input port. This is connected + to corresponding ACIF output port on AHUB (Audio Hub). + + port@1: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: | + SFC ACIF output port. This is connected to corresponding ACIF + input port on AHUB. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + + sfc@702d2000 { + compatible = "nvidia,tegra210-sfc"; + reg = <0x702d2000 0x200>; + sound-name-prefix = "SFC1"; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml b/Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml index ffb8fcfeb629..7667471be1e4 100644 --- a/Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml +++ b/Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml @@ -9,6 +9,9 @@ title: NXP/Goodix TFA989X (TFA1) Audio Amplifiers maintainers: - Stephan Gerhold <stephan@gerhold.net> +allOf: + - $ref: name-prefix.yaml# + properties: compatible: enum: @@ -21,11 +24,7 @@ properties: '#sound-dai-cells': const: 0 - sound-name-prefix: - $ref: /schemas/types.yaml#/definitions/string - description: - Used as prefix for sink/source names of the component. Must be a - unique string among multiple instances of the same component. + sound-name-prefix: true vddd-supply: description: regulator phandle for the VDDD power supply. diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml b/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml index 443d556caa69..bc762b39c68a 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml @@ -11,7 +11,9 @@ maintainers: properties: compatible: - const: qcom,sm8250-lpass-rx-macro + enum: + - qcom,sc7280-lpass-rx-macro + - qcom,sm8250-lpass-rx-macro reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-tx-macro.yaml b/Documentation/devicetree/bindings/sound/qcom,lpass-tx-macro.yaml index 6b5ca02ccce4..74f53864e7a7 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-tx-macro.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-tx-macro.yaml @@ -11,7 +11,9 @@ maintainers: properties: compatible: - const: qcom,sm8250-lpass-tx-macro + enum: + - qcom,sc7280-lpass-tx-macro + - qcom,sm8250-lpass-tx-macro reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml b/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml index 679b49cbe30f..99f2c3687fbd 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml @@ -11,7 +11,9 @@ maintainers: properties: compatible: - const: qcom,sm8250-lpass-va-macro + enum: + - qcom,sc7280-lpass-va-macro + - qcom,sm8250-lpass-va-macro reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-wsa-macro.yaml b/Documentation/devicetree/bindings/sound/qcom,lpass-wsa-macro.yaml index 435b019a1e3d..13cdb8a10687 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-wsa-macro.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-wsa-macro.yaml @@ -11,7 +11,9 @@ maintainers: properties: compatible: - const: qcom,sm8250-lpass-wsa-macro + enum: + - qcom,sc7280-lpass-wsa-macro + - qcom,sm8250-lpass-wsa-macro reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sound/qcom,q6afe.txt b/Documentation/devicetree/bindings/sound/qcom,q6afe.txt index 2d6fb2ea75a0..bc6b5f1fe4f1 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6afe.txt +++ b/Documentation/devicetree/bindings/sound/qcom,q6afe.txt @@ -12,190 +12,9 @@ used by all apr services. Must contain the following properties. from DSP. example "qcom,q6afe" -= AFE DAIs (Digial Audio Interface) -"dais" subnode of the AFE node. It represents afe dais, each afe dai is a -subnode of "dais" representing board specific dai setup. -"dais" node should have following properties followed by dai children. - -- compatible: - Usage: required - Value type: <stringlist> - Definition: must be "qcom,q6afe-dais" - -- #sound-dai-cells - Usage: required - Value type: <u32> - Definition: Must be 1 - -- #address-cells - Usage: required - Value type: <u32> - Definition: Must be 1 - -- #size-cells - Usage: required - Value type: <u32> - Definition: Must be 0 - -== AFE DAI is subnode of "dais" and represent a dai, it includes board specific -configuration of each dai. Must contain the following properties. - -- reg - Usage: required - Value type: <u32> - Definition: Must be dai id - -- qcom,sd-lines - Usage: required for mi2s interface - Value type: <prop-encoded-array> - Definition: Must be list of serial data lines used by this dai. - should be one or more of the 0-3 sd lines. - - - qcom,tdm-sync-mode: - Usage: required for tdm interface - Value type: <prop-encoded-array> - Definition: Synchronization mode. - 0 - Short sync bit mode - 1 - Long sync mode - 2 - Short sync slot mode - - - qcom,tdm-sync-src: - Usage: required for tdm interface - Value type: <prop-encoded-array> - Definition: Synchronization source. - 0 - External source - 1 - Internal source - - - qcom,tdm-data-out: - Usage: required for tdm interface - Value type: <prop-encoded-array> - Definition: Data out signal to drive with other masters. - 0 - Disable - 1 - Enable - - - qcom,tdm-invert-sync: - Usage: required for tdm interface - Value type: <prop-encoded-array> - Definition: Invert the sync. - 0 - Normal - 1 - Invert - - - qcom,tdm-data-delay: - Usage: required for tdm interface - Value type: <prop-encoded-array> - Definition: Number of bit clock to delay data - with respect to sync edge. - 0 - 0 bit clock cycle - 1 - 1 bit clock cycle - 2 - 2 bit clock cycle - - - qcom,tdm-data-align: - Usage: required for tdm interface - Value type: <prop-encoded-array> - Definition: Indicate how data is packed - within the slot. For example, 32 slot width in case of - sample bit width is 24. - 0 - MSB - 1 - LSB - -= AFE CLOCKSS -"clocks" subnode of the AFE node. It represents q6afe clocks -"clocks" node should have following properties. -- compatible: - Usage: required - Value type: <stringlist> - Definition: must be "qcom,q6afe-clocks" - -- #clock-cells: - Usage: required - Value type: <u32> - Definition: Must be 2. Clock Id followed by - below valid clock coupling attributes. - 1 - for no coupled clock - 2 - for dividend of the coupled clock - 3 - for divisor of the coupled clock - 4 - for inverted and no couple clock - = EXAMPLE apr-service@4 { compatible = "qcom,q6afe"; reg = <APR_SVC_AFE>; - - dais { - compatible = "qcom,q6afe-dais"; - #sound-dai-cells = <1>; - #address-cells = <1>; - #size-cells = <0>; - - dai@1 { - reg = <HDMI_RX>; - }; - - dai@24 { - reg = <PRIMARY_TDM_RX_0>; - qcom,tdm-sync-mode = <1>: - qcom,tdm-sync-src = <1>; - qcom,tdm-data-out = <0>; - qcom,tdm-invert-sync = <1>; - qcom,tdm-data-delay = <1>; - qcom,tdm-data-align = <0>; - - }; - - dai@25 { - reg = <PRIMARY_TDM_TX_0>; - qcom,tdm-sync-mode = <1>: - qcom,tdm-sync-src = <1>; - qcom,tdm-data-out = <0>; - qcom,tdm-invert-sync = <1>; - qcom,tdm-data-delay <1>: - qcom,tdm-data-align = <0>; - }; - - dai@16 { - reg = <PRIMARY_MI2S_RX>; - qcom,sd-lines = <0 2>; - }; - - dai@17 { - reg = <PRIMARY_MI2S_TX>; - qcom,sd-lines = <1>; - }; - - dai@18 { - reg = <SECONDARY_MI2S_RX>; - qcom,sd-lines = <0 3>; - }; - - dai@19 { - reg = <SECONDARY_MI2S_TX>; - qcom,sd-lines = <1>; - }; - - dai@20 { - reg = <TERTIARY_MI2S_RX>; - qcom,sd-lines = <1 3>; - }; - - dai@21 { - reg = <TERTIARY_MI2S_TX>; - qcom,sd-lines = <0>; - }; - - dai@22 { - reg = <QUATERNARY_MI2S_RX>; - qcom,sd-lines = <0>; - }; - - dai@23 { - reg = <QUATERNARY_MI2S_TX>; - qcom,sd-lines = <1>; - }; - }; - - clocks { - compatible = "qcom,q6afe-clocks"; - #clock-cells = <2>; - }; }; diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml new file mode 100644 index 000000000000..5d972784321d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/sound/qcom,q6apm-dai.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm Audio Process Manager Digital Audio Interfaces binding + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: | + This binding describes the Qualcomm APM DAIs in DSP + +properties: + compatible: + const: qcom,q6apm-dais + + reg: + maxItems: 1 + + iommus: + maxItems: 1 + +required: + - compatible + - iommus + - reg + +additionalProperties: false + +examples: + - | + #include <dt-bindings/soc/qcom,gpr.h> + gpr { + compatible = "qcom,gpr"; + #address-cells = <1>; + #size-cells = <0>; + qcom,domain = <GPR_DOMAIN_ID_ADSP>; + service@1 { + compatible = "qcom,q6apm"; + reg = <1>; + + #address-cells = <1>; + #size-cells = <0>; + + apm-dai@1 { + compatible = "qcom,q6apm-dais"; + iommus = <&apps_smmu 0x1801 0x0>; + reg = <1>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,q6asm.txt b/Documentation/devicetree/bindings/sound/qcom,q6asm.txt index 8c4883becae9..0d0075125243 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6asm.txt +++ b/Documentation/devicetree/bindings/sound/qcom,q6asm.txt @@ -14,7 +14,7 @@ used by the apr service device. from DSP. example "qcom,q6asm-v2.0" -= ASM DAIs (Digial Audio Interface) += ASM DAIs (Digital Audio Interface) "dais" subnode of the ASM node represents dai specific configuration - compatible: diff --git a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-clocks.yaml b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-clocks.yaml new file mode 100644 index 000000000000..f83f00737a2f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-clocks.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/sound/qcom,q6dsp-lpass-clocks.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm DSP LPASS Clock Controller binding + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: | + This binding describes the Qualcomm DSP Clock Controller + +properties: + compatible: + enum: + - qcom,q6afe-clocks + - qcom,q6prm-lpass-clocks + + reg: + maxItems: 1 + + '#clock-cells': + const: 2 + description: + Clock Id is followed by clock coupling attributes. + 1 = for no coupled clock + 2 = for dividend of the coupled clock + 3 = for divisor of the coupled clock + 4 = for inverted and no couple clock + +required: + - compatible + - reg + - "#clock-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/soc/qcom,apr.h> + #include <dt-bindings/sound/qcom,q6afe.h> + apr { + #address-cells = <1>; + #size-cells = <0>; + apr-service@4 { + reg = <APR_SVC_AFE>; + #address-cells = <1>; + #size-cells = <0>; + clock-controller@2 { + compatible = "qcom,q6afe-clocks"; + reg = <2>; + #clock-cells = <2>; + }; + }; + }; + + - | + #include <dt-bindings/soc/qcom,gpr.h> + gpr { + compatible = "qcom,gpr"; + qcom,domain = <GPR_DOMAIN_ID_ADSP>; + #address-cells = <1>; + #size-cells = <0>; + service@2 { + reg = <GPR_PRM_MODULE_IID>; + compatible = "qcom,q6prm"; + #address-cells = <1>; + #size-cells = <0>; + clock-controller@2 { + compatible = "qcom,q6prm-lpass-clocks"; + reg = <2>; + #clock-cells = <2>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml new file mode 100644 index 000000000000..dc7fba7b92d5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml @@ -0,0 +1,205 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/sound/qcom,q6dsp-lpass-ports.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm DSP LPASS(Low Power Audio SubSystem) Audio Ports binding + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: | + This binding describes the Qualcomm DSP LPASS Audio ports + +properties: + compatible: + enum: + - qcom,q6afe-dais + - qcom,q6apm-lpass-dais + + reg: + maxItems: 1 + + '#sound-dai-cells': + const: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +#Digital Audio Interfaces +patternProperties: + '^dai@[0-9]+$': + type: object + description: + Q6DSP Digital Audio Interfaces. + + properties: + reg: + description: + Digital Audio Interface ID + + qcom,sd-lines: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + List of serial data lines used by this dai.should be one or more of the 0-3 sd lines. + minItems: 1 + maxItems: 4 + uniqueItems: true + items: + minimum: 0 + maximum: 3 + + qcom,tdm-sync-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + description: + TDM Synchronization mode + 0 = Short sync bit mode + 1 = Long sync mode + 2 = Short sync slot mode + + qcom,tdm-sync-src: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + description: + TDM Synchronization source + 0 = External source + 1 = Internal source + + qcom,tdm-data-out: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + description: + TDM Data out signal to drive with other masters + 0 = Disable + 1 = Enable + + qcom,tdm-invert-sync: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + description: + TDM Invert the sync + 0 = Normal + 1 = Invert + + qcom,tdm-data-delay: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + description: + TDM Number of bit clock to delay data + 0 = 0 bit clock cycle + 1 = 1 bit clock cycle + 2 = 2 bit clock cycle + + qcom,tdm-data-align: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + description: + Indicate how data is packed within the slot. For example, 32 slot + width in case of sample bit width is 24TDM Invert the sync. + 0 = MSB + 1 = LSB + + required: + - reg + + allOf: + - if: + properties: + reg: + contains: + # TDM DAI ID range from PRIMARY_TDM_RX_0 - QUINARY_TDM_TX_7 + items: + minimum: 24 + maximum: 103 + then: + required: + - qcom,tdm-sync-mode + - qcom,tdm-sync-src + - qcom,tdm-data-out + - qcom,tdm-invert-sync + - qcom,tdm-data-delay + - qcom,tdm-data-align + + - if: + properties: + reg: + contains: + # MI2S DAI ID range PRIMARY_MI2S_RX - QUATERNARY_MI2S_TX and + # QUINARY_MI2S_RX - QUINARY_MI2S_TX + items: + oneOf: + - minimum: 16 + maximum: 23 + - minimum: 127 + maximum: 128 + then: + required: + - qcom,sd-lines + + additionalProperties: false + +required: + - compatible + - reg + - "#sound-dai-cells" + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/soc/qcom,apr.h> + #include <dt-bindings/sound/qcom,q6afe.h> + apr { + #address-cells = <1>; + #size-cells = <0>; + apr-service@4 { + reg = <APR_SVC_AFE>; + #address-cells = <1>; + #size-cells = <0>; + q6afedai@1 { + compatible = "qcom,q6afe-dais"; + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + #sound-dai-cells = <1>; + + dai@22 { + reg = <QUATERNARY_MI2S_RX>; + qcom,sd-lines = <0 1 2 3>; + }; + }; + }; + }; + - | + #include <dt-bindings/soc/qcom,gpr.h> + gpr { + compatible = "qcom,gpr"; + #address-cells = <1>; + #size-cells = <0>; + qcom,domain = <GPR_DOMAIN_ID_ADSP>; + service@1 { + compatible = "qcom,q6apm"; + reg = <GPR_APM_MODULE_IID>; + #address-cells = <1>; + #size-cells = <0>; + q6apmdai@1 { + compatible = "qcom,q6apm-lpass-dais"; + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + #sound-dai-cells = <1>; + + dai@22 { + reg = <QUATERNARY_MI2S_RX>; + qcom,sd-lines = <0 1 2 3>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/realtek,rt5682s.yaml b/Documentation/devicetree/bindings/sound/realtek,rt5682s.yaml new file mode 100644 index 000000000000..2b8b7b51fe55 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/realtek,rt5682s.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/realtek,rt5682s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek rt5682s codec devicetree bindings + +maintainers: + - Derek Fang <derek.fang@realtek.com> + +description: | + Rt5682s(ALC5682I-VS) is a rt5682i variant which supports I2C only. + +properties: + compatible: + const: realtek,rt5682s + + reg: + maxItems: 1 + description: I2C address of the device. + + interrupts: + description: The CODEC's interrupt output. + + realtek,dmic1-data-pin: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: + - 0 # dmic1 data is not used + - 1 # using GPIO2 pin as dmic1 data pin + - 2 # using GPIO5 pin as dmic1 data pin + description: | + Specify which GPIO pin be used as DMIC1 data pin. + + realtek,dmic1-clk-pin: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: + - 0 # dmic1 clk is not used + - 1 # using GPIO1 pin as dmic1 clock pin + - 2 # using GPIO3 pin as dmic1 clock pin + description: | + Specify which GPIO pin be used as DMIC1 clk pin. + + realtek,jd-src: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: + - 0 # No JD is used + - 1 # using JD1 as JD source + description: | + Specify which JD source be used. + + realtek,ldo1-en-gpios: + description: | + The GPIO that controls the CODEC's LDO1_EN pin. + + realtek,dmic-clk-rate-hz: + description: | + Set the clock rate (hz) for the requirement of the particular DMIC. + + realtek,dmic-delay-ms: + description: | + Set the delay time (ms) for the requirement of the particular DMIC. + + realtek,dmic-clk-driving-high: + type: boolean + description: | + Set the high driving of the DMIC clock out. + + clocks: + items: + - description: phandle and clock specifier for codec MCLK. + + clock-names: + items: + - const: mclk + + "#clock-cells": + const: 1 + + clock-output-names: + minItems: 2 + maxItems: 2 + description: Name given for DAI word clock and bit clock outputs. + +additionalProperties: false + +required: + - compatible + - reg + +examples: + - | + #include <dt-bindings/gpio/tegra-gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + codec@1a { + compatible = "realtek,rt5682s"; + reg = <0x1a>; + interrupt-parent = <&gpio>; + interrupts = <TEGRA_GPIO(U, 6) IRQ_TYPE_LEVEL_HIGH>; + realtek,ldo1-en-gpios = + <&gpio TEGRA_GPIO(R, 2) GPIO_ACTIVE_HIGH>; + realtek,dmic1-data-pin = <1>; + realtek,dmic1-clk-pin = <1>; + realtek,jd-src = <1>; + + #clock-cells = <1>; + clock-output-names = "rt5682-dai-wclk", "rt5682-dai-bclk"; + + clocks = <&osc>; + clock-names = "mclk"; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/richtek,rt9120.yaml b/Documentation/devicetree/bindings/sound/richtek,rt9120.yaml new file mode 100644 index 000000000000..5655ca568240 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/richtek,rt9120.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/richtek,rt9120.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Richtek RT9120 Class-D audio amplifier + +maintainers: + - ChiYuan Huang <cy_huang@richtek.com> + +description: | + The RT9120 is a high efficiency, I2S-input, stereo audio power amplifier + delivering 2*20W into 8 Ohm BTL speaker loads. It supports the wide input + voltage range from 4.5V to 26.4V to meet the need on most common + applications like as TV, monitors. home entertainment, electronic music + equipment. + +properties: + compatible: + enum: + - richtek,rt9120 + + reg: + description: I2C device address + maxItems: 1 + + pwdnn-gpios: + description: GPIO used for power down, low active + maxItems: 1 + + dvdd-supply: + description: | + Supply for the default on DVDD power, voltage domain must be 3P3V or 1P8V + + '#sound-dai-cells': + const: 0 + +required: + - compatible + - reg + - dvdd-supply + - '#sound-dai-cells' + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + rt9120@1a { + compatible = "richtek,rt9120"; + reg = <0x1a>; + pwdnn-gpios = <&gpio26 2 0>; + dvdd-supply = <&vdd_io_reg>; + #sound-dai-cells = <0>; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml b/Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml new file mode 100644 index 000000000000..6a7c004bef17 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/rockchip,i2s-tdm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip I2S/TDM Controller + +description: + The Rockchip I2S/TDM Controller is a Time Division Multiplexed + audio interface found in various Rockchip SoCs, allowing up + to 8 channels of audio over a serial interface. + +maintainers: + - Nicolas Frattaroli <frattaroli.nicolas@gmail.com> + +properties: + compatible: + enum: + - rockchip,px30-i2s-tdm + - rockchip,rk1808-i2s-tdm + - rockchip,rk3308-i2s-tdm + - rockchip,rk3568-i2s-tdm + - rockchip,rv1126-i2s-tdm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + dmas: + minItems: 1 + maxItems: 2 + + dma-names: + minItems: 1 + maxItems: 2 + items: + enum: + - rx + - tx + + clocks: + minItems: 3 + items: + - description: clock for TX + - description: clock for RX + - description: AHB clock driving the interface + - description: + Parent clock for mclk_tx (only required when using mclk-calibrate) + - description: + Parent clock for mclk_rx (only required when using mclk-calibrate) + - description: + Clock for sample rates that are an integer multiple of 8000 + (only required when using mclk-calibrate) + - description: + Clock for sample rates that are an integer multiple of 11025 + (only required when using mclk-calibrate) + + clock-names: + minItems: 3 + items: + - const: mclk_tx + - const: mclk_rx + - const: hclk + - const: mclk_tx_src + - const: mclk_rx_src + - const: mclk_root0 + - const: mclk_root1 + + resets: + minItems: 1 + maxItems: 2 + description: resets for the tx and rx directions + + reset-names: + minItems: 1 + maxItems: 2 + items: + enum: + - tx-m + - rx-m + + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + The phandle of the syscon node for the GRF register. + + rockchip,trcm-sync-tx-only: + type: boolean + description: Use TX BCLK/LRCK for both TX and RX. + + rockchip,trcm-sync-rx-only: + type: boolean + description: Use RX BCLK/LRCK for both TX and RX. + + "#sound-dai-cells": + const: 0 + + rockchip,i2s-rx-route: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Defines the mapping of I2S RX sdis to I2S data bus lines. + By default, they are mapped one-to-one. + rockchip,i2s-rx-route = <3> would mean sdi3 is receiving from data0. + maxItems: 4 + items: + enum: [0, 1, 2, 3] + + rockchip,i2s-tx-route: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Defines the mapping of I2S TX sdos to I2S data bus lines. + By default, they are mapped one-to-one. + rockchip,i2s-tx-route = <3> would mean sdo3 is sending to data0. + maxItems: 4 + items: + enum: [0, 1, 2, 3] + + rockchip,io-multiplex: + description: + Specify that the GPIO lines on the I2S bus are multiplexed such that + the direction (input/output) needs to be dynamically adjusted. + type: boolean + + +required: + - compatible + - reg + - interrupts + - dmas + - dma-names + - clocks + - clock-names + - resets + - reset-names + - rockchip,grf + - "#sound-dai-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/rk3568-cru.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/pinctrl/rockchip.h> + + bus { + #address-cells = <2>; + #size-cells = <2>; + i2s@fe410000 { + compatible = "rockchip,rk3568-i2s-tdm"; + reg = <0x0 0xfe410000 0x0 0x1000>; + interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&cru MCLK_I2S1_8CH_TX>, <&cru MCLK_I2S1_8CH_RX>, + <&cru HCLK_I2S1_8CH>; + clock-names = "mclk_tx", "mclk_rx", "hclk"; + dmas = <&dmac1 3>, <&dmac1 2>; + dma-names = "rx", "tx"; + resets = <&cru SRST_M_I2S1_8CH_TX>, <&cru SRST_M_I2S1_8CH_RX>; + reset-names = "tx-m", "rx-m"; + rockchip,trcm-sync-tx-only; + rockchip,grf = <&grf>; + #sound-dai-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = + <&i2s1m0_sclktx + &i2s1m0_sclkrx + &i2s1m0_lrcktx + &i2s1m0_lrckrx + &i2s1m0_sdi0 + &i2s1m0_sdi1 + &i2s1m0_sdi2 + &i2s1m0_sdi3 + &i2s1m0_sdo0 + &i2s1m0_sdo1 + &i2s1m0_sdo2 + &i2s1m0_sdo3>; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/rockchip,pdm.txt b/Documentation/devicetree/bindings/sound/rockchip,pdm.txt deleted file mode 100644 index 98572a25122f..000000000000 --- a/Documentation/devicetree/bindings/sound/rockchip,pdm.txt +++ /dev/null @@ -1,46 +0,0 @@ -* Rockchip PDM controller - -Required properties: - -- compatible: "rockchip,pdm" - - "rockchip,px30-pdm" - - "rockchip,rk1808-pdm" - - "rockchip,rk3308-pdm" -- reg: physical base address of the controller and length of memory mapped - region. -- dmas: DMA specifiers for rx dma. See the DMA client binding, - Documentation/devicetree/bindings/dma/dma.txt -- dma-names: should include "rx". -- clocks: a list of phandle + clock-specifer pairs, one for each entry in clock-names. -- clock-names: should contain following: - - "pdm_hclk": clock for PDM BUS - - "pdm_clk" : clock for PDM controller -- resets: a list of phandle + reset-specifer paris, one for each entry in reset-names. -- reset-names: reset names, should include "pdm-m". -- pinctrl-names: Must contain a "default" entry. -- pinctrl-N: One property must exist for each entry in - pinctrl-names. See ../pinctrl/pinctrl-bindings.txt - for details of the property values. - -Example for rk3328 PDM controller: - -pdm: pdm@ff040000 { - compatible = "rockchip,pdm"; - reg = <0x0 0xff040000 0x0 0x1000>; - clocks = <&clk_pdm>, <&clk_gates28 0>; - clock-names = "pdm_clk", "pdm_hclk"; - dmas = <&pdma 16>; - #dma-cells = <1>; - dma-names = "rx"; - pinctrl-names = "default", "sleep"; - pinctrl-0 = <&pdmm0_clk - &pdmm0_sdi0 - &pdmm0_sdi1 - &pdmm0_sdi2 - &pdmm0_sdi3>; - pinctrl-1 = <&pdmm0_clk_sleep - &pdmm0_sdi0_sleep - &pdmm0_sdi1_sleep - &pdmm0_sdi2_sleep - &pdmm0_sdi3_sleep>; -}; diff --git a/Documentation/devicetree/bindings/sound/rockchip,pdm.yaml b/Documentation/devicetree/bindings/sound/rockchip,pdm.yaml new file mode 100644 index 000000000000..22e1cf6c0592 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/rockchip,pdm.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/rockchip,pdm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip PDM controller + +description: + The Pulse Density Modulation Interface Controller (PDMC) is + a PDM interface controller and decoder that support PDM format. + It integrates a clock generator driving the PDM microphone + and embeds filters which decimate the incoming bit stream to + obtain most common audio rates. + +maintainers: + - Heiko Stuebner <heiko@sntech.de> + +properties: + compatible: + enum: + - rockchip,pdm + - rockchip,px30-pdm + - rockchip,rk1808-pdm + - rockchip,rk3308-pdm + - rockchip,rk3568-pdm + - rockchip,rv1126-pdm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: clock for PDM controller + - description: clock for PDM BUS + + clock-names: + items: + - const: pdm_clk + - const: pdm_hclk + + dmas: + maxItems: 1 + + dma-names: + items: + - const: rx + + power-domains: + maxItems: 1 + + resets: + items: + - description: reset for PDM controller + + reset-names: + items: + - const: pdm-m + + rockchip,path-map: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Defines the mapping of PDM SDIx to PDM PATHx. + By default, they are mapped one-to-one. + maxItems: 4 + uniqueItems: true + items: + enum: [ 0, 1, 2, 3 ] + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - dmas + - dma-names + - "#sound-dai-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/rk3328-cru.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/pinctrl/rockchip.h> + + bus { + #address-cells = <2>; + #size-cells = <2>; + + pdm@ff040000 { + compatible = "rockchip,pdm"; + reg = <0x0 0xff040000 0x0 0x1000>; + interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&cru SCLK_PDM>, <&cru HCLK_PDM>; + clock-names = "pdm_clk", "pdm_hclk"; + dmas = <&dmac 16>; + dma-names = "rx"; + #sound-dai-cells = <0>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&pdmm0_clk + &pdmm0_sdi0 + &pdmm0_sdi1 + &pdmm0_sdi2 + &pdmm0_sdi3>; + pinctrl-1 = <&pdmm0_clk_sleep + &pdmm0_sdi0_sleep + &pdmm0_sdi1_sleep + &pdmm0_sdi2_sleep + &pdmm0_sdi3_sleep>; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/rt5659.txt b/Documentation/devicetree/bindings/sound/rt5659.txt index c473df5c878c..013f534fa059 100644 --- a/Documentation/devicetree/bindings/sound/rt5659.txt +++ b/Documentation/devicetree/bindings/sound/rt5659.txt @@ -42,7 +42,7 @@ Optional properties: - realtek,ldo1-en-gpios : The GPIO that controls the CODEC's LDO1_EN pin. - realtek,reset-gpios : The GPIO that controls the CODEC's RESET pin. -- sound-name-prefix: Please refer to name-prefix.txt +- sound-name-prefix: Please refer to name-prefix.yaml - ports: A Codec may have a single or multiple I2S interfaces. These interfaces on Codec side can be described under 'ports' or 'port'. diff --git a/Documentation/devicetree/bindings/sound/simple-amplifier.txt b/Documentation/devicetree/bindings/sound/simple-amplifier.txt deleted file mode 100644 index b1b097cc9b68..000000000000 --- a/Documentation/devicetree/bindings/sound/simple-amplifier.txt +++ /dev/null @@ -1,17 +0,0 @@ -Simple Amplifier Audio Driver - -Required properties: -- compatible : "dioo,dio2125" or "simple-audio-amplifier" - -Optional properties: -- enable-gpios : the gpio connected to the enable pin of the simple amplifier -- VCC-supply : power supply for the device, as covered - in Documentation/devicetree/bindings/regulator/regulator.txt - -Example: - -amp: analog-amplifier { - compatible = "simple-audio-amplifier"; - VCC-supply = <®ulator>; - enable-gpios = <&gpio GPIOH_3 0>; -}; diff --git a/Documentation/devicetree/bindings/sound/simple-audio-amplifier.yaml b/Documentation/devicetree/bindings/sound/simple-audio-amplifier.yaml new file mode 100644 index 000000000000..26379377a7ac --- /dev/null +++ b/Documentation/devicetree/bindings/sound/simple-audio-amplifier.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/simple-audio-amplifier.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Simple Audio Amplifier Device Tree Bindings + +maintainers: + - Jerome Brunet <jbrunet@baylibre.com> + +properties: + compatible: + enum: + - dioo,dio2125 + - simple-audio-amplifier + + enable-gpios: + maxItems: 1 + + VCC-supply: + description: > + power supply for the device + + sound-name-prefix: + $ref: /schemas/types.yaml#/definitions/string + description: > + See ./name-prefix.txt + +required: + - compatible + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/meson8-gpio.h> + + analog-amplifier { + compatible = "simple-audio-amplifier"; + VCC-supply = <®ulator>; + enable-gpios = <&gpio GPIOH_3 0>; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/simple-audio-mux.yaml b/Documentation/devicetree/bindings/sound/simple-audio-mux.yaml index 5986d1fcbb54..b5fc35ee9b65 100644 --- a/Documentation/devicetree/bindings/sound/simple-audio-mux.yaml +++ b/Documentation/devicetree/bindings/sound/simple-audio-mux.yaml @@ -13,6 +13,9 @@ description: | Simple audio multiplexers are driven using gpios, allowing to select which of their input line is connected to the output line. +allOf: + - $ref: name-prefix.yaml# + properties: compatible: const: simple-audio-mux @@ -21,11 +24,7 @@ properties: description: | GPIOs used to select the input line. - sound-name-prefix: - $ref: /schemas/types.yaml#/definitions/string - description: - Used as prefix for sink/source names of the component. Must be a - unique string among multiple instances of the same component. + sound-name-prefix: true required: - compatible diff --git a/Documentation/devicetree/bindings/sound/socionext,uniphier-aio.yaml b/Documentation/devicetree/bindings/sound/socionext,uniphier-aio.yaml index 55ae198220f4..70f62ecd6eb2 100644 --- a/Documentation/devicetree/bindings/sound/socionext,uniphier-aio.yaml +++ b/Documentation/devicetree/bindings/sound/socionext,uniphier-aio.yaml @@ -46,7 +46,27 @@ properties: patternProperties: "^port@[0-9]$": - description: FIXME, Need to define what each port is. + description: | + Port number of DT node is specified by the following DAI channels that + depends on SoC. + ld11-aio,ld20-aio: + 0: hdmi + 1: pcmin2 + 2: line + 3: hpcmout1 + 4: pcmout3 + 5: hiecout1 + 6: epcmout2 + 7: epcmout3 + 8: hieccompout1 + pxs2-aio: + 0: hdmi + 1: line + 2: aux + 3: hiecout1 + 4: iecout1 + 5: hieccompout1 + 6: ieccompout1 $ref: audio-graph-port.yaml# unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/sound/socionext,uniphier-evea.yaml b/Documentation/devicetree/bindings/sound/socionext,uniphier-evea.yaml index 48ddfcbbcbae..be6acfda9999 100644 --- a/Documentation/devicetree/bindings/sound/socionext,uniphier-evea.yaml +++ b/Documentation/devicetree/bindings/sound/socionext,uniphier-evea.yaml @@ -40,7 +40,11 @@ properties: patternProperties: "^port@[0-9]$": - description: FIXME, Need to define what each port is. + description: | + Port number of DT node is specified by the following DAI channels. + 0: line1 + 1: hp + 2: line2 $ref: audio-graph-port.yaml# unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/sound/spdif-transmitter.txt b/Documentation/devicetree/bindings/sound/spdif-transmitter.txt deleted file mode 100644 index 55a85841dd85..000000000000 --- a/Documentation/devicetree/bindings/sound/spdif-transmitter.txt +++ /dev/null @@ -1,10 +0,0 @@ -Device-Tree bindings for dummy spdif transmitter - -Required properties: - - compatible: should be "linux,spdif-dit". - -Example node: - - codec: spdif-transmitter { - compatible = "linux,spdif-dit"; - }; diff --git a/Documentation/devicetree/bindings/sound/test-component.yaml b/Documentation/devicetree/bindings/sound/test-component.yaml new file mode 100644 index 000000000000..17fdb4317239 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/test-component.yaml @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/test-component.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Test Component Device Tree Bindings + +maintainers: + - Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + +properties: + compatible: + enum: + - test-cpu + - test-cpu-verbose + - test-cpu-verbose-dai + - test-cpu-verbose-component + - test-codec + - test-codec-verbose + - test-codec-verbose-dai + - test-codec-verbose-component + +required: + - compatible + +additionalProperties: true + +examples: + - | + test_cpu { + compatible = "test-cpu"; + }; diff --git a/Documentation/devicetree/bindings/sound/wlf,wm8962.yaml b/Documentation/devicetree/bindings/sound/wlf,wm8962.yaml new file mode 100644 index 000000000000..0e6249d7c133 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wlf,wm8962.yaml @@ -0,0 +1,118 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/wlf,wm8962.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Wolfson WM8962 Ultra-Low Power Stereo CODEC + +maintainers: + - patches@opensource.cirrus.com + +properties: + compatible: + const: wlf,wm8962 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + "#sound-dai-cells": + const: 0 + + AVDD-supply: + description: Analogue supply. + + CPVDD-supply: + description: Charge pump power supply. + + DBVDD-supply: + description: Digital Buffer Supply. + + DCVDD-supply: + description: Digital Core Supply. + + MICVDD-supply: + description: Microphone bias amp supply. + + PLLVDD-supply: + description: PLL Supply + + SPKVDD1-supply: + description: Supply for left speaker drivers. + + SPKVDD2-supply: + description: Supply for right speaker drivers. + + spk-mono: + $ref: /schemas/types.yaml#/definitions/flag + description: + If present, the SPK_MONO bit of R51 (Class D Control 2) gets set, + indicating that the speaker is in mono mode. + + mic-cfg: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Default register value for R48 (Additional Control 4). + If absent, the default should be the register default. + + gpio-cfg: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 6 + maxItems: 6 + description: + A list of GPIO configuration register values. If absent, no + configuration of these registers is performed. Note that only values + within [0x0, 0xffff] are valid. Any other value is regarded as setting + the GPIO register to its reset value 0x0. + + port: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + - AVDD-supply + - CPVDD-supply + - DBVDD-supply + - DCVDD-supply + - MICVDD-supply + - PLLVDD-supply + - SPKVDD1-supply + - SPKVDD2-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/imx6qdl-clock.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + wm8962: codec@1a { + compatible = "wlf,wm8962"; + reg = <0x1a>; + clocks = <&clks IMX6QDL_CLK_CKO>; + DCVDD-supply = <®_audio>; + DBVDD-supply = <®_audio>; + AVDD-supply = <®_audio>; + CPVDD-supply = <®_audio>; + MICVDD-supply = <®_audio>; + PLLVDD-supply = <®_audio>; + SPKVDD1-supply = <®_audio>; + SPKVDD2-supply = <®_audio>; + gpio-cfg = < + 0x0000 /* 0:Default */ + 0x0000 /* 1:Default */ + 0x0013 /* 2:FN_DMICCLK */ + 0x0000 /* 3:Default */ + 0x8014 /* 4:FN_DMICCDAT */ + 0x0000 /* 5:Default */ + >; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/wlf,wm8978.yaml b/Documentation/devicetree/bindings/sound/wlf,wm8978.yaml new file mode 100644 index 000000000000..96cf9fc9c8b0 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wlf,wm8978.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/wlf,wm8978.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Wolfson WM8978 Codec Device Tree Bindings + +maintainers: + - patches@opensource.cirrus.com + +properties: + '#sound-dai-cells': + const: 0 + + compatible: + const: wlf,wm8978 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 526000 + +required: + - '#sound-dai-cells' + - compatible + - reg + +additionalProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + codec@0 { + #sound-dai-cells = <0>; + compatible = "wlf,wm8978"; + reg = <0>; + spi-max-frequency = <500000>; + }; + }; + + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + codec@1a { + #sound-dai-cells = <0>; + compatible = "wlf,wm8978"; + reg = <0x1a>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/wm8962.txt b/Documentation/devicetree/bindings/sound/wm8962.txt deleted file mode 100644 index c36c649ddfd0..000000000000 --- a/Documentation/devicetree/bindings/sound/wm8962.txt +++ /dev/null @@ -1,43 +0,0 @@ -WM8962 audio CODEC - -This device supports I2C only. - -Required properties: - - - compatible : "wlf,wm8962" - - - reg : the I2C address of the device. - -Optional properties: - - - clocks : The clock source of the mclk - - - spk-mono: This is a boolean property. If present, the SPK_MONO bit - of R51 (Class D Control 2) gets set, indicating that the speaker is - in mono mode. - - - mic-cfg : Default register value for R48 (Additional Control 4). - If absent, the default should be the register default. - - - gpio-cfg : A list of GPIO configuration register values. The list must - be 6 entries long. If absent, no configuration of these registers is - performed. And note that only the value within [0x0, 0xffff] is valid. - Any other value is regarded as setting the GPIO register by its reset - value 0x0. - -Example: - -wm8962: codec@1a { - compatible = "wlf,wm8962"; - reg = <0x1a>; - clocks = <&clks IMX6QDL_CLK_CKO>; - - gpio-cfg = < - 0x0000 /* 0:Default */ - 0x0000 /* 1:Default */ - 0x0013 /* 2:FN_DMICCLK */ - 0x0000 /* 3:Default */ - 0x8014 /* 4:FN_DMICCDAT */ - 0x0000 /* 5:Default */ - >; -}; diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index 65f61695f561..34888d4fc4a8 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -100,6 +100,15 @@ amidi_map MIDI device number maps assigned to the 2st OSS device; Default: 1 +Module snd-soc-core +------------------- + +The soc core module. It is used by all ALSA card drivers. +It takes the following options which have global effects. + +prealloc_buffer_size_kbytes + Specify prealloc buffer size in kbytes (default: 512). + Common parameters for top sound card modules -------------------------------------------- diff --git a/Documentation/sound/soc/codec.rst b/Documentation/sound/soc/codec.rst index 8a9737eb7597..57df149acafc 100644 --- a/Documentation/sound/soc/codec.rst +++ b/Documentation/sound/soc/codec.rst @@ -40,7 +40,7 @@ e.g. .prepare = wm8731_pcm_prepare, .hw_params = wm8731_hw_params, .shutdown = wm8731_shutdown, - .digital_mute = wm8731_mute, + .mute_stream = wm8731_mute, .set_sysclk = wm8731_set_dai_sysclk, .set_fmt = wm8731_set_dai_fmt, }; @@ -60,7 +60,7 @@ e.g. .rates = WM8731_RATES, .formats = WM8731_FORMATS,}, .ops = &wm8731_dai_ops, - .symmetric_rates = 1, + .symmetric_rate = 1, }; @@ -177,10 +177,10 @@ when the mute is applied or freed. i.e. :: - static int wm8974_mute(struct snd_soc_dai *dai, int mute) + static int wm8974_mute(struct snd_soc_dai *dai, int mute, int direction) { struct snd_soc_component *component = dai->component; - u16 mute_reg = snd_soc_component_read32(component, WM8974_DAC) & 0xffbf; + u16 mute_reg = snd_soc_component_read(component, WM8974_DAC) & 0xffbf; if (mute) snd_soc_component_write(component, WM8974_DAC, mute_reg | 0x40); diff --git a/MAINTAINERS b/MAINTAINERS index a941b85a20c9..2c9070aeba2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4468,6 +4468,17 @@ L: patches@opensource.cirrus.com S: Maintained F: sound/soc/codecs/cs* +CIRRUS LOGIC DSP FIRMWARE DRIVER +M: Simon Trimmer <simont@opensource.cirrus.com> +M: Charles Keepax <ckeepax@opensource.cirrus.com> +M: Richard Fitzgerald <rf@opensource.cirrus.com> +L: patches@opensource.cirrus.com +S: Supported +W: https://github.com/CirrusLogic/linux-drivers/wiki +T: git https://github.com/CirrusLogic/linux-drivers.git +F: drivers/firmware/cirrus/* +F: include/linux/firmware/cirrus/* + CIRRUS LOGIC EP93XX ETHERNET DRIVER M: Hartley Sweeten <hsweeten@visionengravers.com> L: netdev@vger.kernel.org @@ -16227,6 +16238,13 @@ F: Documentation/ABI/*/sysfs-driver-hid-roccat* F: drivers/hid/hid-roccat* F: include/linux/hid-roccat* +ROCKCHIP I2S TDM DRIVER +M: Nicolas Frattaroli <frattaroli.nicolas@gmail.com> +L: linux-rockchip@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml +F: sound/soc/rockchip/rockchip_i2s_tdm.* + ROCKCHIP ISP V1 DRIVER M: Helen Koike <helen.koike@collabora.com> M: Dafna Hirschfeld <dafna.hirschfeld@collabora.com> diff --git a/arch/sh/boards/mach-ecovec24/setup.c b/arch/sh/boards/mach-ecovec24/setup.c index bab91a99124e..97c5703b1818 100644 --- a/arch/sh/boards/mach-ecovec24/setup.c +++ b/arch/sh/boards/mach-ecovec24/setup.c @@ -886,7 +886,7 @@ static struct asoc_simple_card_info fsi_da7210_info = { .card = "FSIB-DA7210", .codec = "da7210.0-001a", .platform = "sh_fsi.0", - .daifmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM, + .daifmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBP_CFP, .cpu_dai = { .name = "fsib-dai", }, diff --git a/arch/sh/boards/mach-se/7724/setup.c b/arch/sh/boards/mach-se/7724/setup.c index 8d6541ba0186..d9b31d4560c0 100644 --- a/arch/sh/boards/mach-se/7724/setup.c +++ b/arch/sh/boards/mach-se/7724/setup.c @@ -305,7 +305,7 @@ static struct asoc_simple_card_info fsi_ak4642_info = { .card = "FSIA-AK4642", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi.0", - .daifmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM, + .daifmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBP_CFP, .cpu_dai = { .name = "fsia-dai", }, diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index cda7d7162cbb..75cb91055c17 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -295,6 +295,7 @@ config TURRIS_MOX_RWTM source "drivers/firmware/arm_ffa/Kconfig" source "drivers/firmware/broadcom/Kconfig" +source "drivers/firmware/cirrus/Kconfig" source "drivers/firmware/google/Kconfig" source "drivers/firmware/efi/Kconfig" source "drivers/firmware/imx/Kconfig" diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 5ced0673d94b..4e58cb474a68 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_TURRIS_MOX_RWTM) += turris-mox-rwtm.o obj-y += arm_ffa/ obj-y += arm_scmi/ obj-y += broadcom/ +obj-y += cirrus/ obj-y += meson/ obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ obj-$(CONFIG_EFI) += efi/ diff --git a/drivers/firmware/cirrus/Kconfig b/drivers/firmware/cirrus/Kconfig new file mode 100644 index 000000000000..f9503cb481d2 --- /dev/null +++ b/drivers/firmware/cirrus/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config CS_DSP + tristate + default n diff --git a/drivers/firmware/cirrus/Makefile b/drivers/firmware/cirrus/Makefile new file mode 100644 index 000000000000..f074e2638c9c --- /dev/null +++ b/drivers/firmware/cirrus/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +# +obj-$(CONFIG_CS_DSP) += cs_dsp.o diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c new file mode 100644 index 000000000000..948dd8382686 --- /dev/null +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -0,0 +1,3109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cs_dsp.c -- Cirrus Logic DSP firmware support + * + * Based on sound/soc/codecs/wm_adsp.c + * + * Copyright 2012 Wolfson Microelectronics plc + * Copyright (C) 2015-2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#include <linux/ctype.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/workqueue.h> + +#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> + +#define cs_dsp_err(_dsp, fmt, ...) \ + dev_err(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#define cs_dsp_warn(_dsp, fmt, ...) \ + dev_warn(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#define cs_dsp_info(_dsp, fmt, ...) \ + dev_info(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#define cs_dsp_dbg(_dsp, fmt, ...) \ + dev_dbg(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) + +#define ADSP1_CONTROL_1 0x00 +#define ADSP1_CONTROL_2 0x02 +#define ADSP1_CONTROL_3 0x03 +#define ADSP1_CONTROL_4 0x04 +#define ADSP1_CONTROL_5 0x06 +#define ADSP1_CONTROL_6 0x07 +#define ADSP1_CONTROL_7 0x08 +#define ADSP1_CONTROL_8 0x09 +#define ADSP1_CONTROL_9 0x0A +#define ADSP1_CONTROL_10 0x0B +#define ADSP1_CONTROL_11 0x0C +#define ADSP1_CONTROL_12 0x0D +#define ADSP1_CONTROL_13 0x0F +#define ADSP1_CONTROL_14 0x10 +#define ADSP1_CONTROL_15 0x11 +#define ADSP1_CONTROL_16 0x12 +#define ADSP1_CONTROL_17 0x13 +#define ADSP1_CONTROL_18 0x14 +#define ADSP1_CONTROL_19 0x16 +#define ADSP1_CONTROL_20 0x17 +#define ADSP1_CONTROL_21 0x18 +#define ADSP1_CONTROL_22 0x1A +#define ADSP1_CONTROL_23 0x1B +#define ADSP1_CONTROL_24 0x1C +#define ADSP1_CONTROL_25 0x1E +#define ADSP1_CONTROL_26 0x20 +#define ADSP1_CONTROL_27 0x21 +#define ADSP1_CONTROL_28 0x22 +#define ADSP1_CONTROL_29 0x23 +#define ADSP1_CONTROL_30 0x24 +#define ADSP1_CONTROL_31 0x26 + +/* + * ADSP1 Control 19 + */ +#define ADSP1_WDMA_BUFFER_LENGTH_MASK 0x00FF /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define ADSP1_WDMA_BUFFER_LENGTH_SHIFT 0 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define ADSP1_WDMA_BUFFER_LENGTH_WIDTH 8 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ + +/* + * ADSP1 Control 30 + */ +#define ADSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ +#define ADSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ +#define ADSP1_START 0x0001 /* DSP1_START */ +#define ADSP1_START_MASK 0x0001 /* DSP1_START */ +#define ADSP1_START_SHIFT 0 /* DSP1_START */ +#define ADSP1_START_WIDTH 1 /* DSP1_START */ + +/* + * ADSP1 Control 31 + */ +#define ADSP1_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */ +#define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ +#define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +#define ADSP2_CONTROL 0x0 +#define ADSP2_CLOCKING 0x1 +#define ADSP2V2_CLOCKING 0x2 +#define ADSP2_STATUS1 0x4 +#define ADSP2_WDMA_CONFIG_1 0x30 +#define ADSP2_WDMA_CONFIG_2 0x31 +#define ADSP2V2_WDMA_CONFIG_2 0x32 +#define ADSP2_RDMA_CONFIG_1 0x34 + +#define ADSP2_SCRATCH0 0x40 +#define ADSP2_SCRATCH1 0x41 +#define ADSP2_SCRATCH2 0x42 +#define ADSP2_SCRATCH3 0x43 + +#define ADSP2V2_SCRATCH0_1 0x40 +#define ADSP2V2_SCRATCH2_3 0x42 + +/* + * ADSP2 Control + */ +#define ADSP2_MEM_ENA 0x0010 /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_MASK 0x0010 /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_SHIFT 4 /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_WIDTH 1 /* DSP1_MEM_ENA */ +#define ADSP2_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ +#define ADSP2_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ +#define ADSP2_START 0x0001 /* DSP1_START */ +#define ADSP2_START_MASK 0x0001 /* DSP1_START */ +#define ADSP2_START_SHIFT 0 /* DSP1_START */ +#define ADSP2_START_WIDTH 1 /* DSP1_START */ + +/* + * ADSP2 clocking + */ +#define ADSP2_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */ +#define ADSP2_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ +#define ADSP2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +/* + * ADSP2V2 clocking + */ +#define ADSP2V2_CLK_SEL_MASK 0x70000 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_SHIFT 16 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +#define ADSP2V2_RATE_MASK 0x7800 /* DSP_RATE */ +#define ADSP2V2_RATE_SHIFT 11 /* DSP_RATE */ +#define ADSP2V2_RATE_WIDTH 4 /* DSP_RATE */ + +/* + * ADSP2 Status 1 + */ +#define ADSP2_RAM_RDY 0x0001 +#define ADSP2_RAM_RDY_MASK 0x0001 +#define ADSP2_RAM_RDY_SHIFT 0 +#define ADSP2_RAM_RDY_WIDTH 1 + +/* + * ADSP2 Lock support + */ +#define ADSP2_LOCK_CODE_0 0x5555 +#define ADSP2_LOCK_CODE_1 0xAAAA + +#define ADSP2_WATCHDOG 0x0A +#define ADSP2_BUS_ERR_ADDR 0x52 +#define ADSP2_REGION_LOCK_STATUS 0x64 +#define ADSP2_LOCK_REGION_1_LOCK_REGION_0 0x66 +#define ADSP2_LOCK_REGION_3_LOCK_REGION_2 0x68 +#define ADSP2_LOCK_REGION_5_LOCK_REGION_4 0x6A +#define ADSP2_LOCK_REGION_7_LOCK_REGION_6 0x6C +#define ADSP2_LOCK_REGION_9_LOCK_REGION_8 0x6E +#define ADSP2_LOCK_REGION_CTRL 0x7A +#define ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR 0x7C + +#define ADSP2_REGION_LOCK_ERR_MASK 0x8000 +#define ADSP2_ADDR_ERR_MASK 0x4000 +#define ADSP2_WDT_TIMEOUT_STS_MASK 0x2000 +#define ADSP2_CTRL_ERR_PAUSE_ENA 0x0002 +#define ADSP2_CTRL_ERR_EINT 0x0001 + +#define ADSP2_BUS_ERR_ADDR_MASK 0x00FFFFFF +#define ADSP2_XMEM_ERR_ADDR_MASK 0x0000FFFF +#define ADSP2_PMEM_ERR_ADDR_MASK 0x7FFF0000 +#define ADSP2_PMEM_ERR_ADDR_SHIFT 16 +#define ADSP2_WDT_ENA_MASK 0xFFFFFFFD + +#define ADSP2_LOCK_REGION_SHIFT 16 + +/* + * Event control messages + */ +#define CS_DSP_FW_EVENT_SHUTDOWN 0x000001 + +/* + * HALO system info + */ +#define HALO_AHBM_WINDOW_DEBUG_0 0x02040 +#define HALO_AHBM_WINDOW_DEBUG_1 0x02044 + +/* + * HALO core + */ +#define HALO_SCRATCH1 0x005c0 +#define HALO_SCRATCH2 0x005c8 +#define HALO_SCRATCH3 0x005d0 +#define HALO_SCRATCH4 0x005d8 +#define HALO_CCM_CORE_CONTROL 0x41000 +#define HALO_CORE_SOFT_RESET 0x00010 +#define HALO_WDT_CONTROL 0x47000 + +/* + * HALO MPU banks + */ +#define HALO_MPU_XMEM_ACCESS_0 0x43000 +#define HALO_MPU_YMEM_ACCESS_0 0x43004 +#define HALO_MPU_WINDOW_ACCESS_0 0x43008 +#define HALO_MPU_XREG_ACCESS_0 0x4300C +#define HALO_MPU_YREG_ACCESS_0 0x43014 +#define HALO_MPU_XMEM_ACCESS_1 0x43018 +#define HALO_MPU_YMEM_ACCESS_1 0x4301C +#define HALO_MPU_WINDOW_ACCESS_1 0x43020 +#define HALO_MPU_XREG_ACCESS_1 0x43024 +#define HALO_MPU_YREG_ACCESS_1 0x4302C +#define HALO_MPU_XMEM_ACCESS_2 0x43030 +#define HALO_MPU_YMEM_ACCESS_2 0x43034 +#define HALO_MPU_WINDOW_ACCESS_2 0x43038 +#define HALO_MPU_XREG_ACCESS_2 0x4303C +#define HALO_MPU_YREG_ACCESS_2 0x43044 +#define HALO_MPU_XMEM_ACCESS_3 0x43048 +#define HALO_MPU_YMEM_ACCESS_3 0x4304C +#define HALO_MPU_WINDOW_ACCESS_3 0x43050 +#define HALO_MPU_XREG_ACCESS_3 0x43054 +#define HALO_MPU_YREG_ACCESS_3 0x4305C +#define HALO_MPU_XM_VIO_ADDR 0x43100 +#define HALO_MPU_XM_VIO_STATUS 0x43104 +#define HALO_MPU_YM_VIO_ADDR 0x43108 +#define HALO_MPU_YM_VIO_STATUS 0x4310C +#define HALO_MPU_PM_VIO_ADDR 0x43110 +#define HALO_MPU_PM_VIO_STATUS 0x43114 +#define HALO_MPU_LOCK_CONFIG 0x43140 + +/* + * HALO_AHBM_WINDOW_DEBUG_1 + */ +#define HALO_AHBM_CORE_ERR_ADDR_MASK 0x0fffff00 +#define HALO_AHBM_CORE_ERR_ADDR_SHIFT 8 +#define HALO_AHBM_FLAGS_ERR_MASK 0x000000ff + +/* + * HALO_CCM_CORE_CONTROL + */ +#define HALO_CORE_RESET 0x00000200 +#define HALO_CORE_EN 0x00000001 + +/* + * HALO_CORE_SOFT_RESET + */ +#define HALO_CORE_SOFT_RESET_MASK 0x00000001 + +/* + * HALO_WDT_CONTROL + */ +#define HALO_WDT_EN_MASK 0x00000001 + +/* + * HALO_MPU_?M_VIO_STATUS + */ +#define HALO_MPU_VIO_STS_MASK 0x007e0000 +#define HALO_MPU_VIO_STS_SHIFT 17 +#define HALO_MPU_VIO_ERR_WR_MASK 0x00008000 +#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff +#define HALO_MPU_VIO_ERR_SRC_SHIFT 0 + +struct cs_dsp_ops { + bool (*validate_version)(struct cs_dsp *dsp, unsigned int version); + unsigned int (*parse_sizes)(struct cs_dsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware); + int (*setup_algs)(struct cs_dsp *dsp); + unsigned int (*region_to_reg)(struct cs_dsp_region const *mem, + unsigned int offset); + + void (*show_fw_status)(struct cs_dsp *dsp); + void (*stop_watchdog)(struct cs_dsp *dsp); + + int (*enable_memory)(struct cs_dsp *dsp); + void (*disable_memory)(struct cs_dsp *dsp); + int (*lock_memory)(struct cs_dsp *dsp, unsigned int lock_regions); + + int (*enable_core)(struct cs_dsp *dsp); + void (*disable_core)(struct cs_dsp *dsp); + + int (*start_core)(struct cs_dsp *dsp); + void (*stop_core)(struct cs_dsp *dsp); +}; + +static const struct cs_dsp_ops cs_dsp_adsp1_ops; +static const struct cs_dsp_ops cs_dsp_adsp2_ops[]; +static const struct cs_dsp_ops cs_dsp_halo_ops; + +struct cs_dsp_buf { + struct list_head list; + void *buf; +}; + +static struct cs_dsp_buf *cs_dsp_buf_alloc(const void *src, size_t len, + struct list_head *list) +{ + struct cs_dsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL); + + if (buf == NULL) + return NULL; + + buf->buf = vmalloc(len); + if (!buf->buf) { + kfree(buf); + return NULL; + } + memcpy(buf->buf, src, len); + + if (list) + list_add_tail(&buf->list, list); + + return buf; +} + +static void cs_dsp_buf_free(struct list_head *list) +{ + while (!list_empty(list)) { + struct cs_dsp_buf *buf = list_first_entry(list, + struct cs_dsp_buf, + list); + list_del(&buf->list); + vfree(buf->buf); + kfree(buf); + } +} + +/** + * cs_dsp_mem_region_name() - Return a name string for a memory type + * @type: the memory type to match + * + * Return: A const string identifying the memory region. + */ +const char *cs_dsp_mem_region_name(unsigned int type) +{ + switch (type) { + case WMFW_ADSP1_PM: + return "PM"; + case WMFW_HALO_PM_PACKED: + return "PM_PACKED"; + case WMFW_ADSP1_DM: + return "DM"; + case WMFW_ADSP2_XM: + return "XM"; + case WMFW_HALO_XM_PACKED: + return "XM_PACKED"; + case WMFW_ADSP2_YM: + return "YM"; + case WMFW_HALO_YM_PACKED: + return "YM_PACKED"; + case WMFW_ADSP1_ZM: + return "ZM"; + default: + return NULL; + } +} +EXPORT_SYMBOL_GPL(cs_dsp_mem_region_name); + +#ifdef CONFIG_DEBUG_FS +static void cs_dsp_debugfs_save_wmfwname(struct cs_dsp *dsp, const char *s) +{ + char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); + + kfree(dsp->wmfw_file_name); + dsp->wmfw_file_name = tmp; +} + +static void cs_dsp_debugfs_save_binname(struct cs_dsp *dsp, const char *s) +{ + char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); + + kfree(dsp->bin_file_name); + dsp->bin_file_name = tmp; +} + +static void cs_dsp_debugfs_clear(struct cs_dsp *dsp) +{ + kfree(dsp->wmfw_file_name); + kfree(dsp->bin_file_name); + dsp->wmfw_file_name = NULL; + dsp->bin_file_name = NULL; +} + +static ssize_t cs_dsp_debugfs_wmfw_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct cs_dsp *dsp = file->private_data; + ssize_t ret; + + mutex_lock(&dsp->pwr_lock); + + if (!dsp->wmfw_file_name || !dsp->booted) + ret = 0; + else + ret = simple_read_from_buffer(user_buf, count, ppos, + dsp->wmfw_file_name, + strlen(dsp->wmfw_file_name)); + + mutex_unlock(&dsp->pwr_lock); + return ret; +} + +static ssize_t cs_dsp_debugfs_bin_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct cs_dsp *dsp = file->private_data; + ssize_t ret; + + mutex_lock(&dsp->pwr_lock); + + if (!dsp->bin_file_name || !dsp->booted) + ret = 0; + else + ret = simple_read_from_buffer(user_buf, count, ppos, + dsp->bin_file_name, + strlen(dsp->bin_file_name)); + + mutex_unlock(&dsp->pwr_lock); + return ret; +} + +static const struct { + const char *name; + const struct file_operations fops; +} cs_dsp_debugfs_fops[] = { + { + .name = "wmfw_file_name", + .fops = { + .open = simple_open, + .read = cs_dsp_debugfs_wmfw_read, + }, + }, + { + .name = "bin_file_name", + .fops = { + .open = simple_open, + .read = cs_dsp_debugfs_bin_read, + }, + }, +}; + +/** + * cs_dsp_init_debugfs() - Create and populate DSP representation in debugfs + * @dsp: pointer to DSP structure + * @debugfs_root: pointer to debugfs directory in which to create this DSP + * representation + */ +void cs_dsp_init_debugfs(struct cs_dsp *dsp, struct dentry *debugfs_root) +{ + struct dentry *root = NULL; + int i; + + root = debugfs_create_dir(dsp->name, debugfs_root); + + debugfs_create_bool("booted", 0444, root, &dsp->booted); + debugfs_create_bool("running", 0444, root, &dsp->running); + debugfs_create_x32("fw_id", 0444, root, &dsp->fw_id); + debugfs_create_x32("fw_version", 0444, root, &dsp->fw_id_version); + + for (i = 0; i < ARRAY_SIZE(cs_dsp_debugfs_fops); ++i) + debugfs_create_file(cs_dsp_debugfs_fops[i].name, 0444, root, + dsp, &cs_dsp_debugfs_fops[i].fops); + + dsp->debugfs_root = root; +} +EXPORT_SYMBOL_GPL(cs_dsp_init_debugfs); + +/** + * cs_dsp_cleanup_debugfs() - Removes DSP representation from debugfs + * @dsp: pointer to DSP structure + */ +void cs_dsp_cleanup_debugfs(struct cs_dsp *dsp) +{ + cs_dsp_debugfs_clear(dsp); + debugfs_remove_recursive(dsp->debugfs_root); + dsp->debugfs_root = NULL; +} +EXPORT_SYMBOL_GPL(cs_dsp_cleanup_debugfs); +#else +void cs_dsp_init_debugfs(struct cs_dsp *dsp, struct dentry *debugfs_root) +{ +} +EXPORT_SYMBOL_GPL(cs_dsp_init_debugfs); + +void cs_dsp_cleanup_debugfs(struct cs_dsp *dsp) +{ +} +EXPORT_SYMBOL_GPL(cs_dsp_cleanup_debugfs); + +static inline void cs_dsp_debugfs_save_wmfwname(struct cs_dsp *dsp, + const char *s) +{ +} + +static inline void cs_dsp_debugfs_save_binname(struct cs_dsp *dsp, + const char *s) +{ +} + +static inline void cs_dsp_debugfs_clear(struct cs_dsp *dsp) +{ +} +#endif + +static const struct cs_dsp_region *cs_dsp_find_region(struct cs_dsp *dsp, + int type) +{ + int i; + + for (i = 0; i < dsp->num_mems; i++) + if (dsp->mem[i].type == type) + return &dsp->mem[i]; + + return NULL; +} + +static unsigned int cs_dsp_region_to_reg(struct cs_dsp_region const *mem, + unsigned int offset) +{ + switch (mem->type) { + case WMFW_ADSP1_PM: + return mem->base + (offset * 3); + case WMFW_ADSP1_DM: + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + case WMFW_ADSP1_ZM: + return mem->base + (offset * 2); + default: + WARN(1, "Unknown memory region type"); + return offset; + } +} + +static unsigned int cs_dsp_halo_region_to_reg(struct cs_dsp_region const *mem, + unsigned int offset) +{ + switch (mem->type) { + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + return mem->base + (offset * 4); + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + return (mem->base + (offset * 3)) & ~0x3; + case WMFW_HALO_PM_PACKED: + return mem->base + (offset * 5); + default: + WARN(1, "Unknown memory region type"); + return offset; + } +} + +static void cs_dsp_read_fw_status(struct cs_dsp *dsp, + int noffs, unsigned int *offs) +{ + unsigned int i; + int ret; + + for (i = 0; i < noffs; ++i) { + ret = regmap_read(dsp->regmap, dsp->base + offs[i], &offs[i]); + if (ret) { + cs_dsp_err(dsp, "Failed to read SCRATCH%u: %d\n", i, ret); + return; + } + } +} + +static void cs_dsp_adsp2_show_fw_status(struct cs_dsp *dsp) +{ + unsigned int offs[] = { + ADSP2_SCRATCH0, ADSP2_SCRATCH1, ADSP2_SCRATCH2, ADSP2_SCRATCH3, + }; + + cs_dsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); + + cs_dsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + offs[0], offs[1], offs[2], offs[3]); +} + +static void cs_dsp_adsp2v2_show_fw_status(struct cs_dsp *dsp) +{ + unsigned int offs[] = { ADSP2V2_SCRATCH0_1, ADSP2V2_SCRATCH2_3 }; + + cs_dsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); + + cs_dsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + offs[0] & 0xFFFF, offs[0] >> 16, + offs[1] & 0xFFFF, offs[1] >> 16); +} + +static void cs_dsp_halo_show_fw_status(struct cs_dsp *dsp) +{ + unsigned int offs[] = { + HALO_SCRATCH1, HALO_SCRATCH2, HALO_SCRATCH3, HALO_SCRATCH4, + }; + + cs_dsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); + + cs_dsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + offs[0], offs[1], offs[2], offs[3]); +} + +static int cs_dsp_coeff_base_reg(struct cs_dsp_coeff_ctl *ctl, unsigned int *reg) +{ + const struct cs_dsp_alg_region *alg_region = &ctl->alg_region; + struct cs_dsp *dsp = ctl->dsp; + const struct cs_dsp_region *mem; + + mem = cs_dsp_find_region(dsp, alg_region->type); + if (!mem) { + cs_dsp_err(dsp, "No base for region %x\n", + alg_region->type); + return -EINVAL; + } + + *reg = dsp->ops->region_to_reg(mem, ctl->alg_region.base + ctl->offset); + + return 0; +} + +/** + * cs_dsp_coeff_write_acked_control() - Sends event_id to the acked control + * @ctl: pointer to acked coefficient control + * @event_id: the value to write to the given acked control + * + * Once the value has been written to the control the function shall block + * until the running firmware acknowledges the write or timeout is exceeded. + * + * Must be called with pwr_lock held. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_coeff_write_acked_control(struct cs_dsp_coeff_ctl *ctl, unsigned int event_id) +{ + struct cs_dsp *dsp = ctl->dsp; + __be32 val = cpu_to_be32(event_id); + unsigned int reg; + int i, ret; + + if (!dsp->running) + return -EPERM; + + ret = cs_dsp_coeff_base_reg(ctl, ®); + if (ret) + return ret; + + cs_dsp_dbg(dsp, "Sending 0x%x to acked control alg 0x%x %s:0x%x\n", + event_id, ctl->alg_region.alg, + cs_dsp_mem_region_name(ctl->alg_region.type), ctl->offset); + + ret = regmap_raw_write(dsp->regmap, reg, &val, sizeof(val)); + if (ret) { + cs_dsp_err(dsp, "Failed to write %x: %d\n", reg, ret); + return ret; + } + + /* + * Poll for ack, we initially poll at ~1ms intervals for firmwares + * that respond quickly, then go to ~10ms polls. A firmware is unlikely + * to ack instantly so we do the first 1ms delay before reading the + * control to avoid a pointless bus transaction + */ + for (i = 0; i < CS_DSP_ACKED_CTL_TIMEOUT_MS;) { + switch (i) { + case 0 ... CS_DSP_ACKED_CTL_N_QUICKPOLLS - 1: + usleep_range(1000, 2000); + i++; + break; + default: + usleep_range(10000, 20000); + i += 10; + break; + } + + ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); + if (ret) { + cs_dsp_err(dsp, "Failed to read %x: %d\n", reg, ret); + return ret; + } + + if (val == 0) { + cs_dsp_dbg(dsp, "Acked control ACKED at poll %u\n", i); + return 0; + } + } + + cs_dsp_warn(dsp, "Acked control @0x%x alg:0x%x %s:0x%x timed out\n", + reg, ctl->alg_region.alg, + cs_dsp_mem_region_name(ctl->alg_region.type), + ctl->offset); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_GPL(cs_dsp_coeff_write_acked_control); + +static int cs_dsp_coeff_write_ctrl_raw(struct cs_dsp_coeff_ctl *ctl, + const void *buf, size_t len) +{ + struct cs_dsp *dsp = ctl->dsp; + void *scratch; + int ret; + unsigned int reg; + + ret = cs_dsp_coeff_base_reg(ctl, ®); + if (ret) + return ret; + + scratch = kmemdup(buf, len, GFP_KERNEL | GFP_DMA); + if (!scratch) + return -ENOMEM; + + ret = regmap_raw_write(dsp->regmap, reg, scratch, + len); + if (ret) { + cs_dsp_err(dsp, "Failed to write %zu bytes to %x: %d\n", + len, reg, ret); + kfree(scratch); + return ret; + } + cs_dsp_dbg(dsp, "Wrote %zu bytes to %x\n", len, reg); + + kfree(scratch); + + return 0; +} + +/** + * cs_dsp_coeff_write_ctrl() - Writes the given buffer to the given coefficient control + * @ctl: pointer to coefficient control + * @buf: the buffer to write to the given control + * @len: the length of the buffer + * + * Must be called with pwr_lock held. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_coeff_write_ctrl(struct cs_dsp_coeff_ctl *ctl, const void *buf, size_t len) +{ + int ret = 0; + + if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) + ret = -EPERM; + else if (buf != ctl->cache) + memcpy(ctl->cache, buf, len); + + ctl->set = 1; + if (ctl->enabled && ctl->dsp->running) + ret = cs_dsp_coeff_write_ctrl_raw(ctl, buf, len); + + return ret; +} +EXPORT_SYMBOL_GPL(cs_dsp_coeff_write_ctrl); + +static int cs_dsp_coeff_read_ctrl_raw(struct cs_dsp_coeff_ctl *ctl, void *buf, size_t len) +{ + struct cs_dsp *dsp = ctl->dsp; + void *scratch; + int ret; + unsigned int reg; + + ret = cs_dsp_coeff_base_reg(ctl, ®); + if (ret) + return ret; + + scratch = kmalloc(len, GFP_KERNEL | GFP_DMA); + if (!scratch) + return -ENOMEM; + + ret = regmap_raw_read(dsp->regmap, reg, scratch, len); + if (ret) { + cs_dsp_err(dsp, "Failed to read %zu bytes from %x: %d\n", + len, reg, ret); + kfree(scratch); + return ret; + } + cs_dsp_dbg(dsp, "Read %zu bytes from %x\n", len, reg); + + memcpy(buf, scratch, len); + kfree(scratch); + + return 0; +} + +/** + * cs_dsp_coeff_read_ctrl() - Reads the given coefficient control into the given buffer + * @ctl: pointer to coefficient control + * @buf: the buffer to store to the given control + * @len: the length of the buffer + * + * Must be called with pwr_lock held. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_coeff_read_ctrl(struct cs_dsp_coeff_ctl *ctl, void *buf, size_t len) +{ + int ret = 0; + + if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) { + if (ctl->enabled && ctl->dsp->running) + return cs_dsp_coeff_read_ctrl_raw(ctl, buf, len); + else + return -EPERM; + } else { + if (!ctl->flags && ctl->enabled && ctl->dsp->running) + ret = cs_dsp_coeff_read_ctrl_raw(ctl, ctl->cache, ctl->len); + + if (buf != ctl->cache) + memcpy(buf, ctl->cache, len); + } + + return ret; +} +EXPORT_SYMBOL_GPL(cs_dsp_coeff_read_ctrl); + +static int cs_dsp_coeff_init_control_caches(struct cs_dsp *dsp) +{ + struct cs_dsp_coeff_ctl *ctl; + int ret; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (!ctl->enabled || ctl->set) + continue; + if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) + continue; + + /* + * For readable controls populate the cache from the DSP memory. + * For non-readable controls the cache was zero-filled when + * created so we don't need to do anything. + */ + if (!ctl->flags || (ctl->flags & WMFW_CTL_FLAG_READABLE)) { + ret = cs_dsp_coeff_read_ctrl_raw(ctl, ctl->cache, ctl->len); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int cs_dsp_coeff_sync_controls(struct cs_dsp *dsp) +{ + struct cs_dsp_coeff_ctl *ctl; + int ret; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (!ctl->enabled) + continue; + if (ctl->set && !(ctl->flags & WMFW_CTL_FLAG_VOLATILE)) { + ret = cs_dsp_coeff_write_ctrl_raw(ctl, ctl->cache, + ctl->len); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static void cs_dsp_signal_event_controls(struct cs_dsp *dsp, + unsigned int event) +{ + struct cs_dsp_coeff_ctl *ctl; + int ret; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (ctl->type != WMFW_CTL_TYPE_HOSTEVENT) + continue; + + if (!ctl->enabled) + continue; + + ret = cs_dsp_coeff_write_acked_control(ctl, event); + if (ret) + cs_dsp_warn(dsp, + "Failed to send 0x%x event to alg 0x%x (%d)\n", + event, ctl->alg_region.alg, ret); + } +} + +static void cs_dsp_free_ctl_blk(struct cs_dsp_coeff_ctl *ctl) +{ + kfree(ctl->cache); + kfree(ctl->subname); + kfree(ctl); +} + +static int cs_dsp_create_control(struct cs_dsp *dsp, + const struct cs_dsp_alg_region *alg_region, + unsigned int offset, unsigned int len, + const char *subname, unsigned int subname_len, + unsigned int flags, unsigned int type) +{ + struct cs_dsp_coeff_ctl *ctl; + int ret; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (ctl->fw_name == dsp->fw_name && + ctl->alg_region.alg == alg_region->alg && + ctl->alg_region.type == alg_region->type) { + if ((!subname && !ctl->subname) || + (subname && !strncmp(ctl->subname, subname, ctl->subname_len))) { + if (!ctl->enabled) + ctl->enabled = 1; + return 0; + } + } + } + + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return -ENOMEM; + + ctl->fw_name = dsp->fw_name; + ctl->alg_region = *alg_region; + if (subname && dsp->fw_ver >= 2) { + ctl->subname_len = subname_len; + ctl->subname = kmemdup(subname, + strlen(subname) + 1, GFP_KERNEL); + if (!ctl->subname) { + ret = -ENOMEM; + goto err_ctl; + } + } + ctl->enabled = 1; + ctl->set = 0; + ctl->dsp = dsp; + + ctl->flags = flags; + ctl->type = type; + ctl->offset = offset; + ctl->len = len; + ctl->cache = kzalloc(ctl->len, GFP_KERNEL); + if (!ctl->cache) { + ret = -ENOMEM; + goto err_ctl_subname; + } + + list_add(&ctl->list, &dsp->ctl_list); + + if (dsp->client_ops->control_add) { + ret = dsp->client_ops->control_add(ctl); + if (ret) + goto err_list_del; + } + + return 0; + +err_list_del: + list_del(&ctl->list); + kfree(ctl->cache); +err_ctl_subname: + kfree(ctl->subname); +err_ctl: + kfree(ctl); + + return ret; +} + +struct cs_dsp_coeff_parsed_alg { + int id; + const u8 *name; + int name_len; + int ncoeff; +}; + +struct cs_dsp_coeff_parsed_coeff { + int offset; + int mem_type; + const u8 *name; + int name_len; + unsigned int ctl_type; + int flags; + int len; +}; + +static int cs_dsp_coeff_parse_string(int bytes, const u8 **pos, const u8 **str) +{ + int length; + + switch (bytes) { + case 1: + length = **pos; + break; + case 2: + length = le16_to_cpu(*((__le16 *)*pos)); + break; + default: + return 0; + } + + if (str) + *str = *pos + bytes; + + *pos += ((length + bytes) + 3) & ~0x03; + + return length; +} + +static int cs_dsp_coeff_parse_int(int bytes, const u8 **pos) +{ + int val = 0; + + switch (bytes) { + case 2: + val = le16_to_cpu(*((__le16 *)*pos)); + break; + case 4: + val = le32_to_cpu(*((__le32 *)*pos)); + break; + default: + break; + } + + *pos += bytes; + + return val; +} + +static inline void cs_dsp_coeff_parse_alg(struct cs_dsp *dsp, const u8 **data, + struct cs_dsp_coeff_parsed_alg *blk) +{ + const struct wmfw_adsp_alg_data *raw; + + switch (dsp->fw_ver) { + case 0: + case 1: + raw = (const struct wmfw_adsp_alg_data *)*data; + *data = raw->data; + + blk->id = le32_to_cpu(raw->id); + blk->name = raw->name; + blk->name_len = strlen(raw->name); + blk->ncoeff = le32_to_cpu(raw->ncoeff); + break; + default: + blk->id = cs_dsp_coeff_parse_int(sizeof(raw->id), data); + blk->name_len = cs_dsp_coeff_parse_string(sizeof(u8), data, + &blk->name); + cs_dsp_coeff_parse_string(sizeof(u16), data, NULL); + blk->ncoeff = cs_dsp_coeff_parse_int(sizeof(raw->ncoeff), data); + break; + } + + cs_dsp_dbg(dsp, "Algorithm ID: %#x\n", blk->id); + cs_dsp_dbg(dsp, "Algorithm name: %.*s\n", blk->name_len, blk->name); + cs_dsp_dbg(dsp, "# of coefficient descriptors: %#x\n", blk->ncoeff); +} + +static inline void cs_dsp_coeff_parse_coeff(struct cs_dsp *dsp, const u8 **data, + struct cs_dsp_coeff_parsed_coeff *blk) +{ + const struct wmfw_adsp_coeff_data *raw; + const u8 *tmp; + int length; + + switch (dsp->fw_ver) { + case 0: + case 1: + raw = (const struct wmfw_adsp_coeff_data *)*data; + *data = *data + sizeof(raw->hdr) + le32_to_cpu(raw->hdr.size); + + blk->offset = le16_to_cpu(raw->hdr.offset); + blk->mem_type = le16_to_cpu(raw->hdr.type); + blk->name = raw->name; + blk->name_len = strlen(raw->name); + blk->ctl_type = le16_to_cpu(raw->ctl_type); + blk->flags = le16_to_cpu(raw->flags); + blk->len = le32_to_cpu(raw->len); + break; + default: + tmp = *data; + blk->offset = cs_dsp_coeff_parse_int(sizeof(raw->hdr.offset), &tmp); + blk->mem_type = cs_dsp_coeff_parse_int(sizeof(raw->hdr.type), &tmp); + length = cs_dsp_coeff_parse_int(sizeof(raw->hdr.size), &tmp); + blk->name_len = cs_dsp_coeff_parse_string(sizeof(u8), &tmp, + &blk->name); + cs_dsp_coeff_parse_string(sizeof(u8), &tmp, NULL); + cs_dsp_coeff_parse_string(sizeof(u16), &tmp, NULL); + blk->ctl_type = cs_dsp_coeff_parse_int(sizeof(raw->ctl_type), &tmp); + blk->flags = cs_dsp_coeff_parse_int(sizeof(raw->flags), &tmp); + blk->len = cs_dsp_coeff_parse_int(sizeof(raw->len), &tmp); + + *data = *data + sizeof(raw->hdr) + length; + break; + } + + cs_dsp_dbg(dsp, "\tCoefficient type: %#x\n", blk->mem_type); + cs_dsp_dbg(dsp, "\tCoefficient offset: %#x\n", blk->offset); + cs_dsp_dbg(dsp, "\tCoefficient name: %.*s\n", blk->name_len, blk->name); + cs_dsp_dbg(dsp, "\tCoefficient flags: %#x\n", blk->flags); + cs_dsp_dbg(dsp, "\tALSA control type: %#x\n", blk->ctl_type); + cs_dsp_dbg(dsp, "\tALSA control len: %#x\n", blk->len); +} + +static int cs_dsp_check_coeff_flags(struct cs_dsp *dsp, + const struct cs_dsp_coeff_parsed_coeff *coeff_blk, + unsigned int f_required, + unsigned int f_illegal) +{ + if ((coeff_blk->flags & f_illegal) || + ((coeff_blk->flags & f_required) != f_required)) { + cs_dsp_err(dsp, "Illegal flags 0x%x for control type 0x%x\n", + coeff_blk->flags, coeff_blk->ctl_type); + return -EINVAL; + } + + return 0; +} + +static int cs_dsp_parse_coeff(struct cs_dsp *dsp, + const struct wmfw_region *region) +{ + struct cs_dsp_alg_region alg_region = {}; + struct cs_dsp_coeff_parsed_alg alg_blk; + struct cs_dsp_coeff_parsed_coeff coeff_blk; + const u8 *data = region->data; + int i, ret; + + cs_dsp_coeff_parse_alg(dsp, &data, &alg_blk); + for (i = 0; i < alg_blk.ncoeff; i++) { + cs_dsp_coeff_parse_coeff(dsp, &data, &coeff_blk); + + switch (coeff_blk.ctl_type) { + case WMFW_CTL_TYPE_BYTES: + break; + case WMFW_CTL_TYPE_ACKED: + if (coeff_blk.flags & WMFW_CTL_FLAG_SYS) + continue; /* ignore */ + + ret = cs_dsp_check_coeff_flags(dsp, &coeff_blk, + WMFW_CTL_FLAG_VOLATILE | + WMFW_CTL_FLAG_WRITEABLE | + WMFW_CTL_FLAG_READABLE, + 0); + if (ret) + return -EINVAL; + break; + case WMFW_CTL_TYPE_HOSTEVENT: + ret = cs_dsp_check_coeff_flags(dsp, &coeff_blk, + WMFW_CTL_FLAG_SYS | + WMFW_CTL_FLAG_VOLATILE | + WMFW_CTL_FLAG_WRITEABLE | + WMFW_CTL_FLAG_READABLE, + 0); + if (ret) + return -EINVAL; + break; + case WMFW_CTL_TYPE_HOST_BUFFER: + ret = cs_dsp_check_coeff_flags(dsp, &coeff_blk, + WMFW_CTL_FLAG_SYS | + WMFW_CTL_FLAG_VOLATILE | + WMFW_CTL_FLAG_READABLE, + 0); + if (ret) + return -EINVAL; + break; + default: + cs_dsp_err(dsp, "Unknown control type: %d\n", + coeff_blk.ctl_type); + return -EINVAL; + } + + alg_region.type = coeff_blk.mem_type; + alg_region.alg = alg_blk.id; + + ret = cs_dsp_create_control(dsp, &alg_region, + coeff_blk.offset, + coeff_blk.len, + coeff_blk.name, + coeff_blk.name_len, + coeff_blk.flags, + coeff_blk.ctl_type); + if (ret < 0) + cs_dsp_err(dsp, "Failed to create control: %.*s, %d\n", + coeff_blk.name_len, coeff_blk.name, ret); + } + + return 0; +} + +static unsigned int cs_dsp_adsp1_parse_sizes(struct cs_dsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware) +{ + const struct wmfw_adsp1_sizes *adsp1_sizes; + + adsp1_sizes = (void *)&firmware->data[pos]; + + cs_dsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", file, + le32_to_cpu(adsp1_sizes->dm), le32_to_cpu(adsp1_sizes->pm), + le32_to_cpu(adsp1_sizes->zm)); + + return pos + sizeof(*adsp1_sizes); +} + +static unsigned int cs_dsp_adsp2_parse_sizes(struct cs_dsp *dsp, + const char * const file, + unsigned int pos, + const struct firmware *firmware) +{ + const struct wmfw_adsp2_sizes *adsp2_sizes; + + adsp2_sizes = (void *)&firmware->data[pos]; + + cs_dsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", file, + le32_to_cpu(adsp2_sizes->xm), le32_to_cpu(adsp2_sizes->ym), + le32_to_cpu(adsp2_sizes->pm), le32_to_cpu(adsp2_sizes->zm)); + + return pos + sizeof(*adsp2_sizes); +} + +static bool cs_dsp_validate_version(struct cs_dsp *dsp, unsigned int version) +{ + switch (version) { + case 0: + cs_dsp_warn(dsp, "Deprecated file format %d\n", version); + return true; + case 1: + case 2: + return true; + default: + return false; + } +} + +static bool cs_dsp_halo_validate_version(struct cs_dsp *dsp, unsigned int version) +{ + switch (version) { + case 3: + return true; + default: + return false; + } +} + +static int cs_dsp_load(struct cs_dsp *dsp, const struct firmware *firmware, + const char *file) +{ + LIST_HEAD(buf_list); + struct regmap *regmap = dsp->regmap; + unsigned int pos = 0; + const struct wmfw_header *header; + const struct wmfw_adsp1_sizes *adsp1_sizes; + const struct wmfw_footer *footer; + const struct wmfw_region *region; + const struct cs_dsp_region *mem; + const char *region_name; + char *text = NULL; + struct cs_dsp_buf *buf; + unsigned int reg; + int regions = 0; + int ret, offset, type; + + ret = -EINVAL; + + pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); + if (pos >= firmware->size) { + cs_dsp_err(dsp, "%s: file too short, %zu bytes\n", + file, firmware->size); + goto out_fw; + } + + header = (void *)&firmware->data[0]; + + if (memcmp(&header->magic[0], "WMFW", 4) != 0) { + cs_dsp_err(dsp, "%s: invalid magic\n", file); + goto out_fw; + } + + if (!dsp->ops->validate_version(dsp, header->ver)) { + cs_dsp_err(dsp, "%s: unknown file format %d\n", + file, header->ver); + goto out_fw; + } + + cs_dsp_info(dsp, "Firmware version: %d\n", header->ver); + dsp->fw_ver = header->ver; + + if (header->core != dsp->type) { + cs_dsp_err(dsp, "%s: invalid core %d != %d\n", + file, header->core, dsp->type); + goto out_fw; + } + + pos = sizeof(*header); + pos = dsp->ops->parse_sizes(dsp, file, pos, firmware); + + footer = (void *)&firmware->data[pos]; + pos += sizeof(*footer); + + if (le32_to_cpu(header->len) != pos) { + cs_dsp_err(dsp, "%s: unexpected header length %d\n", + file, le32_to_cpu(header->len)); + goto out_fw; + } + + cs_dsp_dbg(dsp, "%s: timestamp %llu\n", file, + le64_to_cpu(footer->timestamp)); + + while (pos < firmware->size && + sizeof(*region) < firmware->size - pos) { + region = (void *)&(firmware->data[pos]); + region_name = "Unknown"; + reg = 0; + text = NULL; + offset = le32_to_cpu(region->offset) & 0xffffff; + type = be32_to_cpu(region->type) & 0xff; + + switch (type) { + case WMFW_NAME_TEXT: + region_name = "Firmware name"; + text = kzalloc(le32_to_cpu(region->len) + 1, + GFP_KERNEL); + break; + case WMFW_ALGORITHM_DATA: + region_name = "Algorithm"; + ret = cs_dsp_parse_coeff(dsp, region); + if (ret != 0) + goto out_fw; + break; + case WMFW_INFO_TEXT: + region_name = "Information"; + text = kzalloc(le32_to_cpu(region->len) + 1, + GFP_KERNEL); + break; + case WMFW_ABSOLUTE: + region_name = "Absolute"; + reg = offset; + break; + case WMFW_ADSP1_PM: + case WMFW_ADSP1_DM: + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + case WMFW_ADSP1_ZM: + case WMFW_HALO_PM_PACKED: + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + mem = cs_dsp_find_region(dsp, type); + if (!mem) { + cs_dsp_err(dsp, "No region of type: %x\n", type); + ret = -EINVAL; + goto out_fw; + } + + region_name = cs_dsp_mem_region_name(type); + reg = dsp->ops->region_to_reg(mem, offset); + break; + default: + cs_dsp_warn(dsp, + "%s.%d: Unknown region type %x at %d(%x)\n", + file, regions, type, pos, pos); + break; + } + + cs_dsp_dbg(dsp, "%s.%d: %d bytes at %d in %s\n", file, + regions, le32_to_cpu(region->len), offset, + region_name); + + if (le32_to_cpu(region->len) > + firmware->size - pos - sizeof(*region)) { + cs_dsp_err(dsp, + "%s.%d: %s region len %d bytes exceeds file length %zu\n", + file, regions, region_name, + le32_to_cpu(region->len), firmware->size); + ret = -EINVAL; + goto out_fw; + } + + if (text) { + memcpy(text, region->data, le32_to_cpu(region->len)); + cs_dsp_info(dsp, "%s: %s\n", file, text); + kfree(text); + text = NULL; + } + + if (reg) { + buf = cs_dsp_buf_alloc(region->data, + le32_to_cpu(region->len), + &buf_list); + if (!buf) { + cs_dsp_err(dsp, "Out of memory\n"); + ret = -ENOMEM; + goto out_fw; + } + + ret = regmap_raw_write_async(regmap, reg, buf->buf, + le32_to_cpu(region->len)); + if (ret != 0) { + cs_dsp_err(dsp, + "%s.%d: Failed to write %d bytes at %d in %s: %d\n", + file, regions, + le32_to_cpu(region->len), offset, + region_name, ret); + goto out_fw; + } + } + + pos += le32_to_cpu(region->len) + sizeof(*region); + regions++; + } + + ret = regmap_async_complete(regmap); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to complete async write: %d\n", ret); + goto out_fw; + } + + if (pos > firmware->size) + cs_dsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", + file, regions, pos - firmware->size); + + cs_dsp_debugfs_save_wmfwname(dsp, file); + +out_fw: + regmap_async_complete(regmap); + cs_dsp_buf_free(&buf_list); + kfree(text); + + return ret; +} + +/** + * cs_dsp_get_ctl() - Finds a matching coefficient control + * @dsp: pointer to DSP structure + * @name: pointer to string to match with a control's subname + * @type: the algorithm type to match + * @alg: the algorithm id to match + * + * Find cs_dsp_coeff_ctl with input name as its subname + * + * Return: pointer to the control on success, NULL if not found + */ +struct cs_dsp_coeff_ctl *cs_dsp_get_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg) +{ + struct cs_dsp_coeff_ctl *pos, *rslt = NULL; + + list_for_each_entry(pos, &dsp->ctl_list, list) { + if (!pos->subname) + continue; + if (strncmp(pos->subname, name, pos->subname_len) == 0 && + pos->fw_name == dsp->fw_name && + pos->alg_region.alg == alg && + pos->alg_region.type == type) { + rslt = pos; + break; + } + } + + return rslt; +} +EXPORT_SYMBOL_GPL(cs_dsp_get_ctl); + +static void cs_dsp_ctl_fixup_base(struct cs_dsp *dsp, + const struct cs_dsp_alg_region *alg_region) +{ + struct cs_dsp_coeff_ctl *ctl; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (ctl->fw_name == dsp->fw_name && + alg_region->alg == ctl->alg_region.alg && + alg_region->type == ctl->alg_region.type) { + ctl->alg_region.base = alg_region->base; + } + } +} + +static void *cs_dsp_read_algs(struct cs_dsp *dsp, size_t n_algs, + const struct cs_dsp_region *mem, + unsigned int pos, unsigned int len) +{ + void *alg; + unsigned int reg; + int ret; + __be32 val; + + if (n_algs == 0) { + cs_dsp_err(dsp, "No algorithms\n"); + return ERR_PTR(-EINVAL); + } + + if (n_algs > 1024) { + cs_dsp_err(dsp, "Algorithm count %zx excessive\n", n_algs); + return ERR_PTR(-EINVAL); + } + + /* Read the terminator first to validate the length */ + reg = dsp->ops->region_to_reg(mem, pos + len); + + ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to read algorithm list end: %d\n", + ret); + return ERR_PTR(ret); + } + + if (be32_to_cpu(val) != 0xbedead) + cs_dsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbedead\n", + reg, be32_to_cpu(val)); + + /* Convert length from DSP words to bytes */ + len *= sizeof(u32); + + alg = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!alg) + return ERR_PTR(-ENOMEM); + + reg = dsp->ops->region_to_reg(mem, pos); + + ret = regmap_raw_read(dsp->regmap, reg, alg, len); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to read algorithm list: %d\n", ret); + kfree(alg); + return ERR_PTR(ret); + } + + return alg; +} + +/** + * cs_dsp_find_alg_region() - Finds a matching algorithm region + * @dsp: pointer to DSP structure + * @type: the algorithm type to match + * @id: the algorithm id to match + * + * Return: Pointer to matching algorithm region, or NULL if not found. + */ +struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp, + int type, unsigned int id) +{ + struct cs_dsp_alg_region *alg_region; + + list_for_each_entry(alg_region, &dsp->alg_regions, list) { + if (id == alg_region->alg && type == alg_region->type) + return alg_region; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(cs_dsp_find_alg_region); + +static struct cs_dsp_alg_region *cs_dsp_create_region(struct cs_dsp *dsp, + int type, __be32 id, + __be32 base) +{ + struct cs_dsp_alg_region *alg_region; + + alg_region = kzalloc(sizeof(*alg_region), GFP_KERNEL); + if (!alg_region) + return ERR_PTR(-ENOMEM); + + alg_region->type = type; + alg_region->alg = be32_to_cpu(id); + alg_region->base = be32_to_cpu(base); + + list_add_tail(&alg_region->list, &dsp->alg_regions); + + if (dsp->fw_ver > 0) + cs_dsp_ctl_fixup_base(dsp, alg_region); + + return alg_region; +} + +static void cs_dsp_free_alg_regions(struct cs_dsp *dsp) +{ + struct cs_dsp_alg_region *alg_region; + + while (!list_empty(&dsp->alg_regions)) { + alg_region = list_first_entry(&dsp->alg_regions, + struct cs_dsp_alg_region, + list); + list_del(&alg_region->list); + kfree(alg_region); + } +} + +static void cs_dsp_parse_wmfw_id_header(struct cs_dsp *dsp, + struct wmfw_id_hdr *fw, int nalgs) +{ + dsp->fw_id = be32_to_cpu(fw->id); + dsp->fw_id_version = be32_to_cpu(fw->ver); + + cs_dsp_info(dsp, "Firmware: %x v%d.%d.%d, %d algorithms\n", + dsp->fw_id, (dsp->fw_id_version & 0xff0000) >> 16, + (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, + nalgs); +} + +static void cs_dsp_parse_wmfw_v3_id_header(struct cs_dsp *dsp, + struct wmfw_v3_id_hdr *fw, int nalgs) +{ + dsp->fw_id = be32_to_cpu(fw->id); + dsp->fw_id_version = be32_to_cpu(fw->ver); + dsp->fw_vendor_id = be32_to_cpu(fw->vendor_id); + + cs_dsp_info(dsp, "Firmware: %x vendor: 0x%x v%d.%d.%d, %d algorithms\n", + dsp->fw_id, dsp->fw_vendor_id, + (dsp->fw_id_version & 0xff0000) >> 16, + (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, + nalgs); +} + +static int cs_dsp_create_regions(struct cs_dsp *dsp, __be32 id, int nregions, + const int *type, __be32 *base) +{ + struct cs_dsp_alg_region *alg_region; + int i; + + for (i = 0; i < nregions; i++) { + alg_region = cs_dsp_create_region(dsp, type[i], id, base[i]); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + } + + return 0; +} + +static int cs_dsp_adsp1_setup_algs(struct cs_dsp *dsp) +{ + struct wmfw_adsp1_id_hdr adsp1_id; + struct wmfw_adsp1_alg_hdr *adsp1_alg; + struct cs_dsp_alg_region *alg_region; + const struct cs_dsp_region *mem; + unsigned int pos, len; + size_t n_algs; + int i, ret; + + mem = cs_dsp_find_region(dsp, WMFW_ADSP1_DM); + if (WARN_ON(!mem)) + return -EINVAL; + + ret = regmap_raw_read(dsp->regmap, mem->base, &adsp1_id, + sizeof(adsp1_id)); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to read algorithm info: %d\n", + ret); + return ret; + } + + n_algs = be32_to_cpu(adsp1_id.n_algs); + + cs_dsp_parse_wmfw_id_header(dsp, &adsp1_id.fw, n_algs); + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP1_ZM, + adsp1_id.fw.id, adsp1_id.zm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP1_DM, + adsp1_id.fw.id, adsp1_id.dm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + /* Calculate offset and length in DSP words */ + pos = sizeof(adsp1_id) / sizeof(u32); + len = (sizeof(*adsp1_alg) * n_algs) / sizeof(u32); + + adsp1_alg = cs_dsp_read_algs(dsp, n_algs, mem, pos, len); + if (IS_ERR(adsp1_alg)) + return PTR_ERR(adsp1_alg); + + for (i = 0; i < n_algs; i++) { + cs_dsp_info(dsp, "%d: ID %x v%d.%d.%d DM@%x ZM@%x\n", + i, be32_to_cpu(adsp1_alg[i].alg.id), + (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff0000) >> 16, + (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff00) >> 8, + be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff, + be32_to_cpu(adsp1_alg[i].dm), + be32_to_cpu(adsp1_alg[i].zm)); + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP1_DM, + adsp1_alg[i].alg.id, + adsp1_alg[i].dm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp1_alg[i + 1].dm); + len -= be32_to_cpu(adsp1_alg[i].dm); + len *= 4; + cs_dsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + WMFW_CTL_TYPE_BYTES); + } else { + cs_dsp_warn(dsp, "Missing length info for region DM with ID %x\n", + be32_to_cpu(adsp1_alg[i].alg.id)); + } + } + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP1_ZM, + adsp1_alg[i].alg.id, + adsp1_alg[i].zm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp1_alg[i + 1].zm); + len -= be32_to_cpu(adsp1_alg[i].zm); + len *= 4; + cs_dsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + WMFW_CTL_TYPE_BYTES); + } else { + cs_dsp_warn(dsp, "Missing length info for region ZM with ID %x\n", + be32_to_cpu(adsp1_alg[i].alg.id)); + } + } + } + +out: + kfree(adsp1_alg); + return ret; +} + +static int cs_dsp_adsp2_setup_algs(struct cs_dsp *dsp) +{ + struct wmfw_adsp2_id_hdr adsp2_id; + struct wmfw_adsp2_alg_hdr *adsp2_alg; + struct cs_dsp_alg_region *alg_region; + const struct cs_dsp_region *mem; + unsigned int pos, len; + size_t n_algs; + int i, ret; + + mem = cs_dsp_find_region(dsp, WMFW_ADSP2_XM); + if (WARN_ON(!mem)) + return -EINVAL; + + ret = regmap_raw_read(dsp->regmap, mem->base, &adsp2_id, + sizeof(adsp2_id)); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to read algorithm info: %d\n", + ret); + return ret; + } + + n_algs = be32_to_cpu(adsp2_id.n_algs); + + cs_dsp_parse_wmfw_id_header(dsp, &adsp2_id.fw, n_algs); + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_XM, + adsp2_id.fw.id, adsp2_id.xm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_YM, + adsp2_id.fw.id, adsp2_id.ym); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_ZM, + adsp2_id.fw.id, adsp2_id.zm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + /* Calculate offset and length in DSP words */ + pos = sizeof(adsp2_id) / sizeof(u32); + len = (sizeof(*adsp2_alg) * n_algs) / sizeof(u32); + + adsp2_alg = cs_dsp_read_algs(dsp, n_algs, mem, pos, len); + if (IS_ERR(adsp2_alg)) + return PTR_ERR(adsp2_alg); + + for (i = 0; i < n_algs; i++) { + cs_dsp_info(dsp, + "%d: ID %x v%d.%d.%d XM@%x YM@%x ZM@%x\n", + i, be32_to_cpu(adsp2_alg[i].alg.id), + (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff0000) >> 16, + (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff00) >> 8, + be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff, + be32_to_cpu(adsp2_alg[i].xm), + be32_to_cpu(adsp2_alg[i].ym), + be32_to_cpu(adsp2_alg[i].zm)); + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_XM, + adsp2_alg[i].alg.id, + adsp2_alg[i].xm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp2_alg[i + 1].xm); + len -= be32_to_cpu(adsp2_alg[i].xm); + len *= 4; + cs_dsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + WMFW_CTL_TYPE_BYTES); + } else { + cs_dsp_warn(dsp, "Missing length info for region XM with ID %x\n", + be32_to_cpu(adsp2_alg[i].alg.id)); + } + } + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_YM, + adsp2_alg[i].alg.id, + adsp2_alg[i].ym); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp2_alg[i + 1].ym); + len -= be32_to_cpu(adsp2_alg[i].ym); + len *= 4; + cs_dsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + WMFW_CTL_TYPE_BYTES); + } else { + cs_dsp_warn(dsp, "Missing length info for region YM with ID %x\n", + be32_to_cpu(adsp2_alg[i].alg.id)); + } + } + + alg_region = cs_dsp_create_region(dsp, WMFW_ADSP2_ZM, + adsp2_alg[i].alg.id, + adsp2_alg[i].zm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp2_alg[i + 1].zm); + len -= be32_to_cpu(adsp2_alg[i].zm); + len *= 4; + cs_dsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + WMFW_CTL_TYPE_BYTES); + } else { + cs_dsp_warn(dsp, "Missing length info for region ZM with ID %x\n", + be32_to_cpu(adsp2_alg[i].alg.id)); + } + } + } + +out: + kfree(adsp2_alg); + return ret; +} + +static int cs_dsp_halo_create_regions(struct cs_dsp *dsp, __be32 id, + __be32 xm_base, __be32 ym_base) +{ + static const int types[] = { + WMFW_ADSP2_XM, WMFW_HALO_XM_PACKED, + WMFW_ADSP2_YM, WMFW_HALO_YM_PACKED + }; + __be32 bases[] = { xm_base, xm_base, ym_base, ym_base }; + + return cs_dsp_create_regions(dsp, id, ARRAY_SIZE(types), types, bases); +} + +static int cs_dsp_halo_setup_algs(struct cs_dsp *dsp) +{ + struct wmfw_halo_id_hdr halo_id; + struct wmfw_halo_alg_hdr *halo_alg; + const struct cs_dsp_region *mem; + unsigned int pos, len; + size_t n_algs; + int i, ret; + + mem = cs_dsp_find_region(dsp, WMFW_ADSP2_XM); + if (WARN_ON(!mem)) + return -EINVAL; + + ret = regmap_raw_read(dsp->regmap, mem->base, &halo_id, + sizeof(halo_id)); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to read algorithm info: %d\n", + ret); + return ret; + } + + n_algs = be32_to_cpu(halo_id.n_algs); + + cs_dsp_parse_wmfw_v3_id_header(dsp, &halo_id.fw, n_algs); + + ret = cs_dsp_halo_create_regions(dsp, halo_id.fw.id, + halo_id.xm_base, halo_id.ym_base); + if (ret) + return ret; + + /* Calculate offset and length in DSP words */ + pos = sizeof(halo_id) / sizeof(u32); + len = (sizeof(*halo_alg) * n_algs) / sizeof(u32); + + halo_alg = cs_dsp_read_algs(dsp, n_algs, mem, pos, len); + if (IS_ERR(halo_alg)) + return PTR_ERR(halo_alg); + + for (i = 0; i < n_algs; i++) { + cs_dsp_info(dsp, + "%d: ID %x v%d.%d.%d XM@%x YM@%x\n", + i, be32_to_cpu(halo_alg[i].alg.id), + (be32_to_cpu(halo_alg[i].alg.ver) & 0xff0000) >> 16, + (be32_to_cpu(halo_alg[i].alg.ver) & 0xff00) >> 8, + be32_to_cpu(halo_alg[i].alg.ver) & 0xff, + be32_to_cpu(halo_alg[i].xm_base), + be32_to_cpu(halo_alg[i].ym_base)); + + ret = cs_dsp_halo_create_regions(dsp, halo_alg[i].alg.id, + halo_alg[i].xm_base, + halo_alg[i].ym_base); + if (ret) + goto out; + } + +out: + kfree(halo_alg); + return ret; +} + +static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware, + const char *file) +{ + LIST_HEAD(buf_list); + struct regmap *regmap = dsp->regmap; + struct wmfw_coeff_hdr *hdr; + struct wmfw_coeff_item *blk; + const struct cs_dsp_region *mem; + struct cs_dsp_alg_region *alg_region; + const char *region_name; + int ret, pos, blocks, type, offset, reg; + struct cs_dsp_buf *buf; + + if (!firmware) + return 0; + + ret = -EINVAL; + + if (sizeof(*hdr) >= firmware->size) { + cs_dsp_err(dsp, "%s: coefficient file too short, %zu bytes\n", + file, firmware->size); + goto out_fw; + } + + hdr = (void *)&firmware->data[0]; + if (memcmp(hdr->magic, "WMDR", 4) != 0) { + cs_dsp_err(dsp, "%s: invalid coefficient magic\n", file); + goto out_fw; + } + + switch (be32_to_cpu(hdr->rev) & 0xff) { + case 1: + break; + default: + cs_dsp_err(dsp, "%s: Unsupported coefficient file format %d\n", + file, be32_to_cpu(hdr->rev) & 0xff); + ret = -EINVAL; + goto out_fw; + } + + cs_dsp_dbg(dsp, "%s: v%d.%d.%d\n", file, + (le32_to_cpu(hdr->ver) >> 16) & 0xff, + (le32_to_cpu(hdr->ver) >> 8) & 0xff, + le32_to_cpu(hdr->ver) & 0xff); + + pos = le32_to_cpu(hdr->len); + + blocks = 0; + while (pos < firmware->size && + sizeof(*blk) < firmware->size - pos) { + blk = (void *)(&firmware->data[pos]); + + type = le16_to_cpu(blk->type); + offset = le16_to_cpu(blk->offset); + + cs_dsp_dbg(dsp, "%s.%d: %x v%d.%d.%d\n", + file, blocks, le32_to_cpu(blk->id), + (le32_to_cpu(blk->ver) >> 16) & 0xff, + (le32_to_cpu(blk->ver) >> 8) & 0xff, + le32_to_cpu(blk->ver) & 0xff); + cs_dsp_dbg(dsp, "%s.%d: %d bytes at 0x%x in %x\n", + file, blocks, le32_to_cpu(blk->len), offset, type); + + reg = 0; + region_name = "Unknown"; + switch (type) { + case (WMFW_NAME_TEXT << 8): + case (WMFW_INFO_TEXT << 8): + case (WMFW_METADATA << 8): + break; + case (WMFW_ABSOLUTE << 8): + /* + * Old files may use this for global + * coefficients. + */ + if (le32_to_cpu(blk->id) == dsp->fw_id && + offset == 0) { + region_name = "global coefficients"; + mem = cs_dsp_find_region(dsp, type); + if (!mem) { + cs_dsp_err(dsp, "No ZM\n"); + break; + } + reg = dsp->ops->region_to_reg(mem, 0); + + } else { + region_name = "register"; + reg = offset; + } + break; + + case WMFW_ADSP1_DM: + case WMFW_ADSP1_ZM: + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + case WMFW_HALO_XM_PACKED: + case WMFW_HALO_YM_PACKED: + case WMFW_HALO_PM_PACKED: + cs_dsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n", + file, blocks, le32_to_cpu(blk->len), + type, le32_to_cpu(blk->id)); + + mem = cs_dsp_find_region(dsp, type); + if (!mem) { + cs_dsp_err(dsp, "No base for region %x\n", type); + break; + } + + alg_region = cs_dsp_find_alg_region(dsp, type, + le32_to_cpu(blk->id)); + if (alg_region) { + reg = alg_region->base; + reg = dsp->ops->region_to_reg(mem, reg); + reg += offset; + } else { + cs_dsp_err(dsp, "No %x for algorithm %x\n", + type, le32_to_cpu(blk->id)); + } + break; + + default: + cs_dsp_err(dsp, "%s.%d: Unknown region type %x at %d\n", + file, blocks, type, pos); + break; + } + + if (reg) { + if (le32_to_cpu(blk->len) > + firmware->size - pos - sizeof(*blk)) { + cs_dsp_err(dsp, + "%s.%d: %s region len %d bytes exceeds file length %zu\n", + file, blocks, region_name, + le32_to_cpu(blk->len), + firmware->size); + ret = -EINVAL; + goto out_fw; + } + + buf = cs_dsp_buf_alloc(blk->data, + le32_to_cpu(blk->len), + &buf_list); + if (!buf) { + cs_dsp_err(dsp, "Out of memory\n"); + ret = -ENOMEM; + goto out_fw; + } + + cs_dsp_dbg(dsp, "%s.%d: Writing %d bytes at %x\n", + file, blocks, le32_to_cpu(blk->len), + reg); + ret = regmap_raw_write_async(regmap, reg, buf->buf, + le32_to_cpu(blk->len)); + if (ret != 0) { + cs_dsp_err(dsp, + "%s.%d: Failed to write to %x in %s: %d\n", + file, blocks, reg, region_name, ret); + } + } + + pos += (le32_to_cpu(blk->len) + sizeof(*blk) + 3) & ~0x03; + blocks++; + } + + ret = regmap_async_complete(regmap); + if (ret != 0) + cs_dsp_err(dsp, "Failed to complete async write: %d\n", ret); + + if (pos > firmware->size) + cs_dsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", + file, blocks, pos - firmware->size); + + cs_dsp_debugfs_save_binname(dsp, file); + +out_fw: + regmap_async_complete(regmap); + cs_dsp_buf_free(&buf_list); + return ret; +} + +static int cs_dsp_create_name(struct cs_dsp *dsp) +{ + if (!dsp->name) { + dsp->name = devm_kasprintf(dsp->dev, GFP_KERNEL, "DSP%d", + dsp->num); + if (!dsp->name) + return -ENOMEM; + } + + return 0; +} + +static int cs_dsp_common_init(struct cs_dsp *dsp) +{ + int ret; + + ret = cs_dsp_create_name(dsp); + if (ret) + return ret; + + INIT_LIST_HEAD(&dsp->alg_regions); + INIT_LIST_HEAD(&dsp->ctl_list); + + mutex_init(&dsp->pwr_lock); + + return 0; +} + +/** + * cs_dsp_adsp1_init() - Initialise a cs_dsp structure representing a ADSP1 device + * @dsp: pointer to DSP structure + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_adsp1_init(struct cs_dsp *dsp) +{ + dsp->ops = &cs_dsp_adsp1_ops; + + return cs_dsp_common_init(dsp); +} +EXPORT_SYMBOL_GPL(cs_dsp_adsp1_init); + +/** + * cs_dsp_adsp1_power_up() - Load and start the named firmware + * @dsp: pointer to DSP structure + * @wmfw_firmware: the firmware to be sent + * @wmfw_filename: file name of firmware to be sent + * @coeff_firmware: the coefficient data to be sent + * @coeff_filename: file name of coefficient to data be sent + * @fw_name: the user-friendly firmware name + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_adsp1_power_up(struct cs_dsp *dsp, + const struct firmware *wmfw_firmware, char *wmfw_filename, + const struct firmware *coeff_firmware, char *coeff_filename, + const char *fw_name) +{ + unsigned int val; + int ret; + + mutex_lock(&dsp->pwr_lock); + + dsp->fw_name = fw_name; + + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_SYS_ENA, ADSP1_SYS_ENA); + + /* + * For simplicity set the DSP clock rate to be the + * SYSCLK rate rather than making it configurable. + */ + if (dsp->sysclk_reg) { + ret = regmap_read(dsp->regmap, dsp->sysclk_reg, &val); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to read SYSCLK state: %d\n", ret); + goto err_mutex; + } + + val = (val & dsp->sysclk_mask) >> dsp->sysclk_shift; + + ret = regmap_update_bits(dsp->regmap, + dsp->base + ADSP1_CONTROL_31, + ADSP1_CLK_SEL_MASK, val); + if (ret != 0) { + cs_dsp_err(dsp, "Failed to set clock rate: %d\n", ret); + goto err_mutex; + } + } + + ret = cs_dsp_load(dsp, wmfw_firmware, wmfw_filename); + if (ret != 0) + goto err_ena; + + ret = cs_dsp_adsp1_setup_algs(dsp); + if (ret != 0) + goto err_ena; + + ret = cs_dsp_load_coeff(dsp, coeff_firmware, coeff_filename); + if (ret != 0) + goto err_ena; + + /* Initialize caches for enabled and unset controls */ + ret = cs_dsp_coeff_init_control_caches(dsp); + if (ret != 0) + goto err_ena; + + /* Sync set controls */ + ret = cs_dsp_coeff_sync_controls(dsp); + if (ret != 0) + goto err_ena; + + dsp->booted = true; + + /* Start the core running */ + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_CORE_ENA | ADSP1_START, + ADSP1_CORE_ENA | ADSP1_START); + + dsp->running = true; + + mutex_unlock(&dsp->pwr_lock); + + return 0; + +err_ena: + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_SYS_ENA, 0); +err_mutex: + mutex_unlock(&dsp->pwr_lock); + return ret; +} +EXPORT_SYMBOL_GPL(cs_dsp_adsp1_power_up); + +/** + * cs_dsp_adsp1_power_down() - Halts the DSP + * @dsp: pointer to DSP structure + */ +void cs_dsp_adsp1_power_down(struct cs_dsp *dsp) +{ + struct cs_dsp_coeff_ctl *ctl; + + mutex_lock(&dsp->pwr_lock); + + dsp->running = false; + dsp->booted = false; + + /* Halt the core */ + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_CORE_ENA | ADSP1_START, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_19, + ADSP1_WDMA_BUFFER_LENGTH_MASK, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_SYS_ENA, 0); + + list_for_each_entry(ctl, &dsp->ctl_list, list) + ctl->enabled = 0; + + cs_dsp_free_alg_regions(dsp); + + mutex_unlock(&dsp->pwr_lock); +} +EXPORT_SYMBOL_GPL(cs_dsp_adsp1_power_down); + +static int cs_dsp_adsp2v2_enable_core(struct cs_dsp *dsp) +{ + unsigned int val; + int ret, count; + + /* Wait for the RAM to start, should be near instantaneous */ + for (count = 0; count < 10; ++count) { + ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, &val); + if (ret != 0) + return ret; + + if (val & ADSP2_RAM_RDY) + break; + + usleep_range(250, 500); + } + + if (!(val & ADSP2_RAM_RDY)) { + cs_dsp_err(dsp, "Failed to start DSP RAM\n"); + return -EBUSY; + } + + cs_dsp_dbg(dsp, "RAM ready after %d polls\n", count); + + return 0; +} + +static int cs_dsp_adsp2_enable_core(struct cs_dsp *dsp) +{ + int ret; + + ret = regmap_update_bits_async(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, ADSP2_SYS_ENA); + if (ret != 0) + return ret; + + return cs_dsp_adsp2v2_enable_core(dsp); +} + +static int cs_dsp_adsp2_lock(struct cs_dsp *dsp, unsigned int lock_regions) +{ + struct regmap *regmap = dsp->regmap; + unsigned int code0, code1, lock_reg; + + if (!(lock_regions & CS_ADSP2_REGION_ALL)) + return 0; + + lock_regions &= CS_ADSP2_REGION_ALL; + lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; + + while (lock_regions) { + code0 = code1 = 0; + if (lock_regions & BIT(0)) { + code0 = ADSP2_LOCK_CODE_0; + code1 = ADSP2_LOCK_CODE_1; + } + if (lock_regions & BIT(1)) { + code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; + code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; + } + regmap_write(regmap, lock_reg, code0); + regmap_write(regmap, lock_reg, code1); + lock_regions >>= 2; + lock_reg += 2; + } + + return 0; +} + +static int cs_dsp_adsp2_enable_memory(struct cs_dsp *dsp) +{ + return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, ADSP2_MEM_ENA); +} + +static void cs_dsp_adsp2_disable_memory(struct cs_dsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); +} + +static void cs_dsp_adsp2_disable_core(struct cs_dsp *dsp) +{ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); +} + +static void cs_dsp_adsp2v2_disable_core(struct cs_dsp *dsp) +{ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); +} + +static int cs_dsp_halo_configure_mpu(struct cs_dsp *dsp, unsigned int lock_regions) +{ + struct reg_sequence config[] = { + { dsp->base + HALO_MPU_LOCK_CONFIG, 0x5555 }, + { dsp->base + HALO_MPU_LOCK_CONFIG, 0xAAAA }, + { dsp->base + HALO_MPU_XMEM_ACCESS_0, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_0, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_0, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_1, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_1, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_1, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_2, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_2, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_2, lock_regions }, + { dsp->base + HALO_MPU_XMEM_ACCESS_3, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_YMEM_ACCESS_3, 0xFFFFFFFF }, + { dsp->base + HALO_MPU_WINDOW_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_XREG_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_YREG_ACCESS_3, lock_regions }, + { dsp->base + HALO_MPU_LOCK_CONFIG, 0 }, + }; + + return regmap_multi_reg_write(dsp->regmap, config, ARRAY_SIZE(config)); +} + +/** + * cs_dsp_set_dspclk() - Applies the given frequency to the given cs_dsp + * @dsp: pointer to DSP structure + * @freq: clock rate to set + * + * This is only for use on ADSP2 cores. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_set_dspclk(struct cs_dsp *dsp, unsigned int freq) +{ + int ret; + + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CLOCKING, + ADSP2_CLK_SEL_MASK, + freq << ADSP2_CLK_SEL_SHIFT); + if (ret) + cs_dsp_err(dsp, "Failed to set clock rate: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(cs_dsp_set_dspclk); + +static void cs_dsp_stop_watchdog(struct cs_dsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, + ADSP2_WDT_ENA_MASK, 0); +} + +static void cs_dsp_halo_stop_watchdog(struct cs_dsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + HALO_WDT_CONTROL, + HALO_WDT_EN_MASK, 0); +} + +/** + * cs_dsp_power_up() - Downloads firmware to the DSP + * @dsp: pointer to DSP structure + * @wmfw_firmware: the firmware to be sent + * @wmfw_filename: file name of firmware to be sent + * @coeff_firmware: the coefficient data to be sent + * @coeff_filename: file name of coefficient to data be sent + * @fw_name: the user-friendly firmware name + * + * This function is used on ADSP2 and Halo DSP cores, it powers-up the DSP core + * and downloads the firmware but does not start the firmware running. The + * cs_dsp booted flag will be set once completed and if the core has a low-power + * memory retention mode it will be put into this state after the firmware is + * downloaded. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_power_up(struct cs_dsp *dsp, + const struct firmware *wmfw_firmware, char *wmfw_filename, + const struct firmware *coeff_firmware, char *coeff_filename, + const char *fw_name) +{ + int ret; + + mutex_lock(&dsp->pwr_lock); + + dsp->fw_name = fw_name; + + if (dsp->ops->enable_memory) { + ret = dsp->ops->enable_memory(dsp); + if (ret != 0) + goto err_mutex; + } + + if (dsp->ops->enable_core) { + ret = dsp->ops->enable_core(dsp); + if (ret != 0) + goto err_mem; + } + + ret = cs_dsp_load(dsp, wmfw_firmware, wmfw_filename); + if (ret != 0) + goto err_ena; + + ret = dsp->ops->setup_algs(dsp); + if (ret != 0) + goto err_ena; + + ret = cs_dsp_load_coeff(dsp, coeff_firmware, coeff_filename); + if (ret != 0) + goto err_ena; + + /* Initialize caches for enabled and unset controls */ + ret = cs_dsp_coeff_init_control_caches(dsp); + if (ret != 0) + goto err_ena; + + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); + + dsp->booted = true; + + mutex_unlock(&dsp->pwr_lock); + + return 0; +err_ena: + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); +err_mem: + if (dsp->ops->disable_memory) + dsp->ops->disable_memory(dsp); +err_mutex: + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cs_dsp_power_up); + +/** + * cs_dsp_power_down() - Powers-down the DSP + * @dsp: pointer to DSP structure + * + * cs_dsp_stop() must have been called before this function. The core will be + * fully powered down and so the memory will not be retained. + */ +void cs_dsp_power_down(struct cs_dsp *dsp) +{ + struct cs_dsp_coeff_ctl *ctl; + + mutex_lock(&dsp->pwr_lock); + + cs_dsp_debugfs_clear(dsp); + + dsp->fw_id = 0; + dsp->fw_id_version = 0; + + dsp->booted = false; + + if (dsp->ops->disable_memory) + dsp->ops->disable_memory(dsp); + + list_for_each_entry(ctl, &dsp->ctl_list, list) + ctl->enabled = 0; + + cs_dsp_free_alg_regions(dsp); + + mutex_unlock(&dsp->pwr_lock); + + cs_dsp_dbg(dsp, "Shutdown complete\n"); +} +EXPORT_SYMBOL_GPL(cs_dsp_power_down); + +static int cs_dsp_adsp2_start_core(struct cs_dsp *dsp) +{ + return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, + ADSP2_CORE_ENA | ADSP2_START); +} + +static void cs_dsp_adsp2_stop_core(struct cs_dsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, 0); +} + +/** + * cs_dsp_run() - Starts the firmware running + * @dsp: pointer to DSP structure + * + * cs_dsp_power_up() must have previously been called successfully. + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_run(struct cs_dsp *dsp) +{ + int ret; + + mutex_lock(&dsp->pwr_lock); + + if (!dsp->booted) { + ret = -EIO; + goto err; + } + + if (dsp->ops->enable_core) { + ret = dsp->ops->enable_core(dsp); + if (ret != 0) + goto err; + } + + /* Sync set controls */ + ret = cs_dsp_coeff_sync_controls(dsp); + if (ret != 0) + goto err; + + if (dsp->ops->lock_memory) { + ret = dsp->ops->lock_memory(dsp, dsp->lock_regions); + if (ret != 0) { + cs_dsp_err(dsp, "Error configuring MPU: %d\n", ret); + goto err; + } + } + + if (dsp->ops->start_core) { + ret = dsp->ops->start_core(dsp); + if (ret != 0) + goto err; + } + + dsp->running = true; + + if (dsp->client_ops->post_run) { + ret = dsp->client_ops->post_run(dsp); + if (ret) + goto err; + } + + mutex_unlock(&dsp->pwr_lock); + + return 0; + +err: + if (dsp->ops->stop_core) + dsp->ops->stop_core(dsp); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cs_dsp_run); + +/** + * cs_dsp_stop() - Stops the firmware + * @dsp: pointer to DSP structure + * + * Memory will not be disabled so firmware will remain loaded. + */ +void cs_dsp_stop(struct cs_dsp *dsp) +{ + /* Tell the firmware to cleanup */ + cs_dsp_signal_event_controls(dsp, CS_DSP_FW_EVENT_SHUTDOWN); + + if (dsp->ops->stop_watchdog) + dsp->ops->stop_watchdog(dsp); + + /* Log firmware state, it can be useful for analysis */ + if (dsp->ops->show_fw_status) + dsp->ops->show_fw_status(dsp); + + mutex_lock(&dsp->pwr_lock); + + dsp->running = false; + + if (dsp->ops->stop_core) + dsp->ops->stop_core(dsp); + if (dsp->ops->disable_core) + dsp->ops->disable_core(dsp); + + if (dsp->client_ops->post_stop) + dsp->client_ops->post_stop(dsp); + + mutex_unlock(&dsp->pwr_lock); + + cs_dsp_dbg(dsp, "Execution stopped\n"); +} +EXPORT_SYMBOL_GPL(cs_dsp_stop); + +static int cs_dsp_halo_start_core(struct cs_dsp *dsp) +{ + return regmap_update_bits(dsp->regmap, + dsp->base + HALO_CCM_CORE_CONTROL, + HALO_CORE_RESET | HALO_CORE_EN, + HALO_CORE_RESET | HALO_CORE_EN); +} + +static void cs_dsp_halo_stop_core(struct cs_dsp *dsp) +{ + regmap_update_bits(dsp->regmap, dsp->base + HALO_CCM_CORE_CONTROL, + HALO_CORE_EN, 0); + + /* reset halo core with CORE_SOFT_RESET */ + regmap_update_bits(dsp->regmap, dsp->base + HALO_CORE_SOFT_RESET, + HALO_CORE_SOFT_RESET_MASK, 1); +} + +/** + * cs_dsp_adsp2_init() - Initialise a cs_dsp structure representing a ADSP2 core + * @dsp: pointer to DSP structure + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_adsp2_init(struct cs_dsp *dsp) +{ + int ret; + + switch (dsp->rev) { + case 0: + /* + * Disable the DSP memory by default when in reset for a small + * power saving. + */ + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); + if (ret) { + cs_dsp_err(dsp, + "Failed to clear memory retention: %d\n", ret); + return ret; + } + + dsp->ops = &cs_dsp_adsp2_ops[0]; + break; + case 1: + dsp->ops = &cs_dsp_adsp2_ops[1]; + break; + default: + dsp->ops = &cs_dsp_adsp2_ops[2]; + break; + } + + return cs_dsp_common_init(dsp); +} +EXPORT_SYMBOL_GPL(cs_dsp_adsp2_init); + +/** + * cs_dsp_halo_init() - Initialise a cs_dsp structure representing a HALO Core DSP + * @dsp: pointer to DSP structure + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_halo_init(struct cs_dsp *dsp) +{ + dsp->ops = &cs_dsp_halo_ops; + + return cs_dsp_common_init(dsp); +} +EXPORT_SYMBOL_GPL(cs_dsp_halo_init); + +/** + * cs_dsp_remove() - Clean a cs_dsp before deletion + * @dsp: pointer to DSP structure + */ +void cs_dsp_remove(struct cs_dsp *dsp) +{ + struct cs_dsp_coeff_ctl *ctl; + + while (!list_empty(&dsp->ctl_list)) { + ctl = list_first_entry(&dsp->ctl_list, struct cs_dsp_coeff_ctl, list); + + if (dsp->client_ops->control_remove) + dsp->client_ops->control_remove(ctl); + + list_del(&ctl->list); + cs_dsp_free_ctl_blk(ctl); + } +} +EXPORT_SYMBOL_GPL(cs_dsp_remove); + +/** + * cs_dsp_read_raw_data_block() - Reads a block of data from DSP memory + * @dsp: pointer to DSP structure + * @mem_type: the type of DSP memory containing the data to be read + * @mem_addr: the address of the data within the memory region + * @num_words: the length of the data to read + * @data: a buffer to store the fetched data + * + * If this is used to read unpacked 24-bit memory, each 24-bit DSP word will + * occupy 32-bits in data (MSbyte will be 0). This padding can be removed using + * cs_dsp_remove_padding() + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_read_raw_data_block(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, + unsigned int num_words, __be32 *data) +{ + struct cs_dsp_region const *mem = cs_dsp_find_region(dsp, mem_type); + unsigned int reg; + int ret; + + if (!mem) + return -EINVAL; + + reg = dsp->ops->region_to_reg(mem, mem_addr); + + ret = regmap_raw_read(dsp->regmap, reg, data, + sizeof(*data) * num_words); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(cs_dsp_read_raw_data_block); + +/** + * cs_dsp_read_data_word() - Reads a word from DSP memory + * @dsp: pointer to DSP structure + * @mem_type: the type of DSP memory containing the data to be read + * @mem_addr: the address of the data within the memory region + * @data: a buffer to store the fetched data + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_read_data_word(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, u32 *data) +{ + __be32 raw; + int ret; + + ret = cs_dsp_read_raw_data_block(dsp, mem_type, mem_addr, 1, &raw); + if (ret < 0) + return ret; + + *data = be32_to_cpu(raw) & 0x00ffffffu; + + return 0; +} +EXPORT_SYMBOL_GPL(cs_dsp_read_data_word); + +/** + * cs_dsp_write_data_word() - Writes a word to DSP memory + * @dsp: pointer to DSP structure + * @mem_type: the type of DSP memory containing the data to be written + * @mem_addr: the address of the data within the memory region + * @data: the data to be written + * + * Return: Zero for success, a negative number on error. + */ +int cs_dsp_write_data_word(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, u32 data) +{ + struct cs_dsp_region const *mem = cs_dsp_find_region(dsp, mem_type); + __be32 val = cpu_to_be32(data & 0x00ffffffu); + unsigned int reg; + + if (!mem) + return -EINVAL; + + reg = dsp->ops->region_to_reg(mem, mem_addr); + + return regmap_raw_write(dsp->regmap, reg, &val, sizeof(val)); +} +EXPORT_SYMBOL_GPL(cs_dsp_write_data_word); + +/** + * cs_dsp_remove_padding() - Convert unpacked words to packed bytes + * @buf: buffer containing DSP words read from DSP memory + * @nwords: number of words to convert + * + * DSP words from the register map have pad bytes and the data bytes + * are in swapped order. This swaps to the native endian order and + * strips the pad bytes. + */ +void cs_dsp_remove_padding(u32 *buf, int nwords) +{ + const __be32 *pack_in = (__be32 *)buf; + u8 *pack_out = (u8 *)buf; + int i; + + for (i = 0; i < nwords; i++) { + u32 word = be32_to_cpu(*pack_in++); + *pack_out++ = (u8)word; + *pack_out++ = (u8)(word >> 8); + *pack_out++ = (u8)(word >> 16); + } +} +EXPORT_SYMBOL_GPL(cs_dsp_remove_padding); + +/** + * cs_dsp_adsp2_bus_error() - Handle a DSP bus error interrupt + * @dsp: pointer to DSP structure + * + * The firmware and DSP state will be logged for future analysis. + */ +void cs_dsp_adsp2_bus_error(struct cs_dsp *dsp) +{ + unsigned int val; + struct regmap *regmap = dsp->regmap; + int ret = 0; + + mutex_lock(&dsp->pwr_lock); + + ret = regmap_read(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, &val); + if (ret) { + cs_dsp_err(dsp, + "Failed to read Region Lock Ctrl register: %d\n", ret); + goto error; + } + + if (val & ADSP2_WDT_TIMEOUT_STS_MASK) { + cs_dsp_err(dsp, "watchdog timeout error\n"); + dsp->ops->stop_watchdog(dsp); + if (dsp->client_ops->watchdog_expired) + dsp->client_ops->watchdog_expired(dsp); + } + + if (val & (ADSP2_ADDR_ERR_MASK | ADSP2_REGION_LOCK_ERR_MASK)) { + if (val & ADSP2_ADDR_ERR_MASK) + cs_dsp_err(dsp, "bus error: address error\n"); + else + cs_dsp_err(dsp, "bus error: region lock error\n"); + + ret = regmap_read(regmap, dsp->base + ADSP2_BUS_ERR_ADDR, &val); + if (ret) { + cs_dsp_err(dsp, + "Failed to read Bus Err Addr register: %d\n", + ret); + goto error; + } + + cs_dsp_err(dsp, "bus error address = 0x%x\n", + val & ADSP2_BUS_ERR_ADDR_MASK); + + ret = regmap_read(regmap, + dsp->base + ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR, + &val); + if (ret) { + cs_dsp_err(dsp, + "Failed to read Pmem Xmem Err Addr register: %d\n", + ret); + goto error; + } + + cs_dsp_err(dsp, "xmem error address = 0x%x\n", + val & ADSP2_XMEM_ERR_ADDR_MASK); + cs_dsp_err(dsp, "pmem error address = 0x%x\n", + (val & ADSP2_PMEM_ERR_ADDR_MASK) >> + ADSP2_PMEM_ERR_ADDR_SHIFT); + } + + regmap_update_bits(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, + ADSP2_CTRL_ERR_EINT, ADSP2_CTRL_ERR_EINT); + +error: + mutex_unlock(&dsp->pwr_lock); +} +EXPORT_SYMBOL_GPL(cs_dsp_adsp2_bus_error); + +/** + * cs_dsp_halo_bus_error() - Handle a DSP bus error interrupt + * @dsp: pointer to DSP structure + * + * The firmware and DSP state will be logged for future analysis. + */ +void cs_dsp_halo_bus_error(struct cs_dsp *dsp) +{ + struct regmap *regmap = dsp->regmap; + unsigned int fault[6]; + struct reg_sequence clear[] = { + { dsp->base + HALO_MPU_XM_VIO_STATUS, 0x0 }, + { dsp->base + HALO_MPU_YM_VIO_STATUS, 0x0 }, + { dsp->base + HALO_MPU_PM_VIO_STATUS, 0x0 }, + }; + int ret; + + mutex_lock(&dsp->pwr_lock); + + ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_1, + fault); + if (ret) { + cs_dsp_warn(dsp, "Failed to read AHB DEBUG_1: %d\n", ret); + goto exit_unlock; + } + + cs_dsp_warn(dsp, "AHB: STATUS: 0x%x ADDR: 0x%x\n", + *fault & HALO_AHBM_FLAGS_ERR_MASK, + (*fault & HALO_AHBM_CORE_ERR_ADDR_MASK) >> + HALO_AHBM_CORE_ERR_ADDR_SHIFT); + + ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_0, + fault); + if (ret) { + cs_dsp_warn(dsp, "Failed to read AHB DEBUG_0: %d\n", ret); + goto exit_unlock; + } + + cs_dsp_warn(dsp, "AHB: SYS_ADDR: 0x%x\n", *fault); + + ret = regmap_bulk_read(regmap, dsp->base + HALO_MPU_XM_VIO_ADDR, + fault, ARRAY_SIZE(fault)); + if (ret) { + cs_dsp_warn(dsp, "Failed to read MPU fault info: %d\n", ret); + goto exit_unlock; + } + + cs_dsp_warn(dsp, "XM: STATUS:0x%x ADDR:0x%x\n", fault[1], fault[0]); + cs_dsp_warn(dsp, "YM: STATUS:0x%x ADDR:0x%x\n", fault[3], fault[2]); + cs_dsp_warn(dsp, "PM: STATUS:0x%x ADDR:0x%x\n", fault[5], fault[4]); + + ret = regmap_multi_reg_write(dsp->regmap, clear, ARRAY_SIZE(clear)); + if (ret) + cs_dsp_warn(dsp, "Failed to clear MPU status: %d\n", ret); + +exit_unlock: + mutex_unlock(&dsp->pwr_lock); +} +EXPORT_SYMBOL_GPL(cs_dsp_halo_bus_error); + +/** + * cs_dsp_halo_wdt_expire() - Handle DSP watchdog expiry + * @dsp: pointer to DSP structure + * + * This is logged for future analysis. + */ +void cs_dsp_halo_wdt_expire(struct cs_dsp *dsp) +{ + mutex_lock(&dsp->pwr_lock); + + cs_dsp_warn(dsp, "WDT Expiry Fault\n"); + + dsp->ops->stop_watchdog(dsp); + if (dsp->client_ops->watchdog_expired) + dsp->client_ops->watchdog_expired(dsp); + + mutex_unlock(&dsp->pwr_lock); +} +EXPORT_SYMBOL_GPL(cs_dsp_halo_wdt_expire); + +static const struct cs_dsp_ops cs_dsp_adsp1_ops = { + .validate_version = cs_dsp_validate_version, + .parse_sizes = cs_dsp_adsp1_parse_sizes, + .region_to_reg = cs_dsp_region_to_reg, +}; + +static const struct cs_dsp_ops cs_dsp_adsp2_ops[] = { + { + .parse_sizes = cs_dsp_adsp2_parse_sizes, + .validate_version = cs_dsp_validate_version, + .setup_algs = cs_dsp_adsp2_setup_algs, + .region_to_reg = cs_dsp_region_to_reg, + + .show_fw_status = cs_dsp_adsp2_show_fw_status, + + .enable_memory = cs_dsp_adsp2_enable_memory, + .disable_memory = cs_dsp_adsp2_disable_memory, + + .enable_core = cs_dsp_adsp2_enable_core, + .disable_core = cs_dsp_adsp2_disable_core, + + .start_core = cs_dsp_adsp2_start_core, + .stop_core = cs_dsp_adsp2_stop_core, + + }, + { + .parse_sizes = cs_dsp_adsp2_parse_sizes, + .validate_version = cs_dsp_validate_version, + .setup_algs = cs_dsp_adsp2_setup_algs, + .region_to_reg = cs_dsp_region_to_reg, + + .show_fw_status = cs_dsp_adsp2v2_show_fw_status, + + .enable_memory = cs_dsp_adsp2_enable_memory, + .disable_memory = cs_dsp_adsp2_disable_memory, + .lock_memory = cs_dsp_adsp2_lock, + + .enable_core = cs_dsp_adsp2v2_enable_core, + .disable_core = cs_dsp_adsp2v2_disable_core, + + .start_core = cs_dsp_adsp2_start_core, + .stop_core = cs_dsp_adsp2_stop_core, + }, + { + .parse_sizes = cs_dsp_adsp2_parse_sizes, + .validate_version = cs_dsp_validate_version, + .setup_algs = cs_dsp_adsp2_setup_algs, + .region_to_reg = cs_dsp_region_to_reg, + + .show_fw_status = cs_dsp_adsp2v2_show_fw_status, + .stop_watchdog = cs_dsp_stop_watchdog, + + .enable_memory = cs_dsp_adsp2_enable_memory, + .disable_memory = cs_dsp_adsp2_disable_memory, + .lock_memory = cs_dsp_adsp2_lock, + + .enable_core = cs_dsp_adsp2v2_enable_core, + .disable_core = cs_dsp_adsp2v2_disable_core, + + .start_core = cs_dsp_adsp2_start_core, + .stop_core = cs_dsp_adsp2_stop_core, + }, +}; + +static const struct cs_dsp_ops cs_dsp_halo_ops = { + .parse_sizes = cs_dsp_adsp2_parse_sizes, + .validate_version = cs_dsp_halo_validate_version, + .setup_algs = cs_dsp_halo_setup_algs, + .region_to_reg = cs_dsp_halo_region_to_reg, + + .show_fw_status = cs_dsp_halo_show_fw_status, + .stop_watchdog = cs_dsp_halo_stop_watchdog, + + .lock_memory = cs_dsp_halo_configure_mpu, + + .start_core = cs_dsp_halo_start_core, + .stop_core = cs_dsp_halo_stop_core, +}; + +MODULE_DESCRIPTION("Cirrus Logic DSP Support"); +MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 79b568f82a1c..bfa2ab5772cf 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -199,7 +199,7 @@ config QCOM_WCNSS_CTRL firmware to a newly booted WCNSS chip. config QCOM_APR - tristate "Qualcomm APR Bus (Asynchronous Packet Router)" + tristate "Qualcomm APR/GPR Bus (Asynchronous/Generic Packet Router)" depends on ARCH_QCOM || COMPILE_TEST depends on RPMSG depends on NET diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c index 475a57b435b2..8a9bfbcd4bb9 100644 --- a/drivers/soc/qcom/apr.c +++ b/drivers/soc/qcom/apr.c @@ -15,13 +15,23 @@ #include <linux/rpmsg.h> #include <linux/of.h> -struct apr { +enum { + PR_TYPE_APR = 0, + PR_TYPE_GPR, +}; + +/* Some random values tbh which does not collide with static modules */ +#define GPR_DYNAMIC_PORT_START 0x10000000 +#define GPR_DYNAMIC_PORT_END 0x20000000 + +struct packet_router { struct rpmsg_endpoint *ch; struct device *dev; spinlock_t svcs_lock; spinlock_t rx_lock; struct idr svcs_idr; int dest_domain_id; + int type; struct pdr_handle *pdr; struct workqueue_struct *rxwq; struct work_struct rx_work; @@ -44,26 +54,103 @@ struct apr_rx_buf { */ int apr_send_pkt(struct apr_device *adev, struct apr_pkt *pkt) { - struct apr *apr = dev_get_drvdata(adev->dev.parent); + struct packet_router *apr = dev_get_drvdata(adev->dev.parent); struct apr_hdr *hdr; unsigned long flags; int ret; - spin_lock_irqsave(&adev->lock, flags); + spin_lock_irqsave(&adev->svc.lock, flags); hdr = &pkt->hdr; hdr->src_domain = APR_DOMAIN_APPS; - hdr->src_svc = adev->svc_id; + hdr->src_svc = adev->svc.id; hdr->dest_domain = adev->domain_id; - hdr->dest_svc = adev->svc_id; + hdr->dest_svc = adev->svc.id; ret = rpmsg_trysend(apr->ch, pkt, hdr->pkt_size); - spin_unlock_irqrestore(&adev->lock, flags); + spin_unlock_irqrestore(&adev->svc.lock, flags); return ret ? ret : hdr->pkt_size; } EXPORT_SYMBOL_GPL(apr_send_pkt); +void gpr_free_port(gpr_port_t *port) +{ + struct packet_router *gpr = port->pr; + unsigned long flags; + + spin_lock_irqsave(&gpr->svcs_lock, flags); + idr_remove(&gpr->svcs_idr, port->id); + spin_unlock_irqrestore(&gpr->svcs_lock, flags); + + kfree(port); +} +EXPORT_SYMBOL_GPL(gpr_free_port); + +gpr_port_t *gpr_alloc_port(struct apr_device *gdev, struct device *dev, + gpr_port_cb cb, void *priv) +{ + struct packet_router *pr = dev_get_drvdata(gdev->dev.parent); + gpr_port_t *port; + struct pkt_router_svc *svc; + int id; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + svc = port; + svc->callback = cb; + svc->pr = pr; + svc->priv = priv; + svc->dev = dev; + spin_lock_init(&svc->lock); + + spin_lock(&pr->svcs_lock); + id = idr_alloc_cyclic(&pr->svcs_idr, svc, GPR_DYNAMIC_PORT_START, + GPR_DYNAMIC_PORT_END, GFP_ATOMIC); + if (id < 0) { + dev_err(dev, "Unable to allocate dynamic GPR src port\n"); + kfree(port); + spin_unlock(&pr->svcs_lock); + return ERR_PTR(id); + } + + svc->id = id; + spin_unlock(&pr->svcs_lock); + + return port; +} +EXPORT_SYMBOL_GPL(gpr_alloc_port); + +static int pkt_router_send_svc_pkt(struct pkt_router_svc *svc, struct gpr_pkt *pkt) +{ + struct packet_router *pr = svc->pr; + struct gpr_hdr *hdr; + unsigned long flags; + int ret; + + hdr = &pkt->hdr; + + spin_lock_irqsave(&svc->lock, flags); + ret = rpmsg_trysend(pr->ch, pkt, hdr->pkt_size); + spin_unlock_irqrestore(&svc->lock, flags); + + return ret ? ret : hdr->pkt_size; +} + +int gpr_send_pkt(struct apr_device *gdev, struct gpr_pkt *pkt) +{ + return pkt_router_send_svc_pkt(&gdev->svc, pkt); +} +EXPORT_SYMBOL_GPL(gpr_send_pkt); + +int gpr_send_port_pkt(gpr_port_t *port, struct gpr_pkt *pkt) +{ + return pkt_router_send_svc_pkt(port, pkt); +} +EXPORT_SYMBOL_GPL(gpr_send_port_pkt); + static void apr_dev_release(struct device *dev) { struct apr_device *adev = to_apr_device(dev); @@ -74,7 +161,7 @@ static void apr_dev_release(struct device *dev) static int apr_callback(struct rpmsg_device *rpdev, void *buf, int len, void *priv, u32 addr) { - struct apr *apr = dev_get_drvdata(&rpdev->dev); + struct packet_router *apr = dev_get_drvdata(&rpdev->dev); struct apr_rx_buf *abuf; unsigned long flags; @@ -100,11 +187,11 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf, return 0; } - -static int apr_do_rx_callback(struct apr *apr, struct apr_rx_buf *abuf) +static int apr_do_rx_callback(struct packet_router *apr, struct apr_rx_buf *abuf) { uint16_t hdr_size, msg_type, ver, svc_id; - struct apr_device *svc = NULL; + struct pkt_router_svc *svc; + struct apr_device *adev; struct apr_driver *adrv = NULL; struct apr_resp_pkt resp; struct apr_hdr *hdr; @@ -145,12 +232,15 @@ static int apr_do_rx_callback(struct apr *apr, struct apr_rx_buf *abuf) svc_id = hdr->dest_svc; spin_lock_irqsave(&apr->svcs_lock, flags); svc = idr_find(&apr->svcs_idr, svc_id); - if (svc && svc->dev.driver) - adrv = to_apr_driver(svc->dev.driver); + if (svc && svc->dev->driver) { + adev = svc_to_apr_device(svc); + adrv = to_apr_driver(adev->dev.driver); + } spin_unlock_irqrestore(&apr->svcs_lock, flags); - if (!adrv) { - dev_err(apr->dev, "APR: service is not registered\n"); + if (!adrv || !adev) { + dev_err(apr->dev, "APR: service is not registered (%d)\n", + svc_id); return -EINVAL; } @@ -164,20 +254,82 @@ static int apr_do_rx_callback(struct apr *apr, struct apr_rx_buf *abuf) if (resp.payload_size > 0) resp.payload = buf + hdr_size; - adrv->callback(svc, &resp); + adrv->callback(adev, &resp); + + return 0; +} + +static int gpr_do_rx_callback(struct packet_router *gpr, struct apr_rx_buf *abuf) +{ + uint16_t hdr_size, ver; + struct pkt_router_svc *svc = NULL; + struct gpr_resp_pkt resp; + struct gpr_hdr *hdr; + unsigned long flags; + void *buf = abuf->buf; + int len = abuf->len; + + hdr = buf; + ver = hdr->version; + if (ver > GPR_PKT_VER + 1) + return -EINVAL; + + hdr_size = hdr->hdr_size; + if (hdr_size < GPR_PKT_HEADER_WORD_SIZE) { + dev_err(gpr->dev, "GPR: Wrong hdr size:%d\n", hdr_size); + return -EINVAL; + } + + if (hdr->pkt_size < GPR_PKT_HEADER_BYTE_SIZE || hdr->pkt_size != len) { + dev_err(gpr->dev, "GPR: Wrong packet size\n"); + return -EINVAL; + } + + resp.hdr = *hdr; + resp.payload_size = hdr->pkt_size - (hdr_size * 4); + + /* + * NOTE: hdr_size is not same as GPR_HDR_SIZE as remote can include + * optional headers in to gpr_hdr which should be ignored + */ + if (resp.payload_size > 0) + resp.payload = buf + (hdr_size * 4); + + + spin_lock_irqsave(&gpr->svcs_lock, flags); + svc = idr_find(&gpr->svcs_idr, hdr->dest_port); + spin_unlock_irqrestore(&gpr->svcs_lock, flags); + + if (!svc) { + dev_err(gpr->dev, "GPR: Port(%x) is not registered\n", + hdr->dest_port); + return -EINVAL; + } + + if (svc->callback) + svc->callback(&resp, svc->priv, 0); return 0; } static void apr_rxwq(struct work_struct *work) { - struct apr *apr = container_of(work, struct apr, rx_work); + struct packet_router *apr = container_of(work, struct packet_router, rx_work); struct apr_rx_buf *abuf, *b; unsigned long flags; if (!list_empty(&apr->rx_list)) { list_for_each_entry_safe(abuf, b, &apr->rx_list, node) { - apr_do_rx_callback(apr, abuf); + switch (apr->type) { + case PR_TYPE_APR: + apr_do_rx_callback(apr, abuf); + break; + case PR_TYPE_GPR: + gpr_do_rx_callback(apr, abuf); + break; + default: + break; + } spin_lock_irqsave(&apr->rx_lock, flags); list_del(&abuf->node); spin_unlock_irqrestore(&apr->rx_lock, flags); @@ -201,7 +353,7 @@ static int apr_device_match(struct device *dev, struct device_driver *drv) while (id->domain_id != 0 || id->svc_id != 0) { if (id->domain_id == adev->domain_id && - id->svc_id == adev->svc_id) + id->svc_id == adev->svc.id) return 1; id++; } @@ -213,22 +365,27 @@ static int apr_device_probe(struct device *dev) { struct apr_device *adev = to_apr_device(dev); struct apr_driver *adrv = to_apr_driver(dev->driver); + int ret; - return adrv->probe(adev); + ret = adrv->probe(adev); + if (!ret) + adev->svc.callback = adrv->gpr_callback; + + return ret; } static void apr_device_remove(struct device *dev) { struct apr_device *adev = to_apr_device(dev); struct apr_driver *adrv; - struct apr *apr = dev_get_drvdata(adev->dev.parent); + struct packet_router *apr = dev_get_drvdata(adev->dev.parent); if (dev->driver) { adrv = to_apr_driver(dev->driver); if (adrv->remove) adrv->remove(adev); spin_lock(&apr->svcs_lock); - idr_remove(&apr->svcs_idr, adev->svc_id); + idr_remove(&apr->svcs_idr, adev->svc.id); spin_unlock(&apr->svcs_lock); } } @@ -255,28 +412,43 @@ struct bus_type aprbus = { EXPORT_SYMBOL_GPL(aprbus); static int apr_add_device(struct device *dev, struct device_node *np, - const struct apr_device_id *id) + u32 svc_id, u32 domain_id) { - struct apr *apr = dev_get_drvdata(dev); + struct packet_router *apr = dev_get_drvdata(dev); struct apr_device *adev = NULL; + struct pkt_router_svc *svc; int ret; adev = kzalloc(sizeof(*adev), GFP_KERNEL); if (!adev) return -ENOMEM; - spin_lock_init(&adev->lock); + adev->svc_id = svc_id; + svc = &adev->svc; + + svc->id = svc_id; + svc->pr = apr; + svc->priv = adev; + svc->dev = dev; + spin_lock_init(&svc->lock); + + adev->domain_id = domain_id; - adev->svc_id = id->svc_id; - adev->domain_id = id->domain_id; - adev->version = id->svc_version; if (np) snprintf(adev->name, APR_NAME_SIZE, "%pOFn", np); - else - strscpy(adev->name, id->name, APR_NAME_SIZE); - dev_set_name(&adev->dev, "aprsvc:%s:%x:%x", adev->name, - id->domain_id, id->svc_id); + switch (apr->type) { + case PR_TYPE_APR: + dev_set_name(&adev->dev, "aprsvc:%s:%x:%x", adev->name, + domain_id, svc_id); + break; + case PR_TYPE_GPR: + dev_set_name(&adev->dev, "gprsvc:%s:%x:%x", adev->name, + domain_id, svc_id); + break; + default: + break; + } adev->dev.bus = &aprbus; adev->dev.parent = dev; @@ -285,14 +457,13 @@ static int apr_add_device(struct device *dev, struct device_node *np, adev->dev.driver = NULL; spin_lock(&apr->svcs_lock); - idr_alloc(&apr->svcs_idr, adev, id->svc_id, - id->svc_id + 1, GFP_ATOMIC); + idr_alloc(&apr->svcs_idr, svc, svc_id, svc_id + 1, GFP_ATOMIC); spin_unlock(&apr->svcs_lock); of_property_read_string_index(np, "qcom,protection-domain", 1, &adev->service_path); - dev_info(dev, "Adding APR dev: %s\n", dev_name(&adev->dev)); + dev_info(dev, "Adding APR/GPR dev: %s\n", dev_name(&adev->dev)); ret = device_register(&adev->dev); if (ret) { @@ -306,7 +477,7 @@ static int apr_add_device(struct device *dev, struct device_node *np, static int of_apr_add_pd_lookups(struct device *dev) { const char *service_name, *service_path; - struct apr *apr = dev_get_drvdata(dev); + struct packet_router *apr = dev_get_drvdata(dev); struct device_node *node; struct pdr_service *pds; int ret; @@ -336,13 +507,14 @@ static int of_apr_add_pd_lookups(struct device *dev) static void of_register_apr_devices(struct device *dev, const char *svc_path) { - struct apr *apr = dev_get_drvdata(dev); + struct packet_router *apr = dev_get_drvdata(dev); struct device_node *node; const char *service_path; int ret; for_each_child_of_node(dev->of_node, node) { - struct apr_device_id id = { {0} }; + u32 svc_id; + u32 domain_id; /* * This function is called with svc_path NULL during @@ -372,13 +544,13 @@ static void of_register_apr_devices(struct device *dev, const char *svc_path) continue; } - if (of_property_read_u32(node, "reg", &id.svc_id)) + if (of_property_read_u32(node, "reg", &svc_id)) continue; - id.domain_id = apr->dest_domain_id; + domain_id = apr->dest_domain_id; - if (apr_add_device(dev, node, &id)) - dev_err(dev, "Failed to add apr %d svc\n", id.svc_id); + if (apr_add_device(dev, node, svc_id, domain_id)) + dev_err(dev, "Failed to add apr %d svc\n", svc_id); } } @@ -398,7 +570,7 @@ static int apr_remove_device(struct device *dev, void *svc_path) static void apr_pd_status(int state, char *svc_path, void *priv) { - struct apr *apr = (struct apr *)priv; + struct packet_router *apr = (struct packet_router *)priv; switch (state) { case SERVREG_SERVICE_STATE_UP: @@ -413,16 +585,26 @@ static void apr_pd_status(int state, char *svc_path, void *priv) static int apr_probe(struct rpmsg_device *rpdev) { struct device *dev = &rpdev->dev; - struct apr *apr; + struct packet_router *apr; int ret; apr = devm_kzalloc(dev, sizeof(*apr), GFP_KERNEL); if (!apr) return -ENOMEM; - ret = of_property_read_u32(dev->of_node, "qcom,apr-domain", &apr->dest_domain_id); + ret = of_property_read_u32(dev->of_node, "qcom,domain", &apr->dest_domain_id); + + if (of_device_is_compatible(dev->of_node, "qcom,gpr")) { + apr->type = PR_TYPE_GPR; + } else { + if (ret) /* try deprecated apr-domain property */ + ret = of_property_read_u32(dev->of_node, "qcom,apr-domain", + &apr->dest_domain_id); + apr->type = PR_TYPE_APR; + } + if (ret) { - dev_err(dev, "APR Domain ID not specified in DT\n"); + dev_err(dev, "Domain ID not specified in DT\n"); return ret; } @@ -465,7 +647,7 @@ destroy_wq: static void apr_remove(struct rpmsg_device *rpdev) { - struct apr *apr = dev_get_drvdata(&rpdev->dev); + struct packet_router *apr = dev_get_drvdata(&rpdev->dev); pdr_handle_release(apr->pdr); device_for_each_child(&rpdev->dev, NULL, apr_remove_device); @@ -502,20 +684,21 @@ void apr_driver_unregister(struct apr_driver *drv) } EXPORT_SYMBOL_GPL(apr_driver_unregister); -static const struct of_device_id apr_of_match[] = { +static const struct of_device_id pkt_router_of_match[] = { { .compatible = "qcom,apr"}, { .compatible = "qcom,apr-v2"}, + { .compatible = "qcom,gpr"}, {} }; -MODULE_DEVICE_TABLE(of, apr_of_match); +MODULE_DEVICE_TABLE(of, pkt_router_of_match); -static struct rpmsg_driver apr_driver = { +static struct rpmsg_driver packet_router_driver = { .probe = apr_probe, .remove = apr_remove, .callback = apr_callback, .drv = { .name = "qcom,apr", - .of_match_table = apr_of_match, + .of_match_table = pkt_router_of_match, }, }; @@ -525,7 +708,7 @@ static int __init apr_init(void) ret = bus_register(&aprbus); if (!ret) - ret = register_rpmsg_driver(&apr_driver); + ret = register_rpmsg_driver(&packet_router_driver); else bus_unregister(&aprbus); @@ -535,7 +718,7 @@ static int __init apr_init(void) static void __exit apr_exit(void) { bus_unregister(&aprbus); - unregister_rpmsg_driver(&apr_driver); + unregister_rpmsg_driver(&packet_router_driver); } subsys_initcall(apr_init); diff --git a/include/dt-bindings/soc/qcom,gpr.h b/include/dt-bindings/soc/qcom,gpr.h new file mode 100644 index 000000000000..3107da59319c --- /dev/null +++ b/include/dt-bindings/soc/qcom,gpr.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#ifndef __DT_BINDINGS_QCOM_GPR_H +#define __DT_BINDINGS_QCOM_GPR_H + +/* DOMAINS */ + +#define GPR_DOMAIN_ID_MODEM 1 +#define GPR_DOMAIN_ID_ADSP 2 +#define GPR_DOMAIN_ID_APPS 3 + +/* Static Services */ + +#define GPR_APM_MODULE_IID 1 +#define GPR_PRM_MODULE_IID 2 +#define GPR_AMDB_MODULE_IID 3 +#define GPR_VCPM_MODULE_IID 4 + +#endif /* __DT_BINDINGS_QCOM_GPR_H */ diff --git a/include/dt-bindings/sound/qcom,lpass.h b/include/dt-bindings/sound/qcom,lpass.h index 7b0b80b38699..a9404c3b8884 100644 --- a/include/dt-bindings/sound/qcom,lpass.h +++ b/include/dt-bindings/sound/qcom,lpass.h @@ -10,6 +10,37 @@ #define LPASS_DP_RX 5 +#define LPASS_CDC_DMA_RX0 6 +#define LPASS_CDC_DMA_RX1 7 +#define LPASS_CDC_DMA_RX2 8 +#define LPASS_CDC_DMA_RX3 9 +#define LPASS_CDC_DMA_RX4 10 +#define LPASS_CDC_DMA_RX5 11 +#define LPASS_CDC_DMA_RX6 12 +#define LPASS_CDC_DMA_RX7 13 +#define LPASS_CDC_DMA_RX8 14 +#define LPASS_CDC_DMA_RX9 15 + +#define LPASS_CDC_DMA_TX0 16 +#define LPASS_CDC_DMA_TX1 17 +#define LPASS_CDC_DMA_TX2 18 +#define LPASS_CDC_DMA_TX3 19 +#define LPASS_CDC_DMA_TX4 20 +#define LPASS_CDC_DMA_TX5 21 +#define LPASS_CDC_DMA_TX6 22 +#define LPASS_CDC_DMA_TX7 23 +#define LPASS_CDC_DMA_TX8 24 + +#define LPASS_CDC_DMA_VA_TX0 25 +#define LPASS_CDC_DMA_VA_TX1 26 +#define LPASS_CDC_DMA_VA_TX2 27 +#define LPASS_CDC_DMA_VA_TX3 28 +#define LPASS_CDC_DMA_VA_TX4 29 +#define LPASS_CDC_DMA_VA_TX5 30 +#define LPASS_CDC_DMA_VA_TX6 31 +#define LPASS_CDC_DMA_VA_TX7 32 +#define LPASS_CDC_DMA_VA_TX8 33 + #define LPASS_MCLK0 0 #endif /* __DT_QCOM_LPASS_H */ diff --git a/include/dt-bindings/sound/qcom,q6afe.h b/include/dt-bindings/sound/qcom,q6afe.h index 66c21ab03eef..9d5d89cfabcf 100644 --- a/include/dt-bindings/sound/qcom,q6afe.h +++ b/include/dt-bindings/sound/qcom,q6afe.h @@ -2,207 +2,8 @@ #ifndef __DT_BINDINGS_Q6_AFE_H__ #define __DT_BINDINGS_Q6_AFE_H__ -/* Audio Front End (AFE) virtual ports IDs */ -#define HDMI_RX 1 -#define SLIMBUS_0_RX 2 -#define SLIMBUS_0_TX 3 -#define SLIMBUS_1_RX 4 -#define SLIMBUS_1_TX 5 -#define SLIMBUS_2_RX 6 -#define SLIMBUS_2_TX 7 -#define SLIMBUS_3_RX 8 -#define SLIMBUS_3_TX 9 -#define SLIMBUS_4_RX 10 -#define SLIMBUS_4_TX 11 -#define SLIMBUS_5_RX 12 -#define SLIMBUS_5_TX 13 -#define SLIMBUS_6_RX 14 -#define SLIMBUS_6_TX 15 -#define PRIMARY_MI2S_RX 16 -#define PRIMARY_MI2S_TX 17 -#define SECONDARY_MI2S_RX 18 -#define SECONDARY_MI2S_TX 19 -#define TERTIARY_MI2S_RX 20 -#define TERTIARY_MI2S_TX 21 -#define QUATERNARY_MI2S_RX 22 -#define QUATERNARY_MI2S_TX 23 -#define PRIMARY_TDM_RX_0 24 -#define PRIMARY_TDM_TX_0 25 -#define PRIMARY_TDM_RX_1 26 -#define PRIMARY_TDM_TX_1 27 -#define PRIMARY_TDM_RX_2 28 -#define PRIMARY_TDM_TX_2 29 -#define PRIMARY_TDM_RX_3 30 -#define PRIMARY_TDM_TX_3 31 -#define PRIMARY_TDM_RX_4 32 -#define PRIMARY_TDM_TX_4 33 -#define PRIMARY_TDM_RX_5 34 -#define PRIMARY_TDM_TX_5 35 -#define PRIMARY_TDM_RX_6 36 -#define PRIMARY_TDM_TX_6 37 -#define PRIMARY_TDM_RX_7 38 -#define PRIMARY_TDM_TX_7 39 -#define SECONDARY_TDM_RX_0 40 -#define SECONDARY_TDM_TX_0 41 -#define SECONDARY_TDM_RX_1 42 -#define SECONDARY_TDM_TX_1 43 -#define SECONDARY_TDM_RX_2 44 -#define SECONDARY_TDM_TX_2 45 -#define SECONDARY_TDM_RX_3 46 -#define SECONDARY_TDM_TX_3 47 -#define SECONDARY_TDM_RX_4 48 -#define SECONDARY_TDM_TX_4 49 -#define SECONDARY_TDM_RX_5 50 -#define SECONDARY_TDM_TX_5 51 -#define SECONDARY_TDM_RX_6 52 -#define SECONDARY_TDM_TX_6 53 -#define SECONDARY_TDM_RX_7 54 -#define SECONDARY_TDM_TX_7 55 -#define TERTIARY_TDM_RX_0 56 -#define TERTIARY_TDM_TX_0 57 -#define TERTIARY_TDM_RX_1 58 -#define TERTIARY_TDM_TX_1 59 -#define TERTIARY_TDM_RX_2 60 -#define TERTIARY_TDM_TX_2 61 -#define TERTIARY_TDM_RX_3 62 -#define TERTIARY_TDM_TX_3 63 -#define TERTIARY_TDM_RX_4 64 -#define TERTIARY_TDM_TX_4 65 -#define TERTIARY_TDM_RX_5 66 -#define TERTIARY_TDM_TX_5 67 -#define TERTIARY_TDM_RX_6 68 -#define TERTIARY_TDM_TX_6 69 -#define TERTIARY_TDM_RX_7 70 -#define TERTIARY_TDM_TX_7 71 -#define QUATERNARY_TDM_RX_0 72 -#define QUATERNARY_TDM_TX_0 73 -#define QUATERNARY_TDM_RX_1 74 -#define QUATERNARY_TDM_TX_1 75 -#define QUATERNARY_TDM_RX_2 76 -#define QUATERNARY_TDM_TX_2 77 -#define QUATERNARY_TDM_RX_3 78 -#define QUATERNARY_TDM_TX_3 79 -#define QUATERNARY_TDM_RX_4 80 -#define QUATERNARY_TDM_TX_4 81 -#define QUATERNARY_TDM_RX_5 82 -#define QUATERNARY_TDM_TX_5 83 -#define QUATERNARY_TDM_RX_6 84 -#define QUATERNARY_TDM_TX_6 85 -#define QUATERNARY_TDM_RX_7 86 -#define QUATERNARY_TDM_TX_7 87 -#define QUINARY_TDM_RX_0 88 -#define QUINARY_TDM_TX_0 89 -#define QUINARY_TDM_RX_1 90 -#define QUINARY_TDM_TX_1 91 -#define QUINARY_TDM_RX_2 92 -#define QUINARY_TDM_TX_2 93 -#define QUINARY_TDM_RX_3 94 -#define QUINARY_TDM_TX_3 95 -#define QUINARY_TDM_RX_4 96 -#define QUINARY_TDM_TX_4 97 -#define QUINARY_TDM_RX_5 98 -#define QUINARY_TDM_TX_5 99 -#define QUINARY_TDM_RX_6 100 -#define QUINARY_TDM_TX_6 101 -#define QUINARY_TDM_RX_7 102 -#define QUINARY_TDM_TX_7 103 -#define DISPLAY_PORT_RX 104 -#define WSA_CODEC_DMA_RX_0 105 -#define WSA_CODEC_DMA_TX_0 106 -#define WSA_CODEC_DMA_RX_1 107 -#define WSA_CODEC_DMA_TX_1 108 -#define WSA_CODEC_DMA_TX_2 109 -#define VA_CODEC_DMA_TX_0 110 -#define VA_CODEC_DMA_TX_1 111 -#define VA_CODEC_DMA_TX_2 112 -#define RX_CODEC_DMA_RX_0 113 -#define TX_CODEC_DMA_TX_0 114 -#define RX_CODEC_DMA_RX_1 115 -#define TX_CODEC_DMA_TX_1 116 -#define RX_CODEC_DMA_RX_2 117 -#define TX_CODEC_DMA_TX_2 118 -#define RX_CODEC_DMA_RX_3 119 -#define TX_CODEC_DMA_TX_3 120 -#define RX_CODEC_DMA_RX_4 121 -#define TX_CODEC_DMA_TX_4 122 -#define RX_CODEC_DMA_RX_5 123 -#define TX_CODEC_DMA_TX_5 124 -#define RX_CODEC_DMA_RX_6 125 -#define RX_CODEC_DMA_RX_7 126 -#define QUINARY_MI2S_RX 127 -#define QUINARY_MI2S_TX 128 +/* This file exists due to backward compatibility reasons, Please do not DELETE! */ -#define LPASS_CLK_ID_PRI_MI2S_IBIT 1 -#define LPASS_CLK_ID_PRI_MI2S_EBIT 2 -#define LPASS_CLK_ID_SEC_MI2S_IBIT 3 -#define LPASS_CLK_ID_SEC_MI2S_EBIT 4 -#define LPASS_CLK_ID_TER_MI2S_IBIT 5 -#define LPASS_CLK_ID_TER_MI2S_EBIT 6 -#define LPASS_CLK_ID_QUAD_MI2S_IBIT 7 -#define LPASS_CLK_ID_QUAD_MI2S_EBIT 8 -#define LPASS_CLK_ID_SPEAKER_I2S_IBIT 9 -#define LPASS_CLK_ID_SPEAKER_I2S_EBIT 10 -#define LPASS_CLK_ID_SPEAKER_I2S_OSR 11 -#define LPASS_CLK_ID_QUI_MI2S_IBIT 12 -#define LPASS_CLK_ID_QUI_MI2S_EBIT 13 -#define LPASS_CLK_ID_SEN_MI2S_IBIT 14 -#define LPASS_CLK_ID_SEN_MI2S_EBIT 15 -#define LPASS_CLK_ID_INT0_MI2S_IBIT 16 -#define LPASS_CLK_ID_INT1_MI2S_IBIT 17 -#define LPASS_CLK_ID_INT2_MI2S_IBIT 18 -#define LPASS_CLK_ID_INT3_MI2S_IBIT 19 -#define LPASS_CLK_ID_INT4_MI2S_IBIT 20 -#define LPASS_CLK_ID_INT5_MI2S_IBIT 21 -#define LPASS_CLK_ID_INT6_MI2S_IBIT 22 -#define LPASS_CLK_ID_QUI_MI2S_OSR 23 -#define LPASS_CLK_ID_PRI_PCM_IBIT 24 -#define LPASS_CLK_ID_PRI_PCM_EBIT 25 -#define LPASS_CLK_ID_SEC_PCM_IBIT 26 -#define LPASS_CLK_ID_SEC_PCM_EBIT 27 -#define LPASS_CLK_ID_TER_PCM_IBIT 28 -#define LPASS_CLK_ID_TER_PCM_EBIT 29 -#define LPASS_CLK_ID_QUAD_PCM_IBIT 30 -#define LPASS_CLK_ID_QUAD_PCM_EBIT 31 -#define LPASS_CLK_ID_QUIN_PCM_IBIT 32 -#define LPASS_CLK_ID_QUIN_PCM_EBIT 33 -#define LPASS_CLK_ID_QUI_PCM_OSR 34 -#define LPASS_CLK_ID_PRI_TDM_IBIT 35 -#define LPASS_CLK_ID_PRI_TDM_EBIT 36 -#define LPASS_CLK_ID_SEC_TDM_IBIT 37 -#define LPASS_CLK_ID_SEC_TDM_EBIT 38 -#define LPASS_CLK_ID_TER_TDM_IBIT 39 -#define LPASS_CLK_ID_TER_TDM_EBIT 40 -#define LPASS_CLK_ID_QUAD_TDM_IBIT 41 -#define LPASS_CLK_ID_QUAD_TDM_EBIT 42 -#define LPASS_CLK_ID_QUIN_TDM_IBIT 43 -#define LPASS_CLK_ID_QUIN_TDM_EBIT 44 -#define LPASS_CLK_ID_QUIN_TDM_OSR 45 -#define LPASS_CLK_ID_MCLK_1 46 -#define LPASS_CLK_ID_MCLK_2 47 -#define LPASS_CLK_ID_MCLK_3 48 -#define LPASS_CLK_ID_MCLK_4 49 -#define LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE 50 -#define LPASS_CLK_ID_INT_MCLK_0 51 -#define LPASS_CLK_ID_INT_MCLK_1 52 -#define LPASS_CLK_ID_MCLK_5 53 -#define LPASS_CLK_ID_WSA_CORE_MCLK 54 -#define LPASS_CLK_ID_WSA_CORE_NPL_MCLK 55 -#define LPASS_CLK_ID_VA_CORE_MCLK 56 -#define LPASS_CLK_ID_TX_CORE_MCLK 57 -#define LPASS_CLK_ID_TX_CORE_NPL_MCLK 58 -#define LPASS_CLK_ID_RX_CORE_MCLK 59 -#define LPASS_CLK_ID_RX_CORE_NPL_MCLK 60 -#define LPASS_CLK_ID_VA_CORE_2X_MCLK 61 - -#define LPASS_HW_AVTIMER_VOTE 101 -#define LPASS_HW_MACRO_VOTE 102 -#define LPASS_HW_DCODEC_VOTE 103 - -#define Q6AFE_MAX_CLK_ID 104 - -#define LPASS_CLK_ATTRIBUTE_INVALID 0x0 -#define LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 -#define LPASS_CLK_ATTRIBUTE_COUPLE_DIVIDEND 0x2 -#define LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR 0x3 +#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h> #endif /* __DT_BINDINGS_Q6_AFE_H__ */ diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h new file mode 100644 index 000000000000..0d3276c8fc11 --- /dev/null +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DT_BINDINGS_Q6_AUDIO_PORTS_H__ +#define __DT_BINDINGS_Q6_AUDIO_PORTS_H__ + +/* LPASS Audio virtual ports IDs */ +#define HDMI_RX 1 +#define SLIMBUS_0_RX 2 +#define SLIMBUS_0_TX 3 +#define SLIMBUS_1_RX 4 +#define SLIMBUS_1_TX 5 +#define SLIMBUS_2_RX 6 +#define SLIMBUS_2_TX 7 +#define SLIMBUS_3_RX 8 +#define SLIMBUS_3_TX 9 +#define SLIMBUS_4_RX 10 +#define SLIMBUS_4_TX 11 +#define SLIMBUS_5_RX 12 +#define SLIMBUS_5_TX 13 +#define SLIMBUS_6_RX 14 +#define SLIMBUS_6_TX 15 +#define PRIMARY_MI2S_RX 16 +#define PRIMARY_MI2S_TX 17 +#define SECONDARY_MI2S_RX 18 +#define SECONDARY_MI2S_TX 19 +#define TERTIARY_MI2S_RX 20 +#define TERTIARY_MI2S_TX 21 +#define QUATERNARY_MI2S_RX 22 +#define QUATERNARY_MI2S_TX 23 +#define PRIMARY_TDM_RX_0 24 +#define PRIMARY_TDM_TX_0 25 +#define PRIMARY_TDM_RX_1 26 +#define PRIMARY_TDM_TX_1 27 +#define PRIMARY_TDM_RX_2 28 +#define PRIMARY_TDM_TX_2 29 +#define PRIMARY_TDM_RX_3 30 +#define PRIMARY_TDM_TX_3 31 +#define PRIMARY_TDM_RX_4 32 +#define PRIMARY_TDM_TX_4 33 +#define PRIMARY_TDM_RX_5 34 +#define PRIMARY_TDM_TX_5 35 +#define PRIMARY_TDM_RX_6 36 +#define PRIMARY_TDM_TX_6 37 +#define PRIMARY_TDM_RX_7 38 +#define PRIMARY_TDM_TX_7 39 +#define SECONDARY_TDM_RX_0 40 +#define SECONDARY_TDM_TX_0 41 +#define SECONDARY_TDM_RX_1 42 +#define SECONDARY_TDM_TX_1 43 +#define SECONDARY_TDM_RX_2 44 +#define SECONDARY_TDM_TX_2 45 +#define SECONDARY_TDM_RX_3 46 +#define SECONDARY_TDM_TX_3 47 +#define SECONDARY_TDM_RX_4 48 +#define SECONDARY_TDM_TX_4 49 +#define SECONDARY_TDM_RX_5 50 +#define SECONDARY_TDM_TX_5 51 +#define SECONDARY_TDM_RX_6 52 +#define SECONDARY_TDM_TX_6 53 +#define SECONDARY_TDM_RX_7 54 +#define SECONDARY_TDM_TX_7 55 +#define TERTIARY_TDM_RX_0 56 +#define TERTIARY_TDM_TX_0 57 +#define TERTIARY_TDM_RX_1 58 +#define TERTIARY_TDM_TX_1 59 +#define TERTIARY_TDM_RX_2 60 +#define TERTIARY_TDM_TX_2 61 +#define TERTIARY_TDM_RX_3 62 +#define TERTIARY_TDM_TX_3 63 +#define TERTIARY_TDM_RX_4 64 +#define TERTIARY_TDM_TX_4 65 +#define TERTIARY_TDM_RX_5 66 +#define TERTIARY_TDM_TX_5 67 +#define TERTIARY_TDM_RX_6 68 +#define TERTIARY_TDM_TX_6 69 +#define TERTIARY_TDM_RX_7 70 +#define TERTIARY_TDM_TX_7 71 +#define QUATERNARY_TDM_RX_0 72 +#define QUATERNARY_TDM_TX_0 73 +#define QUATERNARY_TDM_RX_1 74 +#define QUATERNARY_TDM_TX_1 75 +#define QUATERNARY_TDM_RX_2 76 +#define QUATERNARY_TDM_TX_2 77 +#define QUATERNARY_TDM_RX_3 78 +#define QUATERNARY_TDM_TX_3 79 +#define QUATERNARY_TDM_RX_4 80 +#define QUATERNARY_TDM_TX_4 81 +#define QUATERNARY_TDM_RX_5 82 +#define QUATERNARY_TDM_TX_5 83 +#define QUATERNARY_TDM_RX_6 84 +#define QUATERNARY_TDM_TX_6 85 +#define QUATERNARY_TDM_RX_7 86 +#define QUATERNARY_TDM_TX_7 87 +#define QUINARY_TDM_RX_0 88 +#define QUINARY_TDM_TX_0 89 +#define QUINARY_TDM_RX_1 90 +#define QUINARY_TDM_TX_1 91 +#define QUINARY_TDM_RX_2 92 +#define QUINARY_TDM_TX_2 93 +#define QUINARY_TDM_RX_3 94 +#define QUINARY_TDM_TX_3 95 +#define QUINARY_TDM_RX_4 96 +#define QUINARY_TDM_TX_4 97 +#define QUINARY_TDM_RX_5 98 +#define QUINARY_TDM_TX_5 99 +#define QUINARY_TDM_RX_6 100 +#define QUINARY_TDM_TX_6 101 +#define QUINARY_TDM_RX_7 102 +#define QUINARY_TDM_TX_7 103 +#define DISPLAY_PORT_RX 104 +#define WSA_CODEC_DMA_RX_0 105 +#define WSA_CODEC_DMA_TX_0 106 +#define WSA_CODEC_DMA_RX_1 107 +#define WSA_CODEC_DMA_TX_1 108 +#define WSA_CODEC_DMA_TX_2 109 +#define VA_CODEC_DMA_TX_0 110 +#define VA_CODEC_DMA_TX_1 111 +#define VA_CODEC_DMA_TX_2 112 +#define RX_CODEC_DMA_RX_0 113 +#define TX_CODEC_DMA_TX_0 114 +#define RX_CODEC_DMA_RX_1 115 +#define TX_CODEC_DMA_TX_1 116 +#define RX_CODEC_DMA_RX_2 117 +#define TX_CODEC_DMA_TX_2 118 +#define RX_CODEC_DMA_RX_3 119 +#define TX_CODEC_DMA_TX_3 120 +#define RX_CODEC_DMA_RX_4 121 +#define TX_CODEC_DMA_TX_4 122 +#define RX_CODEC_DMA_RX_5 123 +#define TX_CODEC_DMA_TX_5 124 +#define RX_CODEC_DMA_RX_6 125 +#define RX_CODEC_DMA_RX_7 126 +#define QUINARY_MI2S_RX 127 +#define QUINARY_MI2S_TX 128 + +#define LPASS_CLK_ID_PRI_MI2S_IBIT 1 +#define LPASS_CLK_ID_PRI_MI2S_EBIT 2 +#define LPASS_CLK_ID_SEC_MI2S_IBIT 3 +#define LPASS_CLK_ID_SEC_MI2S_EBIT 4 +#define LPASS_CLK_ID_TER_MI2S_IBIT 5 +#define LPASS_CLK_ID_TER_MI2S_EBIT 6 +#define LPASS_CLK_ID_QUAD_MI2S_IBIT 7 +#define LPASS_CLK_ID_QUAD_MI2S_EBIT 8 +#define LPASS_CLK_ID_SPEAKER_I2S_IBIT 9 +#define LPASS_CLK_ID_SPEAKER_I2S_EBIT 10 +#define LPASS_CLK_ID_SPEAKER_I2S_OSR 11 +#define LPASS_CLK_ID_QUI_MI2S_IBIT 12 +#define LPASS_CLK_ID_QUI_MI2S_EBIT 13 +#define LPASS_CLK_ID_SEN_MI2S_IBIT 14 +#define LPASS_CLK_ID_SEN_MI2S_EBIT 15 +#define LPASS_CLK_ID_INT0_MI2S_IBIT 16 +#define LPASS_CLK_ID_INT1_MI2S_IBIT 17 +#define LPASS_CLK_ID_INT2_MI2S_IBIT 18 +#define LPASS_CLK_ID_INT3_MI2S_IBIT 19 +#define LPASS_CLK_ID_INT4_MI2S_IBIT 20 +#define LPASS_CLK_ID_INT5_MI2S_IBIT 21 +#define LPASS_CLK_ID_INT6_MI2S_IBIT 22 +#define LPASS_CLK_ID_QUI_MI2S_OSR 23 +#define LPASS_CLK_ID_PRI_PCM_IBIT 24 +#define LPASS_CLK_ID_PRI_PCM_EBIT 25 +#define LPASS_CLK_ID_SEC_PCM_IBIT 26 +#define LPASS_CLK_ID_SEC_PCM_EBIT 27 +#define LPASS_CLK_ID_TER_PCM_IBIT 28 +#define LPASS_CLK_ID_TER_PCM_EBIT 29 +#define LPASS_CLK_ID_QUAD_PCM_IBIT 30 +#define LPASS_CLK_ID_QUAD_PCM_EBIT 31 +#define LPASS_CLK_ID_QUIN_PCM_IBIT 32 +#define LPASS_CLK_ID_QUIN_PCM_EBIT 33 +#define LPASS_CLK_ID_QUI_PCM_OSR 34 +#define LPASS_CLK_ID_PRI_TDM_IBIT 35 +#define LPASS_CLK_ID_PRI_TDM_EBIT 36 +#define LPASS_CLK_ID_SEC_TDM_IBIT 37 +#define LPASS_CLK_ID_SEC_TDM_EBIT 38 +#define LPASS_CLK_ID_TER_TDM_IBIT 39 +#define LPASS_CLK_ID_TER_TDM_EBIT 40 +#define LPASS_CLK_ID_QUAD_TDM_IBIT 41 +#define LPASS_CLK_ID_QUAD_TDM_EBIT 42 +#define LPASS_CLK_ID_QUIN_TDM_IBIT 43 +#define LPASS_CLK_ID_QUIN_TDM_EBIT 44 +#define LPASS_CLK_ID_QUIN_TDM_OSR 45 +#define LPASS_CLK_ID_MCLK_1 46 +#define LPASS_CLK_ID_MCLK_2 47 +#define LPASS_CLK_ID_MCLK_3 48 +#define LPASS_CLK_ID_MCLK_4 49 +#define LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE 50 +#define LPASS_CLK_ID_INT_MCLK_0 51 +#define LPASS_CLK_ID_INT_MCLK_1 52 +#define LPASS_CLK_ID_MCLK_5 53 +#define LPASS_CLK_ID_WSA_CORE_MCLK 54 +#define LPASS_CLK_ID_WSA_CORE_NPL_MCLK 55 +#define LPASS_CLK_ID_VA_CORE_MCLK 56 +#define LPASS_CLK_ID_TX_CORE_MCLK 57 +#define LPASS_CLK_ID_TX_CORE_NPL_MCLK 58 +#define LPASS_CLK_ID_RX_CORE_MCLK 59 +#define LPASS_CLK_ID_RX_CORE_NPL_MCLK 60 +#define LPASS_CLK_ID_VA_CORE_2X_MCLK 61 + +#define LPASS_HW_AVTIMER_VOTE 101 +#define LPASS_HW_MACRO_VOTE 102 +#define LPASS_HW_DCODEC_VOTE 103 + +#define Q6AFE_MAX_CLK_ID 104 + +#define LPASS_CLK_ATTRIBUTE_INVALID 0x0 +#define LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +#define LPASS_CLK_ATTRIBUTE_COUPLE_DIVIDEND 0x2 +#define LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR 0x3 + +#endif /* __DT_BINDINGS_Q6_AUDIO_PORTS_H__ */ diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h new file mode 100644 index 000000000000..9ad9eaaaa552 --- /dev/null +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * cs_dsp.h -- Cirrus Logic DSP firmware support + * + * Based on sound/soc/codecs/wm_adsp.h + * + * Copyright 2012 Wolfson Microelectronics plc + * Copyright (C) 2015-2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ +#ifndef __CS_DSP_H +#define __CS_DSP_H + +#define CS_ADSP2_REGION_0 BIT(0) +#define CS_ADSP2_REGION_1 BIT(1) +#define CS_ADSP2_REGION_2 BIT(2) +#define CS_ADSP2_REGION_3 BIT(3) +#define CS_ADSP2_REGION_4 BIT(4) +#define CS_ADSP2_REGION_5 BIT(5) +#define CS_ADSP2_REGION_6 BIT(6) +#define CS_ADSP2_REGION_7 BIT(7) +#define CS_ADSP2_REGION_8 BIT(8) +#define CS_ADSP2_REGION_9 BIT(9) +#define CS_ADSP2_REGION_1_9 (CS_ADSP2_REGION_1 | \ + CS_ADSP2_REGION_2 | CS_ADSP2_REGION_3 | \ + CS_ADSP2_REGION_4 | CS_ADSP2_REGION_5 | \ + CS_ADSP2_REGION_6 | CS_ADSP2_REGION_7 | \ + CS_ADSP2_REGION_8 | CS_ADSP2_REGION_9) +#define CS_ADSP2_REGION_ALL (CS_ADSP2_REGION_0 | CS_ADSP2_REGION_1_9) + +#define CS_DSP_DATA_WORD_SIZE 3 + +#define CS_DSP_ACKED_CTL_TIMEOUT_MS 100 +#define CS_DSP_ACKED_CTL_N_QUICKPOLLS 10 +#define CS_DSP_ACKED_CTL_MIN_VALUE 0 +#define CS_DSP_ACKED_CTL_MAX_VALUE 0xFFFFFF + +/** + * struct cs_dsp_region - Describes a logical memory region in DSP address space + * @type: Memory region type + * @base: Address of region + */ +struct cs_dsp_region { + int type; + unsigned int base; +}; + +/** + * struct cs_dsp_alg_region - Describes a logical algorithm region in DSP address space + * @list: List node for internal use + * @alg: Algorithm id + * @type: Memory region type + * @base: Address of region + */ +struct cs_dsp_alg_region { + struct list_head list; + unsigned int alg; + int type; + unsigned int base; +}; + +/** + * struct cs_dsp_coeff_ctl - Describes a coefficient control + * @fw_name: Name of the firmware + * @subname: Name of the control parsed from the WMFW + * @subname_len: Length of subname + * @alg_region: Logical region associated with this control + * @dsp: DSP instance associated with this control + * @enabled: Flag indicating whether control is enabled + * @list: List node for internal use + * @cache: Cached value of the control + * @offset: Offset of control within alg_region + * @len: Length of the cached value + * @set: Flag indicating the value has been written by the user + * @flags: Bitfield of WMFW_CTL_FLAG_ control flags defined in wmfw.h + * @type: One of the WMFW_CTL_TYPE_ control types defined in wmfw.h + * @priv: For use by the client + */ +struct cs_dsp_coeff_ctl { + const char *fw_name; + /* Subname is needed to match with firmware */ + const char *subname; + unsigned int subname_len; + struct cs_dsp_alg_region alg_region; + struct cs_dsp *dsp; + unsigned int enabled:1; + struct list_head list; + void *cache; + unsigned int offset; + size_t len; + unsigned int set:1; + unsigned int flags; + unsigned int type; + + void *priv; +}; + +struct cs_dsp_ops; +struct cs_dsp_client_ops; + +/** + * struct cs_dsp - Configuration and state of a Cirrus Logic DSP + * @name: The name of the DSP instance + * @rev: Revision of the DSP + * @num: DSP instance number + * @type: Type of DSP + * @dev: Driver model representation of the device + * @regmap: Register map of the device + * @ops: Function pointers for internal callbacks + * @client_ops: Function pointers for client callbacks + * @base: Address of the DSP registers + * @base_sysinfo: Address of the sysinfo register (Halo only) + * @sysclk_reg: Address of the sysclk register (ADSP1 only) + * @sysclk_mask: Mask of frequency bits within sysclk register (ADSP1 only) + * @sysclk_shift: Shift of frequency bits within sysclk register (ADSP1 only) + * @alg_regions: List of currently loaded algorithm regions + * @fw_file_name: Filename of the current firmware + * @fw_name: Name of the current firmware + * @fw_id: ID of the current firmware, obtained from the wmfw + * @fw_id_version: Version of the firmware, obtained from the wmfw + * @fw_vendor_id: Vendor of the firmware, obtained from the wmfw + * @mem: DSP memory region descriptions + * @num_mems: Number of memory regions in this DSP + * @fw_ver: Version of the wmfw file format + * @booted: Flag indicating DSP has been configured + * @running: Flag indicating DSP is executing firmware + * @ctl_list: Controls defined within the loaded DSP firmware + * @lock_regions: Enable MPU traps on specified memory regions + * @pwr_lock: Lock used to serialize accesses + * @debugfs_root: Debugfs directory for this DSP instance + * @wmfw_file_name: Filename of the currently loaded firmware + * @bin_file_name: Filename of the currently loaded coefficients + */ +struct cs_dsp { + const char *name; + int rev; + int num; + int type; + struct device *dev; + struct regmap *regmap; + + const struct cs_dsp_ops *ops; + const struct cs_dsp_client_ops *client_ops; + + unsigned int base; + unsigned int base_sysinfo; + unsigned int sysclk_reg; + unsigned int sysclk_mask; + unsigned int sysclk_shift; + + struct list_head alg_regions; + + const char *fw_name; + unsigned int fw_id; + unsigned int fw_id_version; + unsigned int fw_vendor_id; + + const struct cs_dsp_region *mem; + int num_mems; + + int fw_ver; + + bool booted; + bool running; + + struct list_head ctl_list; + + struct mutex pwr_lock; + + unsigned int lock_regions; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; + char *wmfw_file_name; + char *bin_file_name; +#endif +}; + +/** + * struct cs_dsp_client_ops - client callbacks + * @control_add: Called under the pwr_lock when a control is created + * @control_remove: Called under the pwr_lock when a control is destroyed + * @post_run: Called under the pwr_lock by cs_dsp_run() + * @post_stop: Called under the pwr_lock by cs_dsp_stop() + * @watchdog_expired: Called when a watchdog expiry is detected + * + * These callbacks give the cs_dsp client an opportunity to respond to events + * or to perform actions atomically. + */ +struct cs_dsp_client_ops { + int (*control_add)(struct cs_dsp_coeff_ctl *ctl); + void (*control_remove)(struct cs_dsp_coeff_ctl *ctl); + int (*post_run)(struct cs_dsp *dsp); + void (*post_stop)(struct cs_dsp *dsp); + void (*watchdog_expired)(struct cs_dsp *dsp); +}; + +int cs_dsp_adsp1_init(struct cs_dsp *dsp); +int cs_dsp_adsp2_init(struct cs_dsp *dsp); +int cs_dsp_halo_init(struct cs_dsp *dsp); + +int cs_dsp_adsp1_power_up(struct cs_dsp *dsp, + const struct firmware *wmfw_firmware, char *wmfw_filename, + const struct firmware *coeff_firmware, char *coeff_filename, + const char *fw_name); +void cs_dsp_adsp1_power_down(struct cs_dsp *dsp); +int cs_dsp_power_up(struct cs_dsp *dsp, + const struct firmware *wmfw_firmware, char *wmfw_filename, + const struct firmware *coeff_firmware, char *coeff_filename, + const char *fw_name); +void cs_dsp_power_down(struct cs_dsp *dsp); +int cs_dsp_run(struct cs_dsp *dsp); +void cs_dsp_stop(struct cs_dsp *dsp); + +void cs_dsp_remove(struct cs_dsp *dsp); + +int cs_dsp_set_dspclk(struct cs_dsp *dsp, unsigned int freq); +void cs_dsp_adsp2_bus_error(struct cs_dsp *dsp); +void cs_dsp_halo_bus_error(struct cs_dsp *dsp); +void cs_dsp_halo_wdt_expire(struct cs_dsp *dsp); + +void cs_dsp_init_debugfs(struct cs_dsp *dsp, struct dentry *debugfs_root); +void cs_dsp_cleanup_debugfs(struct cs_dsp *dsp); + +int cs_dsp_coeff_write_acked_control(struct cs_dsp_coeff_ctl *ctl, unsigned int event_id); +int cs_dsp_coeff_write_ctrl(struct cs_dsp_coeff_ctl *ctl, const void *buf, size_t len); +int cs_dsp_coeff_read_ctrl(struct cs_dsp_coeff_ctl *ctl, void *buf, size_t len); +struct cs_dsp_coeff_ctl *cs_dsp_get_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg); + +int cs_dsp_read_raw_data_block(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, + unsigned int num_words, __be32 *data); +int cs_dsp_read_data_word(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, u32 *data); +int cs_dsp_write_data_word(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, u32 data); +void cs_dsp_remove_padding(u32 *buf, int nwords); + +struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp, + int type, unsigned int id); + +const char *cs_dsp_mem_region_name(unsigned int type); + +#endif diff --git a/sound/soc/codecs/wmfw.h b/include/linux/firmware/cirrus/wmfw.h index f3d51602f85c..a19bf7c6fc8b 100644 --- a/sound/soc/codecs/wmfw.h +++ b/include/linux/firmware/cirrus/wmfw.h @@ -23,10 +23,12 @@ #define WMFW_CTL_FLAG_WRITEABLE 0x0002 #define WMFW_CTL_FLAG_READABLE 0x0001 +#define WMFW_CTL_TYPE_BYTES 0x0004 /* byte control */ + /* Non-ALSA coefficient types start at 0x1000 */ -#define WMFW_CTL_TYPE_ACKED ((__force snd_ctl_elem_type_t)0x1000) /* acked control */ -#define WMFW_CTL_TYPE_HOSTEVENT ((__force snd_ctl_elem_type_t)0x1001) /* event control */ -#define WMFW_CTL_TYPE_HOST_BUFFER ((__force snd_ctl_elem_type_t)0x1002) /* host buffer pointer */ +#define WMFW_CTL_TYPE_ACKED 0x1000 /* acked control */ +#define WMFW_CTL_TYPE_HOSTEVENT 0x1001 /* event control */ +#define WMFW_CTL_TYPE_HOST_BUFFER 0x1002 /* host buffer pointer */ struct wmfw_header { char magic[4]; diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h index 137f9f2ac4c3..23c5b30f3511 100644 --- a/include/linux/soc/qcom/apr.h +++ b/include/linux/soc/qcom/apr.h @@ -7,6 +7,7 @@ #include <linux/device.h> #include <linux/mod_devicetable.h> #include <dt-bindings/soc/qcom,apr.h> +#include <dt-bindings/soc/qcom,gpr.h> extern struct bus_type aprbus; @@ -75,10 +76,65 @@ struct apr_resp_pkt { int payload_size; }; +struct gpr_hdr { + uint32_t version:4; + uint32_t hdr_size:4; + uint32_t pkt_size:24; + uint32_t dest_domain:8; + uint32_t src_domain:8; + uint32_t reserved:16; + uint32_t src_port; + uint32_t dest_port; + uint32_t token; + uint32_t opcode; +} __packed; + +struct gpr_pkt { + struct gpr_hdr hdr; + uint32_t payload[]; +}; + +struct gpr_resp_pkt { + struct gpr_hdr hdr; + void *payload; + int payload_size; +}; + +#define GPR_HDR_SIZE sizeof(struct gpr_hdr) +#define GPR_PKT_VER 0x0 +#define GPR_PKT_HEADER_WORD_SIZE ((sizeof(struct gpr_pkt) + 3) >> 2) +#define GPR_PKT_HEADER_BYTE_SIZE (GPR_PKT_HEADER_WORD_SIZE << 2) + +#define GPR_BASIC_RSP_RESULT 0x02001005 + +struct gpr_ibasic_rsp_result_t { + uint32_t opcode; + uint32_t status; +}; + +#define GPR_BASIC_EVT_ACCEPTED 0x02001006 + +struct gpr_ibasic_rsp_accepted_t { + uint32_t opcode; +}; + /* Bits 0 to 15 -- Minor version, Bits 16 to 31 -- Major version */ #define APR_SVC_MAJOR_VERSION(v) ((v >> 16) & 0xFF) #define APR_SVC_MINOR_VERSION(v) (v & 0xFF) +typedef int (*gpr_port_cb) (struct gpr_resp_pkt *d, void *priv, int op); +struct packet_router; +struct pkt_router_svc { + struct device *dev; + gpr_port_cb callback; + struct packet_router *pr; + spinlock_t lock; + int id; + void *priv; +}; + +typedef struct pkt_router_svc gpr_port_t; + struct apr_device { struct device dev; uint16_t svc_id; @@ -86,21 +142,26 @@ struct apr_device { uint32_t version; char name[APR_NAME_SIZE]; const char *service_path; - spinlock_t lock; + struct pkt_router_svc svc; struct list_head node; }; +typedef struct apr_device gpr_device_t; + #define to_apr_device(d) container_of(d, struct apr_device, dev) +#define svc_to_apr_device(d) container_of(d, struct apr_device, svc) struct apr_driver { int (*probe)(struct apr_device *sl); int (*remove)(struct apr_device *sl); int (*callback)(struct apr_device *a, struct apr_resp_pkt *d); + int (*gpr_callback)(struct gpr_resp_pkt *d, void *data, int op); struct device_driver driver; const struct apr_device_id *id_table; }; +typedef struct apr_driver gpr_driver_t; #define to_apr_driver(d) container_of(d, struct apr_driver, driver) /* @@ -123,7 +184,14 @@ void apr_driver_unregister(struct apr_driver *drv); #define module_apr_driver(__apr_driver) \ module_driver(__apr_driver, apr_driver_register, \ apr_driver_unregister) +#define module_gpr_driver(__gpr_driver) module_apr_driver(__gpr_driver) int apr_send_pkt(struct apr_device *adev, struct apr_pkt *pkt); +gpr_port_t *gpr_alloc_port(gpr_device_t *gdev, struct device *dev, + gpr_port_cb cb, void *priv); +void gpr_free_port(gpr_port_t *port); +int gpr_send_port_pkt(gpr_port_t *port, struct gpr_pkt *pkt); +int gpr_send_pkt(gpr_device_t *gdev, struct gpr_pkt *pkt); + #endif /* __QCOM_APR_H_ */ diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h new file mode 100644 index 000000000000..1f1e3c6c9be1 --- /dev/null +++ b/include/sound/cs35l41.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * linux/sound/cs35l41.h -- Platform data for CS35L41 + * + * Copyright (c) 2017-2021 Cirrus Logic Inc. + * + * Author: David Rhodes <david.rhodes@cirrus.com> + */ + +#ifndef __CS35L41_H +#define __CS35L41_H + +enum cs35l41_clk_ids { + CS35L41_CLKID_SCLK = 0, + CS35L41_CLKID_LRCLK = 1, + CS35L41_CLKID_MCLK = 4, +}; + +struct cs35l41_irq_cfg { + bool irq_pol_inv; + bool irq_out_en; + int irq_src_sel; +}; + +struct cs35l41_platform_data { + int bst_ind; + int bst_ipk; + int bst_cap; + int dout_hiz; + struct cs35l41_irq_cfg irq_config1; + struct cs35l41_irq_cfg irq_config2; +}; + +#endif /* __CS35L41_H */ diff --git a/include/sound/graph_card.h b/include/sound/graph_card.h index 6f10bfb0d5ee..4c8b94c77b8e 100644 --- a/include/sound/graph_card.h +++ b/include/sound/graph_card.h @@ -9,6 +9,27 @@ #include <sound/simple_card_utils.h> +typedef int (*GRAPH2_CUSTOM)(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li); + +struct graph2_custom_hooks { + int (*hook_pre)(struct asoc_simple_priv *priv); + int (*hook_post)(struct asoc_simple_priv *priv); + GRAPH2_CUSTOM custom_normal; + GRAPH2_CUSTOM custom_dpcm; + GRAPH2_CUSTOM custom_c2c; +}; + int audio_graph_parse_of(struct asoc_simple_priv *priv, struct device *dev); +int audio_graph2_parse_of(struct asoc_simple_priv *priv, struct device *dev, + struct graph2_custom_hooks *hooks); + +int audio_graph2_link_normal(struct asoc_simple_priv *priv, + struct device_node *lnk, struct link_info *li); +int audio_graph2_link_dpcm(struct asoc_simple_priv *priv, + struct device_node *lnk, struct link_info *li); +int audio_graph2_link_c2c(struct asoc_simple_priv *priv, + struct device_node *lnk, struct link_info *li); #endif /* __GRAPH_CARD_H */ diff --git a/include/sound/hdaudio_ext.h b/include/sound/hdaudio_ext.h index 375581634143..d4e31ea16aba 100644 --- a/include/sound/hdaudio_ext.h +++ b/include/sound/hdaudio_ext.h @@ -88,6 +88,8 @@ struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream, int type); void snd_hdac_ext_stream_release(struct hdac_ext_stream *azx_dev, int type); +void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus, + struct hdac_ext_stream *azx_dev, bool decouple); void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, struct hdac_ext_stream *azx_dev, bool decouple); void snd_hdac_ext_stop_streams(struct hdac_bus *bus); diff --git a/include/sound/memalloc.h b/include/sound/memalloc.h index b197e3f431c1..653dfffb3ac8 100644 --- a/include/sound/memalloc.h +++ b/include/sound/memalloc.h @@ -9,16 +9,20 @@ #ifndef __SOUND_MEMALLOC_H #define __SOUND_MEMALLOC_H +#include <linux/dma-direction.h> #include <asm/page.h> struct device; struct vm_area_struct; +struct sg_table; /* * buffer device info */ struct snd_dma_device { int type; /* SNDRV_DMA_TYPE_XXX */ + enum dma_data_direction dir; /* DMA direction */ + bool need_sync; /* explicit sync needed? */ struct device *dev; /* generic device */ }; @@ -32,19 +36,21 @@ struct snd_dma_device { #define SNDRV_DMA_TYPE_CONTINUOUS 1 /* continuous no-DMA memory */ #define SNDRV_DMA_TYPE_DEV 2 /* generic device continuous */ #define SNDRV_DMA_TYPE_DEV_WC 5 /* continuous write-combined */ -#ifdef CONFIG_SND_DMA_SGBUF -#define SNDRV_DMA_TYPE_DEV_SG 3 /* generic device SG-buffer */ -#define SNDRV_DMA_TYPE_DEV_WC_SG 6 /* SG write-combined */ -#else -#define SNDRV_DMA_TYPE_DEV_SG SNDRV_DMA_TYPE_DEV /* no SG-buf support */ -#define SNDRV_DMA_TYPE_DEV_WC_SG SNDRV_DMA_TYPE_DEV_WC -#endif #ifdef CONFIG_GENERIC_ALLOCATOR #define SNDRV_DMA_TYPE_DEV_IRAM 4 /* generic device iram-buffer */ #else #define SNDRV_DMA_TYPE_DEV_IRAM SNDRV_DMA_TYPE_DEV #endif #define SNDRV_DMA_TYPE_VMALLOC 7 /* vmalloc'ed buffer */ +#define SNDRV_DMA_TYPE_NONCONTIG 8 /* non-coherent SG buffer */ +#define SNDRV_DMA_TYPE_NONCOHERENT 9 /* non-coherent buffer */ +#ifdef CONFIG_SND_DMA_SGBUF +#define SNDRV_DMA_TYPE_DEV_SG SNDRV_DMA_TYPE_NONCONTIG +#define SNDRV_DMA_TYPE_DEV_WC_SG 6 /* SG write-combined */ +#else +#define SNDRV_DMA_TYPE_DEV_SG SNDRV_DMA_TYPE_DEV /* no SG-buf support */ +#define SNDRV_DMA_TYPE_DEV_WC_SG SNDRV_DMA_TYPE_DEV_WC +#endif /* * info for buffer allocation @@ -66,22 +72,52 @@ static inline unsigned int snd_sgbuf_aligned_pages(size_t size) } /* allocate/release a buffer */ -int snd_dma_alloc_pages(int type, struct device *dev, size_t size, - struct snd_dma_buffer *dmab); +int snd_dma_alloc_dir_pages(int type, struct device *dev, + enum dma_data_direction dir, size_t size, + struct snd_dma_buffer *dmab); + +static inline int snd_dma_alloc_pages(int type, struct device *dev, + size_t size, struct snd_dma_buffer *dmab) +{ + return snd_dma_alloc_dir_pages(type, dev, DMA_BIDIRECTIONAL, size, dmab); +} + int snd_dma_alloc_pages_fallback(int type, struct device *dev, size_t size, struct snd_dma_buffer *dmab); void snd_dma_free_pages(struct snd_dma_buffer *dmab); int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab, struct vm_area_struct *area); +enum snd_dma_sync_mode { SNDRV_DMA_SYNC_CPU, SNDRV_DMA_SYNC_DEVICE }; +#ifdef CONFIG_HAS_DMA +void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode); +#else +static inline void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) {} +#endif + dma_addr_t snd_sgbuf_get_addr(struct snd_dma_buffer *dmab, size_t offset); struct page *snd_sgbuf_get_page(struct snd_dma_buffer *dmab, size_t offset); unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size); /* device-managed memory allocator */ -struct snd_dma_buffer *snd_devm_alloc_pages(struct device *dev, int type, - size_t size); +struct snd_dma_buffer *snd_devm_alloc_dir_pages(struct device *dev, int type, + enum dma_data_direction dir, + size_t size); + +static inline struct snd_dma_buffer * +snd_devm_alloc_pages(struct device *dev, int type, size_t size) +{ + return snd_devm_alloc_dir_pages(dev, type, DMA_BIDIRECTIONAL, size); +} + +static inline struct sg_table * +snd_dma_noncontig_sg_table(struct snd_dma_buffer *dmab) +{ + return dmab->private_data; +} #endif /* __SOUND_MEMALLOC_H */ diff --git a/include/sound/rt5682s.h b/include/sound/rt5682s.h new file mode 100644 index 000000000000..accfbc2dcdd2 --- /dev/null +++ b/include/sound/rt5682s.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * linux/sound/rt5682s.h -- Platform data for RT5682I-VS + * + * Copyright 2021 Realtek Microelectronics + */ + +#ifndef __LINUX_SND_RT5682S_H +#define __LINUX_SND_RT5682S_H + +enum rt5682s_dmic1_data_pin { + RT5682S_DMIC1_DATA_NULL, + RT5682S_DMIC1_DATA_GPIO2, + RT5682S_DMIC1_DATA_GPIO5, +}; + +enum rt5682s_dmic1_clk_pin { + RT5682S_DMIC1_CLK_NULL, + RT5682S_DMIC1_CLK_GPIO1, + RT5682S_DMIC1_CLK_GPIO3, +}; + +enum rt5682s_jd_src { + RT5682S_JD_NULL, + RT5682S_JD1, +}; + +enum rt5682s_dai_clks { + RT5682S_DAI_WCLK_IDX, + RT5682S_DAI_BCLK_IDX, + RT5682S_DAI_NUM_CLKS, +}; + +struct rt5682s_platform_data { + + int ldo1_en; /* GPIO for LDO1_EN */ + + enum rt5682s_dmic1_data_pin dmic1_data_pin; + enum rt5682s_dmic1_clk_pin dmic1_clk_pin; + enum rt5682s_jd_src jd_src; + unsigned int dmic_clk_rate; + unsigned int dmic_delay; + bool dmic_clk_driving_high; + + const char *dai_clk_names[RT5682S_DAI_NUM_CLKS]; +}; + +#endif diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 51b3b485a92e..df430f1c2a10 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -42,6 +42,7 @@ struct prop_nums { int cpus; int codecs; int platforms; + int c2c; }; struct asoc_simple_priv { @@ -54,6 +55,7 @@ struct asoc_simple_priv { struct snd_soc_dai_link_component *platforms; struct asoc_simple_data adata; struct snd_soc_codec_conf *codec_conf; + struct snd_soc_pcm_stream *c2c_conf; struct prop_nums num; unsigned int mclk_fs; } *dai_props; @@ -64,6 +66,7 @@ struct asoc_simple_priv { struct snd_soc_dai_link_component *dlcs; struct snd_soc_dai_link_component dummy; struct snd_soc_codec_conf *codec_conf; + struct snd_soc_pcm_stream *c2c_conf; struct gpio_desc *pa_gpio; const struct snd_soc_ops *ops; unsigned int dpcm_selectable:1; @@ -115,7 +118,7 @@ struct asoc_simple_priv { ((codec) = simple_props_to_dai_codec(props, i)); \ (i)++) -#define SNDRV_MAX_LINKS 128 +#define SNDRV_MAX_LINKS 512 struct link_info { int link; /* number of link */ @@ -180,6 +183,7 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, int asoc_simple_remove(struct platform_device *pdev); int asoc_graph_card_probe(struct snd_soc_card *card); +int asoc_graph_is_ports0(struct device_node *port); #ifdef DEBUG static inline void asoc_simple_debug_dai(struct asoc_simple_priv *priv, diff --git a/include/sound/soc-acpi.h b/include/sound/soc-acpi.h index 2f3fa385c092..31f4c4f9aeea 100644 --- a/include/sound/soc-acpi.h +++ b/include/sound/soc-acpi.h @@ -129,6 +129,8 @@ struct snd_soc_acpi_link_adr { * all firmware/topology related fields. * * @id: ACPI ID (usually the codec's) used to find a matching machine driver. + * @comp_ids: list of compatible audio codecs using the same machine driver, + * firmware and topology * @link_mask: describes required board layout, e.g. for SoundWire. * @links: array of link _ADR descriptors, null terminated. * @drv_name: machine driver name @@ -146,6 +148,7 @@ struct snd_soc_acpi_link_adr { /* Descriptor for SST ASoC machine driver */ struct snd_soc_acpi_mach { const u8 id[ACPI_ID_LEN]; + const struct snd_soc_acpi_codecs *comp_ids; const u32 link_mask; const struct snd_soc_acpi_link_adr *links; const char *drv_name; diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 8c4d6830597f..a4317144ab62 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -220,17 +220,15 @@ struct snd_soc_component { int (*init)(struct snd_soc_component *component); /* function mark */ - struct snd_pcm_substream *mark_module; + void *mark_module; struct snd_pcm_substream *mark_open; struct snd_pcm_substream *mark_hw_params; struct snd_pcm_substream *mark_trigger; struct snd_compr_stream *mark_compr_open; void *mark_pm; -#ifdef CONFIG_DEBUG_FS struct dentry *debugfs_root; const char *debugfs_prefix; -#endif }; #define for_each_component_dais(component, dai)\ @@ -335,6 +333,11 @@ static inline int snd_soc_component_cache_sync( return regcache_sync(component->regmap); } +static inline int snd_soc_component_is_codec(struct snd_soc_component *component) +{ + return component->driver->non_legacy_dai_naming; +} + void snd_soc_component_set_aux(struct snd_soc_component *component, struct snd_soc_aux_dev *aux); int snd_soc_component_init(struct snd_soc_component *component); @@ -391,15 +394,13 @@ void snd_soc_component_exit_regmap(struct snd_soc_component *component); #define snd_soc_component_module_get_when_open(component, substream) \ snd_soc_component_module_get(component, substream, 1) int snd_soc_component_module_get(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - int upon_open); + void *mark, int upon_open); #define snd_soc_component_module_put_when_remove(component) \ snd_soc_component_module_put(component, NULL, 0, 0) #define snd_soc_component_module_put_when_close(component, substream, rollback) \ snd_soc_component_module_put(component, substream, 1, rollback) void snd_soc_component_module_put(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - int upon_open, int rollback); + void *mark, int upon_open, int rollback); static inline void snd_soc_component_set_drvdata(struct snd_soc_component *c, void *data) @@ -455,8 +456,10 @@ int snd_soc_component_of_xlate_dai_id(struct snd_soc_component *component, int snd_soc_component_of_xlate_dai_name(struct snd_soc_component *component, const struct of_phandle_args *args, const char **dai_name); -int snd_soc_component_compr_open(struct snd_compr_stream *cstream); -void snd_soc_component_compr_free(struct snd_compr_stream *cstream, +int snd_soc_component_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream); +void snd_soc_component_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int rollback); int snd_soc_component_compr_trigger(struct snd_compr_stream *cstream, int cmd); int snd_soc_component_compr_set_params(struct snd_compr_stream *cstream, diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index e296a3949b18..bc7af90099a8 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -159,6 +159,7 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, int cmd); int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream); int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event); +bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, enum snd_soc_dapm_direction dir); #define dpcm_be_dai_startup_rollback(fe, stream, last) \ dpcm_be_dai_stop(fe, stream, 0, last) diff --git a/include/sound/soc-topology.h b/include/sound/soc-topology.h index 4afd667e124c..b4b896f83b94 100644 --- a/include/sound/soc-topology.h +++ b/include/sound/soc-topology.h @@ -151,7 +151,7 @@ struct snd_soc_tplg_ops { struct snd_soc_tplg_hdr *); /* completion - called at completion of firmware loading */ - void (*complete)(struct snd_soc_component *); + int (*complete)(struct snd_soc_component *comp); /* manifest - optional to inform component of manifest */ int (*manifest)(struct snd_soc_component *, int index, @@ -188,8 +188,7 @@ int snd_soc_tplg_widget_bind_event(struct snd_soc_dapm_widget *w, #else -static inline int snd_soc_tplg_component_remove(struct snd_soc_component *comp, - u32 index) +static inline int snd_soc_tplg_component_remove(struct snd_soc_component *comp) { return 0; } diff --git a/include/sound/sof.h b/include/sound/sof.h index 6a1cd8e783d8..23b374311d16 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -74,11 +74,6 @@ struct sof_dev_desc { int resindex_pcicfg_base; int resindex_imr_base; int irqindex_host_ipc; - int resindex_dma_base; - - /* DMA only valid when resindex_dma_base != -1*/ - int dma_engine; - int dma_size; /* IPC timeouts in ms */ int ipc_timeout; diff --git a/include/sound/sof/dai-intel.h b/include/sound/sof/dai-intel.h index 136adf6686e2..7a266f41983c 100644 --- a/include/sound/sof/dai-intel.h +++ b/include/sound/sof/dai-intel.h @@ -48,6 +48,10 @@ #define SOF_DAI_INTEL_SSP_CLKCTRL_FS_KA BIT(4) /* bclk idle */ #define SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_IDLE_HIGH BIT(5) +/* mclk early start */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_MCLK_ES BIT(6) +/* bclk early start */ +#define SOF_DAI_INTEL_SSP_CLKCTRL_BCLK_ES BIT(7) /* DMIC max. four controllers for eight microphone channels */ #define SOF_DAI_INTEL_DMIC_NUM_CTRL 4 diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h index 6bb403e8c5ee..9625f47557b8 100644 --- a/include/sound/sof/dai.h +++ b/include/sound/sof/dai.h @@ -50,6 +50,13 @@ #define SOF_DAI_FMT_INV_MASK 0x0f00 #define SOF_DAI_FMT_CLOCK_PROVIDER_MASK 0xf000 +/* DAI_CONFIG flags */ +#define SOF_DAI_CONFIG_FLAGS_MASK 0x3 +#define SOF_DAI_CONFIG_FLAGS_NONE (0 << 0) /**< DAI_CONFIG sent without stage information */ +#define SOF_DAI_CONFIG_FLAGS_HW_PARAMS (1 << 0) /**< DAI_CONFIG sent during hw_params stage */ +#define SOF_DAI_CONFIG_FLAGS_HW_FREE (2 << 0) /**< DAI_CONFIG sent during hw_free stage */ +#define SOF_DAI_CONFIG_FLAGS_RFU (3 << 0) /**< not used, reserved for future use */ + /** \brief Types of DAI */ enum sof_ipc_dai_type { SOF_DAI_INTEL_NONE = 0, /**< None */ @@ -69,7 +76,8 @@ struct sof_ipc_dai_config { /* physical protocol and clocking */ uint16_t format; /**< SOF_DAI_FMT_ */ - uint16_t reserved16; /**< alignment */ + uint8_t group_id; /**< group ID, 0 means no group (ABI 3.17) */ + uint8_t flags; /**< SOF_DAI_CONFIG_FLAGS_ (ABI 3.19) */ /* reserved for future use */ uint32_t reserved[8]; diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 5859ca0a1439..5fbb79e30819 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -1002,7 +1002,7 @@ typedef int __bitwise snd_ctl_elem_iface_t; #define SNDRV_CTL_ELEM_ACCESS_WRITE (1<<1) #define SNDRV_CTL_ELEM_ACCESS_READWRITE (SNDRV_CTL_ELEM_ACCESS_READ|SNDRV_CTL_ELEM_ACCESS_WRITE) #define SNDRV_CTL_ELEM_ACCESS_VOLATILE (1<<2) /* control value may be changed without a notification */ -// (1 << 3) is unused. +/* (1 << 3) is unused. */ #define SNDRV_CTL_ELEM_ACCESS_TLV_READ (1<<4) /* TLV read is possible */ #define SNDRV_CTL_ELEM_ACCESS_TLV_WRITE (1<<5) /* TLV write is possible */ #define SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE (SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index ae12826ed641..39cf6eb75940 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -13,6 +13,7 @@ #define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c #define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479 #define SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL 0x7473636d +#define SNDRV_FIREWIRE_EVENT_MOTU_REGISTER_DSP_CHANGE 0x4d545244 struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ @@ -65,6 +66,12 @@ struct snd_firewire_event_tascam_control { struct snd_firewire_tascam_change changes[0]; }; +struct snd_firewire_event_motu_register_dsp_change { + unsigned int type; + __u32 count; /* The number of changes. */ + __u32 changes[]; /* Encoded event for change of register DSP. */ +}; + union snd_firewire_event { struct snd_firewire_event_common common; struct snd_firewire_event_lock_status lock_status; @@ -73,6 +80,7 @@ union snd_firewire_event { struct snd_firewire_event_digi00x_message digi00x_message; struct snd_firewire_event_tascam_control tascam_control; struct snd_firewire_event_motu_notification motu_notification; + struct snd_firewire_event_motu_register_dsp_change motu_register_dsp_change; }; @@ -80,6 +88,9 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_IOCTL_LOCK _IO('H', 0xf9) #define SNDRV_FIREWIRE_IOCTL_UNLOCK _IO('H', 0xfa) #define SNDRV_FIREWIRE_IOCTL_TASCAM_STATE _IOR('H', 0xfb, struct snd_firewire_tascam_state) +#define SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_METER _IOR('H', 0xfc, struct snd_firewire_motu_register_dsp_meter) +#define SNDRV_FIREWIRE_IOCTL_MOTU_COMMAND_DSP_METER _IOR('H', 0xfd, struct snd_firewire_motu_command_dsp_meter) +#define SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_PARAMETER _IOR('H', 0xfe, struct snd_firewire_motu_register_dsp_parameter) #define SNDRV_FIREWIRE_TYPE_DICE 1 #define SNDRV_FIREWIRE_TYPE_FIREWORKS 2 @@ -108,4 +119,143 @@ struct snd_firewire_tascam_state { __be32 data[SNDRV_FIREWIRE_TASCAM_STATE_COUNT]; }; +/* + * In below MOTU models, software is allowed to control their DSP by accessing to registers. + * - 828mk2 + * - 896hd + * - Traveler + * - 8 pre + * - Ultralite + * - 4 pre + * - Audio Express + * + * On the other hand, the status of DSP is split into specific messages included in the sequence of + * isochronous packet. ALSA firewire-motu driver gathers the messages and allow userspace applications + * to read it via ioctl. In 828mk2, 896hd, and Traveler, hardware meter for all of physical inputs + * are put into the message, while one pair of physical outputs is selected. The selection is done by + * LSB one byte in asynchronous write quadlet transaction to 0x'ffff'f000'0b2c. + * + * I note that V3HD/V4HD uses asynchronous transaction for the purpose. The destination address is + * registered to 0x'ffff'f000'0b38 and '0b3c by asynchronous write quadlet request. The size of + * message differs between 23 and 51 quadlets. For the case, the number of mixer bus can be extended + * up to 12. + */ + +#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT 24 +#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_OUTPUT_COUNT 24 +#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT \ + (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT + SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_OUTPUT_COUNT) + +/** + * struct snd_firewire_motu_register_dsp_meter - the container for meter information in DSP + * controlled by register access + * @data: Signal level meters. The mapping between position and input/output channel is + * model-dependent. + * + * The structure expresses the part of DSP status for hardware meter. The u8 storage includes linear + * value for audio signal level between 0x00 and 0x7f. + */ +struct snd_firewire_motu_register_dsp_meter { + __u8 data[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT]; +}; + +#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT 4 +#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT 20 +#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT 10 +#define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT + 2) + +/** + * snd_firewire_motu_register_dsp_parameter - the container for parameters of DSP controlled + * by register access. + * @mixer.source.gain: The gain of source to mixer. + * @mixer.source.pan: The L/R balance of source to mixer. + * @mixer.source.flag: The flag of source to mixer, including mute, solo. + * @mixer.source.paired_balance: The L/R balance of paired source to mixer, only for 4 pre and + * Audio Express. + * @mixer.source.paired_width: The width of paired source to mixer, only for 4 pre and + * Audio Express. + * @mixer.output.paired_volume: The volume of paired output from mixer. + * @mixer.output.paired_flag: The flag of paired output from mixer. + * @output.main_paired_volume: The volume of paired main output. + * @output.hp_paired_volume: The volume of paired hp output. + * @output.hp_paired_assignment: The source assigned to paired hp output. + * @output.reserved: Padding for 32 bit alignment for future extension. + * @line_input.boost_flag: The flags of boost for line inputs, only for 828mk2 and Traveler. + * @line_input.nominal_level_flag: The flags of nominal level for line inputs, only for 828mk2 and + * Traveler. + * @line_input.reserved: Padding for 32 bit alignment for future extension. + * @input.gain_and_invert: The value including gain and invert for input, only for Ultralite, 4 pre + * and Audio Express. + * @input.flag: The flag of input; e.g. jack detection, phantom power, and pad, only for Ultralite, + * 4 pre and Audio express. + * @reserved: Padding so that the size of structure is kept to 512 byte, but for future extension. + * + * The structure expresses the set of parameters for DSP controlled by register access. + */ +struct snd_firewire_motu_register_dsp_parameter { + struct { + struct { + __u8 gain[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT]; + __u8 pan[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT]; + __u8 flag[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT]; + __u8 paired_balance[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT]; + __u8 paired_width[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT]; + } source[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT]; + struct { + __u8 paired_volume[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT]; + __u8 paired_flag[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT]; + } output; + } mixer; + struct { + __u8 main_paired_volume; + __u8 hp_paired_volume; + __u8 hp_paired_assignment; + __u8 reserved[5]; + } output; + struct { + __u8 boost_flag; + __u8 nominal_level_flag; + __u8 reserved[6]; + } line_input; + struct { + __u8 gain_and_invert[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT]; + __u8 flag[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT]; + } input; + __u8 reserved[64]; +}; + +/* + * In below MOTU models, software is allowed to control their DSP by command in frame of + * asynchronous transaction to 0x'ffff'0001'0000: + * + * - 828 mk3 (FireWire only and Hybrid) + * - 896 mk3 (FireWire only and Hybrid) + * - Ultralite mk3 (FireWire only and Hybrid) + * - Traveler mk3 + * - Track 16 + * + * On the other hand, the states of hardware meter is split into specific messages included in the + * sequence of isochronous packet. ALSA firewire-motu driver gathers the message and allow userspace + * application to read it via ioctl. + */ + +#define SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT 400 + +/** + * struct snd_firewire_motu_command_dsp_meter - the container for meter information in DSP + * controlled by command + * @data: Signal level meters. The mapping between position and signal channel is model-dependent. + * + * The structure expresses the part of DSP status for hardware meter. The 32 bit storage is + * estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is + * expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0. + */ +struct snd_firewire_motu_command_dsp_meter { +#ifdef __KERNEL__ + __u32 data[SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT]; +#else + float data[SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT]; +#endif +}; + #endif /* _UAPI_SOUND_FIREWIRE_H_INCLUDED */ diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h new file mode 100644 index 000000000000..440c0725660b --- /dev/null +++ b/include/uapi/sound/snd_ar_tokens.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef __SND_AR_TOKENS_H__ +#define __SND_AR_TOKENS_H__ + +#define APM_SUB_GRAPH_PERF_MODE_LOW_POWER 0x1 +#define APM_SUB_GRAPH_PERF_MODE_LOW_LATENCY 0x2 + +#define APM_SUB_GRAPH_DIRECTION_TX 0x1 +#define APM_SUB_GRAPH_DIRECTION_RX 0x2 + +/** Scenario ID Audio Playback */ +#define APM_SUB_GRAPH_SID_AUDIO_PLAYBACK 0x1 +/* Scenario ID Audio Record */ +#define APM_SUB_GRAPH_SID_AUDIO_RECORD 0x2 +/* Scenario ID Voice call. */ +#define APM_SUB_GRAPH_SID_VOICE_CALL 0x3 + +/* container capability ID Pre/Post Processing (PP) */ +#define APM_CONTAINER_CAP_ID_PP 0x1 +/* container capability ID Compression/Decompression (CD) */ +#define APM_CONTAINER_CAP_ID_CD 0x2 +/* container capability ID End Point(EP) */ +#define APM_CONTAINER_CAP_ID_EP 0x3 +/* container capability ID Offload (OLC) */ +#define APM_CONTAINER_CAP_ID_OLC 0x4 + +/* container graph position Stream */ +#define APM_CONT_GRAPH_POS_STREAM 0x1 +/* container graph position Per Stream Per Device*/ +#define APM_CONT_GRAPH_POS_PER_STR_PER_DEV 0x2 +/* container graph position Stream-Device */ +#define APM_CONT_GRAPH_POS_STR_DEV 0x3 +/* container graph position Global Device */ +#define APM_CONT_GRAPH_POS_GLOBAL_DEV 0x4 + +#define APM_PROC_DOMAIN_ID_MDSP 0x1 +#define APM_PROC_DOMAIN_ID_ADSP 0x2 +#define APM_PROC_DOMAIN_ID_SDSP 0x4 +#define APM_PROC_DOMAIN_ID_CDSP 0x5 + +#define PCM_INTERLEAVED 1 +#define PCM_DEINTERLEAVED_PACKED 2 +#define PCM_DEINTERLEAVED_UNPACKED 3 +#define AR_I2S_WS_SRC_EXTERNAL 0 +#define AR_I2S_WS_SRC_INTERNAL 1 + +enum ar_event_types { + AR_EVENT_NONE = 0, + AR_PGA_DAPM_EVENT +}; + +/* + * Kcontrol IDs + */ +#define SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX 256 +#define SND_SOC_AR_TPLG_VOL_CTL 257 + +/** + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: Sub Graph Instance Id + * + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE: Performance mode of subgraph + * APM_SUB_GRAPH_PERF_MODE_LOW_POWER = 1, + * APM_SUB_GRAPH_PERF_MODE_LOW_LATENCY = 2 + * + * %AR_TKN_U32_SUB_GRAPH_DIRECTION: Direction of subgraph + * APM_SUB_GRAPH_DIRECTION_TX = 1, + * APM_SUB_GRAPH_DIRECTION_RX = 2 + * + * %AR_TKN_U32_SUB_GRAPH_SCENARIO_ID: Scenario ID for subgraph + * APM_SUB_GRAPH_SID_AUDIO_PLAYBACK = 1, + * APM_SUB_GRAPH_SID_AUDIO_RECORD = 2, + * APM_SUB_GRAPH_SID_VOICE_CALL = 3 + * + * %AR_TKN_U32_CONTAINER_INSTANCE_ID: Container Instance ID + * + * %AR_TKN_U32_CONTAINER_CAPABILITY_ID: Container capability ID + * APM_CONTAINER_CAP_ID_PP = 1, + * APM_CONTAINER_CAP_ID_CD = 2, + * APM_CONTAINER_CAP_ID_EP = 3, + * APM_CONTAINER_CAP_ID_OLC = 4 + * + * %AR_TKN_U32_CONTAINER_STACK_SIZE: Stack size in the container. + * + * %AR_TKN_U32_CONTAINER_GRAPH_POS: Graph Position + * APM_CONT_GRAPH_POS_STREAM = 1, + * APM_CONT_GRAPH_POS_PER_STR_PER_DEV = 2, + * APM_CONT_GRAPH_POS_STR_DEV = 3, + * APM_CONT_GRAPH_POS_GLOBAL_DEV = 4 + * + * %AR_TKN_U32_CONTAINER_PROC_DOMAIN: Processor domain of container + * APM_PROC_DOMAIN_ID_MDSP = 1, + * APM_PROC_DOMAIN_ID_ADSP = 2, + * APM_PROC_DOMAIN_ID_SDSP = 4, + * APM_PROC_DOMAIN_ID_CDSP = 5 + * + * %AR_TKN_U32_MODULE_ID: Module ID + * + * %AR_TKN_U32_MODULE_INSTANCE_ID: Module Instance ID. + * + * %AR_TKN_U32_MODULE_MAX_IP_PORTS: Module maximum input ports + * + * %AR_TKN_U32_MODULE_MAX_OP_PORTS: Module maximum output ports. + * + * %AR_TKN_U32_MODULE_IN_PORTS: Number of in ports + * + * %AR_TKN_U32_MODULE_OUT_PORTS: Number of out ports. + * + * %AR_TKN_U32_MODULE_SRC_OP_PORT_ID: Source module output port ID + * + * %AR_TKN_U32_MODULE_DST_IN_PORT_ID: Destination module input port ID + * + * %AR_TKN_U32_MODULE_HW_IF_IDX: Interface index types for I2S/LPAIF + * + * %AR_TKN_U32_MODULE_HW_IF_TYPE: Interface type + * LPAIF = 0, + * LPAIF_RXTX = 1, + * LPAIF_WSA = 2, + * LPAIF_VA = 3, + * LPAIF_AXI = 4 + * + * %AR_TKN_U32_MODULE_FMT_INTERLEAVE: PCM Interleaving + * PCM_INTERLEAVED = 1, + * PCM_DEINTERLEAVED_PACKED = 2, + * PCM_DEINTERLEAVED_UNPACKED = 3 + * + * %AR_TKN_U32_MODULE_FMT_DATA: data format + * FIXED POINT = 1, + * IEC60958 PACKETIZED = 3, + * IEC60958 PACKETIZED NON LINEAR = 8, + * COMPR OVER PCM PACKETIZED = 7, + * IEC61937 PACKETIZED = 2, + * GENERIC COMPRESSED = 5 + * + * %AR_TKN_U32_MODULE_FMT_SAMPLE_RATE: sample rate + * + * %AR_TKN_U32_MODULE_FMT_BIT_DEPTH: bit depth + * + * %AR_TKN_U32_MODULE_SD_LINE_IDX: I2S serial data line idx + * I2S_SD0 = 1, + * I2S_SD1 = 2, + * I2S_SD2 = 3, + * I2S_SD3 = 4, + * I2S_QUAD01 = 5, + * I2S_QUAD23 = 6, + * I2S_6CHS = 7, + * I2S_8CHS = 8 + * + * %AR_TKN_U32_MODULE_WS_SRC: Word Select Source + * AR_I2S_WS_SRC_EXTERNAL = 0, + * AR_I2S_WS_SRC_INTERNAL = 1, + * + * %AR_TKN_U32_MODULE_FRAME_SZ_FACTOR: Frame size factor + * + * %AR_TKN_U32_MODULE_LOG_CODE: Log Module Code + * + * %AR_TKN_U32_MODULE_LOG_TAP_POINT_ID: logging tap point of this module + * + * %AR_TKN_U32_MODULE_LOG_MODE: logging mode + * LOG_WAIT = 0, + * LOG_IMMEDIATELY = 1 + * + * %AR_TKN_DAI_INDEX: dai index + * + */ + +/* DAI Tokens */ +#define AR_TKN_DAI_INDEX 1 +/* SUB GRAPH Tokens */ +#define AR_TKN_U32_SUB_GRAPH_INSTANCE_ID 2 +#define AR_TKN_U32_SUB_GRAPH_PERF_MODE 3 +#define AR_TKN_U32_SUB_GRAPH_DIRECTION 4 +#define AR_TKN_U32_SUB_GRAPH_SCENARIO_ID 5 + +/* Container Tokens */ +#define AR_TKN_U32_CONTAINER_INSTANCE_ID 100 +#define AR_TKN_U32_CONTAINER_CAPABILITY_ID 101 +#define AR_TKN_U32_CONTAINER_STACK_SIZE 102 +#define AR_TKN_U32_CONTAINER_GRAPH_POS 103 +#define AR_TKN_U32_CONTAINER_PROC_DOMAIN 104 + +/* Module Tokens */ +#define AR_TKN_U32_MODULE_ID 200 +#define AR_TKN_U32_MODULE_INSTANCE_ID 201 +#define AR_TKN_U32_MODULE_MAX_IP_PORTS 202 +#define AR_TKN_U32_MODULE_MAX_OP_PORTS 203 +#define AR_TKN_U32_MODULE_IN_PORTS 204 +#define AR_TKN_U32_MODULE_OUT_PORTS 205 +#define AR_TKN_U32_MODULE_SRC_OP_PORT_ID 206 +#define AR_TKN_U32_MODULE_DST_IN_PORT_ID 207 +#define AR_TKN_U32_MODULE_SRC_INSTANCE_ID 208 +#define AR_TKN_U32_MODULE_DST_INSTANCE_ID 209 + + +#define AR_TKN_U32_MODULE_HW_IF_IDX 250 +#define AR_TKN_U32_MODULE_HW_IF_TYPE 251 +#define AR_TKN_U32_MODULE_FMT_INTERLEAVE 252 +#define AR_TKN_U32_MODULE_FMT_DATA 253 +#define AR_TKN_U32_MODULE_FMT_SAMPLE_RATE 254 +#define AR_TKN_U32_MODULE_FMT_BIT_DEPTH 255 +#define AR_TKN_U32_MODULE_SD_LINE_IDX 256 +#define AR_TKN_U32_MODULE_WS_SRC 257 +#define AR_TKN_U32_MODULE_FRAME_SZ_FACTOR 258 +#define AR_TKN_U32_MODULE_LOG_CODE 259 +#define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 +#define AR_TKN_U32_MODULE_LOG_MODE 261 + +#endif /* __SND_AR_TOKENS_H__ */ diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h index a642bf30c027..02b71a8deea4 100644 --- a/include/uapi/sound/sof/tokens.h +++ b/include/uapi/sound/sof/tokens.h @@ -51,6 +51,7 @@ #define SOF_TKN_SCHED_CORE 203 #define SOF_TKN_SCHED_FRAMES 204 #define SOF_TKN_SCHED_TIME_DOMAIN 205 +#define SOF_TKN_SCHED_DYNAMIC_PIPELINE 206 /* volume */ #define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250 diff --git a/sound/core/Makefile b/sound/core/Makefile index d774792850f3..350d704ced98 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -9,7 +9,9 @@ ifneq ($(CONFIG_SND_PROC_FS),) snd-y += info.o snd-$(CONFIG_SND_OSSEMUL) += info_oss.o endif +ifneq ($(CONFIG_M68K),y) snd-$(CONFIG_ISA_DMA_API) += isadma.o +endif snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o snd-$(CONFIG_SND_VMASTER) += vmaster.o snd-$(CONFIG_SND_JACK) += ctljack.o jack.o @@ -17,7 +19,6 @@ snd-$(CONFIG_SND_JACK) += ctljack.o jack.o snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_misc.o \ pcm_memory.o memalloc.o snd-pcm-$(CONFIG_SND_PCM_TIMER) += pcm_timer.o -snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o snd-pcm-$(CONFIG_SND_PCM_ELD) += pcm_drm_eld.o snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index c7c943c661e6..99cd0f67daa1 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -10,6 +10,7 @@ #include <linux/mm.h> #include <linux/dma-mapping.h> #include <linux/genalloc.h> +#include <linux/highmem.h> #include <linux/vmalloc.h> #ifdef CONFIG_X86 #include <asm/set_memory.h> @@ -39,9 +40,11 @@ static void *__snd_dma_alloc_pages(struct snd_dma_buffer *dmab, size_t size) } /** - * snd_dma_alloc_pages - allocate the buffer area according to the given type + * snd_dma_alloc_dir_pages - allocate the buffer area according to the given + * type and direction * @type: the DMA buffer type * @device: the device pointer + * @dir: DMA direction * @size: the buffer size to allocate * @dmab: buffer allocation record to store the allocated data * @@ -51,8 +54,9 @@ static void *__snd_dma_alloc_pages(struct snd_dma_buffer *dmab, size_t size) * Return: Zero if the buffer with the given size is allocated successfully, * otherwise a negative value on error. */ -int snd_dma_alloc_pages(int type, struct device *device, size_t size, - struct snd_dma_buffer *dmab) +int snd_dma_alloc_dir_pages(int type, struct device *device, + enum dma_data_direction dir, size_t size, + struct snd_dma_buffer *dmab) { if (WARN_ON(!size)) return -ENXIO; @@ -62,6 +66,7 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size, size = PAGE_ALIGN(size); dmab->dev.type = type; dmab->dev.dev = device; + dmab->dev.dir = dir; dmab->bytes = 0; dmab->addr = 0; dmab->private_data = NULL; @@ -71,7 +76,7 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size, dmab->bytes = size; return 0; } -EXPORT_SYMBOL(snd_dma_alloc_pages); +EXPORT_SYMBOL(snd_dma_alloc_dir_pages); /** * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback @@ -129,9 +134,10 @@ static void __snd_release_pages(struct device *dev, void *res) } /** - * snd_devm_alloc_pages - allocate the buffer and manage with devres + * snd_devm_alloc_dir_pages - allocate the buffer and manage with devres * @dev: the device pointer * @type: the DMA buffer type + * @dir: DMA direction * @size: the buffer size to allocate * * Allocate buffer pages depending on the given type and manage using devres. @@ -144,7 +150,8 @@ static void __snd_release_pages(struct device *dev, void *res) * The function returns the snd_dma_buffer object at success, or NULL if failed. */ struct snd_dma_buffer * -snd_devm_alloc_pages(struct device *dev, int type, size_t size) +snd_devm_alloc_dir_pages(struct device *dev, int type, + enum dma_data_direction dir, size_t size) { struct snd_dma_buffer *dmab; int err; @@ -157,7 +164,7 @@ snd_devm_alloc_pages(struct device *dev, int type, size_t size) if (!dmab) return NULL; - err = snd_dma_alloc_pages(type, dev, size, dmab); + err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab); if (err < 0) { devres_free(dmab); return NULL; @@ -166,7 +173,7 @@ snd_devm_alloc_pages(struct device *dev, int type, size_t size) devres_add(dev, dmab); return dmab; } -EXPORT_SYMBOL_GPL(snd_devm_alloc_pages); +EXPORT_SYMBOL_GPL(snd_devm_alloc_dir_pages); /** * snd_dma_buffer_mmap - perform mmap of the given DMA buffer @@ -185,6 +192,26 @@ int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab, } EXPORT_SYMBOL(snd_dma_buffer_mmap); +#ifdef CONFIG_HAS_DMA +/** + * snd_dma_buffer_sync - sync DMA buffer between CPU and device + * @dmab: buffer allocation information + * @mode: sync mode + */ +void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + const struct snd_malloc_ops *ops; + + if (!dmab || !dmab->dev.need_sync) + return; + ops = snd_dma_get_ops(dmab); + if (ops && ops->sync) + ops->sync(dmab, mode); +} +EXPORT_SYMBOL_GPL(snd_dma_buffer_sync); +#endif /* CONFIG_HAS_DMA */ + /** * snd_sgbuf_get_addr - return the physical address at the corresponding offset * @dmab: buffer allocation information @@ -468,6 +495,161 @@ static const struct snd_malloc_ops snd_dma_wc_ops = { .mmap = snd_dma_wc_mmap, }; #endif /* CONFIG_X86 */ + +/* + * Non-contiguous pages allocator + */ +static void *snd_dma_noncontig_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + struct sg_table *sgt; + void *p; + + sgt = dma_alloc_noncontiguous(dmab->dev.dev, size, dmab->dev.dir, + DEFAULT_GFP, 0); + if (!sgt) + return NULL; + dmab->dev.need_sync = dma_need_sync(dmab->dev.dev, dmab->dev.dir); + p = dma_vmap_noncontiguous(dmab->dev.dev, size, sgt); + if (p) + dmab->private_data = sgt; + else + dma_free_noncontiguous(dmab->dev.dev, size, sgt, dmab->dev.dir); + return p; +} + +static void snd_dma_noncontig_free(struct snd_dma_buffer *dmab) +{ + dma_vunmap_noncontiguous(dmab->dev.dev, dmab->area); + dma_free_noncontiguous(dmab->dev.dev, dmab->bytes, dmab->private_data, + dmab->dev.dir); +} + +static int snd_dma_noncontig_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + return dma_mmap_noncontiguous(dmab->dev.dev, area, + dmab->bytes, dmab->private_data); +} + +static void snd_dma_noncontig_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + if (mode == SNDRV_DMA_SYNC_CPU) { + if (dmab->dev.dir == DMA_TO_DEVICE) + return; + dma_sync_sgtable_for_cpu(dmab->dev.dev, dmab->private_data, + dmab->dev.dir); + invalidate_kernel_vmap_range(dmab->area, dmab->bytes); + } else { + if (dmab->dev.dir == DMA_FROM_DEVICE) + return; + flush_kernel_vmap_range(dmab->area, dmab->bytes); + dma_sync_sgtable_for_device(dmab->dev.dev, dmab->private_data, + dmab->dev.dir); + } +} + +static const struct snd_malloc_ops snd_dma_noncontig_ops = { + .alloc = snd_dma_noncontig_alloc, + .free = snd_dma_noncontig_free, + .mmap = snd_dma_noncontig_mmap, + .sync = snd_dma_noncontig_sync, + /* re-use vmalloc helpers for get_* ops */ + .get_addr = snd_dma_vmalloc_get_addr, + .get_page = snd_dma_vmalloc_get_page, + .get_chunk_size = snd_dma_vmalloc_get_chunk_size, +}; + +/* x86-specific SG-buffer with WC pages */ +#ifdef CONFIG_SND_DMA_SGBUF +#define vmalloc_to_virt(v) (unsigned long)page_to_virt(vmalloc_to_page(v)) + +static void *snd_dma_sg_wc_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + void *p = snd_dma_noncontig_alloc(dmab, size); + size_t ofs; + + if (!p) + return NULL; + for (ofs = 0; ofs < size; ofs += PAGE_SIZE) + set_memory_uc(vmalloc_to_virt(p + ofs), 1); + return p; +} + +static void snd_dma_sg_wc_free(struct snd_dma_buffer *dmab) +{ + size_t ofs; + + for (ofs = 0; ofs < dmab->bytes; ofs += PAGE_SIZE) + set_memory_wb(vmalloc_to_virt(dmab->area + ofs), 1); + snd_dma_noncontig_free(dmab); +} + +static int snd_dma_sg_wc_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); + /* FIXME: dma_mmap_noncontiguous() works? */ + return -ENOENT; /* continue with the default mmap handler */ +} + +const struct snd_malloc_ops snd_dma_sg_wc_ops = { + .alloc = snd_dma_sg_wc_alloc, + .free = snd_dma_sg_wc_free, + .mmap = snd_dma_sg_wc_mmap, + .sync = snd_dma_noncontig_sync, + .get_addr = snd_dma_vmalloc_get_addr, + .get_page = snd_dma_vmalloc_get_page, + .get_chunk_size = snd_dma_vmalloc_get_chunk_size, +}; +#endif /* CONFIG_SND_DMA_SGBUF */ + +/* + * Non-coherent pages allocator + */ +static void *snd_dma_noncoherent_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + dmab->dev.need_sync = dma_need_sync(dmab->dev.dev, dmab->dev.dir); + return dma_alloc_noncoherent(dmab->dev.dev, size, &dmab->addr, + dmab->dev.dir, DEFAULT_GFP); +} + +static void snd_dma_noncoherent_free(struct snd_dma_buffer *dmab) +{ + dma_free_noncoherent(dmab->dev.dev, dmab->bytes, dmab->area, + dmab->addr, dmab->dev.dir); +} + +static int snd_dma_noncoherent_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + area->vm_page_prot = vm_get_page_prot(area->vm_flags); + return dma_mmap_pages(dmab->dev.dev, area, + area->vm_end - area->vm_start, + virt_to_page(dmab->area)); +} + +static void snd_dma_noncoherent_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + if (mode == SNDRV_DMA_SYNC_CPU) { + if (dmab->dev.dir != DMA_TO_DEVICE) + dma_sync_single_for_cpu(dmab->dev.dev, dmab->addr, + dmab->bytes, dmab->dev.dir); + } else { + if (dmab->dev.dir != DMA_FROM_DEVICE) + dma_sync_single_for_device(dmab->dev.dev, dmab->addr, + dmab->bytes, dmab->dev.dir); + } +} + +static const struct snd_malloc_ops snd_dma_noncoherent_ops = { + .alloc = snd_dma_noncoherent_alloc, + .free = snd_dma_noncoherent_free, + .mmap = snd_dma_noncoherent_mmap, + .sync = snd_dma_noncoherent_sync, +}; + #endif /* CONFIG_HAS_DMA */ /* @@ -479,14 +661,15 @@ static const struct snd_malloc_ops *dma_ops[] = { #ifdef CONFIG_HAS_DMA [SNDRV_DMA_TYPE_DEV] = &snd_dma_dev_ops, [SNDRV_DMA_TYPE_DEV_WC] = &snd_dma_wc_ops, + [SNDRV_DMA_TYPE_NONCONTIG] = &snd_dma_noncontig_ops, + [SNDRV_DMA_TYPE_NONCOHERENT] = &snd_dma_noncoherent_ops, +#ifdef CONFIG_SND_DMA_SGBUF + [SNDRV_DMA_TYPE_DEV_WC_SG] = &snd_dma_sg_wc_ops, +#endif #ifdef CONFIG_GENERIC_ALLOCATOR [SNDRV_DMA_TYPE_DEV_IRAM] = &snd_dma_iram_ops, #endif /* CONFIG_GENERIC_ALLOCATOR */ #endif /* CONFIG_HAS_DMA */ -#ifdef CONFIG_SND_DMA_SGBUF - [SNDRV_DMA_TYPE_DEV_SG] = &snd_dma_sg_ops, - [SNDRV_DMA_TYPE_DEV_WC_SG] = &snd_dma_sg_ops, -#endif }; static const struct snd_malloc_ops *snd_dma_get_ops(struct snd_dma_buffer *dmab) diff --git a/sound/core/memalloc_local.h b/sound/core/memalloc_local.h index 9f2e0a608b49..a6f3a87194da 100644 --- a/sound/core/memalloc_local.h +++ b/sound/core/memalloc_local.h @@ -10,6 +10,7 @@ struct snd_malloc_ops { unsigned int (*get_chunk_size)(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size); int (*mmap)(struct snd_dma_buffer *dmab, struct vm_area_struct *area); + void (*sync)(struct snd_dma_buffer *dmab, enum snd_dma_sync_mode mode); }; #ifdef CONFIG_SND_DMA_SGBUF diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index 6a5abdd4271b..9620115cfdc0 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -130,11 +130,13 @@ static int snd_mixer_oss_devmask(struct snd_mixer_oss_file *fmixer) if (mixer == NULL) return -EIO; + mutex_lock(&mixer->reg_mutex); for (chn = 0; chn < 31; chn++) { pslot = &mixer->slots[chn]; if (pslot->put_volume || pslot->put_recsrc) result |= 1 << chn; } + mutex_unlock(&mixer->reg_mutex); return result; } @@ -146,11 +148,13 @@ static int snd_mixer_oss_stereodevs(struct snd_mixer_oss_file *fmixer) if (mixer == NULL) return -EIO; + mutex_lock(&mixer->reg_mutex); for (chn = 0; chn < 31; chn++) { pslot = &mixer->slots[chn]; if (pslot->put_volume && pslot->stereo) result |= 1 << chn; } + mutex_unlock(&mixer->reg_mutex); return result; } @@ -161,6 +165,7 @@ static int snd_mixer_oss_recmask(struct snd_mixer_oss_file *fmixer) if (mixer == NULL) return -EIO; + mutex_lock(&mixer->reg_mutex); if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ result = mixer->mask_recsrc; } else { @@ -172,6 +177,7 @@ static int snd_mixer_oss_recmask(struct snd_mixer_oss_file *fmixer) result |= 1 << chn; } } + mutex_unlock(&mixer->reg_mutex); return result; } @@ -182,12 +188,12 @@ static int snd_mixer_oss_get_recsrc(struct snd_mixer_oss_file *fmixer) if (mixer == NULL) return -EIO; + mutex_lock(&mixer->reg_mutex); if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ - int err; unsigned int index; - err = mixer->get_recsrc(fmixer, &index); - if (err < 0) - return err; + result = mixer->get_recsrc(fmixer, &index); + if (result < 0) + goto unlock; result = 1 << index; } else { struct snd_mixer_oss_slot *pslot; @@ -202,7 +208,10 @@ static int snd_mixer_oss_get_recsrc(struct snd_mixer_oss_file *fmixer) } } } - return mixer->oss_recsrc = result; + mixer->oss_recsrc = result; + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; } static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsrc) @@ -215,6 +224,7 @@ static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsr if (mixer == NULL) return -EIO; + mutex_lock(&mixer->reg_mutex); if (mixer->get_recsrc && mixer->put_recsrc) { /* exclusive input */ if (recsrc & ~mixer->oss_recsrc) recsrc &= ~mixer->oss_recsrc; @@ -240,6 +250,7 @@ static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsr } } } + mutex_unlock(&mixer->reg_mutex); return result; } @@ -251,6 +262,7 @@ static int snd_mixer_oss_get_volume(struct snd_mixer_oss_file *fmixer, int slot) if (mixer == NULL || slot > 30) return -EIO; + mutex_lock(&mixer->reg_mutex); pslot = &mixer->slots[slot]; left = pslot->volume[0]; right = pslot->volume[1]; @@ -258,15 +270,21 @@ static int snd_mixer_oss_get_volume(struct snd_mixer_oss_file *fmixer, int slot) result = pslot->get_volume(fmixer, pslot, &left, &right); if (!pslot->stereo) right = left; - if (snd_BUG_ON(left < 0 || left > 100)) - return -EIO; - if (snd_BUG_ON(right < 0 || right > 100)) - return -EIO; + if (snd_BUG_ON(left < 0 || left > 100)) { + result = -EIO; + goto unlock; + } + if (snd_BUG_ON(right < 0 || right > 100)) { + result = -EIO; + goto unlock; + } if (result >= 0) { pslot->volume[0] = left; pslot->volume[1] = right; result = (left & 0xff) | ((right & 0xff) << 8); } + unlock: + mutex_unlock(&mixer->reg_mutex); return result; } @@ -279,6 +297,7 @@ static int snd_mixer_oss_set_volume(struct snd_mixer_oss_file *fmixer, if (mixer == NULL || slot > 30) return -EIO; + mutex_lock(&mixer->reg_mutex); pslot = &mixer->slots[slot]; if (left > 100) left = 100; @@ -289,10 +308,13 @@ static int snd_mixer_oss_set_volume(struct snd_mixer_oss_file *fmixer, if (pslot->put_volume) result = pslot->put_volume(fmixer, pslot, left, right); if (result < 0) - return result; + goto unlock; pslot->volume[0] = left; pslot->volume[1] = right; - return (left & 0xff) | ((right & 0xff) << 8); + result = (left & 0xff) | ((right & 0xff) << 8); + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; } static int snd_mixer_oss_ioctl1(struct snd_mixer_oss_file *fmixer, unsigned int cmd, unsigned long arg) diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index dfe5a64e19d2..e4e176854ce7 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -453,6 +453,8 @@ static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream, sstatus.suspended_state = status->suspended_state; sstatus.audio_tstamp = status->audio_tstamp; snd_pcm_stream_unlock_irq(substream); + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); if (put_user(sstatus.state, &src->s.status.state) || put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) || @@ -533,6 +535,8 @@ static int snd_pcm_ioctl_sync_ptr_buggy(struct snd_pcm_substream *substream, sync_ptr.s.status.suspended_state = status->suspended_state; sync_ptr.s.status.audio_tstamp = status->audio_tstamp; snd_pcm_stream_unlock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr))) return -EFAULT; return 0; diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index a144a3f68e9e..4f4b4739f987 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -106,6 +106,7 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram frames -= transfer; ofs = 0; } + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); } #ifdef CONFIG_SND_DEBUG @@ -2256,8 +2257,12 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, goto _end_unlock; } snd_pcm_stream_unlock_irq(substream); + if (!is_playback) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); err = writer(substream, appl_ofs, data, offset, frames, transfer); + if (is_playback) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); snd_pcm_stream_lock_irq(substream); if (err < 0) goto _end_unlock; diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h index fe9689b8a6a6..ecb21697ae3a 100644 --- a/sound/core/pcm_local.h +++ b/sound/core/pcm_local.h @@ -73,4 +73,11 @@ void snd_pcm_sync_stop(struct snd_pcm_substream *substream, bool sync_irq); for ((subs) = (pcm)->streams[str].substream; (subs); \ (subs) = (subs)->next) +static inline void snd_pcm_dma_buffer_sync(struct snd_pcm_substream *substream, + enum snd_dma_sync_mode mode) +{ + if (substream->runtime->info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), mode); +} + #endif /* __SOUND_CORE_PCM_LOCAL_H */ diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index 7fbd1ccbb5b0..b70ce3b69ab4 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -32,15 +32,20 @@ module_param(max_alloc_per_card, ulong, 0644); MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card."); static int do_alloc_pages(struct snd_card *card, int type, struct device *dev, - size_t size, struct snd_dma_buffer *dmab) + int str, size_t size, struct snd_dma_buffer *dmab) { + enum dma_data_direction dir; int err; if (max_alloc_per_card && card->total_pcm_alloc_bytes + size > max_alloc_per_card) return -ENOMEM; - err = snd_dma_alloc_pages(type, dev, size, dmab); + if (str == SNDRV_PCM_STREAM_PLAYBACK) + dir = DMA_TO_DEVICE; + else + dir = DMA_FROM_DEVICE; + err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab); if (!err) { mutex_lock(&card->memory_mutex); card->total_pcm_alloc_bytes += dmab->bytes; @@ -77,7 +82,7 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream, do { err = do_alloc_pages(card, dmab->dev.type, dmab->dev.dev, - size, dmab); + substream->stream, size, dmab); if (err != -ENOMEM) return err; if (no_fallback) @@ -177,6 +182,7 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, if (do_alloc_pages(card, substream->dma_buffer.dev.type, substream->dma_buffer.dev.dev, + substream->stream, size, &new_dmab) < 0) { buffer->error = -ENOMEM; pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n", @@ -418,6 +424,7 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) if (do_alloc_pages(card, substream->dma_buffer.dev.type, substream->dma_buffer.dev.dev, + substream->stream, size, dmab) < 0) { kfree(dmab); pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n", diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index d233cb3b41d8..621883e71194 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2685,6 +2685,13 @@ int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, goto error; } + /* automatically set EXPLICIT_SYNC flag in the managed mode whenever + * the DMA buffer requires it + */ + if (substream->managed_buffer_alloc && + substream->dma_buffer.dev.need_sync) + substream->runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC; + *rsubstream = substream; return 0; @@ -2912,6 +2919,8 @@ static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, ret = rewind_appl_ptr(substream, frames, snd_pcm_hw_avail(substream)); snd_pcm_stream_unlock_irq(substream); + if (ret >= 0) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); return ret; } @@ -2929,35 +2938,31 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, ret = forward_appl_ptr(substream, frames, snd_pcm_avail(substream)); snd_pcm_stream_unlock_irq(substream); + if (ret >= 0) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); return ret; } -static int snd_pcm_hwsync(struct snd_pcm_substream *substream) -{ - int err; - - snd_pcm_stream_lock_irq(substream); - err = do_pcm_hwsync(substream); - snd_pcm_stream_unlock_irq(substream); - return err; -} - static int snd_pcm_delay(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay) { int err; - snd_pcm_sframes_t n = 0; snd_pcm_stream_lock_irq(substream); err = do_pcm_hwsync(substream); - if (!err) - n = snd_pcm_calc_delay(substream); + if (delay && !err) + *delay = snd_pcm_calc_delay(substream); snd_pcm_stream_unlock_irq(substream); - if (!err) - *delay = n; + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); + return err; } +static inline int snd_pcm_hwsync(struct snd_pcm_substream *substream) +{ + return snd_pcm_delay(substream, NULL); +} + static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, struct snd_pcm_sync_ptr __user *_sync_ptr) { @@ -3000,6 +3005,8 @@ static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, sync_ptr.s.status.suspended_state = status->suspended_state; sync_ptr.s.status.audio_tstamp = status->audio_tstamp; snd_pcm_stream_unlock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr))) return -EFAULT; return 0; @@ -3096,6 +3103,8 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream, sstatus.suspended_state = status->suspended_state; sstatus.audio_tstamp = status->audio_tstamp; snd_pcm_stream_unlock_irq(substream); + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); if (put_user(sstatus.state, &src->s.status.state) || put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) || @@ -3218,6 +3227,9 @@ static int snd_pcm_common_ioctl(struct file *file, if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; + if (substream->runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + res = snd_power_wait(substream->pcm->card); if (res < 0) return res; @@ -3272,7 +3284,7 @@ static int snd_pcm_common_ioctl(struct file *file, return snd_pcm_hwsync(substream); case SNDRV_PCM_IOCTL_DELAY: { - snd_pcm_sframes_t delay; + snd_pcm_sframes_t delay = 0; snd_pcm_sframes_t __user *res = arg; int err; @@ -3344,6 +3356,9 @@ int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, snd_pcm_uframes_t *frames = arg; snd_pcm_sframes_t result; + if (substream->runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + switch (cmd) { case SNDRV_PCM_IOCTL_FORWARD: { @@ -3386,7 +3401,8 @@ static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count, if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; - if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + if (runtime->status->state == SNDRV_PCM_STATE_OPEN || + runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; if (!frame_aligned(runtime, count)) return -EINVAL; @@ -3410,7 +3426,8 @@ static ssize_t snd_pcm_write(struct file *file, const char __user *buf, if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; - if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + if (runtime->status->state == SNDRV_PCM_STATE_OPEN || + runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; if (!frame_aligned(runtime, count)) return -EINVAL; @@ -3436,7 +3453,8 @@ static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to) if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; - if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + if (runtime->status->state == SNDRV_PCM_STATE_OPEN || + runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; if (!iter_is_iovec(to)) return -EINVAL; @@ -3472,7 +3490,8 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from) if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; - if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + if (runtime->status->state == SNDRV_PCM_STATE_OPEN || + runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; if (!iter_is_iovec(from)) return -EINVAL; @@ -3511,6 +3530,9 @@ static __poll_t snd_pcm_poll(struct file *file, poll_table *wait) return ok | EPOLLERR; runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) + return ok | EPOLLERR; + poll_wait(file, &runtime->sleep, wait); mask = 0; @@ -3820,6 +3842,8 @@ static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area) substream = pcm_file->substream; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; + if (substream->runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; offset = area->vm_pgoff << PAGE_SHIFT; switch (offset) { @@ -3856,6 +3880,8 @@ static int snd_pcm_fasync(int fd, struct file * file, int on) if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; return fasync_helper(fd, file, on, &runtime->fasync); } diff --git a/sound/core/sgbuf.c b/sound/core/sgbuf.c deleted file mode 100644 index 8352a5cdb19f..000000000000 --- a/sound/core/sgbuf.c +++ /dev/null @@ -1,201 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Scatter-Gather buffer - * - * Copyright (c) by Takashi Iwai <tiwai@suse.de> - */ - -#include <linux/slab.h> -#include <linux/mm.h> -#include <linux/vmalloc.h> -#include <linux/export.h> -#include <sound/memalloc.h> -#include "memalloc_local.h" - -struct snd_sg_page { - void *buf; - dma_addr_t addr; -}; - -struct snd_sg_buf { - int size; /* allocated byte size */ - int pages; /* allocated pages */ - int tblsize; /* allocated table size */ - struct snd_sg_page *table; /* address table */ - struct page **page_table; /* page table (for vmap/vunmap) */ - struct device *dev; -}; - -/* table entries are align to 32 */ -#define SGBUF_TBL_ALIGN 32 -#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN) - -static void snd_dma_sg_free(struct snd_dma_buffer *dmab) -{ - struct snd_sg_buf *sgbuf = dmab->private_data; - struct snd_dma_buffer tmpb; - int i; - - if (!sgbuf) - return; - - vunmap(dmab->area); - dmab->area = NULL; - - tmpb.dev.type = SNDRV_DMA_TYPE_DEV; - if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) - tmpb.dev.type = SNDRV_DMA_TYPE_DEV_WC; - tmpb.dev.dev = sgbuf->dev; - for (i = 0; i < sgbuf->pages; i++) { - if (!(sgbuf->table[i].addr & ~PAGE_MASK)) - continue; /* continuous pages */ - tmpb.area = sgbuf->table[i].buf; - tmpb.addr = sgbuf->table[i].addr & PAGE_MASK; - tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT; - snd_dma_free_pages(&tmpb); - } - - kfree(sgbuf->table); - kfree(sgbuf->page_table); - kfree(sgbuf); - dmab->private_data = NULL; -} - -#define MAX_ALLOC_PAGES 32 - -static void *snd_dma_sg_alloc(struct snd_dma_buffer *dmab, size_t size) -{ - struct snd_sg_buf *sgbuf; - unsigned int i, pages, chunk, maxpages; - struct snd_dma_buffer tmpb; - struct snd_sg_page *table; - struct page **pgtable; - int type = SNDRV_DMA_TYPE_DEV; - pgprot_t prot = PAGE_KERNEL; - void *area; - - dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL); - if (!sgbuf) - return NULL; - if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) { - type = SNDRV_DMA_TYPE_DEV_WC; -#ifdef pgprot_noncached - prot = pgprot_noncached(PAGE_KERNEL); -#endif - } - sgbuf->dev = dmab->dev.dev; - pages = snd_sgbuf_aligned_pages(size); - sgbuf->tblsize = sgbuf_align_table(pages); - table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL); - if (!table) - goto _failed; - sgbuf->table = table; - pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL); - if (!pgtable) - goto _failed; - sgbuf->page_table = pgtable; - - /* allocate pages */ - maxpages = MAX_ALLOC_PAGES; - while (pages > 0) { - chunk = pages; - /* don't be too eager to take a huge chunk */ - if (chunk > maxpages) - chunk = maxpages; - chunk <<= PAGE_SHIFT; - if (snd_dma_alloc_pages_fallback(type, dmab->dev.dev, - chunk, &tmpb) < 0) { - if (!sgbuf->pages) - goto _failed; - size = sgbuf->pages * PAGE_SIZE; - break; - } - chunk = tmpb.bytes >> PAGE_SHIFT; - for (i = 0; i < chunk; i++) { - table->buf = tmpb.area; - table->addr = tmpb.addr; - if (!i) - table->addr |= chunk; /* mark head */ - table++; - *pgtable++ = virt_to_page(tmpb.area); - tmpb.area += PAGE_SIZE; - tmpb.addr += PAGE_SIZE; - } - sgbuf->pages += chunk; - pages -= chunk; - if (chunk < maxpages) - maxpages = chunk; - } - - sgbuf->size = size; - area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, prot); - if (!area) - goto _failed; - return area; - - _failed: - snd_dma_sg_free(dmab); /* free the table */ - return NULL; -} - -static dma_addr_t snd_dma_sg_get_addr(struct snd_dma_buffer *dmab, - size_t offset) -{ - struct snd_sg_buf *sgbuf = dmab->private_data; - dma_addr_t addr; - - addr = sgbuf->table[offset >> PAGE_SHIFT].addr; - addr &= ~((dma_addr_t)PAGE_SIZE - 1); - return addr + offset % PAGE_SIZE; -} - -static struct page *snd_dma_sg_get_page(struct snd_dma_buffer *dmab, - size_t offset) -{ - struct snd_sg_buf *sgbuf = dmab->private_data; - unsigned int idx = offset >> PAGE_SHIFT; - - if (idx >= (unsigned int)sgbuf->pages) - return NULL; - return sgbuf->page_table[idx]; -} - -static unsigned int snd_dma_sg_get_chunk_size(struct snd_dma_buffer *dmab, - unsigned int ofs, - unsigned int size) -{ - struct snd_sg_buf *sg = dmab->private_data; - unsigned int start, end, pg; - - start = ofs >> PAGE_SHIFT; - end = (ofs + size - 1) >> PAGE_SHIFT; - /* check page continuity */ - pg = sg->table[start].addr >> PAGE_SHIFT; - for (;;) { - start++; - if (start > end) - break; - pg++; - if ((sg->table[start].addr >> PAGE_SHIFT) != pg) - return (start << PAGE_SHIFT) - ofs; - } - /* ok, all on continuous pages */ - return size; -} - -static int snd_dma_sg_mmap(struct snd_dma_buffer *dmab, - struct vm_area_struct *area) -{ - if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) - area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); - return -ENOENT; /* continue with the default mmap handler */ -} - -const struct snd_malloc_ops snd_dma_sg_ops = { - .alloc = snd_dma_sg_alloc, - .free = snd_dma_sg_free, - .get_addr = snd_dma_sg_get_addr, - .get_page = snd_dma_sg_get_page, - .get_chunk_size = snd_dma_sg_get_chunk_size, - .mmap = snd_dma_sg_mmap, -}; diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index acdf66564fb0..3bef2a0b1e2e 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -4,5 +4,6 @@ CFLAGS_amdtp-motu.o := -I$(src) snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ motu-protocol-v2.o motu-protocol-v3.o \ - motu-protocol-v1.o + motu-protocol-v1.o motu-register-dsp-message-parser.o \ + motu-command-dsp-message-parser.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index a18c2c033e83..2fb52f481d12 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -333,6 +333,7 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, unsigned int packets, struct snd_pcm_substream *pcm) { + struct snd_motu *motu = container_of(s, struct snd_motu, tx_stream); struct amdtp_motu *p = s->protocol; unsigned int pcm_frames = 0; int i; @@ -357,6 +358,14 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, read_midi_messages(s, buf, data_blocks); } + if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) { + snd_motu_register_dsp_message_parser_parse(motu, descs, packets, + s->data_block_quadlets); + } else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) { + snd_motu_command_dsp_message_parser_parse(motu, descs, packets, + s->data_block_quadlets); + } + // For tracepoints. if (trace_data_block_sph_enabled() || trace_data_block_message_enabled()) @@ -415,8 +424,6 @@ static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, if (p->midi_ports) write_midi_messages(s, buf, data_blocks); - // TODO: how to interact control messages between userspace? - write_sph(p->cache, buf, data_blocks, s->data_block_quadlets); } diff --git a/sound/firewire/motu/motu-command-dsp-message-parser.c b/sound/firewire/motu/motu-command-dsp-message-parser.c new file mode 100644 index 000000000000..9efe4d364baf --- /dev/null +++ b/sound/firewire/motu/motu-command-dsp-message-parser.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// motu-command-dsp-message-parser.c - a part of driver for MOTU FireWire series +// +// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp> + +// Below models allow software to configure their DSP function by command transferred in +// asynchronous transaction: +// * 828 mk3 (FireWire only and Hybrid) +// * 896 mk3 (FireWire only and Hybrid) +// * Ultralite mk3 (FireWire only and Hybrid) +// * Traveler mk3 +// * Track 16 +// +// Isochronous packets from the above models includes messages to report state of hardware meter. + +#include "motu.h" + +enum msg_parser_state { + INITIALIZED, + FRAGMENT_DETECTED, + AVAILABLE, +}; + +struct msg_parser { + spinlock_t lock; + enum msg_parser_state state; + unsigned int interval; + unsigned int message_count; + unsigned int fragment_pos; + unsigned int value_index; + u64 value; + struct snd_firewire_motu_command_dsp_meter meter; +}; + +int snd_motu_command_dsp_message_parser_new(struct snd_motu *motu) +{ + struct msg_parser *parser; + + parser = devm_kzalloc(&motu->card->card_dev, sizeof(*parser), GFP_KERNEL); + if (!parser) + return -ENOMEM; + spin_lock_init(&parser->lock); + motu->message_parser = parser; + + return 0; +} + +int snd_motu_command_dsp_message_parser_init(struct snd_motu *motu, enum cip_sfc sfc) +{ + struct msg_parser *parser = motu->message_parser; + + parser->state = INITIALIZED; + + // All of data blocks don't have messages with meaningful information. + switch (sfc) { + case CIP_SFC_176400: + case CIP_SFC_192000: + parser->interval = 4; + break; + case CIP_SFC_88200: + case CIP_SFC_96000: + parser->interval = 2; + break; + case CIP_SFC_32000: + case CIP_SFC_44100: + case CIP_SFC_48000: + default: + parser->interval = 1; + break; + } + + return 0; +} + +#define FRAGMENT_POS 6 +#define MIDI_BYTE_POS 7 +#define MIDI_FLAG_POS 8 +// One value of hardware meter consists of 4 messages. +#define FRAGMENTS_PER_VALUE 4 +#define VALUES_AT_IMAGE_END 0xffffffffffffffff + +void snd_motu_command_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int data_block_quadlets) +{ + struct msg_parser *parser = motu->message_parser; + unsigned int interval = parser->interval; + unsigned long flags; + int i; + + spin_lock_irqsave(&parser->lock, flags); + + for (i = 0; i < desc_count; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buffer = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + int j; + + for (j = 0; j < data_blocks; ++j) { + u8 *b = (u8 *)buffer; + buffer += data_block_quadlets; + + switch (parser->state) { + case INITIALIZED: + { + u8 fragment = b[FRAGMENT_POS]; + + if (fragment > 0) { + parser->value = fragment; + parser->message_count = 1; + parser->state = FRAGMENT_DETECTED; + } + break; + } + case FRAGMENT_DETECTED: + { + if (parser->message_count % interval == 0) { + u8 fragment = b[FRAGMENT_POS]; + + parser->value >>= 8; + parser->value |= (u64)fragment << 56; + + if (parser->value == VALUES_AT_IMAGE_END) { + parser->state = AVAILABLE; + parser->fragment_pos = 0; + parser->value_index = 0; + parser->message_count = 0; + } + } + ++parser->message_count; + break; + } + case AVAILABLE: + default: + { + if (parser->message_count % interval == 0) { + u8 fragment = b[FRAGMENT_POS]; + + parser->value >>= 8; + parser->value |= (u64)fragment << 56; + ++parser->fragment_pos; + + if (parser->fragment_pos == 4) { + // Skip the last two quadlets since they could be + // invalid value (0xffffffff) as floating point + // number. + if (parser->value_index < + SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT - 2) { + u32 val = (u32)(parser->value >> 32); + parser->meter.data[parser->value_index] = val; + } + ++parser->value_index; + parser->fragment_pos = 0; + } + + if (parser->value == VALUES_AT_IMAGE_END) { + parser->value_index = 0; + parser->fragment_pos = 0; + parser->message_count = 0; + } + } + ++parser->message_count; + break; + } + } + } + } + + spin_unlock_irqrestore(&parser->lock, flags); +} + +void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu, + struct snd_firewire_motu_command_dsp_meter *meter) +{ + struct msg_parser *parser = motu->message_parser; + unsigned long flags; + + spin_lock_irqsave(&parser->lock, flags); + memcpy(meter, &parser->meter, sizeof(*meter)); + spin_unlock_irqrestore(&parser->lock, flags); +} diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c index b5ced5d27758..a900fc0e7644 100644 --- a/sound/firewire/motu/motu-hwdep.c +++ b/sound/firewire/motu/motu-hwdep.c @@ -16,6 +16,14 @@ #include "motu.h" +static bool has_dsp_event(struct snd_motu *motu) +{ + if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) + return (snd_motu_register_dsp_message_parser_count_event(motu) > 0); + else + return false; +} + static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, loff_t *offset) { @@ -25,7 +33,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, spin_lock_irq(&motu->lock); - while (!motu->dev_lock_changed && motu->msg == 0) { + while (!motu->dev_lock_changed && motu->msg == 0 && !has_dsp_event(motu)) { prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); spin_unlock_irq(&motu->lock); schedule(); @@ -40,20 +48,46 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; event.lock_status.status = (motu->dev_lock_count > 0); motu->dev_lock_changed = false; + spin_unlock_irq(&motu->lock); - count = min_t(long, count, sizeof(event.lock_status)); - } else { + count = min_t(long, count, sizeof(event)); + if (copy_to_user(buf, &event, count)) + return -EFAULT; + } else if (motu->msg > 0) { event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; event.motu_notification.message = motu->msg; motu->msg = 0; + spin_unlock_irq(&motu->lock); - count = min_t(long, count, sizeof(event.motu_notification)); - } + count = min_t(long, count, sizeof(event)); + if (copy_to_user(buf, &event, count)) + return -EFAULT; + } else if (has_dsp_event(motu)) { + size_t consumed = 0; + u32 __user *ptr; + u32 ev; - spin_unlock_irq(&motu->lock); + spin_unlock_irq(&motu->lock); - if (copy_to_user(buf, &event, count)) - return -EFAULT; + // Header is filled later. + consumed += sizeof(event.motu_register_dsp_change); + + while (consumed < count && + snd_motu_register_dsp_message_parser_copy_event(motu, &ev)) { + ptr = (u32 __user *)(buf + consumed); + if (put_user(ev, ptr)) + return -EFAULT; + consumed += sizeof(ev); + } + + event.motu_register_dsp_change.type = SNDRV_FIREWIRE_EVENT_MOTU_REGISTER_DSP_CHANGE; + event.motu_register_dsp_change.count = + (consumed - sizeof(event.motu_register_dsp_change)) / 4; + if (copy_to_user(buf, &event, sizeof(event.motu_register_dsp_change))) + return -EFAULT; + + count = consumed; + } return count; } @@ -67,7 +101,7 @@ static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_wait(file, &motu->hwdep_wait, wait); spin_lock_irq(&motu->lock); - if (motu->dev_lock_changed || motu->msg) + if (motu->dev_lock_changed || motu->msg || has_dsp_event(motu)) events = EPOLLIN | EPOLLRDNORM; else events = 0; @@ -155,6 +189,71 @@ static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, return hwdep_lock(motu); case SNDRV_FIREWIRE_IOCTL_UNLOCK: return hwdep_unlock(motu); + case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_METER: + { + struct snd_firewire_motu_register_dsp_meter *meter; + int err; + + if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP)) + return -ENXIO; + + meter = kzalloc(sizeof(*meter), GFP_KERNEL); + if (!meter) + return -ENOMEM; + + snd_motu_register_dsp_message_parser_copy_meter(motu, meter); + + err = copy_to_user((void __user *)arg, meter, sizeof(*meter)); + kfree(meter); + + if (err) + return -EFAULT; + + return 0; + } + case SNDRV_FIREWIRE_IOCTL_MOTU_COMMAND_DSP_METER: + { + struct snd_firewire_motu_command_dsp_meter *meter; + int err; + + if (!(motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP)) + return -ENXIO; + + meter = kzalloc(sizeof(*meter), GFP_KERNEL); + if (!meter) + return -ENOMEM; + + snd_motu_command_dsp_message_parser_copy_meter(motu, meter); + + err = copy_to_user((void __user *)arg, meter, sizeof(*meter)); + kfree(meter); + + if (err) + return -EFAULT; + + return 0; + } + case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_PARAMETER: + { + struct snd_firewire_motu_register_dsp_parameter *param; + int err; + + if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP)) + return -ENXIO; + + param = kzalloc(sizeof(*param), GFP_KERNEL); + if (!param) + return -ENOMEM; + + snd_motu_register_dsp_message_parser_copy_parameter(motu, param); + + err = copy_to_user((void __user *)arg, param, sizeof(*param)); + kfree(param); + if (err) + return -EFAULT; + + return 0; + } default: return -ENOIOCTLCMD; } @@ -193,5 +292,7 @@ int snd_motu_create_hwdep_device(struct snd_motu *motu) hwdep->private_data = motu; hwdep->exclusive = true; + motu->hwdep = hwdep; + return 0; } diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c index 2bd4485e4bc7..a5f70efa2e88 100644 --- a/sound/firewire/motu/motu-protocol-v2.c +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -275,7 +275,8 @@ const struct snd_motu_spec snd_motu_spec_828mk2 = { .name = "828mk2", .protocol_version = SND_MOTU_PROTOCOL_V2, .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | - SND_MOTU_SPEC_TX_MIDI_2ND_Q, + SND_MOTU_SPEC_TX_MIDI_2ND_Q | + SND_MOTU_SPEC_REGISTER_DSP, .tx_fixed_pcm_chunks = {14, 14, 0}, .rx_fixed_pcm_chunks = {14, 14, 0}, }; @@ -283,7 +284,7 @@ const struct snd_motu_spec snd_motu_spec_828mk2 = { const struct snd_motu_spec snd_motu_spec_896hd = { .name = "896HD", .protocol_version = SND_MOTU_PROTOCOL_V2, - // No support for MIDI. + .flags = SND_MOTU_SPEC_REGISTER_DSP, .tx_fixed_pcm_chunks = {14, 14, 8}, .rx_fixed_pcm_chunks = {14, 14, 8}, }; @@ -292,7 +293,8 @@ const struct snd_motu_spec snd_motu_spec_traveler = { .name = "Traveler", .protocol_version = SND_MOTU_PROTOCOL_V2, .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | - SND_MOTU_SPEC_TX_MIDI_2ND_Q, + SND_MOTU_SPEC_TX_MIDI_2ND_Q | + SND_MOTU_SPEC_REGISTER_DSP, .tx_fixed_pcm_chunks = {14, 14, 8}, .rx_fixed_pcm_chunks = {14, 14, 8}, }; @@ -301,7 +303,8 @@ const struct snd_motu_spec snd_motu_spec_ultralite = { .name = "UltraLite", .protocol_version = SND_MOTU_PROTOCOL_V2, .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | - SND_MOTU_SPEC_TX_MIDI_2ND_Q, + SND_MOTU_SPEC_TX_MIDI_2ND_Q | + SND_MOTU_SPEC_REGISTER_DSP, .tx_fixed_pcm_chunks = {14, 14, 0}, .rx_fixed_pcm_chunks = {14, 14, 0}, }; @@ -310,7 +313,8 @@ const struct snd_motu_spec snd_motu_spec_8pre = { .name = "8pre", .protocol_version = SND_MOTU_PROTOCOL_V2, .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | - SND_MOTU_SPEC_TX_MIDI_2ND_Q, + SND_MOTU_SPEC_TX_MIDI_2ND_Q | + SND_MOTU_SPEC_REGISTER_DSP, // Two dummy chunks always in the end of data block. .tx_fixed_pcm_chunks = {10, 10, 0}, .rx_fixed_pcm_chunks = {6, 6, 0}, diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c index 56e4504e7ec9..05608e8ca0bc 100644 --- a/sound/firewire/motu/motu-protocol-v3.c +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -261,12 +261,12 @@ int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu) return 0; } - const struct snd_motu_spec snd_motu_spec_828mk3_fw = { .name = "828mk3", .protocol_version = SND_MOTU_PROTOCOL_V3, .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q | - SND_MOTU_SPEC_TX_MIDI_3RD_Q, + SND_MOTU_SPEC_TX_MIDI_3RD_Q | + SND_MOTU_SPEC_COMMAND_DSP, .tx_fixed_pcm_chunks = {18, 18, 14}, .rx_fixed_pcm_chunks = {14, 14, 10}, }; @@ -275,7 +275,8 @@ const struct snd_motu_spec snd_motu_spec_828mk3_hybrid = { .name = "828mk3", .protocol_version = SND_MOTU_PROTOCOL_V3, .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q | - SND_MOTU_SPEC_TX_MIDI_3RD_Q, + SND_MOTU_SPEC_TX_MIDI_3RD_Q | + SND_MOTU_SPEC_COMMAND_DSP, .tx_fixed_pcm_chunks = {18, 18, 14}, .rx_fixed_pcm_chunks = {14, 14, 14}, // Additional 4 dummy chunks at higher rate. }; @@ -284,7 +285,8 @@ const struct snd_motu_spec snd_motu_spec_ultralite_mk3 = { .name = "UltraLiteMk3", .protocol_version = SND_MOTU_PROTOCOL_V3, .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q | - SND_MOTU_SPEC_TX_MIDI_3RD_Q, + SND_MOTU_SPEC_TX_MIDI_3RD_Q | + SND_MOTU_SPEC_COMMAND_DSP, .tx_fixed_pcm_chunks = {18, 14, 10}, .rx_fixed_pcm_chunks = {14, 14, 14}, }; @@ -293,7 +295,8 @@ const struct snd_motu_spec snd_motu_spec_audio_express = { .name = "AudioExpress", .protocol_version = SND_MOTU_PROTOCOL_V3, .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | - SND_MOTU_SPEC_TX_MIDI_3RD_Q, + SND_MOTU_SPEC_TX_MIDI_3RD_Q | + SND_MOTU_SPEC_REGISTER_DSP, .tx_fixed_pcm_chunks = {10, 10, 0}, .rx_fixed_pcm_chunks = {10, 10, 0}, }; @@ -301,6 +304,7 @@ const struct snd_motu_spec snd_motu_spec_audio_express = { const struct snd_motu_spec snd_motu_spec_4pre = { .name = "4pre", .protocol_version = SND_MOTU_PROTOCOL_V3, + .flags = SND_MOTU_SPEC_REGISTER_DSP, .tx_fixed_pcm_chunks = {10, 10, 0}, .rx_fixed_pcm_chunks = {10, 10, 0}, }; diff --git a/sound/firewire/motu/motu-register-dsp-message-parser.c b/sound/firewire/motu/motu-register-dsp-message-parser.c new file mode 100644 index 000000000000..0c587567540f --- /dev/null +++ b/sound/firewire/motu/motu-register-dsp-message-parser.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// motu-register-dsp-message-parser.c - a part of driver for MOTU FireWire series +// +// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp> + +// Below models allow software to configure their DSP functions by asynchronous transaction +// to access their internal registers. +// * 828 mk2 +// * 896hd +// * Traveler +// * 8 pre +// * Ultralite +// * 4 pre +// * Audio Express +// +// Additionally, isochronous packets from the above models include messages to notify state of +// DSP. The messages are two set of 3 byte data in 2nd and 3rd quadlet of data block. When user +// operates hardware components such as dial and switch, corresponding messages are transferred. +// The messages include Hardware metering and MIDI messages as well. + +#include "motu.h" + +#define MSG_FLAG_POS 4 +#define MSG_FLAG_TYPE_MASK 0xf8 +#define MSG_FLAG_MIDI_MASK 0x01 +#define MSG_FLAG_MODEL_SPECIFIC_MASK 0x06 +#define MSG_FLAG_8PRE 0x00 +#define MSG_FLAG_ULTRALITE 0x04 +#define MSG_FLAG_TRAVELER 0x04 +#define MSG_FLAG_828MK2 0x04 +#define MSG_FLAG_896HD 0x04 +#define MSG_FLAG_4PRE 0x05 // MIDI mask is in 8th byte. +#define MSG_FLAG_AUDIOEXPRESS 0x05 // MIDI mask is in 8th byte. +#define MSG_FLAG_TYPE_SHIFT 3 +#define MSG_VALUE_POS 5 +#define MSG_MIDI_BYTE_POS 6 +#define MSG_METER_IDX_POS 7 + +// In 4 pre and Audio express, meter index is in 6th byte. MIDI flag is in 8th byte and MIDI byte +// is in 7th byte. +#define MSG_METER_IDX_POS_4PRE_AE 6 +#define MSG_MIDI_BYTE_POS_4PRE_AE 7 +#define MSG_FLAG_MIDI_POS_4PRE_AE 8 + +enum register_dsp_msg_type { + // Used for messages with no information. + INVALID = 0x00, + MIXER_SELECT = 0x01, + MIXER_SRC_GAIN = 0x02, + MIXER_SRC_PAN = 0x03, + MIXER_SRC_FLAG = 0x04, + MIXER_OUTPUT_PAIRED_VOLUME = 0x05, + MIXER_OUTPUT_PAIRED_FLAG = 0x06, + MAIN_OUTPUT_PAIRED_VOLUME = 0x07, + HP_OUTPUT_PAIRED_VOLUME = 0x08, + HP_OUTPUT_PAIRED_ASSIGNMENT = 0x09, + // Transferred by all models but the purpose is still unknown. + UNKNOWN_0 = 0x0a, + // Specific to 828mk2, 896hd, Traveler. + UNKNOWN_2 = 0x0c, + // Specific to 828mk2, Traveler, and 896hd (not functional). + LINE_INPUT_BOOST = 0x0d, + // Specific to 828mk2, Traveler, and 896hd (not functional). + LINE_INPUT_NOMINAL_LEVEL = 0x0e, + // Specific to Ultralite, 4 pre, Audio express, and 8 pre (not functional). + INPUT_GAIN_AND_INVERT = 0x15, + // Specific to 4 pre, and Audio express. + INPUT_FLAG = 0x16, + // Specific to 4 pre, and Audio express. + MIXER_SRC_PAIRED_BALANCE = 0x17, + // Specific to 4 pre, and Audio express. + MIXER_SRC_PAIRED_WIDTH = 0x18, + // Transferred by all models. This type of message interposes the series of the other + // messages. The message delivers signal level up to 96.0 kHz. In 828mk2, 896hd, and + // Traveler, one of physical outputs is selected for the message. The selection is done + // by LSB one byte in asynchronous write quadlet transaction to 0x'ffff'f000'0b2c. + METER = 0x1f, +}; + +#define EVENT_QUEUE_SIZE 16 + +struct msg_parser { + spinlock_t lock; + struct snd_firewire_motu_register_dsp_meter meter; + bool meter_pos_quirk; + + struct snd_firewire_motu_register_dsp_parameter param; + u8 prev_mixer_src_type; + u8 mixer_ch; + u8 mixer_src_ch; + + u8 input_ch; + u8 prev_msg_type; + + u32 event_queue[EVENT_QUEUE_SIZE]; + unsigned int push_pos; + unsigned int pull_pos; +}; + +int snd_motu_register_dsp_message_parser_new(struct snd_motu *motu) +{ + struct msg_parser *parser; + parser = devm_kzalloc(&motu->card->card_dev, sizeof(*parser), GFP_KERNEL); + if (!parser) + return -ENOMEM; + spin_lock_init(&parser->lock); + if (motu->spec == &snd_motu_spec_4pre || motu->spec == &snd_motu_spec_audio_express) + parser->meter_pos_quirk = true; + motu->message_parser = parser; + return 0; +} + +int snd_motu_register_dsp_message_parser_init(struct snd_motu *motu) +{ + struct msg_parser *parser = motu->message_parser; + + parser->prev_mixer_src_type = INVALID; + parser->mixer_ch = 0xff; + parser->mixer_src_ch = 0xff; + parser->prev_msg_type = INVALID; + + return 0; +} + +// Rough implementaion of queue without overrun check. +static void queue_event(struct snd_motu *motu, u8 msg_type, u8 identifier0, u8 identifier1, u8 val) +{ + struct msg_parser *parser = motu->message_parser; + unsigned int pos = parser->push_pos; + u32 entry; + + if (!motu->hwdep || motu->hwdep->used == 0) + return; + + entry = (msg_type << 24) | (identifier0 << 16) | (identifier1 << 8) | val; + parser->event_queue[pos] = entry; + + ++pos; + if (pos >= EVENT_QUEUE_SIZE) + pos = 0; + parser->push_pos = pos; +} + +void snd_motu_register_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int data_block_quadlets) +{ + struct msg_parser *parser = motu->message_parser; + bool meter_pos_quirk = parser->meter_pos_quirk; + unsigned int pos = parser->push_pos; + unsigned long flags; + int i; + + spin_lock_irqsave(&parser->lock, flags); + + for (i = 0; i < desc_count; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buffer = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + int j; + + for (j = 0; j < data_blocks; ++j) { + u8 *b = (u8 *)buffer; + u8 msg_type = (b[MSG_FLAG_POS] & MSG_FLAG_TYPE_MASK) >> MSG_FLAG_TYPE_SHIFT; + u8 val = b[MSG_VALUE_POS]; + + buffer += data_block_quadlets; + + switch (msg_type) { + case MIXER_SELECT: + { + u8 mixer_ch = val / 0x20; + if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT) { + parser->mixer_src_ch = 0; + parser->mixer_ch = mixer_ch; + } + break; + } + case MIXER_SRC_GAIN: + case MIXER_SRC_PAN: + case MIXER_SRC_FLAG: + case MIXER_SRC_PAIRED_BALANCE: + case MIXER_SRC_PAIRED_WIDTH: + { + struct snd_firewire_motu_register_dsp_parameter *param = &parser->param; + u8 mixer_ch = parser->mixer_ch; + u8 mixer_src_ch = parser->mixer_src_ch; + + if (msg_type != parser->prev_mixer_src_type) + mixer_src_ch = 0; + else + ++mixer_src_ch; + parser->prev_mixer_src_type = msg_type; + + if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT && + mixer_src_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT) { + u8 mixer_ch = parser->mixer_ch; + + switch (msg_type) { + case MIXER_SRC_GAIN: + if (param->mixer.source[mixer_ch].gain[mixer_src_ch] != val) { + queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val); + param->mixer.source[mixer_ch].gain[mixer_src_ch] = val; + } + break; + case MIXER_SRC_PAN: + if (param->mixer.source[mixer_ch].pan[mixer_src_ch] != val) { + queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val); + param->mixer.source[mixer_ch].pan[mixer_src_ch] = val; + } + break; + case MIXER_SRC_FLAG: + if (param->mixer.source[mixer_ch].flag[mixer_src_ch] != val) { + queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val); + param->mixer.source[mixer_ch].flag[mixer_src_ch] = val; + } + break; + case MIXER_SRC_PAIRED_BALANCE: + if (param->mixer.source[mixer_ch].paired_balance[mixer_src_ch] != val) { + queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val); + param->mixer.source[mixer_ch].paired_balance[mixer_src_ch] = val; + } + break; + case MIXER_SRC_PAIRED_WIDTH: + if (param->mixer.source[mixer_ch].paired_width[mixer_src_ch] != val) { + queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val); + param->mixer.source[mixer_ch].paired_width[mixer_src_ch] = val; + } + break; + default: + break; + } + + parser->mixer_src_ch = mixer_src_ch; + } + break; + } + case MIXER_OUTPUT_PAIRED_VOLUME: + case MIXER_OUTPUT_PAIRED_FLAG: + { + struct snd_firewire_motu_register_dsp_parameter *param = &parser->param; + u8 mixer_ch = parser->mixer_ch; + + if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT) { + switch (msg_type) { + case MIXER_OUTPUT_PAIRED_VOLUME: + if (param->mixer.output.paired_volume[mixer_ch] != val) { + queue_event(motu, msg_type, mixer_ch, 0, val); + param->mixer.output.paired_volume[mixer_ch] = val; + } + break; + case MIXER_OUTPUT_PAIRED_FLAG: + if (param->mixer.output.paired_flag[mixer_ch] != val) { + queue_event(motu, msg_type, mixer_ch, 0, val); + param->mixer.output.paired_flag[mixer_ch] = val; + } + break; + default: + break; + } + } + break; + } + case MAIN_OUTPUT_PAIRED_VOLUME: + if (parser->param.output.main_paired_volume != val) { + queue_event(motu, msg_type, 0, 0, val); + parser->param.output.main_paired_volume = val; + } + break; + case HP_OUTPUT_PAIRED_VOLUME: + if (parser->param.output.hp_paired_volume != val) { + queue_event(motu, msg_type, 0, 0, val); + parser->param.output.hp_paired_volume = val; + } + break; + case HP_OUTPUT_PAIRED_ASSIGNMENT: + if (parser->param.output.hp_paired_assignment != val) { + queue_event(motu, msg_type, 0, 0, val); + parser->param.output.hp_paired_assignment = val; + } + break; + case LINE_INPUT_BOOST: + if (parser->param.line_input.boost_flag != val) { + queue_event(motu, msg_type, 0, 0, val); + parser->param.line_input.boost_flag = val; + } + break; + case LINE_INPUT_NOMINAL_LEVEL: + if (parser->param.line_input.nominal_level_flag != val) { + queue_event(motu, msg_type, 0, 0, val); + parser->param.line_input.nominal_level_flag = val; + } + break; + case INPUT_GAIN_AND_INVERT: + case INPUT_FLAG: + { + struct snd_firewire_motu_register_dsp_parameter *param = &parser->param; + u8 input_ch = parser->input_ch; + + if (parser->prev_msg_type != msg_type) + input_ch = 0; + else + ++input_ch; + + if (input_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT) { + switch (msg_type) { + case INPUT_GAIN_AND_INVERT: + if (param->input.gain_and_invert[input_ch] != val) { + queue_event(motu, msg_type, input_ch, 0, val); + param->input.gain_and_invert[input_ch] = val; + } + break; + case INPUT_FLAG: + if (param->input.flag[input_ch] != val) { + queue_event(motu, msg_type, input_ch, 0, val); + param->input.flag[input_ch] = val; + } + break; + default: + break; + } + parser->input_ch = input_ch; + } + break; + } + case UNKNOWN_0: + case UNKNOWN_2: + break; + case METER: + { + u8 pos; + + if (!meter_pos_quirk) + pos = b[MSG_METER_IDX_POS]; + else + pos = b[MSG_METER_IDX_POS_4PRE_AE]; + + if (pos < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT) { + parser->meter.data[pos] = val; + } else if (pos >= 0x80) { + pos -= (0x80 - SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT); + + if (pos < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT) + parser->meter.data[pos] = val; + } + + // The message for meter is interruptible to the series of other + // types of messages. Don't cache it. + fallthrough; + } + case INVALID: + default: + // Don't cache it. + continue; + } + + parser->prev_msg_type = msg_type; + } + } + + if (pos != parser->push_pos) + wake_up(&motu->hwdep_wait); + + spin_unlock_irqrestore(&parser->lock, flags); +} + +void snd_motu_register_dsp_message_parser_copy_meter(struct snd_motu *motu, + struct snd_firewire_motu_register_dsp_meter *meter) +{ + struct msg_parser *parser = motu->message_parser; + unsigned long flags; + + spin_lock_irqsave(&parser->lock, flags); + memcpy(meter, &parser->meter, sizeof(*meter)); + spin_unlock_irqrestore(&parser->lock, flags); +} + +void snd_motu_register_dsp_message_parser_copy_parameter(struct snd_motu *motu, + struct snd_firewire_motu_register_dsp_parameter *param) +{ + struct msg_parser *parser = motu->message_parser; + unsigned long flags; + + spin_lock_irqsave(&parser->lock, flags); + memcpy(param, &parser->param, sizeof(*param)); + spin_unlock_irqrestore(&parser->lock, flags); +} + +unsigned int snd_motu_register_dsp_message_parser_count_event(struct snd_motu *motu) +{ + struct msg_parser *parser = motu->message_parser; + + if (parser->pull_pos > parser->push_pos) + return EVENT_QUEUE_SIZE - parser->pull_pos + parser->push_pos; + else + return parser->push_pos - parser->pull_pos; +} + +bool snd_motu_register_dsp_message_parser_copy_event(struct snd_motu *motu, u32 *event) +{ + struct msg_parser *parser = motu->message_parser; + unsigned int pos = parser->pull_pos; + unsigned long flags; + + if (pos == parser->push_pos) + return false; + + spin_lock_irqsave(&parser->lock, flags); + + *event = parser->event_queue[pos]; + + ++pos; + if (pos >= EVENT_QUEUE_SIZE) + pos = 0; + parser->pull_pos = pos; + + spin_unlock_irqrestore(&parser->lock, flags); + + return true; +} diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c index 9e6ca39ebd7f..64aec9c3eefd 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -255,6 +255,16 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu) if (err < 0) return err; + if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) { + err = snd_motu_register_dsp_message_parser_init(motu); + if (err < 0) + return err; + } else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) { + err = snd_motu_command_dsp_message_parser_init(motu, motu->tx_stream.sfc); + if (err < 0) + return err; + } + err = begin_session(motu); if (err < 0) { dev_err(&motu->unit->device, diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index f65426238d4c..5fc7ae475537 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -112,6 +112,16 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *ent if (err < 0) goto error; + if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) { + err = snd_motu_register_dsp_message_parser_new(motu); + if (err < 0) + goto error; + } else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) { + err = snd_motu_command_dsp_message_parser_new(motu); + if (err < 0) + goto error; + } + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index f1a830b358d4..79704ae6a73e 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -74,10 +74,13 @@ struct snd_motu { int dev_lock_count; bool dev_lock_changed; wait_queue_head_t hwdep_wait; + struct snd_hwdep *hwdep; struct amdtp_domain domain; struct amdtp_motu_cache cache; + + void *message_parser; }; enum snd_motu_spec_flags { @@ -85,6 +88,8 @@ enum snd_motu_spec_flags { SND_MOTU_SPEC_RX_MIDI_3RD_Q = 0x0002, SND_MOTU_SPEC_TX_MIDI_2ND_Q = 0x0004, SND_MOTU_SPEC_TX_MIDI_3RD_Q = 0x0008, + SND_MOTU_SPEC_REGISTER_DSP = 0x0010, + SND_MOTU_SPEC_COMMAND_DSP = 0x0020, }; #define SND_MOTU_CLOCK_RATE_COUNT 6 @@ -270,4 +275,22 @@ static inline int snd_motu_protocol_cache_packet_formats(struct snd_motu *motu) return -ENXIO; } +int snd_motu_register_dsp_message_parser_new(struct snd_motu *motu); +int snd_motu_register_dsp_message_parser_init(struct snd_motu *motu); +void snd_motu_register_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int data_block_quadlets); +void snd_motu_register_dsp_message_parser_copy_meter(struct snd_motu *motu, + struct snd_firewire_motu_register_dsp_meter *meter); +void snd_motu_register_dsp_message_parser_copy_parameter(struct snd_motu *motu, + struct snd_firewire_motu_register_dsp_parameter *params); +unsigned int snd_motu_register_dsp_message_parser_count_event(struct snd_motu *motu); +bool snd_motu_register_dsp_message_parser_copy_event(struct snd_motu *motu, u32 *event); + +int snd_motu_command_dsp_message_parser_new(struct snd_motu *motu); +int snd_motu_command_dsp_message_parser_init(struct snd_motu *motu, enum cip_sfc sfc); +void snd_motu_command_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int data_block_quadlets); +void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu, + struct snd_firewire_motu_command_dsp_meter *meter); + #endif diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index fff18b5d4e05..f4a702def397 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -9,7 +9,7 @@ #include <linux/delay.h> #define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 -#define READY_TIMEOUT_MS 200 +#define READY_TIMEOUT_MS 600 /* * According to datasheet of Oxford Semiconductor: @@ -367,6 +367,11 @@ int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw) // Just after changing sampling transfer frequency, many cycles are // skipped for packet transmission. tx_init_skip_cycles = 400; + } else if (oxfw->quirks & SND_OXFW_QUIRK_VOLUNTARY_RECOVERY) { + // It takes a bit time for target device to adjust event frequency + // according to nominal event frequency in isochronous packets from + // ALSA oxfw driver. + tx_init_skip_cycles = 4000; } else { replay_seq = true; } diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index daf731364695..b496f87841ae 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -25,6 +25,7 @@ #define MODEL_SATELLITE 0x00200f #define MODEL_SCS1M 0x001000 #define MODEL_DUET_FW 0x01dddd +#define MODEL_ONYX_1640I 0x001640 #define SPECIFIER_1394TA 0x00a02d #define VERSION_AVC 0x010001 @@ -192,6 +193,13 @@ static int detect_quirks(struct snd_oxfw *oxfw, const struct ieee1394_device_id // OXFW971-based models may transfer events by blocking method. if (!(oxfw->quirks & SND_OXFW_QUIRK_JUMBO_PAYLOAD)) oxfw->quirks |= SND_OXFW_QUIRK_BLOCKING_TRANSMISSION; + + if (model == MODEL_ONYX_1640I) { + //Unless receiving packets without NOINFO packet, the device transfers + //mostly half of events in packets than expected. + oxfw->quirks |= SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET | + SND_OXFW_QUIRK_VOLUNTARY_RECOVERY; + } } return 0; diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index c13034f6c2ca..d728e451a25c 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -47,6 +47,11 @@ enum snd_oxfw_quirk { // the device to process audio data even if the value is invalid in a point of // IEC 61883-1/6. SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET = 0x10, + // Loud Technologies Mackie Onyx 1640i seems to configure OXFW971 ASIC so that it decides + // event frequency according to events in received isochronous packets. The device looks to + // performs media clock recovery voluntarily. In the recovery, the packets with NO_INFO + // are ignored, thus driver should transfer packets with timestamp. + SND_OXFW_QUIRK_VOLUNTARY_RECOVERY = 0x20, }; /* This is an arbitrary number for convinience. */ diff --git a/sound/hda/ext/hdac_ext_stream.c b/sound/hda/ext/hdac_ext_stream.c index 0c005d67fa89..37154ed43bd5 100644 --- a/sound/hda/ext/hdac_ext_stream.c +++ b/sound/hda/ext/hdac_ext_stream.c @@ -106,20 +106,14 @@ void snd_hdac_stream_free_all(struct hdac_bus *bus) } EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all); -/** - * snd_hdac_ext_stream_decouple - decouple the hdac stream - * @bus: HD-audio core bus - * @stream: HD-audio ext core stream object to initialize - * @decouple: flag to decouple - */ -void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, - struct hdac_ext_stream *stream, bool decouple) +void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus, + struct hdac_ext_stream *stream, + bool decouple) { struct hdac_stream *hstream = &stream->hstream; u32 val; int mask = AZX_PPCTL_PROCEN(hstream->index); - spin_lock_irq(&bus->reg_lock); val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask; if (decouple && !val) @@ -128,6 +122,20 @@ void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0); stream->decoupled = decouple; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked); + +/** + * snd_hdac_ext_stream_decouple - decouple the hdac stream + * @bus: HD-audio core bus + * @stream: HD-audio ext core stream object to initialize + * @decouple: flag to decouple + */ +void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, + struct hdac_ext_stream *stream, bool decouple) +{ + spin_lock_irq(&bus->reg_lock); + snd_hdac_ext_stream_decouple_locked(bus, stream, decouple); spin_unlock_irq(&bus->reg_lock); } EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); @@ -252,6 +260,7 @@ hdac_ext_link_stream_assign(struct hdac_bus *bus, return NULL; } + spin_lock_irq(&bus->reg_lock); list_for_each_entry(stream, &bus->stream_list, list) { struct hdac_ext_stream *hstream = container_of(stream, struct hdac_ext_stream, @@ -266,17 +275,16 @@ hdac_ext_link_stream_assign(struct hdac_bus *bus, } if (!hstream->link_locked) { - snd_hdac_ext_stream_decouple(bus, hstream, true); + snd_hdac_ext_stream_decouple_locked(bus, hstream, true); res = hstream; break; } } if (res) { - spin_lock_irq(&bus->reg_lock); res->link_locked = 1; res->link_substream = substream; - spin_unlock_irq(&bus->reg_lock); } + spin_unlock_irq(&bus->reg_lock); return res; } @@ -292,6 +300,7 @@ hdac_ext_host_stream_assign(struct hdac_bus *bus, return NULL; } + spin_lock_irq(&bus->reg_lock); list_for_each_entry(stream, &bus->stream_list, list) { struct hdac_ext_stream *hstream = container_of(stream, struct hdac_ext_stream, @@ -301,18 +310,17 @@ hdac_ext_host_stream_assign(struct hdac_bus *bus, if (!stream->opened) { if (!hstream->decoupled) - snd_hdac_ext_stream_decouple(bus, hstream, true); + snd_hdac_ext_stream_decouple_locked(bus, hstream, true); res = hstream; break; } } if (res) { - spin_lock_irq(&bus->reg_lock); res->hstream.opened = 1; res->hstream.running = 0; res->hstream.substream = substream; - spin_unlock_irq(&bus->reg_lock); } + spin_unlock_irq(&bus->reg_lock); return res; } @@ -378,15 +386,17 @@ void snd_hdac_ext_stream_release(struct hdac_ext_stream *stream, int type) break; case HDAC_EXT_STREAM_TYPE_HOST: + spin_lock_irq(&bus->reg_lock); if (stream->decoupled && !stream->link_locked) - snd_hdac_ext_stream_decouple(bus, stream, false); + snd_hdac_ext_stream_decouple_locked(bus, stream, false); + spin_unlock_irq(&bus->reg_lock); snd_hdac_stream_release(&stream->hstream); break; case HDAC_EXT_STREAM_TYPE_LINK: - if (stream->decoupled && !stream->hstream.opened) - snd_hdac_ext_stream_decouple(bus, stream, false); spin_lock_irq(&bus->reg_lock); + if (stream->decoupled && !stream->hstream.opened) + snd_hdac_ext_stream_decouple_locked(bus, stream, false); stream->link_locked = 0; stream->link_substream = NULL; spin_unlock_irq(&bus->reg_lock); diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index 1eb8563db2df..9867555883c3 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -296,6 +296,7 @@ struct hdac_stream *snd_hdac_stream_assign(struct hdac_bus *bus, int key = (substream->pcm->device << 16) | (substream->number << 2) | (substream->stream + 1); + spin_lock_irq(&bus->reg_lock); list_for_each_entry(azx_dev, &bus->stream_list, list) { if (azx_dev->direction != substream->stream) continue; @@ -309,13 +310,12 @@ struct hdac_stream *snd_hdac_stream_assign(struct hdac_bus *bus, res = azx_dev; } if (res) { - spin_lock_irq(&bus->reg_lock); res->opened = 1; res->running = 0; res->assigned_key = key; res->substream = substream; - spin_unlock_irq(&bus->reg_lock); } + spin_unlock_irq(&bus->reg_lock); return res; } EXPORT_SYMBOL_GPL(snd_hdac_stream_assign); diff --git a/sound/hda/intel-dsp-config.c b/sound/hda/intel-dsp-config.c index c9d0ba353463..b9ac9e9e45a4 100644 --- a/sound/hda/intel-dsp-config.c +++ b/sound/hda/intel-dsp-config.c @@ -31,6 +31,7 @@ struct config_entry { u16 device; u8 acpi_hid[ACPI_ID_LEN]; const struct dmi_system_id *dmi_table; + u8 codec_hid[ACPI_ID_LEN]; }; /* @@ -56,7 +57,7 @@ static const struct config_entry config_table[] = { /* * Apollolake (Broxton-P) * the legacy HDAudio driver is used except on Up Squared (SOF) and - * Chromebooks (SST) + * Chromebooks (SST), as well as devices based on the ES8336 codec */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE) { @@ -73,6 +74,11 @@ static const struct config_entry config_table[] = { {} } }, + { + .flags = FLAG_SOF, + .device = 0x5a98, + .codec_hid = "ESSX8336", + }, #endif #if IS_ENABLED(CONFIG_SND_SOC_INTEL_APL) { @@ -137,7 +143,7 @@ static const struct config_entry config_table[] = { /* * Geminilake uses legacy HDAudio driver except for Google - * Chromebooks + * Chromebooks and devices based on the ES8336 codec */ /* Geminilake */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE) @@ -154,6 +160,11 @@ static const struct config_entry config_table[] = { {} } }, + { + .flags = FLAG_SOF, + .device = 0x3198, + .codec_hid = "ESSX8336", + }, #endif /* @@ -311,6 +322,11 @@ static const struct config_entry config_table[] = { .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE, .device = 0x43c8, }, + { + .flags = FLAG_SOF, + .device = 0xa0c8, + .codec_hid = "ESSX8336", + }, #endif /* Elkhart Lake */ @@ -354,6 +370,8 @@ static const struct config_entry *snd_intel_dsp_find_config continue; if (table->dmi_table && !dmi_check_system(table->dmi_table)) continue; + if (table->codec_hid[0] && !acpi_dev_present(table->codec_hid, NULL, -1)) + continue; return table; } return NULL; diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index 6ffa48dd5983..570b88e0b201 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -22,7 +22,7 @@ config SND_SB16_DSP menuconfig SND_ISA bool "ISA sound devices" depends on ISA || COMPILE_TEST - depends on ISA_DMA_API + depends on ISA_DMA_API && !M68K default y help Support for sound devices connected via the ISA bus. diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c index a1c770d826dd..6d664dd8dde0 100644 --- a/sound/isa/gus/gus_dma.c +++ b/sound/isa/gus/gus_dma.c @@ -126,6 +126,8 @@ static void snd_gf1_dma_interrupt(struct snd_gus_card * gus) } block = snd_gf1_dma_next_block(gus); spin_unlock(&gus->dma_lock); + if (!block) + return; snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); kfree(block); #if 0 diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 93bc9bef7641..41ce12597177 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -279,6 +279,7 @@ config SND_CS46XX_NEW_DSP config SND_CS5530 tristate "CS5530 Audio" depends on ISA_DMA_API && (X86_32 || COMPILE_TEST) + depends on !M68K select SND_SB16_DSP help Say Y here to include support for audio on Cyrix/NatSemi CS5530 chips. diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 4d22e7adeee8..7762718cf429 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -638,13 +638,17 @@ static int azx_position_check(struct azx *chip, struct azx_dev *azx_dev) * the update-IRQ timing. The IRQ is issued before actually the * data is processed. So, we need to process it afterwords in a * workqueue. + * + * Returns 1 if OK to proceed, 0 for delay handling, -1 for skipping update */ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) { struct snd_pcm_substream *substream = azx_dev->core.substream; + struct snd_pcm_runtime *runtime = substream->runtime; int stream = substream->stream; u32 wallclk; unsigned int pos; + snd_pcm_uframes_t hwptr, target; wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk; if (wallclk < (azx_dev->core.period_wallclk * 2) / 3) @@ -681,6 +685,24 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) /* NG - it's below the first next period boundary */ return chip->bdl_pos_adj ? 0 : -1; azx_dev->core.start_wallclk += wallclk; + + if (azx_dev->core.no_period_wakeup) + return 1; /* OK, no need to check period boundary */ + + if (runtime->hw_ptr_base != runtime->hw_ptr_interrupt) + return 1; /* OK, already in hwptr updating process */ + + /* check whether the period gets really elapsed */ + pos = bytes_to_frames(runtime, pos); + hwptr = runtime->hw_ptr_base + pos; + if (hwptr < runtime->status->hw_ptr) + hwptr += runtime->buffer_size; + target = runtime->hw_ptr_interrupt + runtime->period_size; + if (hwptr < target) { + /* too early wakeup, process it later */ + return chip->bdl_pos_adj ? 0 : -1; + } + return 1; /* OK, it's fine */ } @@ -859,31 +881,6 @@ static int azx_get_delay_from_fifo(struct azx *chip, struct azx_dev *azx_dev, return substream->runtime->delay; } -static unsigned int azx_skl_get_dpib_pos(struct azx *chip, - struct azx_dev *azx_dev) -{ - return _snd_hdac_chip_readl(azx_bus(chip), - AZX_REG_VS_SDXDPIB_XBASE + - (AZX_REG_VS_SDXDPIB_XINTERVAL * - azx_dev->core.index)); -} - -/* get the current DMA position with correction on SKL+ chips */ -static unsigned int azx_get_pos_skl(struct azx *chip, struct azx_dev *azx_dev) -{ - /* DPIB register gives a more accurate position for playback */ - if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - return azx_skl_get_dpib_pos(chip, azx_dev); - - /* For capture, we need to read posbuf, but it requires a delay - * for the possible boundary overlap; the read of DPIB fetches the - * actual posbuf - */ - udelay(20); - azx_skl_get_dpib_pos(chip, azx_dev); - return azx_get_pos_posbuf(chip, azx_dev); -} - static void __azx_shutdown_chip(struct azx *chip, bool skip_link_reset) { azx_stop_chip(chip); @@ -1573,7 +1570,7 @@ static void assign_position_fix(struct azx *chip, int fix) [POS_FIX_POSBUF] = azx_get_pos_posbuf, [POS_FIX_VIACOMBO] = azx_via_get_position, [POS_FIX_COMBO] = azx_get_pos_lpib, - [POS_FIX_SKL] = azx_get_pos_skl, + [POS_FIX_SKL] = azx_get_pos_posbuf, [POS_FIX_FIFO] = azx_get_pos_fifo, }; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 965b096f416f..8a3e2fe42106 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -2539,6 +2539,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = { SND_PCI_QUIRK(0x1558, 0x67d1, "Clevo PB71[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x67e1, "Clevo PB71[DE][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x67e5, "Clevo PC70D[PRS](?:-D|-G)?", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x67f1, "Clevo PC70H[PRS]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x70d1, "Clevo PC70[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x7714, "Clevo X170SM", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x7715, "Clevo X170KM-G", ALC1220_FIXUP_CLEVO_PB51ED), @@ -4355,6 +4356,16 @@ static void alc287_fixup_hp_gpio_led(struct hda_codec *codec, alc_fixup_hp_gpio_led(codec, action, 0x10, 0); } +static void alc245_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); +} + /* turn on/off mic-mute LED per capture hook via VREF change */ static int vref_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) @@ -6709,6 +6720,7 @@ enum { ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK, ALC287_FIXUP_HP_GPIO_LED, ALC256_FIXUP_HP_HEADSET_MIC, + ALC245_FIXUP_HP_GPIO_LED, ALC236_FIXUP_DELL_AIO_HEADSET_MIC, ALC282_FIXUP_ACER_DISABLE_LINEOUT, ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST, @@ -7333,6 +7345,8 @@ static const struct hda_fixup alc269_fixups[] = { [ALC245_FIXUP_HP_X360_AMP] = { .type = HDA_FIXUP_FUNC, .v.func = alc245_fixup_hp_x360_amp, + .chained = true, + .chain_id = ALC245_FIXUP_HP_GPIO_LED }, [ALC288_FIXUP_DELL_HEADSET_MODE] = { .type = HDA_FIXUP_FUNC, @@ -8432,6 +8446,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_fixup_tongfang_reset_persistent_settings, }, + [ALC245_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc245_fixup_hp_gpio_led, + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -8617,6 +8635,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8783, "HP ZBook Fury 15 G7 Mobile Workstation", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x87e5, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x87e7, "HP ProBook 450 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig index 49ff5e73e9ba..2c6af3f8f296 100644 --- a/sound/soc/amd/Kconfig +++ b/sound/soc/amd/Kconfig @@ -6,6 +6,7 @@ config SND_SOC_AMD_ACP config SND_SOC_AMD_CZ_DA7219MX98357_MACH tristate "AMD CZ support for DA7219, RT5682 and MAX9835" + select CLK_FIXED_FCH select SND_SOC_DA7219 select SND_SOC_RT5682_I2C select SND_SOC_MAX98357A @@ -30,13 +31,14 @@ config SND_SOC_AMD_ACP3x config SND_SOC_AMD_RV_RT5682_MACH tristate "AMD RV support for RT5682" + select CLK_FIXED_FCH select SND_SOC_RT5682_I2C select SND_SOC_MAX98357A select SND_SOC_CROS_EC_CODEC select I2C_CROS_EC_TUNNEL select SND_SOC_RT1015 select SND_SOC_RT1015P - depends on SND_SOC_AMD_ACP3x && I2C && CROS_EC + depends on SND_SOC_AMD_ACP3x && I2C && CROS_EC && GPIOLIB help This option enables machine driver for RT5682 and MAX9835. @@ -49,7 +51,7 @@ config SND_SOC_AMD_RENOIR config SND_SOC_AMD_RENOIR_MACH tristate "AMD Renoir support for DMIC" select SND_SOC_DMIC - depends on SND_SOC_AMD_RENOIR + depends on SND_SOC_AMD_RENOIR && GPIOLIB help This option enables machine driver for DMIC @@ -61,3 +63,37 @@ config SND_SOC_AMD_ACP5x By enabling this flag build will trigger for ACP PCI driver, ACP DMA driver, CPU DAI driver. + +config SND_SOC_AMD_VANGOGH_MACH + tristate "AMD Vangogh support for NAU8821 CS35L41" + select SND_SOC_NAU8821 + select SND_SOC_CS35L41_SPI + depends on SND_SOC_AMD_ACP5x && I2C + help + This option enables machine driver for Vangogh platform + using NAU8821 and CS35L41 codecs. + Say m if you have such a device. + If unsure select "N". + +config SND_SOC_AMD_ACP6x + tristate "AMD Audio Coprocessor-v6.x Yellow Carp support" + depends on X86 && PCI + help + This option enables Audio Coprocessor i.e ACP v6.x support on + AMD Yellow Carp platform. By enabling this flag build will be + triggered for ACP PCI driver, ACP PDM DMA driver. + Say m if you have such a device. + If unsure select "N". + +config SND_SOC_AMD_YC_MACH + tristate "AMD YC support for DMIC" + select SND_SOC_DMIC + depends on SND_SOC_AMD_ACP6x + help + This option enables machine driver for Yellow Carp platform + using dmic. ACP IP has PDM Decoder block with DMA controller. + DMIC can be connected directly to ACP IP. + Say m if you have such a device. + If unsure select "N". + +source "sound/soc/amd/acp/Kconfig" diff --git a/sound/soc/amd/Makefile b/sound/soc/amd/Makefile index 07150d26f315..f1d42bbda709 100644 --- a/sound/soc/amd/Makefile +++ b/sound/soc/amd/Makefile @@ -11,3 +11,5 @@ obj-$(CONFIG_SND_SOC_AMD_ACP3x) += raven/ obj-$(CONFIG_SND_SOC_AMD_RV_RT5682_MACH) += snd-soc-acp-rt5682-mach.o obj-$(CONFIG_SND_SOC_AMD_RENOIR) += renoir/ obj-$(CONFIG_SND_SOC_AMD_ACP5x) += vangogh/ +obj-$(CONFIG_SND_SOC_AMD_ACP6x) += yc/ +obj-$(CONFIG_SND_SOC_AMD_ACP_COMMON) += acp/ diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index b3df98a9f9f3..b2065f3fe42c 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -33,7 +33,7 @@ static struct clk *da7219_dai_wclk; static struct clk *da7219_dai_bclk; static struct clk *rt5682_dai_wclk; static struct clk *rt5682_dai_bclk; -extern bool bt_uart_enable; + void *acp_soc_is_rltk_max(struct device *dev); static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) @@ -760,8 +760,8 @@ static int cz_probe(struct platform_device *pdev) "devm_snd_soc_register_card(%s) failed\n", card->name); } - bt_uart_enable = !device_property_read_bool(&pdev->dev, - "bt-pad-enable"); + acp_bt_uart_enable = !device_property_read_bool(&pdev->dev, + "bt-pad-enable"); return 0; } diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index 11b3c4f39eba..1f322accd9ea 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -36,8 +36,8 @@ #define ST_MIN_BUFFER ST_MAX_BUFFER #define DRV_NAME "acp_audio_dma" -bool bt_uart_enable = true; -EXPORT_SYMBOL(bt_uart_enable); +bool acp_bt_uart_enable = true; +EXPORT_SYMBOL(acp_bt_uart_enable); static const struct snd_pcm_hardware acp_pcm_hardware_playback = { .info = SNDRV_PCM_INFO_INTERLEAVED | @@ -596,7 +596,7 @@ static int acp_init(void __iomem *acp_mmio, u32 asic_type) acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); /* For BT instance change pins from UART to BT */ - if (!bt_uart_enable) { + if (!acp_bt_uart_enable) { val = acp_reg_read(acp_mmio, mmACP_BT_UART_PAD_SEL); val |= ACP_BT_UART_PAD_SELECT_MASK; acp_reg_write(val, acp_mmio, mmACP_BT_UART_PAD_SEL); diff --git a/sound/soc/amd/acp-rt5645.c b/sound/soc/amd/acp-rt5645.c index d6ba94677ac2..6d5c547a32de 100644 --- a/sound/soc/amd/acp-rt5645.c +++ b/sound/soc/amd/acp-rt5645.c @@ -91,7 +91,7 @@ static int cz_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static struct snd_soc_ops cz_aif1_ops = { +static const struct snd_soc_ops cz_aif1_ops = { .hw_params = cz_aif1_hw_params, }; diff --git a/sound/soc/amd/acp.h b/sound/soc/amd/acp.h index e5ab6c6040a6..85529ed7e5f5 100644 --- a/sound/soc/amd/acp.h +++ b/sound/soc/amd/acp.h @@ -204,4 +204,6 @@ typedef struct acp_dma_dscr_transfer { u32 reserved; } acp_dma_dscr_transfer_t; +extern bool acp_bt_uart_enable; + #endif /*__ACP_HW_H */ diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig new file mode 100644 index 000000000000..52a1371f9e61 --- /dev/null +++ b/sound/soc/amd/acp/Kconfig @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. +# + +config SND_SOC_AMD_ACP_COMMON + tristate "AMD Audio ACP Common support" + select SND_AMD_ACP_CONFIG + depends on X86 && PCI + help + This option enables common modules for Audio-Coprocessor i.e. ACP + IP block on AMD platforms. + +if SND_SOC_AMD_ACP_COMMON + +config SND_SOC_AMD_ACP_I2S + tristate + +config SND_SOC_AMD_ACP_PCM + tristate + select SND_SOC_ACPI if ACPI + +config SND_AMD_ASOC_RENOIR + tristate "AMD ACP ASOC Renoir Support" + select SND_SOC_AMD_ACP_PCM + select SND_SOC_AMD_ACP_I2S + depends on X86 && PCI + help + This option enables Renoir I2S support on AMD platform. + +config SND_SOC_AMD_MACH_COMMON + tristate + depends on X86 && PCI && I2C + select CLK_FIXED_FCH + select SND_SOC_RT5682_I2C + select SND_SOC_DMIC + select SND_SOC_RT1019 + select SND_SOC_MAX98357A + select SND_SOC_RT5682S + help + This option enables common Machine driver module for ACP. + +config SND_SOC_AMD_LEGACY_MACH + tristate "AMD Legacy Machine Driver Support" + depends on X86 && PCI && I2C + select SND_SOC_AMD_MACH_COMMON + depends on X86 && PCI && I2C + help + This option enables legacy sound card support for ACP audio. + +config SND_SOC_AMD_SOF_MACH + tristate "AMD SOF Machine Driver Support" + depends on X86 && PCI && I2C + select SND_SOC_AMD_MACH_COMMON + depends on X86 && PCI && I2C + help + This option enables SOF sound card support for ACP audio. + +endif # SND_SOC_AMD_ACP_COMMON diff --git a/sound/soc/amd/acp/Makefile b/sound/soc/amd/acp/Makefile new file mode 100644 index 000000000000..16c144c2965c --- /dev/null +++ b/sound/soc/amd/acp/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + +#common acp driver +snd-acp-pcm-objs := acp-platform.o +snd-acp-i2s-objs := acp-i2s.o + +#platform specific driver +snd-acp-renoir-objs := acp-renoir.o + +#machine specific driver +snd-acp-mach-objs := acp-mach-common.o +snd-acp-legacy-mach-objs := acp-legacy-mach.o +snd-acp-sof-mach-objs := acp-sof-mach.o + +obj-$(CONFIG_SND_SOC_AMD_ACP_PCM) += snd-acp-pcm.o +obj-$(CONFIG_SND_SOC_AMD_ACP_I2S) += snd-acp-i2s.o + +obj-$(CONFIG_SND_AMD_ASOC_RENOIR) += snd-acp-renoir.o + +obj-$(CONFIG_SND_SOC_AMD_MACH_COMMON) += snd-acp-mach.o +obj-$(CONFIG_SND_SOC_AMD_LEGACY_MACH) += snd-acp-legacy-mach.o +obj-$(CONFIG_SND_SOC_AMD_SOF_MACH) += snd-acp-sof-mach.o diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c new file mode 100644 index 000000000000..ce9aca8dd6f5 --- /dev/null +++ b/sound/soc/amd/acp/acp-i2s.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Generic Hardware interface for ACP Audio I2S controller + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_i2s_playcap" + +static int acp_i2s_hwparams(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata; + u32 val; + u32 xfer_resolution; + u32 reg_val; + + adata = snd_soc_dai_get_drvdata(dai); + + /* These values are as per Hardware Spec */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + xfer_resolution = 0x0; + break; + case SNDRV_PCM_FORMAT_S16_LE: + xfer_resolution = 0x02; + break; + case SNDRV_PCM_FORMAT_S24_LE: + xfer_resolution = 0x04; + break; + case SNDRV_PCM_FORMAT_S32_LE: + xfer_resolution = 0x05; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_ITER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_ITER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_IRER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_IRER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + + val = readl(adata->acp_base + reg_val); + val &= ~ACP3x_ITER_IRER_SAMP_LEN_MASK; + val = val | (xfer_resolution << 3); + writel(val, adata->acp_base + reg_val); + + return 0; +} + +static int acp_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + u32 val, period_bytes, reg_val, ier_val, water_val, buf_size, buf_reg; + + period_bytes = frames_to_bytes(substream->runtime, substream->runtime->period_size); + buf_size = frames_to_bytes(substream->runtime, substream->runtime->buffer_size); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stream->bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + water_val = ACP_BT_TX_INTR_WATERMARK_SIZE; + reg_val = ACP_BTTDM_ITER; + ier_val = ACP_BTTDM_IER; + buf_reg = ACP_BT_TX_RINGBUFSIZE; + break; + case I2S_SP_INSTANCE: + water_val = ACP_I2S_TX_INTR_WATERMARK_SIZE; + reg_val = ACP_I2STDM_ITER; + ier_val = ACP_I2STDM_IER; + buf_reg = ACP_I2S_TX_RINGBUFSIZE; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + water_val = ACP_BT_RX_INTR_WATERMARK_SIZE; + reg_val = ACP_BTTDM_IRER; + ier_val = ACP_BTTDM_IER; + buf_reg = ACP_BT_RX_RINGBUFSIZE; + break; + case I2S_SP_INSTANCE: + water_val = ACP_I2S_RX_INTR_WATERMARK_SIZE; + reg_val = ACP_I2STDM_IRER; + ier_val = ACP_I2STDM_IER; + buf_reg = ACP_I2S_RX_RINGBUFSIZE; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + writel(period_bytes, adata->acp_base + water_val); + writel(buf_size, adata->acp_base + buf_reg); + val = readl(adata->acp_base + reg_val); + val = val | BIT(0); + writel(val, adata->acp_base + reg_val); + writel(1, adata->acp_base + ier_val); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_ITER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_ITER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + } else { + switch (dai->driver->id) { + case I2S_BT_INSTANCE: + reg_val = ACP_BTTDM_IRER; + break; + case I2S_SP_INSTANCE: + reg_val = ACP_I2STDM_IRER; + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + } + val = readl(adata->acp_base + reg_val); + val = val & ~BIT(0); + writel(val, adata->acp_base + reg_val); + + if (!(readl(adata->acp_base + ACP_BTTDM_ITER) & BIT(0)) && + !(readl(adata->acp_base + ACP_BTTDM_IRER) & BIT(0))) + writel(0, adata->acp_base + ACP_BTTDM_IER); + if (!(readl(adata->acp_base + ACP_I2STDM_ITER) & BIT(0)) && + !(readl(adata->acp_base + ACP_I2STDM_IRER) & BIT(0))) + writel(0, adata->acp_base + ACP_I2STDM_IER); + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int acp_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream = substream->runtime->private_data; + u32 reg_dma_size = 0, reg_fifo_size = 0, reg_fifo_addr = 0; + u32 phy_addr = 0, acp_fifo_addr = 0, ext_int_ctrl; + unsigned int dir = substream->stream; + + switch (dai->driver->id) { + case I2S_SP_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + reg_dma_size = ACP_I2S_TX_DMA_SIZE; + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + + SP_PB_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_I2S_TX_FIFOADDR; + reg_fifo_size = ACP_I2S_TX_FIFOSIZE; + + phy_addr = I2S_SP_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_I2S_TX_RINGBUFADDR); + } else { + reg_dma_size = ACP_I2S_RX_DMA_SIZE; + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + + SP_CAPT_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_I2S_RX_FIFOADDR; + reg_fifo_size = ACP_I2S_RX_FIFOSIZE; + phy_addr = I2S_SP_RX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_I2S_RX_RINGBUFADDR); + } + break; + case I2S_BT_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + reg_dma_size = ACP_BT_TX_DMA_SIZE; + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + + BT_PB_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_BT_TX_FIFOADDR; + reg_fifo_size = ACP_BT_TX_FIFOSIZE; + + phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_BT_TX_RINGBUFADDR); + } else { + reg_dma_size = ACP_BT_RX_DMA_SIZE; + acp_fifo_addr = ACP_SRAM_PTE_OFFSET + + BT_CAPT_FIFO_ADDR_OFFSET; + reg_fifo_addr = ACP_BT_RX_FIFOADDR; + reg_fifo_size = ACP_BT_RX_FIFOSIZE; + + phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset; + writel(phy_addr, adata->acp_base + ACP_BT_RX_RINGBUFADDR); + } + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + writel(DMA_SIZE, adata->acp_base + reg_dma_size); + writel(acp_fifo_addr, adata->acp_base + reg_fifo_addr); + writel(FIFO_SIZE, adata->acp_base + reg_fifo_size); + + ext_int_ctrl = readl(adata->acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl |= BIT(I2S_RX_THRESHOLD) | BIT(BT_RX_THRESHOLD) + | BIT(I2S_TX_THRESHOLD) | BIT(BT_TX_THRESHOLD); + + writel(ext_int_ctrl, adata->acp_base + ACP_EXTERNAL_INTR_CNTL); + + return 0; +} + +static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct acp_stream *stream = substream->runtime->private_data; + struct device *dev = dai->component->dev; + unsigned int dir = substream->stream; + unsigned int irq_bit = 0; + + switch (dai->driver->id) { + case I2S_SP_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + irq_bit = BIT(I2S_TX_THRESHOLD); + stream->pte_offset = ACP_SRAM_SP_PB_PTE_OFFSET; + stream->fifo_offset = SP_PB_FIFO_ADDR_OFFSET; + } else { + irq_bit = BIT(I2S_RX_THRESHOLD); + stream->pte_offset = ACP_SRAM_SP_CP_PTE_OFFSET; + stream->fifo_offset = SP_CAPT_FIFO_ADDR_OFFSET; + } + break; + case I2S_BT_INSTANCE: + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + irq_bit = BIT(BT_TX_THRESHOLD); + stream->pte_offset = ACP_SRAM_BT_PB_PTE_OFFSET; + stream->fifo_offset = BT_PB_FIFO_ADDR_OFFSET; + } else { + irq_bit = BIT(BT_RX_THRESHOLD); + stream->pte_offset = ACP_SRAM_BT_CP_PTE_OFFSET; + stream->fifo_offset = BT_CAPT_FIFO_ADDR_OFFSET; + } + break; + default: + dev_err(dev, "Invalid dai id %x\n", dai->driver->id); + return -EINVAL; + } + + /* Save runtime dai configuration in stream */ + stream->id = dai->driver->id + dir; + stream->dai_id = dai->driver->id; + stream->irq_bit = irq_bit; + + return 0; +} + +const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, + .prepare = acp_i2s_prepare, + .trigger = acp_i2s_trigger, +}; +EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, SND_SOC_ACP_COMMON); + +int asoc_acp_i2s_probe(struct snd_soc_dai *dai) +{ + struct device *dev = dai->component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + unsigned int val; + + if (!adata->acp_base) { + dev_err(dev, "I2S base is NULL\n"); + return -EINVAL; + } + + val = readl(adata->acp_base + ACP_I2S_PIN_CONFIG); + if (val != I2S_MODE) { + dev_err(dev, "I2S Mode not supported val %x\n", val); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(asoc_acp_i2s_probe, SND_SOC_ACP_COMMON); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-legacy-mach.c b/sound/soc/amd/acp/acp-legacy-mach.c new file mode 100644 index 000000000000..de0f8024e2fb --- /dev/null +++ b/sound/soc/amd/acp/acp-legacy-mach.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Machine Driver Legacy Support for ACP HW block + */ + +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> + +#include "acp-mach.h" + +static struct acp_card_drvdata rt5682_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = NONE, + .hs_codec_id = RT5682, + .amp_codec_id = RT1019, + .dmic_codec_id = NONE, +}; + +static const struct snd_kcontrol_new acp_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), + +}; + +static const struct snd_soc_dapm_widget acp_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static int acp_asoc_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = NULL; + struct device *dev = &pdev->dev; + int ret; + + if (!pdev->id_entry) + return -EINVAL; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = pdev->id_entry->name; + card->dapm_widgets = acp_widgets; + card->num_dapm_widgets = ARRAY_SIZE(acp_widgets); + card->controls = acp_controls; + card->num_controls = ARRAY_SIZE(acp_controls); + card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data; + + acp_legacy_dai_links_create(card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + card->name, ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id board_ids[] = { + { + .name = "rn_rt5682_rt1019", + .driver_data = (kernel_ulong_t)&rt5682_rt1019_data, + }, + { } +}; +static struct platform_driver acp_asoc_audio = { + .driver = { + .name = "acp_mach", + }, + .probe = acp_asoc_probe, + .id_table = board_ids, +}; + +module_platform_driver(acp_asoc_audio); + +MODULE_IMPORT_NS(SND_SOC_AMD_MACH); +MODULE_DESCRIPTION("ACP chrome audio support"); +MODULE_ALIAS("platform:rn_rt5682_rt1019"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c new file mode 100644 index 000000000000..7785f12aa006 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach-common.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// Vijendar Mukunda <Vijendar.Mukunda@amd.com> +// + +/* + * Machine Driver Interface for ACP HW block + */ + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> +#include <linux/input.h> +#include <linux/module.h> + +#include "../../codecs/rt5682.h" +#include "../../codecs/rt1019.h" +#include "../../codecs/rt5682s.h" +#include "acp-mach.h" + +#define PCO_PLAT_CLK 48000000 +#define RT5682_PLL_FREQ (48000 * 512) +#define DUAL_CHANNEL 2 +#define FOUR_CHANNEL 4 + +static struct snd_soc_jack pco_jack; + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int acp_clk_enable(struct acp_card_drvdata *drvdata) +{ + clk_set_rate(drvdata->wclk, 48000); + clk_set_rate(drvdata->bclk, 48000 * 64); + + return clk_prepare_enable(drvdata->wclk); +} + +/* Declare RT5682 codec components */ +SND_SOC_DAILINK_DEF(rt5682, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", "rt5682-aif1"))); + +static const struct snd_soc_dapm_route rt5682_map[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + { "IN1P", NULL, "Headset Mic" }, +}; + +/* Define card ops for RT5682 CODEC */ +static int acp_card_rt5682_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int ret; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + if (drvdata->hs_codec_id != RT5682) + return -EINVAL; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL2, RT5682_PLL2_S_MCLK, + PCO_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret); + return ret; + } + + /* Set tdm/i2s1 master bclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret); + return ret; + } + + drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk"); + drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk"); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682_map, ARRAY_SIZE(rt5682_map)); +} + +static int acp_card_hs_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + ret = acp_clk_enable(drvdata); + if (ret < 0) + dev_err(rtd->card->dev, "Failed to enable HS clk: %d\n", ret); + + return ret; +} + +static void acp_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + clk_disable_unprepare(drvdata->wclk); +} + +static const struct snd_soc_ops acp_card_rt5682_ops = { + .startup = acp_card_hs_startup, + .shutdown = acp_card_shutdown, +}; + +/* Define RT5682S CODEC component*/ +SND_SOC_DAILINK_DEF(rt5682s, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-RTL5682:00", "rt5682s-aif1"))); + +static const struct snd_soc_dapm_route rt5682s_map[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + { "IN1P", NULL, "Headset Mic" }, +}; + +static int acp_card_rt5682s_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + int ret; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + if (drvdata->hs_codec_id != RT5682S) + return -EINVAL; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBP_CFP); + if (ret < 0) { + dev_err(rtd->card->dev, "Failed to set dai fmt: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, RT5682S_PLL2, RT5682S_PLL_S_MCLK, + PCO_PLAT_CLK, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec PLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682S_SCLK_S_PLL2, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set codec SYSCLK: %d\n", ret); + return ret; + } + + /* Set tdm/i2s1 master bclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64); + if (ret < 0) { + dev_err(rtd->dev, "Failed to set rt5682 tdm bclk ratio: %d\n", ret); + return ret; + } + + drvdata->wclk = clk_get(component->dev, "rt5682-dai-wclk"); + drvdata->bclk = clk_get(component->dev, "rt5682-dai-bclk"); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pco_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(pco_jack.jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, &pco_jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt5682s_map, ARRAY_SIZE(rt5682s_map)); +} + +static const struct snd_soc_ops acp_card_rt5682s_ops = { + .startup = acp_card_hs_startup, + .shutdown = acp_card_shutdown, +}; + +/* Declare RT1019 codec components */ +SND_SOC_DAILINK_DEF(rt1019, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC1019:01", "rt1019-aif"), + COMP_CODEC("i2c-10EC1019:02", "rt1019-aif"))); + +static const struct snd_soc_dapm_route rt1019_map_lr[] = { + { "Left Spk", NULL, "Left SPO" }, + { "Right Spk", NULL, "Right SPO" }, +}; + +static struct snd_soc_codec_conf rt1019_conf[] = { + { + .dlc = COMP_CODEC_CONF("i2c-10EC1019:01"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("i2c-10EC1019:02"), + .name_prefix = "Right", + }, +}; + +static int acp_card_rt1019_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + if (drvdata->amp_codec_id != RT1019) + return -EINVAL; + + return snd_soc_dapm_add_routes(&rtd->card->dapm, rt1019_map_lr, + ARRAY_SIZE(rt1019_map_lr)); +} + +static int acp_card_rt1019_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + struct snd_soc_dai *codec_dai; + int srate, i, ret = 0; + + srate = params_rate(params); + + if (drvdata->amp_codec_id != RT1019) + return -EINVAL; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + if (strcmp(codec_dai->name, "rt1019-aif")) + continue; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT1019_PLL_S_BCLK, + 64 * srate, 256 * srate); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT1019_SCLK_S_PLL, + 256 * srate, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + + return 0; +} + +static int acp_card_amp_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + int ret; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + ret = acp_clk_enable(drvdata); + if (ret < 0) + dev_err(rtd->card->dev, "Failed to enable AMP clk: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops acp_card_rt1019_ops = { + .startup = acp_card_amp_startup, + .shutdown = acp_card_shutdown, + .hw_params = acp_card_rt1019_hw_params, +}; + +/* Declare Maxim codec components */ +SND_SOC_DAILINK_DEF(max98360a, + DAILINK_COMP_ARRAY(COMP_CODEC("MX98360A:00", "HiFi"))); + +static const struct snd_soc_dapm_route max98360a_map[] = { + {"Spk", NULL, "Speaker"}, +}; + +static int acp_card_maxim_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct acp_card_drvdata *drvdata = card->drvdata; + + if (drvdata->amp_codec_id != MAX98360A) + return -EINVAL; + + return snd_soc_dapm_add_routes(&rtd->card->dapm, max98360a_map, + ARRAY_SIZE(max98360a_map)); +} + +static const struct snd_soc_ops acp_card_maxim_ops = { + .startup = acp_card_amp_startup, + .shutdown = acp_card_shutdown, +}; + +/* Declare DMIC codec components */ +SND_SOC_DAILINK_DEF(dmic_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); + +/* Declare ACP CPU components */ +static struct snd_soc_dai_link_component dummy_codec[] = { + { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", + } +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + .name = "acp_asoc_renoir.0", + } +}; + +static struct snd_soc_dai_link_component sof_component[] = { + { + .name = "0000:04:00.5", + } +}; + +SND_SOC_DAILINK_DEF(i2s_sp, + DAILINK_COMP_ARRAY(COMP_CPU("acp-i2s-sp"))); +SND_SOC_DAILINK_DEF(sof_sp, + DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-sp"))); +SND_SOC_DAILINK_DEF(sof_dmic, + DAILINK_COMP_ARRAY(COMP_CPU("acp-sof-dmic"))); + +int acp_sofdsp_dai_links_create(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *links; + struct device *dev = card->dev; + struct acp_card_drvdata *drv_data = card->drvdata; + int i = 0, num_links = 0; + + if (drv_data->hs_cpu_id) + num_links++; + if (drv_data->amp_cpu_id) + num_links++; + if (drv_data->dmic_cpu_id) + num_links++; + + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * num_links, GFP_KERNEL); + if (!links) + return -ENOMEM; + + if (drv_data->hs_cpu_id == I2S_SP) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = sof_sp; + links[i].num_cpus = ARRAY_SIZE(sof_sp); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == RT5682) { + links[i].codecs = rt5682; + links[i].num_codecs = ARRAY_SIZE(rt5682); + links[i].init = acp_card_rt5682_init; + links[i].ops = &acp_card_rt5682_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_SP) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = sof_sp; + links[i].num_cpus = ARRAY_SIZE(sof_sp); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_playback = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + i++; + } + + if (drv_data->dmic_cpu_id == DMIC) { + links[i].name = "acp-dmic-codec"; + links[i].id = DMIC_BE_ID; + links[i].codecs = dmic_codec; + links[i].num_codecs = ARRAY_SIZE(dmic_codec); + links[i].cpus = sof_dmic; + links[i].num_cpus = ARRAY_SIZE(sof_dmic); + links[i].platforms = sof_component; + links[i].num_platforms = ARRAY_SIZE(sof_component); + links[i].dpcm_capture = 1; + links[i].nonatomic = true; + links[i].no_pcm = 1; + } + + card->dai_link = links; + card->num_links = num_links; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_sofdsp_dai_links_create, SND_SOC_AMD_MACH); + +int acp_legacy_dai_links_create(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *links; + struct device *dev = card->dev; + struct acp_card_drvdata *drv_data = card->drvdata; + int i = 0, num_links = 0; + + if (drv_data->hs_cpu_id) + num_links++; + if (drv_data->amp_cpu_id) + num_links++; + if (drv_data->dmic_cpu_id) + num_links++; + + links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * num_links, GFP_KERNEL); + + if (drv_data->hs_cpu_id == I2S_SP) { + links[i].name = "acp-headset-codec"; + links[i].id = HEADSET_BE_ID; + links[i].cpus = i2s_sp; + links[i].num_cpus = ARRAY_SIZE(i2s_sp); + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + links[i].dpcm_playback = 1; + links[i].dpcm_capture = 1; + if (!drv_data->hs_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->hs_codec_id == RT5682) { + links[i].codecs = rt5682; + links[i].num_codecs = ARRAY_SIZE(rt5682); + links[i].init = acp_card_rt5682_init; + links[i].ops = &acp_card_rt5682_ops; + } + if (drv_data->hs_codec_id == RT5682S) { + links[i].codecs = rt5682s; + links[i].num_codecs = ARRAY_SIZE(rt5682s); + links[i].init = acp_card_rt5682s_init; + links[i].ops = &acp_card_rt5682s_ops; + } + i++; + } + + if (drv_data->amp_cpu_id == I2S_SP) { + links[i].name = "acp-amp-codec"; + links[i].id = AMP_BE_ID; + links[i].cpus = i2s_sp; + links[i].num_cpus = ARRAY_SIZE(i2s_sp); + links[i].platforms = platform_component; + links[i].num_platforms = ARRAY_SIZE(platform_component); + links[i].dpcm_playback = 1; + if (!drv_data->amp_codec_id) { + /* Use dummy codec if codec id not specified */ + links[i].codecs = dummy_codec; + links[i].num_codecs = ARRAY_SIZE(dummy_codec); + } + if (drv_data->amp_codec_id == RT1019) { + links[i].codecs = rt1019; + links[i].num_codecs = ARRAY_SIZE(rt1019); + links[i].ops = &acp_card_rt1019_ops; + links[i].init = acp_card_rt1019_init; + card->codec_conf = rt1019_conf; + card->num_configs = ARRAY_SIZE(rt1019_conf); + } + if (drv_data->amp_codec_id == MAX98360A) { + links[i].codecs = max98360a; + links[i].num_codecs = ARRAY_SIZE(max98360a); + links[i].ops = &acp_card_maxim_ops; + links[i].init = acp_card_maxim_init; + } + } + + card->dai_link = links; + card->num_links = num_links; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_legacy_dai_links_create, SND_SOC_AMD_MACH); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/acp-mach.h b/sound/soc/amd/acp/acp-mach.h new file mode 100644 index 000000000000..5dc47cfbff10 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ +#ifndef __ACP_MACH_H +#define __ACP_MACH_H + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/input.h> +#include <linux/module.h> +#include <sound/soc.h> + +enum be_id { + HEADSET_BE_ID = 0, + AMP_BE_ID, + DMIC_BE_ID, +}; + +enum cpu_endpoints { + NONE = 0, + I2S_SP, + I2S_BT, + DMIC, +}; + +enum codec_endpoints { + DUMMY = 0, + RT5682, + RT1019, + MAX98360A, + RT5682S, +}; + +struct acp_card_drvdata { + unsigned int hs_cpu_id; + unsigned int amp_cpu_id; + unsigned int dmic_cpu_id; + unsigned int hs_codec_id; + unsigned int amp_codec_id; + unsigned int dmic_codec_id; + unsigned int dai_fmt; + struct clk *wclk; + struct clk *bclk; +}; + +int acp_sofdsp_dai_links_create(struct snd_soc_card *card); +int acp_legacy_dai_links_create(struct snd_soc_card *card); + +#endif diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c new file mode 100644 index 000000000000..65a809e2c29f --- /dev/null +++ b/sound/soc/amd/acp/acp-platform.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Generic interface for ACP audio blck PCM component + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_i2s_dma" + +static const struct snd_pcm_hardware acp_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 96000, + .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, +}; + +int acp_machine_select(struct acp_dev_data *adata) +{ + struct snd_soc_acpi_mach *mach; + int size; + + size = sizeof(*adata->machines); + mach = snd_soc_acpi_find_machine(adata->machines); + if (!mach) { + dev_err(adata->dev, "warning: No matching ASoC machine driver found\n"); + return -EINVAL; + } + + adata->mach_dev = platform_device_register_data(adata->dev, mach->drv_name, + PLATFORM_DEVID_NONE, mach, size); + if (IS_ERR(adata->mach_dev)) + dev_warn(adata->dev, "Unable to register Machine device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_machine_select, SND_SOC_ACP_COMMON); + +static irqreturn_t i2s_irq_handler(int irq, void *data) +{ + struct acp_dev_data *adata = data; + struct acp_stream *stream; + u16 i2s_flag = 0; + u32 val, i; + + if (!adata) + return IRQ_NONE; + + val = readl(adata->acp_base + ACP_EXTERNAL_INTR_STAT); + + for (i = 0; i < ACP_MAX_STREAM; i++) { + stream = adata->stream[i]; + if (stream && (val & stream->irq_bit)) { + writel(stream->irq_bit, adata->acp_base + ACP_EXTERNAL_INTR_STAT); + snd_pcm_period_elapsed(stream->substream); + i2s_flag = 1; + break; + } + } + + if (i2s_flag) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static void config_pte_for_stream(struct acp_dev_data *adata, struct acp_stream *stream) +{ + u32 pte_reg, pte_size, reg_val; + + /* Use ATU base Group5 */ + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5; + stream->reg_offset = 0x02000000; + + /* Group Enable */ + reg_val = ACP_SRAM_PTE_OFFSET; + writel(reg_val | BIT(31), adata->acp_base + pte_reg); + writel(PAGE_SIZE_4K_ENABLE, adata->acp_base + pte_size); +} + +static void config_acp_dma(struct acp_dev_data *adata, int cpu_id, int size) +{ + struct acp_stream *stream = adata->stream[cpu_id]; + struct snd_pcm_substream *substream = stream->substream; + dma_addr_t addr = substream->dma_buffer.addr; + int num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); + u32 low, high, val; + u16 page_idx; + + val = stream->pte_offset; + + for (page_idx = 0; page_idx < num_pages; page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + low = lower_32_bits(addr); + high = upper_32_bits(addr); + writel(low, adata->acp_base + ACP_SCRATCH_REG_0 + val); + high |= BIT(31); + writel(high, adata->acp_base + ACP_SCRATCH_REG_0 + val + 4); + + /* Move to next physically contiguous page */ + val += 8; + addr += PAGE_SIZE; + } +} + +static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream; + int stream_id = cpu_dai->driver->id * 2 + substream->stream; + int ret; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->substream = substream; + adata->stream[stream_id] = stream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = acp_pcm_hardware_playback; + else + runtime->hw = acp_pcm_hardware_capture; + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(component->dev, "set integer constraint failed\n"); + kfree(stream); + return ret; + } + runtime->private_data = stream; + + writel(1, adata->acp_base + ACP_EXTERNAL_INTR_ENB); + + return ret; +} + +static int acp_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct acp_dev_data *adata = snd_soc_component_get_drvdata(component); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct acp_stream *stream = substream->runtime->private_data; + int stream_id = cpu_dai->driver->id * 2 + substream->stream; + u64 size = params_buffer_bytes(params); + + /* Configure ACP DMA block with params */ + config_pte_for_stream(adata, stream); + config_acp_dma(adata, stream_id, size); + + return 0; +} + +static snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream = substream->runtime->private_data; + u32 pos, buffersize; + u64 bytescount; + + buffersize = frames_to_bytes(substream->runtime, + substream->runtime->buffer_size); + + bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream); + + if (bytescount > stream->bytescount) + bytescount -= stream->bytescount; + + pos = do_div(bytescount, buffersize); + + return bytes_to_frames(substream->runtime, pos); +} + +static int acp_dma_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct device *parent = component->dev->parent; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, + parent, MIN_BUFFER, MAX_BUFFER); + return 0; +} + +static int acp_dma_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return snd_pcm_lib_default_mmap(substream, vma); +} + +static int acp_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct device *dev = component->dev; + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct acp_stream *stream; + int stream_id = cpu_dai->driver->id * 2 + substream->stream; + + stream = adata->stream[stream_id]; + kfree(stream); + adata->stream[stream_id] = NULL; + + return 0; +} + +static const struct snd_soc_component_driver acp_pcm_component = { + .name = DRV_NAME, + .open = acp_dma_open, + .close = acp_dma_close, + .hw_params = acp_dma_hw_params, + .pointer = acp_dma_pointer, + .mmap = acp_dma_mmap, + .pcm_construct = acp_dma_new, +}; + +int acp_platform_register(struct device *dev) +{ + struct acp_dev_data *adata = dev_get_drvdata(dev); + struct snd_soc_dai_driver; + unsigned int status; + + status = devm_request_irq(dev, adata->i2s_irq, i2s_irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", adata); + if (status) { + dev_err(dev, "ACP I2S IRQ request failed\n"); + return status; + } + + status = devm_snd_soc_register_component(dev, &acp_pcm_component, + adata->dai_driver, + adata->num_dai); + if (status) { + dev_err(dev, "Fail to register acp i2s component\n"); + return status; + } + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_platform_register, SND_SOC_ACP_COMMON); + +int acp_platform_unregister(struct device *dev) +{ + struct acp_dev_data *adata = dev_get_drvdata(dev); + + if (adata->mach_dev) + platform_device_unregister(adata->mach_dev); + return 0; +} +EXPORT_SYMBOL_NS_GPL(acp_platform_unregister, SND_SOC_ACP_COMMON); + +MODULE_DESCRIPTION("AMD ACP PCM Driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS(DRV_NAME); diff --git a/sound/soc/amd/acp/acp-renoir.c b/sound/soc/amd/acp/acp-renoir.c new file mode 100644 index 000000000000..9b321a055b52 --- /dev/null +++ b/sound/soc/amd/acp/acp-renoir.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * Hardware interface for Renoir ACP block + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/dma-mapping.h> + +#include "amd.h" + +#define DRV_NAME "acp_asoc_renoir" + +static struct snd_soc_acpi_codecs amp_rt1019 = { + .num_codecs = 1, + .codecs = {"10EC1019"} +}; + +static struct snd_soc_acpi_mach snd_soc_acpi_amd_acp_machines[] = { + { + .id = "10EC5682", + .drv_name = "rn_rt5682_rt1019", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &_rt1019, + }, + { + .id = "AMDI1019", + .drv_name = "renoir-acp", + }, + {}, +}; + +static struct snd_soc_dai_driver acp_renoir_dai[] = { +{ + .name = "acp-i2s-sp", + .id = I2S_SP_INSTANCE, + .playback = { + .stream_name = "I2S SP Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S SP Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +{ + .name = "acp-i2s-bt", + .id = I2S_BT_INSTANCE, + .playback = { + .stream_name = "I2S BT Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S BT Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &asoc_acp_cpu_dai_ops, + .probe = &asoc_acp_i2s_probe, +}, +}; + +static int renoir_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acp_dev_data *adata; + struct resource *res; + + adata = devm_kzalloc(dev, sizeof(struct acp_dev_data), GFP_KERNEL); + if (!adata) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acp_mem"); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENODEV; + } + + adata->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!adata->acp_base) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "acp_dai_irq"); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n"); + return -ENODEV; + } + + adata->i2s_irq = res->start; + adata->dev = dev; + adata->dai_driver = acp_renoir_dai; + adata->num_dai = ARRAY_SIZE(acp_renoir_dai); + + adata->machines = snd_soc_acpi_amd_acp_machines; + acp_machine_select(adata); + + dev_set_drvdata(dev, adata); + acp_platform_register(dev); + + return 0; +} + +static int renoir_audio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + acp_platform_unregister(dev); + return 0; +} + +static struct platform_driver renoir_driver = { + .probe = renoir_audio_probe, + .remove = renoir_audio_remove, + .driver = { + .name = "acp_asoc_renoir", + }, +}; + +module_platform_driver(renoir_driver); + +MODULE_DESCRIPTION("AMD ACP Renoir Driver"); +MODULE_IMPORT_NS(SND_SOC_ACP_COMMON); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c new file mode 100644 index 000000000000..854eb7214cea --- /dev/null +++ b/sound/soc/amd/acp/acp-sof-mach.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// + +/* + * SOF Machine Driver Support for ACP HW block + */ + +#include <sound/core.h> +#include <sound/pcm_params.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> + +#include "acp-mach.h" + +static struct acp_card_drvdata sof_rt5682_rt1019_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682, + .amp_codec_id = RT1019, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_rt5682_max_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, +}; + +static struct acp_card_drvdata sof_rt5682s_max_data = { + .hs_cpu_id = I2S_SP, + .amp_cpu_id = I2S_SP, + .dmic_cpu_id = DMIC, + .hs_codec_id = RT5682S, + .amp_codec_id = MAX98360A, + .dmic_codec_id = DMIC, +}; + +static const struct snd_kcontrol_new acp_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget acp_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static int acp_sof_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = NULL; + struct device *dev = &pdev->dev; + int ret; + + if (!pdev->id_entry) + return -EINVAL; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = pdev->id_entry->name; + card->dapm_widgets = acp_widgets; + card->num_dapm_widgets = ARRAY_SIZE(acp_widgets); + card->controls = acp_controls; + card->num_controls = ARRAY_SIZE(acp_controls); + card->drvdata = (struct acp_card_drvdata *)pdev->id_entry->driver_data; + + acp_sofdsp_dai_links_create(card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + card->name, ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", + .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data + }, + { + .name = "rt5682-max", + .driver_data = (kernel_ulong_t)&sof_rt5682_max_data + }, + { + .name = "rt5682s-max", + .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data + }, + { } +}; +static struct platform_driver acp_asoc_audio = { + .driver = { + .name = "sof_mach", + }, + .probe = acp_sof_probe, + .id_table = board_ids, +}; + +module_platform_driver(acp_asoc_audio); + +MODULE_IMPORT_NS(SND_SOC_AMD_MACH); +MODULE_DESCRIPTION("ACP chrome SOF audio support"); +MODULE_ALIAS("platform:rt5682-rt1019"); +MODULE_ALIAS("platform:rt5682-max"); +MODULE_ALIAS("platform:rt5682s-max"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp/amd.h b/sound/soc/amd/acp/amd.h new file mode 100644 index 000000000000..8eee3d34774b --- /dev/null +++ b/sound/soc/amd/acp/amd.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef __AMD_ACP_H +#define __AMD_ACP_H + +#include <sound/pcm.h> +#include <sound/soc-acpi.h> +#include "chip_offset_byte.h" + +#define I2S_SP_INSTANCE 0x00 +#define I2S_BT_INSTANCE 0x01 + +#define MEM_WINDOW_START 0x4000000 + +#define ACP_I2S_REG_START 0x1242400 +#define ACP_I2S_REG_END 0x1242810 +#define ACP3x_I2STDM_REG_START 0x1242400 +#define ACP3x_I2STDM_REG_END 0x1242410 +#define ACP3x_BT_TDM_REG_START 0x1242800 +#define ACP3x_BT_TDM_REG_END 0x1242810 +#define I2S_MODE 0x04 +#define I2S_RX_THRESHOLD 27 +#define I2S_TX_THRESHOLD 28 +#define BT_TX_THRESHOLD 26 +#define BT_RX_THRESHOLD 25 + +#define ACP_SRAM_PTE_OFFSET 0x02052800 + +#define ACP_SRAM_SP_PB_PTE_OFFSET 0x0 +#define ACP_SRAM_SP_CP_PTE_OFFSET 0x100 +#define ACP_SRAM_BT_PB_PTE_OFFSET 0x200 +#define ACP_SRAM_BT_CP_PTE_OFFSET 0x300 +#define PAGE_SIZE_4K_ENABLE 0x2 + +#define I2S_SP_TX_MEM_WINDOW_START 0x4000000 +#define I2S_SP_RX_MEM_WINDOW_START 0x4020000 +#define I2S_BT_TX_MEM_WINDOW_START 0x4040000 +#define I2S_BT_RX_MEM_WINDOW_START 0x4060000 + +#define SP_PB_FIFO_ADDR_OFFSET 0x500 +#define SP_CAPT_FIFO_ADDR_OFFSET 0x700 +#define BT_PB_FIFO_ADDR_OFFSET 0x900 +#define BT_CAPT_FIFO_ADDR_OFFSET 0xB00 +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 8192 +#define PLAYBACK_MIN_PERIOD_SIZE 1024 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 8 +#define CAPTURE_MAX_PERIOD_SIZE 8192 +#define CAPTURE_MIN_PERIOD_SIZE 1024 + +#define MAX_BUFFER 65536 +#define MIN_BUFFER MAX_BUFFER +#define FIFO_SIZE 0x100 +#define DMA_SIZE 0x40 +#define FRM_LEN 0x100 + +#define ACP3x_ITER_IRER_SAMP_LEN_MASK 0x38 + +#define ACP_MAX_STREAM 6 + +struct acp_stream { + struct snd_pcm_substream *substream; + int irq_bit; + int dai_id; + int id; + u64 bytescount; + u32 reg_offset; + u32 pte_offset; + u32 fifo_offset; +}; + +struct acp_dev_data { + char *name; + struct device *dev; + void __iomem *acp_base; + unsigned int i2s_irq; + + /* SOC specific dais */ + struct snd_soc_dai_driver *dai_driver; + int num_dai; + + struct acp_stream *stream[ACP_MAX_STREAM]; + + struct snd_soc_acpi_mach *machines; + struct platform_device *mach_dev; +}; + +extern const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops; + +int asoc_acp_i2s_probe(struct snd_soc_dai *dai); +int acp_platform_register(struct device *dev); +int acp_platform_unregister(struct device *dev); + +int acp_machine_select(struct acp_dev_data *adata); + +static inline u64 acp_get_byte_count(struct acp_dev_data *adata, int dai_id, int direction) +{ + u64 byte_count, low = 0, high = 0; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai_id) { + case I2S_BT_INSTANCE: + high = readl(adata->acp_base + ACP_BT_TX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_BT_TX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_SP_INSTANCE: + high = readl(adata->acp_base + ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_I2S_TX_LINEARPOSITIONCNTR_LOW); + break; + default: + dev_err(adata->dev, "Invalid dai id %x\n", dai_id); + return -EINVAL; + } + } else { + switch (dai_id) { + case I2S_BT_INSTANCE: + high = readl(adata->acp_base + ACP_BT_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_BT_RX_LINEARPOSITIONCNTR_LOW); + break; + case I2S_SP_INSTANCE: + high = readl(adata->acp_base + ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH); + low = readl(adata->acp_base + ACP_I2S_RX_LINEARPOSITIONCNTR_LOW); + break; + default: + dev_err(adata->dev, "Invalid dai id %x\n", dai_id); + return -EINVAL; + } + } + /* Get 64 bit value from two 32 bit registers */ + byte_count = (high << 32) | low; + + return byte_count; +} + +#endif diff --git a/sound/soc/amd/acp/chip_offset_byte.h b/sound/soc/amd/acp/chip_offset_byte.h new file mode 100644 index 000000000000..c7f77e975dc7 --- /dev/null +++ b/sound/soc/amd/acp/chip_offset_byte.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef _ACP_IP_OFFSET_HEADER +#define _ACP_IP_OFFSET_HEADER + +#define ACPAXI2AXI_ATU_CTRL 0xC40 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5 0xC20 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_5 0xC24 +#define ACP_EXTERNAL_INTR_ENB 0x1800 +#define ACP_EXTERNAL_INTR_CNTL 0x1804 +#define ACP_EXTERNAL_INTR_STAT 0x1808 +#define ACP_I2S_PIN_CONFIG 0x1400 +#define ACP_SCRATCH_REG_0 0x12800 + +/* Registers from ACP_AUDIO_BUFFERS block */ + +#define ACP_I2S_RX_RINGBUFADDR 0x2000 +#define ACP_I2S_RX_RINGBUFSIZE 0x2004 +#define ACP_I2S_RX_LINKPOSITIONCNTR 0x2008 +#define ACP_I2S_RX_FIFOADDR 0x200C +#define ACP_I2S_RX_FIFOSIZE 0x2010 +#define ACP_I2S_RX_DMA_SIZE 0x2014 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH 0x2018 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_LOW 0x201C +#define ACP_I2S_RX_INTR_WATERMARK_SIZE 0x2020 +#define ACP_I2S_TX_RINGBUFADDR 0x2024 +#define ACP_I2S_TX_RINGBUFSIZE 0x2028 +#define ACP_I2S_TX_LINKPOSITIONCNTR 0x202C +#define ACP_I2S_TX_FIFOADDR 0x2030 +#define ACP_I2S_TX_FIFOSIZE 0x2034 +#define ACP_I2S_TX_DMA_SIZE 0x2038 +#define ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH 0x203C +#define ACP_I2S_TX_LINEARPOSITIONCNTR_LOW 0x2040 +#define ACP_I2S_TX_INTR_WATERMARK_SIZE 0x2044 +#define ACP_BT_RX_RINGBUFADDR 0x2048 +#define ACP_BT_RX_RINGBUFSIZE 0x204C +#define ACP_BT_RX_LINKPOSITIONCNTR 0x2050 +#define ACP_BT_RX_FIFOADDR 0x2054 +#define ACP_BT_RX_FIFOSIZE 0x2058 +#define ACP_BT_RX_DMA_SIZE 0x205C +#define ACP_BT_RX_LINEARPOSITIONCNTR_HIGH 0x2060 +#define ACP_BT_RX_LINEARPOSITIONCNTR_LOW 0x2064 +#define ACP_BT_RX_INTR_WATERMARK_SIZE 0x2068 +#define ACP_BT_TX_RINGBUFADDR 0x206C +#define ACP_BT_TX_RINGBUFSIZE 0x2070 +#define ACP_BT_TX_LINKPOSITIONCNTR 0x2074 +#define ACP_BT_TX_FIFOADDR 0x2078 +#define ACP_BT_TX_FIFOSIZE 0x207C +#define ACP_BT_TX_DMA_SIZE 0x2080 +#define ACP_BT_TX_LINEARPOSITIONCNTR_HIGH 0x2084 +#define ACP_BT_TX_LINEARPOSITIONCNTR_LOW 0x2088 +#define ACP_BT_TX_INTR_WATERMARK_SIZE 0x208C + +#define ACP_I2STDM_IER 0x2400 +#define ACP_I2STDM_IRER 0x2404 +#define ACP_I2STDM_RXFRMT 0x2408 +#define ACP_I2STDM_ITER 0x240C +#define ACP_I2STDM_TXFRMT 0x2410 + +/* Registers from ACP_BT_TDM block */ + +#define ACP_BTTDM_IER 0x2800 +#define ACP_BTTDM_IRER 0x2804 +#define ACP_BTTDM_RXFRMT 0x2808 +#define ACP_BTTDM_ITER 0x280C +#define ACP_BTTDM_TXFRMT 0x2810 + +#endif diff --git a/sound/soc/amd/vangogh/Makefile b/sound/soc/amd/vangogh/Makefile index 3353f93dc610..c9e53e04e247 100644 --- a/sound/soc/amd/vangogh/Makefile +++ b/sound/soc/amd/vangogh/Makefile @@ -3,7 +3,9 @@ snd-pci-acp5x-objs := pci-acp5x.o snd-acp5x-i2s-objs := acp5x-i2s.o snd-acp5x-pcm-dma-objs := acp5x-pcm-dma.o +snd-soc-acp5x-mach-objs := acp5x-mach.o obj-$(CONFIG_SND_SOC_AMD_ACP5x) += snd-pci-acp5x.o obj-$(CONFIG_SND_SOC_AMD_ACP5x) += snd-acp5x-i2s.o obj-$(CONFIG_SND_SOC_AMD_ACP5x) += snd-acp5x-pcm-dma.o +obj-$(CONFIG_SND_SOC_AMD_VANGOGH_MACH) += snd-soc-acp5x-mach.o diff --git a/sound/soc/amd/vangogh/acp5x-i2s.c b/sound/soc/amd/vangogh/acp5x-i2s.c index 2705e57eb713..002db3971ca9 100644 --- a/sound/soc/amd/vangogh/acp5x-i2s.c +++ b/sound/soc/amd/vangogh/acp5x-i2s.c @@ -348,7 +348,7 @@ static int acp5x_i2s_trigger(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_dai_ops acp5x_i2s_dai_ops = { +static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = { .hw_params = acp5x_i2s_hwparams, .trigger = acp5x_i2s_trigger, .set_fmt = acp5x_i2s_set_fmt, diff --git a/sound/soc/amd/vangogh/acp5x-mach.c b/sound/soc/amd/vangogh/acp5x-mach.c new file mode 100644 index 000000000000..14cf325e4b23 --- /dev/null +++ b/sound/soc/amd/vangogh/acp5x-mach.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Machine driver for AMD Vangogh platform using NAU8821 & CS35L41 + * codecs. + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> +#include <linux/io.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include <sound/jack.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/io.h> +#include <linux/acpi.h> +#include <linux/dmi.h> + +#include "../../codecs/nau8821.h" +#include "../../codecs/cs35l41.h" + +#include "acp5x.h" + +#define DRV_NAME "acp5x_mach" +#define DUAL_CHANNEL 2 +#define ACP5X_NUVOTON_CODEC_DAI "nau8821-hifi" +#define VG_JUPITER 1 + +static unsigned long acp5x_machine_id; +static struct snd_soc_jack vg_headset; + +static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card = rtd->card; + struct snd_soc_component *component = + asoc_rtd_to_codec(rtd, 0)->component; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &vg_headset, acp5x_nau8821_jack_pins, + ARRAY_SIZE(acp5x_nau8821_jack_pins)); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(vg_headset.jack, SND_JACK_BTN_0, KEY_MEDIA); + nau8821_enable_jack_detect(component, &vg_headset); + return ret; +} + +static int acp5x_cs35l41_init(struct snd_soc_pcm_runtime *rtd) +{ + return 0; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int acp5x_8821_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->play_i2s_instance = I2S_SP_INSTANCE; + machine->cap_i2s_instance = I2S_SP_INSTANCE; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + return 0; +} + +static int acp5x_nau8821_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = + snd_soc_card_get_codec_dai(card, + ACP5X_NUVOTON_CODEC_DAI); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "can't set FS clock %d\n", ret); + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, snd_soc_params_to_bclk(params), + params_rate(params) * 256); + if (ret < 0) + dev_err(card->dev, "can't set FLL: %d\n", ret); + + return ret; +} + +static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->play_i2s_instance = I2S_HS_INSTANCE; + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + return 0; +} + +static int acp5x_cs35l41_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai; + int ret, i; + unsigned int num_codecs = rtd->num_codecs; + unsigned int bclk_val; + + for (i = 0; i < num_codecs; i++) { + codec_dai = asoc_rtd_to_codec(rtd, i); + if ((strcmp(codec_dai->name, "spi-VLV1776:00") == 0) || + (strcmp(codec_dai->name, "spi-VLV1776:01") == 0)) { + switch (params_rate(params)) { + case 48000: + bclk_val = 1536000; + break; + default: + dev_err(card->dev, "Invalid Samplerate:0x%x\n", + params_rate(params)); + return -EINVAL; + } + ret = snd_soc_component_set_sysclk(codec_dai->component, + 0, 0, bclk_val, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "failed to set sysclk for CS35l41 dai\n"); + return ret; + } + } + } + + return ret; +} + +static const struct snd_soc_ops acp5x_8821_ops = { + .startup = acp5x_8821_startup, + .hw_params = acp5x_nau8821_hw_params, +}; + +static const struct snd_soc_ops acp5x_cs35l41_play_ops = { + .startup = acp5x_cs35l41_startup, + .hw_params = acp5x_cs35l41_hw_params, +}; + +static struct snd_soc_codec_conf cs35l41_conf[] = { + { + .dlc = COMP_CODEC_CONF("spi-VLV1776:00"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("spi-VLV1776:01"), + .name_prefix = "Right", + }, +}; + +SND_SOC_DAILINK_DEF(acp5x_i2s, + DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0"))); + +SND_SOC_DAILINK_DEF(acp5x_bt, + DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1"))); + +SND_SOC_DAILINK_DEF(nau8821, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-NVTN2020:00", + "nau8821-hifi"))); + +SND_SOC_DAILINK_DEF(cs35l41, + DAILINK_COMP_ARRAY(COMP_CODEC("spi-VLV1776:00", "cs35l41-pcm"), + COMP_CODEC("spi-VLV1776:01", "cs35l41-pcm"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0"))); + +static struct snd_soc_dai_link acp5x_dai[] = { + { + .name = "acp5x-8825-play", + .stream_name = "Playback/Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &acp5x_8821_ops, + .init = acp5x_8821_init, + SND_SOC_DAILINK_REG(acp5x_i2s, nau8821, platform), + }, + { + .name = "acp5x-CS35L41-Stereo", + .stream_name = "CS35L41 Stereo Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .dpcm_playback = 1, + .playback_only = 1, + .ops = &acp5x_cs35l41_play_ops, + .init = acp5x_cs35l41_init, + SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform), + }, +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret = 0; + + codec_dai = snd_soc_card_get_codec_dai(card, ACP5X_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_INTERNAL, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return -EIO; + } + } + return ret; +} + +static const struct snd_kcontrol_new acp5x_8821_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static const struct snd_soc_dapm_widget acp5x_8821_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route acp5x_8821_audio_route[] = { + /* HP jack connectors - unknown if we have jack detection */ + { "Headphone", NULL, "HPOL" }, + { "Headphone", NULL, "HPOR" }, + { "MICL", NULL, "Headset Mic" }, + { "MICR", NULL, "Headset Mic" }, + { "DMIC", NULL, "Int Mic" }, + + { "Headphone", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, + { "Int Mic", NULL, "Platform Clock" }, +}; + +static struct snd_soc_card acp5x_card = { + .name = "acp5x", + .owner = THIS_MODULE, + .dai_link = acp5x_dai, + .num_links = ARRAY_SIZE(acp5x_dai), + .dapm_widgets = acp5x_8821_widgets, + .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_widgets), + .dapm_routes = acp5x_8821_audio_route, + .num_dapm_routes = ARRAY_SIZE(acp5x_8821_audio_route), + .codec_conf = cs35l41_conf, + .num_configs = ARRAY_SIZE(cs35l41_conf), + .controls = acp5x_8821_controls, + .num_controls = ARRAY_SIZE(acp5x_8821_controls), +}; + + +static int acp5x_vg_quirk_cb(const struct dmi_system_id *id) +{ + acp5x_machine_id = VG_JUPITER; + return 1; +} + +static const struct dmi_system_id acp5x_vg_quirk_table[] = { + { + .callback = acp5x_vg_quirk_cb, + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Valve"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"), + } + }, + {} +}; + +static int acp5x_probe(struct platform_device *pdev) +{ + int ret; + struct acp5x_platform_info *machine; + struct snd_soc_card *card; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct acp5x_platform_info), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + dmi_check_system(acp5x_vg_quirk_table); + switch(acp5x_machine_id) { + case VG_JUPITER: + card = &acp5x_card; + acp5x_card.dev = &pdev->dev; + break; + default: + return -ENODEV; + } + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + return dev_err_probe(&pdev->dev, ret, + "snd_soc_register_card(%s) failed\n", + acp5x_card.name); + } + return 0; +} + +static struct platform_driver acp5x_mach_driver = { + .driver = { + .name = "acp5x_mach", + .pm = &snd_soc_pm_ops, + }, + .probe = acp5x_probe, +}; + +module_platform_driver(acp5x_mach_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("NAU8821 & CS35L41 audio support"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/vangogh/acp5x.h b/sound/soc/amd/vangogh/acp5x.h index a808635f9740..fe5e1fa98974 100644 --- a/sound/soc/amd/vangogh/acp5x.h +++ b/sound/soc/amd/vangogh/acp5x.h @@ -23,7 +23,7 @@ #define ACP_ERR_INTR_MASK 0x20000000 #define ACP_EXT_INTR_STAT_CLEAR_MASK 0xFFFFFFFF -#define ACP5x_DEVS 3 +#define ACP5x_DEVS 4 #define ACP5x_REG_START 0x1240000 #define ACP5x_REG_END 0x1250200 #define ACP5x_I2STDM_REG_START 0x1242400 diff --git a/sound/soc/amd/vangogh/pci-acp5x.c b/sound/soc/amd/vangogh/pci-acp5x.c index a57b762d9f2e..2b6b9edc36e2 100644 --- a/sound/soc/amd/vangogh/pci-acp5x.c +++ b/sound/soc/amd/vangogh/pci-acp5x.c @@ -213,6 +213,9 @@ static int snd_acp5x_probe(struct pci_dev *pci, pdevinfo[2].num_res = 1; pdevinfo[2].res = &adata->res[2]; + pdevinfo[3].name = "acp5x_mach"; + pdevinfo[3].id = 0; + pdevinfo[3].parent = &pci->dev; for (i = 0; i < ACP5x_DEVS; i++) { adata->pdev[i] = platform_device_register_full(&pdevinfo[i]); diff --git a/sound/soc/amd/yc/Makefile b/sound/soc/amd/yc/Makefile new file mode 100644 index 000000000000..dc2974440388 --- /dev/null +++ b/sound/soc/amd/yc/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Yellow Carp platform Support +snd-pci-acp6x-objs := pci-acp6x.o +snd-acp6x-pdm-dma-objs := acp6x-pdm-dma.o +snd-soc-acp6x-mach-objs := acp6x-mach.o + +obj-$(CONFIG_SND_SOC_AMD_ACP6x) += snd-pci-acp6x.o +obj-$(CONFIG_SND_SOC_AMD_ACP6x) += snd-acp6x-pdm-dma.o +obj-$(CONFIG_SND_SOC_AMD_YC_MACH) += snd-soc-acp6x-mach.o diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c new file mode 100644 index 000000000000..9a767f47b89f --- /dev/null +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Machine driver for AMD Yellow Carp platform using DMIC + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/io.h> +#include <linux/dmi.h> + +#include "acp6x.h" + +#define DRV_NAME "acp_yc_mach" + +SND_SOC_DAILINK_DEF(acp6x_pdm, + DAILINK_COMP_ARRAY(COMP_CPU("acp_yc_pdm_dma.0"))); + +SND_SOC_DAILINK_DEF(dmic_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec.0", + "dmic-hifi"))); + +SND_SOC_DAILINK_DEF(pdm_platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_yc_pdm_dma.0"))); + +static struct snd_soc_dai_link acp6x_dai_pdm[] = { + { + .name = "acp6x-dmic-capture", + .stream_name = "DMIC capture", + .capture_only = 1, + SND_SOC_DAILINK_REG(acp6x_pdm, dmic_codec, pdm_platform), + }, +}; + +static struct snd_soc_card acp6x_card = { + .name = "acp6x", + .owner = THIS_MODULE, + .dai_link = acp6x_dai_pdm, + .num_links = 1, +}; + +static const struct dmi_system_id yc_acp_quirk_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D2"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D3"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D4"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D5"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CF"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CG"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CQ"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CR"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21AW"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21AX"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21BN"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21BQ"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CH"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CJ"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CK"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21CL"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D8"), + } + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D9"), + } + }, + {} +}; + +static int acp6x_probe(struct platform_device *pdev) +{ + struct acp6x_pdm *machine = NULL; + struct snd_soc_card *card; + int ret; + const struct dmi_system_id *dmi_id; + + dmi_id = dmi_first_match(yc_acp_quirk_table); + if (!dmi_id) + return -ENODEV; + card = &acp6x_card; + acp6x_card.dev = &pdev->dev; + + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + return dev_err_probe(&pdev->dev, ret, + "snd_soc_register_card(%s) failed\n", + card->name); + } + return 0; +} + +static struct platform_driver acp6x_mach_driver = { + .driver = { + .name = "acp_yc_mach", + .pm = &snd_soc_pm_ops, + }, + .probe = acp6x_probe, +}; + +module_platform_driver(acp6x_mach_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c new file mode 100644 index 000000000000..e604f4ea524f --- /dev/null +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AMD ALSA SoC Yellow Carp PDM Driver + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <linux/pm_runtime.h> + +#include "acp6x.h" + +#define DRV_NAME "acp_yc_pdm_dma" + +static const struct snd_pcm_hardware acp6x_pdm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, +}; + +static void acp6x_init_pdm_ring_buffer(u32 physical_addr, u32 buffer_size, + u32 watermark_size, void __iomem *acp_base) +{ + acp6x_writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR); + acp6x_writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE); + acp6x_writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); + acp6x_writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL); +} + +static void acp6x_enable_pdm_clock(void __iomem *acp_base) +{ + u32 pdm_clk_enable, pdm_ctrl; + + pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK; + pdm_ctrl = 0x00; + + acp6x_writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL); + pdm_ctrl = acp6x_readl(acp_base + ACP_WOV_MISC_CTRL); + pdm_ctrl |= ACP_WOV_MISC_CTRL_MASK; + acp6x_writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL); +} + +static void acp6x_enable_pdm_interrupts(void __iomem *acp_base) +{ + u32 ext_int_ctrl; + + ext_int_ctrl = acp6x_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl |= PDM_DMA_INTR_MASK; + acp6x_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); +} + +static void acp6x_disable_pdm_interrupts(void __iomem *acp_base) +{ + u32 ext_int_ctrl; + + ext_int_ctrl = acp6x_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl &= ~PDM_DMA_INTR_MASK; + acp6x_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); +} + +static bool acp6x_check_pdm_dma_status(void __iomem *acp_base) +{ + bool pdm_dma_status; + u32 pdm_enable, pdm_dma_enable; + + pdm_dma_status = false; + pdm_enable = acp6x_readl(acp_base + ACP_WOV_PDM_ENABLE); + pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable & ACP_PDM_DMA_EN_STATUS)) + pdm_dma_status = true; + + return pdm_dma_status; +} + +static int acp6x_start_pdm_dma(void __iomem *acp_base) +{ + u32 pdm_enable; + u32 pdm_dma_enable; + int timeout; + + pdm_enable = 0x01; + pdm_dma_enable = 0x01; + + acp6x_enable_pdm_clock(acp_base); + acp6x_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); + acp6x_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); + timeout = 0; + while (++timeout < ACP_COUNTER) { + pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS) + return 0; + udelay(DELAY_US); + } + return -ETIMEDOUT; +} + +static int acp6x_stop_pdm_dma(void __iomem *acp_base) +{ + u32 pdm_enable, pdm_dma_enable; + int timeout; + + pdm_enable = 0x00; + pdm_dma_enable = 0x00; + + pdm_enable = acp6x_readl(acp_base + ACP_WOV_PDM_ENABLE); + pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + if (pdm_dma_enable & 0x01) { + pdm_dma_enable = 0x02; + acp6x_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); + timeout = 0; + while (++timeout < ACP_COUNTER) { + pdm_dma_enable = acp6x_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + if ((pdm_dma_enable & 0x02) == 0x00) + break; + udelay(DELAY_US); + } + if (timeout == ACP_COUNTER) + return -ETIMEDOUT; + } + if (pdm_enable == ACP_PDM_ENABLE) { + pdm_enable = ACP_PDM_DISABLE; + acp6x_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); + } + acp6x_writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH); + return 0; +} + +static void acp6x_config_dma(struct pdm_stream_instance *rtd, int direction) +{ + u16 page_idx; + u32 low, high, val; + dma_addr_t addr; + + addr = rtd->dma_addr; + val = PDM_PTE_OFFSET; + + /* Group Enable */ + acp6x_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp6x_base + + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1); + acp6x_writel(PAGE_SIZE_4K_ENABLE, rtd->acp6x_base + + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1); + for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + low = lower_32_bits(addr); + high = upper_32_bits(addr); + + acp6x_writel(low, rtd->acp6x_base + ACP_SCRATCH_REG_0 + val); + high |= BIT(31); + acp6x_writel(high, rtd->acp6x_base + ACP_SCRATCH_REG_0 + val + 4); + val += 8; + addr += PAGE_SIZE; + } +} + +static int acp6x_pdm_dma_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct pdm_dev_data *adata; + struct pdm_stream_instance *pdm_data; + int ret; + + runtime = substream->runtime; + adata = dev_get_drvdata(component->dev); + pdm_data = kzalloc(sizeof(*pdm_data), GFP_KERNEL); + if (!pdm_data) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + runtime->hw = acp6x_pdm_hardware_capture; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(component->dev, "set integer constraint failed\n"); + kfree(pdm_data); + return ret; + } + + acp6x_enable_pdm_interrupts(adata->acp6x_base); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + adata->capture_stream = substream; + + pdm_data->acp6x_base = adata->acp6x_base; + runtime->private_data = pdm_data; + return ret; +} + +static int acp6x_pdm_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct pdm_stream_instance *rtd; + size_t size, period_bytes; + + rtd = substream->runtime->private_data; + if (!rtd) + return -EINVAL; + size = params_buffer_bytes(params); + period_bytes = params_period_bytes(params); + rtd->dma_addr = substream->runtime->dma_addr; + rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); + acp6x_config_dma(rtd, substream->stream); + acp6x_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, size, + period_bytes, rtd->acp6x_base); + return 0; +} + +static u64 acp6x_pdm_get_byte_count(struct pdm_stream_instance *rtd, + int direction) +{ + union acp_pdm_dma_count byte_count; + + byte_count.bcount.high = + acp6x_readl(rtd->acp6x_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH); + byte_count.bcount.low = + acp6x_readl(rtd->acp6x_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW); + return byte_count.bytescount; +} + +static snd_pcm_uframes_t acp6x_pdm_dma_pointer(struct snd_soc_component *comp, + struct snd_pcm_substream *stream) +{ + struct pdm_stream_instance *rtd; + u32 pos, buffersize; + u64 bytescount; + + rtd = stream->runtime->private_data; + buffersize = frames_to_bytes(stream->runtime, + stream->runtime->buffer_size); + bytescount = acp6x_pdm_get_byte_count(rtd, stream->stream); + if (bytescount > rtd->bytescount) + bytescount -= rtd->bytescount; + pos = do_div(bytescount, buffersize); + return bytes_to_frames(stream->runtime, pos); +} + +static int acp6x_pdm_dma_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct device *parent = component->dev->parent; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, + parent, MIN_BUFFER, MAX_BUFFER); + return 0; +} + +static int acp6x_pdm_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct pdm_dev_data *adata = dev_get_drvdata(component->dev); + + acp6x_disable_pdm_interrupts(adata->acp6x_base); + adata->capture_stream = NULL; + return 0; +} + +static int acp6x_pdm_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct pdm_stream_instance *rtd; + int ret; + bool pdm_status; + unsigned int ch_mask; + + rtd = substream->runtime->private_data; + ret = 0; + switch (substream->runtime->channels) { + case TWO_CH: + ch_mask = 0x00; + break; + default: + return -EINVAL; + } + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + acp6x_writel(ch_mask, rtd->acp6x_base + ACP_WOV_PDM_NO_OF_CHANNELS); + acp6x_writel(PDM_DECIMATION_FACTOR, rtd->acp6x_base + + ACP_WOV_PDM_DECIMATION_FACTOR); + rtd->bytescount = acp6x_pdm_get_byte_count(rtd, substream->stream); + pdm_status = acp6x_check_pdm_dma_status(rtd->acp6x_base); + if (!pdm_status) + ret = acp6x_start_pdm_dma(rtd->acp6x_base); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pdm_status = acp6x_check_pdm_dma_status(rtd->acp6x_base); + if (pdm_status) + ret = acp6x_stop_pdm_dma(rtd->acp6x_base); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static struct snd_soc_dai_ops acp6x_pdm_dai_ops = { + .trigger = acp6x_pdm_dai_trigger, +}; + +static struct snd_soc_dai_driver acp6x_pdm_dai_driver = { + .capture = { + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 48000, + .rate_max = 48000, + }, + .ops = &acp6x_pdm_dai_ops, +}; + +static const struct snd_soc_component_driver acp6x_pdm_component = { + .name = DRV_NAME, + .open = acp6x_pdm_dma_open, + .close = acp6x_pdm_dma_close, + .hw_params = acp6x_pdm_dma_hw_params, + .pointer = acp6x_pdm_dma_pointer, + .pcm_construct = acp6x_pdm_dma_new, +}; + +static int acp6x_pdm_audio_probe(struct platform_device *pdev) +{ + struct resource *res; + struct pdm_dev_data *adata; + int status; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENODEV; + } + + adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL); + if (!adata) + return -ENOMEM; + + adata->acp6x_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!adata->acp6x_base) + return -ENOMEM; + + adata->capture_stream = NULL; + + dev_set_drvdata(&pdev->dev, adata); + status = devm_snd_soc_register_component(&pdev->dev, + &acp6x_pdm_component, + &acp6x_pdm_dai_driver, 1); + if (status) { + dev_err(&pdev->dev, "Fail to register acp pdm dai\n"); + + return -ENODEV; + } + pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_allow(&pdev->dev); + return 0; +} + +static int acp6x_pdm_audio_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int __maybe_unused acp6x_pdm_resume(struct device *dev) +{ + struct pdm_dev_data *adata; + struct snd_pcm_runtime *runtime; + struct pdm_stream_instance *rtd; + u32 period_bytes, buffer_len; + + adata = dev_get_drvdata(dev); + if (adata->capture_stream && adata->capture_stream->runtime) { + runtime = adata->capture_stream->runtime; + rtd = runtime->private_data; + period_bytes = frames_to_bytes(runtime, runtime->period_size); + buffer_len = frames_to_bytes(runtime, runtime->buffer_size); + acp6x_config_dma(rtd, SNDRV_PCM_STREAM_CAPTURE); + acp6x_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, buffer_len, + period_bytes, adata->acp6x_base); + } + acp6x_enable_pdm_interrupts(adata->acp6x_base); + return 0; +} + +static int __maybe_unused acp6x_pdm_suspend(struct device *dev) +{ + struct pdm_dev_data *adata; + + adata = dev_get_drvdata(dev); + acp6x_disable_pdm_interrupts(adata->acp6x_base); + return 0; +} + +static int __maybe_unused acp6x_pdm_runtime_resume(struct device *dev) +{ + struct pdm_dev_data *adata; + + adata = dev_get_drvdata(dev); + acp6x_enable_pdm_interrupts(adata->acp6x_base); + return 0; +} + +static const struct dev_pm_ops acp6x_pdm_pm_ops = { + SET_RUNTIME_PM_OPS(acp6x_pdm_suspend, acp6x_pdm_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(acp6x_pdm_suspend, acp6x_pdm_resume) +}; + +static struct platform_driver acp6x_pdm_dma_driver = { + .probe = acp6x_pdm_audio_probe, + .remove = acp6x_pdm_audio_remove, + .driver = { + .name = "acp_yc_pdm_dma", + .pm = &acp6x_pdm_pm_ops, + }, +}; + +module_platform_driver(acp6x_pdm_dma_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP6x YC PDM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/amd/yc/acp6x.h b/sound/soc/amd/yc/acp6x.h new file mode 100644 index 000000000000..74b596e6807a --- /dev/null +++ b/sound/soc/amd/yc/acp6x.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * AMD ALSA SoC PDM Driver + * + * Copyright (C) 2021 Advanced Micro Devices, Inc. All rights reserved. + */ + +#include "acp6x_chip_offset_byte.h" + +#define ACP_DEVICE_ID 0x15E2 +#define ACP6x_PHY_BASE_ADDRESS 0x1240000 +#define ACP6x_REG_START 0x1240000 +#define ACP6x_REG_END 0x1250200 +#define ACP6x_DEVS 3 +#define ACP6x_PDM_MODE 1 + +#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 +#define ACP_PGFSM_CNTL_POWER_ON_MASK 1 +#define ACP_PGFSM_CNTL_POWER_OFF_MASK 0 +#define ACP_PGFSM_STATUS_MASK 3 +#define ACP_POWERED_ON 0 +#define ACP_POWER_ON_IN_PROGRESS 1 +#define ACP_POWERED_OFF 2 +#define ACP_POWER_OFF_IN_PROGRESS 3 + +#define ACP_ERROR_MASK 0x20000000 +#define ACP_EXT_INTR_STAT_CLEAR_MASK 0xFFFFFFFF +#define PDM_DMA_STAT 0x10 + +#define PDM_DMA_INTR_MASK 0x10000 +#define ACP_ERROR_STAT 29 +#define PDM_DECIMATION_FACTOR 2 +#define ACP_PDM_CLK_FREQ_MASK 7 +#define ACP_WOV_MISC_CTRL_MASK 0x10 +#define ACP_PDM_ENABLE 1 +#define ACP_PDM_DISABLE 0 +#define ACP_PDM_DMA_EN_STATUS 2 +#define TWO_CH 2 +#define DELAY_US 5 +#define ACP_COUNTER 20000 + +#define ACP_SRAM_PTE_OFFSET 0x03800000 +#define PAGE_SIZE_4K_ENABLE 2 +#define PDM_PTE_OFFSET 0 +#define PDM_MEM_WINDOW_START 0x4000000 + +#define CAPTURE_MIN_NUM_PERIODS 4 +#define CAPTURE_MAX_NUM_PERIODS 4 +#define CAPTURE_MAX_PERIOD_SIZE 8192 +#define CAPTURE_MIN_PERIOD_SIZE 4096 + +#define MAX_BUFFER (CAPTURE_MAX_PERIOD_SIZE * CAPTURE_MAX_NUM_PERIODS) +#define MIN_BUFFER MAX_BUFFER + +/* time in ms for runtime suspend delay */ +#define ACP_SUSPEND_DELAY_MS 2000 + +enum acp_config { + ACP_CONFIG_0 = 0, + ACP_CONFIG_1, + ACP_CONFIG_2, + ACP_CONFIG_3, + ACP_CONFIG_4, + ACP_CONFIG_5, + ACP_CONFIG_6, + ACP_CONFIG_7, + ACP_CONFIG_8, + ACP_CONFIG_9, + ACP_CONFIG_10, + ACP_CONFIG_11, + ACP_CONFIG_12, + ACP_CONFIG_13, + ACP_CONFIG_14, + ACP_CONFIG_15, +}; + +struct pdm_dev_data { + u32 pdm_irq; + void __iomem *acp6x_base; + struct snd_pcm_substream *capture_stream; +}; + +struct pdm_stream_instance { + u16 num_pages; + u16 channels; + dma_addr_t dma_addr; + u64 bytescount; + void __iomem *acp6x_base; +}; + +union acp_pdm_dma_count { + struct { + u32 low; + u32 high; + } bcount; + u64 bytescount; +}; + +static inline u32 acp6x_readl(void __iomem *base_addr) +{ + return readl(base_addr - ACP6x_PHY_BASE_ADDRESS); +} + +static inline void acp6x_writel(u32 val, void __iomem *base_addr) +{ + writel(val, base_addr - ACP6x_PHY_BASE_ADDRESS); +} diff --git a/sound/soc/amd/yc/acp6x_chip_offset_byte.h b/sound/soc/amd/yc/acp6x_chip_offset_byte.h new file mode 100644 index 000000000000..f05fb2dfb5da --- /dev/null +++ b/sound/soc/amd/yc/acp6x_chip_offset_byte.h @@ -0,0 +1,444 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * AMD ACP 6.x Register Documentation + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#ifndef _acp6x_OFFSET_HEADER +#define _acp6x_OFFSET_HEADER + +/* Registers from ACP_DMA block */ +#define ACP_DMA_CNTL_0 0x1240000 +#define ACP_DMA_CNTL_1 0x1240004 +#define ACP_DMA_CNTL_2 0x1240008 +#define ACP_DMA_CNTL_3 0x124000C +#define ACP_DMA_CNTL_4 0x1240010 +#define ACP_DMA_CNTL_5 0x1240014 +#define ACP_DMA_CNTL_6 0x1240018 +#define ACP_DMA_CNTL_7 0x124001C +#define ACP_DMA_DSCR_STRT_IDX_0 0x1240020 +#define ACP_DMA_DSCR_STRT_IDX_1 0x1240024 +#define ACP_DMA_DSCR_STRT_IDX_2 0x1240028 +#define ACP_DMA_DSCR_STRT_IDX_3 0x124002C +#define ACP_DMA_DSCR_STRT_IDX_4 0x1240030 +#define ACP_DMA_DSCR_STRT_IDX_5 0x1240034 +#define ACP_DMA_DSCR_STRT_IDX_6 0x1240038 +#define ACP_DMA_DSCR_STRT_IDX_7 0x124003C +#define ACP_DMA_DSCR_CNT_0 0x1240040 +#define ACP_DMA_DSCR_CNT_1 0x1240044 +#define ACP_DMA_DSCR_CNT_2 0x1240048 +#define ACP_DMA_DSCR_CNT_3 0x124004C +#define ACP_DMA_DSCR_CNT_4 0x1240050 +#define ACP_DMA_DSCR_CNT_5 0x1240054 +#define ACP_DMA_DSCR_CNT_6 0x1240058 +#define ACP_DMA_DSCR_CNT_7 0x124005C +#define ACP_DMA_PRIO_0 0x1240060 +#define ACP_DMA_PRIO_1 0x1240064 +#define ACP_DMA_PRIO_2 0x1240068 +#define ACP_DMA_PRIO_3 0x124006C +#define ACP_DMA_PRIO_4 0x1240070 +#define ACP_DMA_PRIO_5 0x1240074 +#define ACP_DMA_PRIO_6 0x1240078 +#define ACP_DMA_PRIO_7 0x124007C +#define ACP_DMA_CUR_DSCR_0 0x1240080 +#define ACP_DMA_CUR_DSCR_1 0x1240084 +#define ACP_DMA_CUR_DSCR_2 0x1240088 +#define ACP_DMA_CUR_DSCR_3 0x124008C +#define ACP_DMA_CUR_DSCR_4 0x1240090 +#define ACP_DMA_CUR_DSCR_5 0x1240094 +#define ACP_DMA_CUR_DSCR_6 0x1240098 +#define ACP_DMA_CUR_DSCR_7 0x124009C +#define ACP_DMA_CUR_TRANS_CNT_0 0x12400A0 +#define ACP_DMA_CUR_TRANS_CNT_1 0x12400A4 +#define ACP_DMA_CUR_TRANS_CNT_2 0x12400A8 +#define ACP_DMA_CUR_TRANS_CNT_3 0x12400AC +#define ACP_DMA_CUR_TRANS_CNT_4 0x12400B0 +#define ACP_DMA_CUR_TRANS_CNT_5 0x12400B4 +#define ACP_DMA_CUR_TRANS_CNT_6 0x12400B8 +#define ACP_DMA_CUR_TRANS_CNT_7 0x12400BC +#define ACP_DMA_ERR_STS_0 0x12400C0 +#define ACP_DMA_ERR_STS_1 0x12400C4 +#define ACP_DMA_ERR_STS_2 0x12400C8 +#define ACP_DMA_ERR_STS_3 0x12400CC +#define ACP_DMA_ERR_STS_4 0x12400D0 +#define ACP_DMA_ERR_STS_5 0x12400D4 +#define ACP_DMA_ERR_STS_6 0x12400D8 +#define ACP_DMA_ERR_STS_7 0x12400DC +#define ACP_DMA_DESC_BASE_ADDR 0x12400E0 +#define ACP_DMA_DESC_MAX_NUM_DSCR 0x12400E4 +#define ACP_DMA_CH_STS 0x12400E8 +#define ACP_DMA_CH_GROUP 0x12400EC +#define ACP_DMA_CH_RST_STS 0x12400F0 + +/* Registers from ACP_AXI2AXIATU block */ +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0x1240C00 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_1 0x1240C04 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2 0x1240C08 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_2 0x1240C0C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_3 0x1240C10 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_3 0x1240C14 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_4 0x1240C18 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_4 0x1240C1C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5 0x1240C20 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_5 0x1240C24 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_6 0x1240C28 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_6 0x1240C2C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_7 0x1240C30 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_7 0x1240C34 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_8 0x1240C38 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_8 0x1240C3C +#define ACPAXI2AXI_ATU_CTRL 0x1240C40 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_9 0x1240C44 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_9 0x1240C48 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_10 0x1240C4C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_10 0x1240C50 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_11 0x1240C54 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_11 0x1240C58 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_12 0x1240C5C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_12 0x1240C60 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_13 0x1240C64 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_13 0x1240C68 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_14 0x1240C6C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_14 0x1240C70 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_15 0x1240C74 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_15 0x1240C78 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_16 0x1240C7C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_16 0x1240C80 + +/* Registers from ACP_CLKRST block */ +#define ACP_SOFT_RESET 0x1241000 +#define ACP_CONTROL 0x1241004 +#define ACP_STATUS 0x1241008 +#define ACP_DYNAMIC_CG_MASTER_CONTROL 0x1241010 +#define ACP_ZSC_DSP_CTRL 0x1241014 +#define ACP_ZSC_STS 0x1241018 +#define ACP_PGFSM_CONTROL 0x1241024 +#define ACP_PGFSM_STATUS 0x1241028 +#define ACP_CLKMUX_SEL 0x124102C + +/* Registers from ACP_AON block */ +#define ACP_PME_EN 0x1241400 +#define ACP_DEVICE_STATE 0x1241404 +#define AZ_DEVICE_STATE 0x1241408 +#define ACP_PIN_CONFIG 0x1241440 +#define ACP_PAD_PULLUP_CTRL 0x1241444 +#define ACP_PAD_PULLDOWN_CTRL 0x1241448 +#define ACP_PAD_DRIVE_STRENGTH_CTRL 0x124144C +#define ACP_PAD_SCHMEN_CTRL 0x1241450 +#define ACP_SW_PAD_KEEPER_EN 0x1241454 +#define ACP_SW_WAKE_EN 0x1241458 +#define ACP_I2S_WAKE_EN 0x124145C +#define ACP_SW1_WAKE_EN 0x1241460 + +/* Registers from ACP_P1_MISC block */ +#define ACP_EXTERNAL_INTR_ENB 0x1241A00 +#define ACP_EXTERNAL_INTR_CNTL 0x1241A04 +#define ACP_EXTERNAL_INTR_CNTL1 0x1241A08 +#define ACP_EXTERNAL_INTR_STAT 0x1241A0C +#define ACP_EXTERNAL_INTR_STAT1 0x1241A10 +#define ACP_ERROR_STATUS 0x1241A4C +#define ACP_P1_SW_I2S_ERROR_REASON 0x1241A50 +#define ACP_P1_SW_POS_TRACK_I2S_TX_CTRL 0x1241A6C +#define ACP_P1_SW_I2S_TX_DMA_POS 0x1241A70 +#define ACP_P1_SW_POS_TRACK_I2S_RX_CTRL 0x1241A74 +#define ACP_P1_SW_I2S_RX_DMA_POS 0x1241A78 +#define ACP_P1_DMIC_I2S_GPIO_INTR_CTRL 0x1241A7C +#define ACP_P1_DMIC_I2S_GPIO_INTR_STATUS 0x1241A80 +#define ACP_SCRATCH_REG_BASE_ADDR 0x1241A84 +#define ACP_P1_SW_POS_TRACK_BT_TX_CTRL 0x1241A88 +#define ACP_P1_SW_BT_TX_DMA_POS 0x1241A8C +#define ACP_P1_SW_POS_TRACK_HS_TX_CTRL 0x1241A90 +#define ACP_P1_SW_HS_TX_DMA_POS 0x1241A94 +#define ACP_P1_SW_POS_TRACK_BT_RX_CTRL 0x1241A98 +#define ACP_P1_SW_BT_RX_DMA_POS 0x1241A9C +#define ACP_P1_SW_POS_TRACK_HS_RX_CTRL 0x1241AA0 +#define ACP_P1_SW_HS_RX_DMA_POS 0x1241AA4 + +/* Registers from ACP_AUDIO_BUFFERS block */ +#define ACP_I2S_RX_RINGBUFADDR 0x1242000 +#define ACP_I2S_RX_RINGBUFSIZE 0x1242004 +#define ACP_I2S_RX_LINKPOSITIONCNTR 0x1242008 +#define ACP_I2S_RX_FIFOADDR 0x124200C +#define ACP_I2S_RX_FIFOSIZE 0x1242010 +#define ACP_I2S_RX_DMA_SIZE 0x1242014 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_HIGH 0x1242018 +#define ACP_I2S_RX_LINEARPOSITIONCNTR_LOW 0x124201C +#define ACP_I2S_RX_INTR_WATERMARK_SIZE 0x1242020 +#define ACP_I2S_TX_RINGBUFADDR 0x1242024 +#define ACP_I2S_TX_RINGBUFSIZE 0x1242028 +#define ACP_I2S_TX_LINKPOSITIONCNTR 0x124202C +#define ACP_I2S_TX_FIFOADDR 0x1242030 +#define ACP_I2S_TX_FIFOSIZE 0x1242034 +#define ACP_I2S_TX_DMA_SIZE 0x1242038 +#define ACP_I2S_TX_LINEARPOSITIONCNTR_HIGH 0x124203C +#define ACP_I2S_TX_LINEARPOSITIONCNTR_LOW 0x1242040 +#define ACP_I2S_TX_INTR_WATERMARK_SIZE 0x1242044 +#define ACP_BT_RX_RINGBUFADDR 0x1242048 +#define ACP_BT_RX_RINGBUFSIZE 0x124204C +#define ACP_BT_RX_LINKPOSITIONCNTR 0x1242050 +#define ACP_BT_RX_FIFOADDR 0x1242054 +#define ACP_BT_RX_FIFOSIZE 0x1242058 +#define ACP_BT_RX_DMA_SIZE 0x124205C +#define ACP_BT_RX_LINEARPOSITIONCNTR_HIGH 0x1242060 +#define ACP_BT_RX_LINEARPOSITIONCNTR_LOW 0x1242064 +#define ACP_BT_RX_INTR_WATERMARK_SIZE 0x1242068 +#define ACP_BT_TX_RINGBUFADDR 0x124206C +#define ACP_BT_TX_RINGBUFSIZE 0x1242070 +#define ACP_BT_TX_LINKPOSITIONCNTR 0x1242074 +#define ACP_BT_TX_FIFOADDR 0x1242078 +#define ACP_BT_TX_FIFOSIZE 0x124207C +#define ACP_BT_TX_DMA_SIZE 0x1242080 +#define ACP_BT_TX_LINEARPOSITIONCNTR_HIGH 0x1242084 +#define ACP_BT_TX_LINEARPOSITIONCNTR_LOW 0x1242088 +#define ACP_BT_TX_INTR_WATERMARK_SIZE 0x124208C +#define ACP_HS_RX_RINGBUFADDR 0x1242090 +#define ACP_HS_RX_RINGBUFSIZE 0x1242094 +#define ACP_HS_RX_LINKPOSITIONCNTR 0x1242098 +#define ACP_HS_RX_FIFOADDR 0x124209C +#define ACP_HS_RX_FIFOSIZE 0x12420A0 +#define ACP_HS_RX_DMA_SIZE 0x12420A4 +#define ACP_HS_RX_LINEARPOSITIONCNTR_HIGH 0x12420A8 +#define ACP_HS_RX_LINEARPOSITIONCNTR_LOW 0x12420AC +#define ACP_HS_RX_INTR_WATERMARK_SIZE 0x12420B0 +#define ACP_HS_TX_RINGBUFADDR 0x12420B4 +#define ACP_HS_TX_RINGBUFSIZE 0x12420B8 +#define ACP_HS_TX_LINKPOSITIONCNTR 0x12420BC +#define ACP_HS_TX_FIFOADDR 0x12420C0 +#define ACP_HS_TX_FIFOSIZE 0x12420C4 +#define ACP_HS_TX_DMA_SIZE 0x12420C8 +#define ACP_HS_TX_LINEARPOSITIONCNTR_HIGH 0x12420CC +#define ACP_HS_TX_LINEARPOSITIONCNTR_LOW 0x12420D0 +#define ACP_HS_TX_INTR_WATERMARK_SIZE 0x12420D4 + +/* Registers from ACP_I2S_TDM block */ +#define ACP_I2STDM_IER 0x1242400 +#define ACP_I2STDM_IRER 0x1242404 +#define ACP_I2STDM_RXFRMT 0x1242408 +#define ACP_I2STDM_ITER 0x124240C +#define ACP_I2STDM_TXFRMT 0x1242410 +#define ACP_I2STDM0_MSTRCLKGEN 0x1242414 +#define ACP_I2STDM1_MSTRCLKGEN 0x1242418 +#define ACP_I2STDM2_MSTRCLKGEN 0x124241C +#define ACP_I2STDM_REFCLKGEN 0x1242420 + +/* Registers from ACP_BT_TDM block */ +#define ACP_BTTDM_IER 0x1242800 +#define ACP_BTTDM_IRER 0x1242804 +#define ACP_BTTDM_RXFRMT 0x1242808 +#define ACP_BTTDM_ITER 0x124280C +#define ACP_BTTDM_TXFRMT 0x1242810 +#define ACP_HSTDM_IER 0x1242814 +#define ACP_HSTDM_IRER 0x1242818 +#define ACP_HSTDM_RXFRMT 0x124281C +#define ACP_HSTDM_ITER 0x1242820 +#define ACP_HSTDM_TXFRMT 0x1242824 + +/* Registers from ACP_WOV block */ +#define ACP_WOV_PDM_ENABLE 0x1242C04 +#define ACP_WOV_PDM_DMA_ENABLE 0x1242C08 +#define ACP_WOV_RX_RINGBUFADDR 0x1242C0C +#define ACP_WOV_RX_RINGBUFSIZE 0x1242C10 +#define ACP_WOV_RX_LINKPOSITIONCNTR 0x1242C14 +#define ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH 0x1242C18 +#define ACP_WOV_RX_LINEARPOSITIONCNTR_LOW 0x1242C1C +#define ACP_WOV_RX_INTR_WATERMARK_SIZE 0x1242C20 +#define ACP_WOV_PDM_FIFO_FLUSH 0x1242C24 +#define ACP_WOV_PDM_NO_OF_CHANNELS 0x1242C28 +#define ACP_WOV_PDM_DECIMATION_FACTOR 0x1242C2C +#define ACP_WOV_PDM_VAD_CTRL 0x1242C30 +#define ACP_WOV_WAKE 0x1242C54 +#define ACP_WOV_BUFFER_STATUS 0x1242C58 +#define ACP_WOV_MISC_CTRL 0x1242C5C +#define ACP_WOV_CLK_CTRL 0x1242C60 +#define ACP_PDM_VAD_DYNAMIC_CLK_GATING_EN 0x1242C64 +#define ACP_WOV_ERROR_STATUS_REGISTER 0x1242C68 +#define ACP_PDM_CLKDIV 0x1242C6C + +/* Registers from ACP_P1_AUDIO_BUFFERS block */ +#define ACP_P1_I2S_RX_RINGBUFADDR 0x1243A00 +#define ACP_P1_I2S_RX_RINGBUFSIZE 0x1243A04 +#define ACP_P1_I2S_RX_LINKPOSITIONCNTR 0x1243A08 +#define ACP_P1_I2S_RX_FIFOADDR 0x1243A0C +#define ACP_P1_I2S_RX_FIFOSIZE 0x1243A10 +#define ACP_P1_I2S_RX_DMA_SIZE 0x1243A14 +#define ACP_P1_I2S_RX_LINEARPOSITIONCNTR_HIGH 0x1243A18 +#define ACP_P1_I2S_RX_LINEARPOSITIONCNTR_LOW 0x1243A1C +#define ACP_P1_I2S_RX_INTR_WATERMARK_SIZE 0x1243A20 +#define ACP_P1_I2S_TX_RINGBUFADDR 0x1243A24 +#define ACP_P1_I2S_TX_RINGBUFSIZE 0x1243A28 +#define ACP_P1_I2S_TX_LINKPOSITIONCNTR 0x1243A2C +#define ACP_P1_I2S_TX_FIFOADDR 0x1243A30 +#define ACP_P1_I2S_TX_FIFOSIZE 0x1243A34 +#define ACP_P1_I2S_TX_DMA_SIZE 0x1243A38 +#define ACP_P1_I2S_TX_LINEARPOSITIONCNTR_HIGH 0x1243A3C +#define ACP_P1_I2S_TX_LINEARPOSITIONCNTR_LOW 0x1243A40 +#define ACP_P1_I2S_TX_INTR_WATERMARK_SIZE 0x1243A44 +#define ACP_P1_BT_RX_RINGBUFADDR 0x1243A48 +#define ACP_P1_BT_RX_RINGBUFSIZE 0x1243A4C +#define ACP_P1_BT_RX_LINKPOSITIONCNTR 0x1243A50 +#define ACP_P1_BT_RX_FIFOADDR 0x1243A54 +#define ACP_P1_BT_RX_FIFOSIZE 0x1243A58 +#define ACP_P1_BT_RX_DMA_SIZE 0x1243A5C +#define ACP_P1_BT_RX_LINEARPOSITIONCNTR_HIGH 0x1243A60 +#define ACP_P1_BT_RX_LINEARPOSITIONCNTR_LOW 0x1243A64 +#define ACP_P1_BT_RX_INTR_WATERMARK_SIZE 0x1243A68 +#define ACP_P1_BT_TX_RINGBUFADDR 0x1243A6C +#define ACP_P1_BT_TX_RINGBUFSIZE 0x1243A70 +#define ACP_P1_BT_TX_LINKPOSITIONCNTR 0x1243A74 +#define ACP_P1_BT_TX_FIFOADDR 0x1243A78 +#define ACP_P1_BT_TX_FIFOSIZE 0x1243A7C +#define ACP_P1_BT_TX_DMA_SIZE 0x1243A80 +#define ACP_P1_BT_TX_LINEARPOSITIONCNTR_HIGH 0x1243A84 +#define ACP_P1_BT_TX_LINEARPOSITIONCNTR_LOW 0x1243A88 +#define ACP_P1_BT_TX_INTR_WATERMARK_SIZE 0x1243A8C +#define ACP_P1_HS_RX_RINGBUFADDR 0x1243A90 +#define ACP_P1_HS_RX_RINGBUFSIZE 0x1243A94 +#define ACP_P1_HS_RX_LINKPOSITIONCNTR 0x1243A98 +#define ACP_P1_HS_RX_FIFOADDR 0x1243A9C +#define ACP_P1_HS_RX_FIFOSIZE 0x1243AA0 +#define ACP_P1_HS_RX_DMA_SIZE 0x1243AA4 +#define ACP_P1_HS_RX_LINEARPOSITIONCNTR_HIGH 0x1243AA8 +#define ACP_P1_HS_RX_LINEARPOSITIONCNTR_LOW 0x1243AAC +#define ACP_P1_HS_RX_INTR_WATERMARK_SIZE 0x1243AB0 +#define ACP_P1_HS_TX_RINGBUFADDR 0x1243AB4 +#define ACP_P1_HS_TX_RINGBUFSIZE 0x1243AB8 +#define ACP_P1_HS_TX_LINKPOSITIONCNTR 0x1243ABC +#define ACP_P1_HS_TX_FIFOADDR 0x1243AC0 +#define ACP_P1_HS_TX_FIFOSIZE 0x1243AC4 +#define ACP_P1_HS_TX_DMA_SIZE 0x1243AC8 +#define ACP_P1_HS_TX_LINEARPOSITIONCNTR_HIGH 0x1243ACC +#define ACP_P1_HS_TX_LINEARPOSITIONCNTR_LOW 0x1243AD0 +#define ACP_P1_HS_TX_INTR_WATERMARK_SIZE 0x1243AD4 + +/* Registers from ACP_SCRATCH block */ +#define ACP_SCRATCH_REG_0 0x1250000 +#define ACP_SCRATCH_REG_1 0x1250004 +#define ACP_SCRATCH_REG_2 0x1250008 +#define ACP_SCRATCH_REG_3 0x125000C +#define ACP_SCRATCH_REG_4 0x1250010 +#define ACP_SCRATCH_REG_5 0x1250014 +#define ACP_SCRATCH_REG_6 0x1250018 +#define ACP_SCRATCH_REG_7 0x125001C +#define ACP_SCRATCH_REG_8 0x1250020 +#define ACP_SCRATCH_REG_9 0x1250024 +#define ACP_SCRATCH_REG_10 0x1250028 +#define ACP_SCRATCH_REG_11 0x125002C +#define ACP_SCRATCH_REG_12 0x1250030 +#define ACP_SCRATCH_REG_13 0x1250034 +#define ACP_SCRATCH_REG_14 0x1250038 +#define ACP_SCRATCH_REG_15 0x125003C +#define ACP_SCRATCH_REG_16 0x1250040 +#define ACP_SCRATCH_REG_17 0x1250044 +#define ACP_SCRATCH_REG_18 0x1250048 +#define ACP_SCRATCH_REG_19 0x125004C +#define ACP_SCRATCH_REG_20 0x1250050 +#define ACP_SCRATCH_REG_21 0x1250054 +#define ACP_SCRATCH_REG_22 0x1250058 +#define ACP_SCRATCH_REG_23 0x125005C +#define ACP_SCRATCH_REG_24 0x1250060 +#define ACP_SCRATCH_REG_25 0x1250064 +#define ACP_SCRATCH_REG_26 0x1250068 +#define ACP_SCRATCH_REG_27 0x125006C +#define ACP_SCRATCH_REG_28 0x1250070 +#define ACP_SCRATCH_REG_29 0x1250074 +#define ACP_SCRATCH_REG_30 0x1250078 +#define ACP_SCRATCH_REG_31 0x125007C +#define ACP_SCRATCH_REG_32 0x1250080 +#define ACP_SCRATCH_REG_33 0x1250084 +#define ACP_SCRATCH_REG_34 0x1250088 +#define ACP_SCRATCH_REG_35 0x125008C +#define ACP_SCRATCH_REG_36 0x1250090 +#define ACP_SCRATCH_REG_37 0x1250094 +#define ACP_SCRATCH_REG_38 0x1250098 +#define ACP_SCRATCH_REG_39 0x125009C +#define ACP_SCRATCH_REG_40 0x12500A0 +#define ACP_SCRATCH_REG_41 0x12500A4 +#define ACP_SCRATCH_REG_42 0x12500A8 +#define ACP_SCRATCH_REG_43 0x12500AC +#define ACP_SCRATCH_REG_44 0x12500B0 +#define ACP_SCRATCH_REG_45 0x12500B4 +#define ACP_SCRATCH_REG_46 0x12500B8 +#define ACP_SCRATCH_REG_47 0x12500BC +#define ACP_SCRATCH_REG_48 0x12500C0 +#define ACP_SCRATCH_REG_49 0x12500C4 +#define ACP_SCRATCH_REG_50 0x12500C8 +#define ACP_SCRATCH_REG_51 0x12500CC +#define ACP_SCRATCH_REG_52 0x12500D0 +#define ACP_SCRATCH_REG_53 0x12500D4 +#define ACP_SCRATCH_REG_54 0x12500D8 +#define ACP_SCRATCH_REG_55 0x12500DC +#define ACP_SCRATCH_REG_56 0x12500E0 +#define ACP_SCRATCH_REG_57 0x12500E4 +#define ACP_SCRATCH_REG_58 0x12500E8 +#define ACP_SCRATCH_REG_59 0x12500EC +#define ACP_SCRATCH_REG_60 0x12500F0 +#define ACP_SCRATCH_REG_61 0x12500F4 +#define ACP_SCRATCH_REG_62 0x12500F8 +#define ACP_SCRATCH_REG_63 0x12500FC +#define ACP_SCRATCH_REG_64 0x1250100 +#define ACP_SCRATCH_REG_65 0x1250104 +#define ACP_SCRATCH_REG_66 0x1250108 +#define ACP_SCRATCH_REG_67 0x125010C +#define ACP_SCRATCH_REG_68 0x1250110 +#define ACP_SCRATCH_REG_69 0x1250114 +#define ACP_SCRATCH_REG_70 0x1250118 +#define ACP_SCRATCH_REG_71 0x125011C +#define ACP_SCRATCH_REG_72 0x1250120 +#define ACP_SCRATCH_REG_73 0x1250124 +#define ACP_SCRATCH_REG_74 0x1250128 +#define ACP_SCRATCH_REG_75 0x125012C +#define ACP_SCRATCH_REG_76 0x1250130 +#define ACP_SCRATCH_REG_77 0x1250134 +#define ACP_SCRATCH_REG_78 0x1250138 +#define ACP_SCRATCH_REG_79 0x125013C +#define ACP_SCRATCH_REG_80 0x1250140 +#define ACP_SCRATCH_REG_81 0x1250144 +#define ACP_SCRATCH_REG_82 0x1250148 +#define ACP_SCRATCH_REG_83 0x125014C +#define ACP_SCRATCH_REG_84 0x1250150 +#define ACP_SCRATCH_REG_85 0x1250154 +#define ACP_SCRATCH_REG_86 0x1250158 +#define ACP_SCRATCH_REG_87 0x125015C +#define ACP_SCRATCH_REG_88 0x1250160 +#define ACP_SCRATCH_REG_89 0x1250164 +#define ACP_SCRATCH_REG_90 0x1250168 +#define ACP_SCRATCH_REG_91 0x125016C +#define ACP_SCRATCH_REG_92 0x1250170 +#define ACP_SCRATCH_REG_93 0x1250174 +#define ACP_SCRATCH_REG_94 0x1250178 +#define ACP_SCRATCH_REG_95 0x125017C +#define ACP_SCRATCH_REG_96 0x1250180 +#define ACP_SCRATCH_REG_97 0x1250184 +#define ACP_SCRATCH_REG_98 0x1250188 +#define ACP_SCRATCH_REG_99 0x125018C +#define ACP_SCRATCH_REG_100 0x1250190 +#define ACP_SCRATCH_REG_101 0x1250194 +#define ACP_SCRATCH_REG_102 0x1250198 +#define ACP_SCRATCH_REG_103 0x125019C +#define ACP_SCRATCH_REG_104 0x12501A0 +#define ACP_SCRATCH_REG_105 0x12501A4 +#define ACP_SCRATCH_REG_106 0x12501A8 +#define ACP_SCRATCH_REG_107 0x12501AC +#define ACP_SCRATCH_REG_108 0x12501B0 +#define ACP_SCRATCH_REG_109 0x12501B4 +#define ACP_SCRATCH_REG_110 0x12501B8 +#define ACP_SCRATCH_REG_111 0x12501BC +#define ACP_SCRATCH_REG_112 0x12501C0 +#define ACP_SCRATCH_REG_113 0x12501C4 +#define ACP_SCRATCH_REG_114 0x12501C8 +#define ACP_SCRATCH_REG_115 0x12501CC +#define ACP_SCRATCH_REG_116 0x12501D0 +#define ACP_SCRATCH_REG_117 0x12501D4 +#define ACP_SCRATCH_REG_118 0x12501D8 +#define ACP_SCRATCH_REG_119 0x12501DC +#define ACP_SCRATCH_REG_120 0x12501E0 +#define ACP_SCRATCH_REG_121 0x12501E4 +#define ACP_SCRATCH_REG_122 0x12501E8 +#define ACP_SCRATCH_REG_123 0x12501EC +#define ACP_SCRATCH_REG_124 0x12501F0 +#define ACP_SCRATCH_REG_125 0x12501F4 +#define ACP_SCRATCH_REG_126 0x12501F8 +#define ACP_SCRATCH_REG_127 0x12501FC +#define ACP_SCRATCH_REG_128 0x1250200 +#endif diff --git a/sound/soc/amd/yc/pci-acp6x.c b/sound/soc/amd/yc/pci-acp6x.c new file mode 100644 index 000000000000..957eeb6fb8e3 --- /dev/null +++ b/sound/soc/amd/yc/pci-acp6x.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AMD Yellow Carp ACP PCI Driver + * + * Copyright 2021 Advanced Micro Devices, Inc. + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <sound/pcm_params.h> +#include <linux/pm_runtime.h> + +#include "acp6x.h" + +struct acp6x_dev_data { + void __iomem *acp6x_base; + struct resource *res; + bool acp6x_audio_mode; + struct platform_device *pdev[ACP6x_DEVS]; +}; + +static int acp6x_power_on(void __iomem *acp_base) +{ + u32 val; + int timeout; + + val = acp6x_readl(acp_base + ACP_PGFSM_STATUS); + + if (!val) + return val; + + if ((val & ACP_PGFSM_STATUS_MASK) != ACP_POWER_ON_IN_PROGRESS) + acp6x_writel(ACP_PGFSM_CNTL_POWER_ON_MASK, acp_base + ACP_PGFSM_CONTROL); + timeout = 0; + while (++timeout < 500) { + val = acp6x_readl(acp_base + ACP_PGFSM_STATUS); + if (!val) + return 0; + udelay(1); + } + return -ETIMEDOUT; +} + +static int acp6x_reset(void __iomem *acp_base) +{ + u32 val; + int timeout; + + acp6x_writel(1, acp_base + ACP_SOFT_RESET); + timeout = 0; + while (++timeout < 500) { + val = acp6x_readl(acp_base + ACP_SOFT_RESET); + if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) + break; + cpu_relax(); + } + acp6x_writel(0, acp_base + ACP_SOFT_RESET); + timeout = 0; + while (++timeout < 500) { + val = acp6x_readl(acp_base + ACP_SOFT_RESET); + if (!val) + return 0; + cpu_relax(); + } + return -ETIMEDOUT; +} + +static void acp6x_enable_interrupts(void __iomem *acp_base) +{ + acp6x_writel(0x01, acp_base + ACP_EXTERNAL_INTR_ENB); +} + +static void acp6x_disable_interrupts(void __iomem *acp_base) +{ + acp6x_writel(ACP_EXT_INTR_STAT_CLEAR_MASK, acp_base + + ACP_EXTERNAL_INTR_STAT); + acp6x_writel(0x00, acp_base + ACP_EXTERNAL_INTR_CNTL); + acp6x_writel(0x00, acp_base + ACP_EXTERNAL_INTR_ENB); +} + +static int acp6x_init(void __iomem *acp_base) +{ + int ret; + + /* power on */ + ret = acp6x_power_on(acp_base); + if (ret) { + pr_err("ACP power on failed\n"); + return ret; + } + acp6x_writel(0x01, acp_base + ACP_CONTROL); + /* Reset */ + ret = acp6x_reset(acp_base); + if (ret) { + pr_err("ACP reset failed\n"); + return ret; + } + acp6x_writel(0x03, acp_base + ACP_CLKMUX_SEL); + acp6x_enable_interrupts(acp_base); + return 0; +} + +static int acp6x_deinit(void __iomem *acp_base) +{ + int ret; + + acp6x_disable_interrupts(acp_base); + /* Reset */ + ret = acp6x_reset(acp_base); + if (ret) { + pr_err("ACP reset failed\n"); + return ret; + } + acp6x_writel(0x00, acp_base + ACP_CLKMUX_SEL); + acp6x_writel(0x00, acp_base + ACP_CONTROL); + return 0; +} + +static irqreturn_t acp6x_irq_handler(int irq, void *dev_id) +{ + struct acp6x_dev_data *adata; + struct pdm_dev_data *yc_pdm_data; + u32 val; + + adata = dev_id; + if (!adata) + return IRQ_NONE; + + val = acp6x_readl(adata->acp6x_base + ACP_EXTERNAL_INTR_STAT); + if (val & BIT(PDM_DMA_STAT)) { + yc_pdm_data = dev_get_drvdata(&adata->pdev[0]->dev); + acp6x_writel(BIT(PDM_DMA_STAT), adata->acp6x_base + ACP_EXTERNAL_INTR_STAT); + if (yc_pdm_data->capture_stream) + snd_pcm_period_elapsed(yc_pdm_data->capture_stream); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int snd_acp6x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct acp6x_dev_data *adata; + struct platform_device_info pdevinfo[ACP6x_DEVS]; + int ret, index; + int val = 0x00; + u32 addr; + unsigned int irqflags; + + irqflags = IRQF_SHARED; + /* Yellow Carp device check */ + if (pci->revision != 0x60) + return -ENODEV; + + if (pci_enable_device(pci)) { + dev_err(&pci->dev, "pci_enable_device failed\n"); + return -ENODEV; + } + + ret = pci_request_regions(pci, "AMD ACP3x audio"); + if (ret < 0) { + dev_err(&pci->dev, "pci_request_regions failed\n"); + goto disable_pci; + } + + adata = devm_kzalloc(&pci->dev, sizeof(struct acp6x_dev_data), + GFP_KERNEL); + if (!adata) { + ret = -ENOMEM; + goto release_regions; + } + + addr = pci_resource_start(pci, 0); + adata->acp6x_base = devm_ioremap(&pci->dev, addr, + pci_resource_len(pci, 0)); + if (!adata->acp6x_base) { + ret = -ENOMEM; + goto release_regions; + } + pci_set_master(pci); + pci_set_drvdata(pci, adata); + ret = acp6x_init(adata->acp6x_base); + if (ret) + goto release_regions; + val = acp6x_readl(adata->acp6x_base + ACP_PIN_CONFIG); + switch (val) { + case ACP_CONFIG_0: + case ACP_CONFIG_1: + case ACP_CONFIG_2: + case ACP_CONFIG_3: + case ACP_CONFIG_9: + case ACP_CONFIG_15: + dev_info(&pci->dev, "Audio Mode %d\n", val); + break; + default: + adata->res = devm_kzalloc(&pci->dev, + sizeof(struct resource), + GFP_KERNEL); + if (!adata->res) { + ret = -ENOMEM; + goto de_init; + } + + adata->res->name = "acp_iomem"; + adata->res->flags = IORESOURCE_MEM; + adata->res->start = addr; + adata->res->end = addr + (ACP6x_REG_END - ACP6x_REG_START); + + adata->acp6x_audio_mode = ACP6x_PDM_MODE; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo[0].name = "acp_yc_pdm_dma"; + pdevinfo[0].id = 0; + pdevinfo[0].parent = &pci->dev; + pdevinfo[0].num_res = 1; + pdevinfo[0].res = adata->res; + + pdevinfo[1].name = "dmic-codec"; + pdevinfo[1].id = 0; + pdevinfo[1].parent = &pci->dev; + + pdevinfo[2].name = "acp_yc_mach"; + pdevinfo[2].id = 0; + pdevinfo[2].parent = &pci->dev; + + for (index = 0; index < ACP6x_DEVS; index++) { + adata->pdev[index] = + platform_device_register_full(&pdevinfo[index]); + if (IS_ERR(adata->pdev[index])) { + dev_err(&pci->dev, "cannot register %s device\n", + pdevinfo[index].name); + ret = PTR_ERR(adata->pdev[index]); + goto unregister_devs; + } + } + break; + } + ret = devm_request_irq(&pci->dev, pci->irq, acp6x_irq_handler, + irqflags, "ACP_PCI_IRQ", adata); + if (ret) { + dev_err(&pci->dev, "ACP PCI IRQ request failed\n"); + goto unregister_devs; + } + pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pci->dev); + pm_runtime_put_noidle(&pci->dev); + pm_runtime_allow(&pci->dev); + + return 0; +unregister_devs: + for (--index; index >= 0; index--) + platform_device_unregister(adata->pdev[index]); +de_init: + if (acp6x_deinit(adata->acp6x_base)) + dev_err(&pci->dev, "ACP de-init failed\n"); +release_regions: + pci_release_regions(pci); +disable_pci: + pci_disable_device(pci); + + return ret; +} + +static int __maybe_unused snd_acp6x_suspend(struct device *dev) +{ + struct acp6x_dev_data *adata; + int ret; + + adata = dev_get_drvdata(dev); + ret = acp6x_deinit(adata->acp6x_base); + if (ret) + dev_err(dev, "ACP de-init failed\n"); + return ret; +} + +static int __maybe_unused snd_acp6x_resume(struct device *dev) +{ + struct acp6x_dev_data *adata; + int ret; + + adata = dev_get_drvdata(dev); + ret = acp6x_init(adata->acp6x_base); + if (ret) + dev_err(dev, "ACP init failed\n"); + return ret; +} + +static const struct dev_pm_ops acp6x_pm = { + SET_RUNTIME_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(snd_acp6x_suspend, snd_acp6x_resume) +}; + +static void snd_acp6x_remove(struct pci_dev *pci) +{ + struct acp6x_dev_data *adata; + int ret, index; + + adata = pci_get_drvdata(pci); + if (adata->acp6x_audio_mode == ACP6x_PDM_MODE) { + for (index = 0; index < ACP6x_DEVS; index++) + platform_device_unregister(adata->pdev[index]); + } + ret = acp6x_deinit(adata->acp6x_base); + if (ret) + dev_err(&pci->dev, "ACP de-init failed\n"); + pm_runtime_forbid(&pci->dev); + pm_runtime_get_noresume(&pci->dev); + pci_release_regions(pci); + pci_disable_device(pci); +} + +static const struct pci_device_id snd_acp6x_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID), + .class = PCI_CLASS_MULTIMEDIA_OTHER << 8, + .class_mask = 0xffffff }, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, snd_acp6x_ids); + +static struct pci_driver yc_acp6x_driver = { + .name = KBUILD_MODNAME, + .id_table = snd_acp6x_ids, + .probe = snd_acp6x_probe, + .remove = snd_acp6x_remove, + .driver = { + .pm = &acp6x_pm, + } +}; + +module_pci_driver(yc_acp6x_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP Yellow Carp PCI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index 6b3d9c05eaf2..1934767690b5 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -342,8 +342,8 @@ static int atmel_i2s_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: /* codec is slave, so cpu is master */ mr |= ATMEL_I2SC_MR_MODE_MASTER; ret = atmel_i2s_get_gck_param(dev, params_rate(params)); @@ -351,7 +351,7 @@ static int atmel_i2s_hw_params(struct snd_pcm_substream *substream, return ret; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: /* codec is master, so cpu is slave */ mr |= ATMEL_I2SC_MR_MODE_SLAVE; dev->gck_param = NULL; diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index 6a63e8797a0b..26e2bc690d86 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -209,8 +209,8 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params, if (frame_size < 0) return frame_size; - switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFS: + switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFC: if ((ssc_p->dir_mask & SSC_DIR_MASK_CAPTURE) && ssc->clk_from_rk_pin) /* Receiver Frame Synchro (i.e. capture) @@ -220,7 +220,7 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params, mck_div = 3; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: if ((ssc_p->dir_mask & SSC_DIR_MASK_PLAYBACK) && !ssc->clk_from_rk_pin) /* Transmit Frame Synchro (i.e. playback) @@ -232,8 +232,8 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params, break; } - switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: r.num = ssc_p->mck_rate / mck_div / frame_size; ret = snd_interval_ratnum(i, 1, &r, &num, &den); @@ -243,8 +243,8 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params, } break; - case SND_SOC_DAIFMT_CBM_CFS: - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFC: + case SND_SOC_DAIFMT_CBP_CFP: t.min = 8000; t.max = ssc_p->mck_rate / mck_div / frame_size; t.openmin = t.openmax = 0; @@ -429,9 +429,9 @@ static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, /* Is the cpu-dai master of the frame clock? */ static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p) { - switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFS: - case SND_SOC_DAIFMT_CBS_CFS: + switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFC: + case SND_SOC_DAIFMT_CBC_CFC: return 1; } return 0; @@ -440,9 +440,9 @@ static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p) /* Is the cpu-dai master of the bit clock? */ static int atmel_ssc_cbs(struct atmel_ssc_info *ssc_p) { - switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFM: - case SND_SOC_DAIFMT_CBS_CFS: + switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFP: + case SND_SOC_DAIFMT_CBC_CFC: return 1; } return 0; diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c index 9e237580afa9..9c974c4e187d 100644 --- a/sound/soc/atmel/atmel_wm8904.c +++ b/sound/soc/atmel/atmel_wm8904.c @@ -66,7 +66,7 @@ static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { .stream_name = "WM8904 PCM", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBM_CFM, + | SND_SOC_DAIFMT_CBP_CFP, .ops = &atmel_asoc_wm8904_ops, SND_SOC_DAILINK_REG(pcm), }; diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c index 8988f024a732..6d1227a1d67b 100644 --- a/sound/soc/atmel/mchp-i2s-mcc.c +++ b/sound/soc/atmel/mchp-i2s-mcc.c @@ -350,7 +350,7 @@ static int mchp_i2s_mcc_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; /* We can't generate only FSYNC */ - if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFS) + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) == SND_SOC_DAIFMT_CBP_CFC) return -EINVAL; /* We can only reconfigure the IP when it's stopped */ @@ -546,20 +546,20 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: /* cpu is BCLK and LRC master */ mra |= MCHP_I2SMCC_MRA_MODE_MASTER; if (dev->sysclk) mra |= MCHP_I2SMCC_MRA_IMCKMODE_GEN; set_divs = 1; break; - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBC_CFP: /* cpu is BCLK master */ mrb |= MCHP_I2SMCC_MRB_CLKSEL_INT; set_divs = 1; fallthrough; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: /* cpu is slave */ mra |= MCHP_I2SMCC_MRA_MODE_SLAVE; if (dev->sysclk) diff --git a/sound/soc/atmel/mikroe-proto.c b/sound/soc/atmel/mikroe-proto.c index 0be7b4221c14..f9331f7e80fe 100644 --- a/sound/soc/atmel/mikroe-proto.c +++ b/sound/soc/atmel/mikroe-proto.c @@ -129,9 +129,9 @@ static int snd_proto_probe(struct platform_device *pdev) } if (bitclkmaster) { if (codec_np == bitclkmaster) - dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; else - dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; } else { dai_fmt |= snd_soc_daifmt_parse_clock_provider_as_flag(np, NULL); } diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index ed1f69b57024..915da92e1ec8 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -126,7 +126,7 @@ static struct snd_soc_dai_link at91sam9g20ek_dai = { .stream_name = "WM8731 PCM", .init = at91sam9g20ek_wm8731_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM, + SND_SOC_DAIFMT_CBP_CFP, SND_SOC_DAILINK_REG(pcm), }; diff --git a/sound/soc/atmel/sam9x5_wm8731.c b/sound/soc/atmel/sam9x5_wm8731.c index 7745250fd743..7c45dc4f8c1b 100644 --- a/sound/soc/atmel/sam9x5_wm8731.c +++ b/sound/soc/atmel/sam9x5_wm8731.c @@ -115,7 +115,7 @@ static int sam9x5_wm8731_driver_probe(struct platform_device *pdev) dai->codecs->dai_name = "wm8731-hifi"; dai->init = sam9x5_wm8731_init; dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBM_CFM; + | SND_SOC_DAIFMT_CBP_CFP; ret = snd_soc_of_parse_card_name(card, "atmel,model"); if (ret) { diff --git a/sound/soc/atmel/tse850-pcm5142.c b/sound/soc/atmel/tse850-pcm5142.c index 50c3dc6936f9..1b3a31296c9b 100644 --- a/sound/soc/atmel/tse850-pcm5142.c +++ b/sound/soc/atmel/tse850-pcm5142.c @@ -304,7 +304,7 @@ static struct snd_soc_dai_link tse850_dailink = { .stream_name = "TSE-850-PCM", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBM_CFS, + | SND_SOC_DAIFMT_CBP_CFC, SND_SOC_DAILINK_REG(pcm), }; diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c index 5f8baad37a40..400eaf9f8b14 100644 --- a/sound/soc/au1x/db1200.c +++ b/sound/soc/au1x/db1200.c @@ -117,7 +117,7 @@ static struct snd_soc_dai_link db1200_i2s_dai = { .name = "WM8731", .stream_name = "WM8731 PCM", .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM, + SND_SOC_DAIFMT_CBP_CFP, .ops = &db1200_i2s_wm8731_ops, SND_SOC_DAILINK_REG(db1200_i2s), }; @@ -138,7 +138,7 @@ static struct snd_soc_dai_link db1300_i2s_dai = { .name = "WM8731", .stream_name = "WM8731 PCM", .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM, + SND_SOC_DAIFMT_CBP_CFP, .ops = &db1200_i2s_wm8731_ops, SND_SOC_DAILINK_REG(db1300_i2s), }; @@ -159,7 +159,7 @@ static struct snd_soc_dai_link db1550_i2s_dai = { .name = "WM8731", .stream_name = "WM8731 PCM", .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM, + SND_SOC_DAIFMT_CBP_CFP, .ops = &db1200_i2s_wm8731_ops, SND_SOC_DAILINK_REG(db1550_i2s), }; diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c index 65bd39f5032d..740d4e052e4d 100644 --- a/sound/soc/au1x/i2sc.c +++ b/sound/soc/au1x/i2sc.c @@ -119,9 +119,9 @@ static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) goto out; } - /* I2S controller only supports master */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ + /* I2S controller only supports provider */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: /* CODEC consumer */ break; default: goto out; diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c index 767ce950d0da..b2b8896bb593 100644 --- a/sound/soc/au1x/psc-i2s.c +++ b/sound/soc/au1x/psc-i2s.c @@ -90,12 +90,12 @@ static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, goto out; } - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */ - ct |= PSC_I2SCFG_MS; /* PSC I2S slave mode */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: /* CODEC provider */ + ct |= PSC_I2SCFG_MS; /* PSC I2S consumer mode */ break; - case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ - ct &= ~PSC_I2SCFG_MS; /* PSC I2S Master mode */ + case SND_SOC_DAIFMT_CBC_CFC: /* CODEC consumer */ + ct &= ~PSC_I2SCFG_MS; /* PSC I2S provider mode */ break; default: goto out; diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c index 3d668f449bc1..e3fc4bee8cfd 100644 --- a/sound/soc/bcm/bcm2835-i2s.c +++ b/sound/soc/bcm/bcm2835-i2s.c @@ -127,14 +127,14 @@ struct bcm2835_i2s_dev { static void bcm2835_i2s_start_clock(struct bcm2835_i2s_dev *dev) { - unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK; + unsigned int provider = dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK; if (dev->clk_prepared) return; - switch (master) { - case SND_SOC_DAIFMT_CBS_CFS: - case SND_SOC_DAIFMT_CBS_CFM: + switch (provider) { + case SND_SOC_DAIFMT_CBC_CFC: + case SND_SOC_DAIFMT_CBC_CFP: clk_prepare_enable(dev->clk); dev->clk_prepared = true; break; @@ -337,8 +337,8 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, unsigned int rx_mask, tx_mask; unsigned int rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos; unsigned int mode, format; - bool bit_clock_master = false; - bool frame_sync_master = false; + bool bit_clock_provider = false; + bool frame_sync_provider = false; bool frame_start_falling_edge = false; uint32_t csreg; int ret = 0; @@ -383,36 +383,36 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, if (data_length > slot_width) return -EINVAL; - /* Check if CPU is bit clock master */ - switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - case SND_SOC_DAIFMT_CBS_CFM: - bit_clock_master = true; + /* Check if CPU is bit clock provider */ + switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + case SND_SOC_DAIFMT_CBC_CFP: + bit_clock_provider = true; break; - case SND_SOC_DAIFMT_CBM_CFS: - case SND_SOC_DAIFMT_CBM_CFM: - bit_clock_master = false; + case SND_SOC_DAIFMT_CBP_CFC: + case SND_SOC_DAIFMT_CBP_CFP: + bit_clock_provider = false; break; default: return -EINVAL; } - /* Check if CPU is frame sync master */ - switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - case SND_SOC_DAIFMT_CBM_CFS: - frame_sync_master = true; + /* Check if CPU is frame sync provider */ + switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + case SND_SOC_DAIFMT_CBP_CFC: + frame_sync_provider = true; break; - case SND_SOC_DAIFMT_CBS_CFM: - case SND_SOC_DAIFMT_CBM_CFM: - frame_sync_master = false; + case SND_SOC_DAIFMT_CBC_CFP: + case SND_SOC_DAIFMT_CBP_CFP: + frame_sync_provider = false; break; default: return -EINVAL; } /* Clock should only be set up here if CPU is clock master */ - if (bit_clock_master && + if (bit_clock_provider && (!dev->clk_prepared || dev->clk_rate != bclk_rate)) { if (dev->clk_prepared) bcm2835_i2s_stop_clock(dev); @@ -501,11 +501,11 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, /* * Transmitting data immediately after frame start, eg * in left-justified or DSP mode A, only works stable - * if bcm2835 is the frame clock master. + * if bcm2835 is the frame clock provider. */ - if ((!rx_ch1_pos || !tx_ch1_pos) && !frame_sync_master) + if ((!rx_ch1_pos || !tx_ch1_pos) && !frame_sync_provider) dev_warn(dev->dev, - "Unstable slave config detected, L/R may be swapped"); + "Unstable consumer config detected, L/R may be swapped"); /* * Set format for both streams. @@ -538,11 +538,11 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, mode |= BCM2835_I2S_FSLEN(framesync_length); /* CLKM selects bcm2835 clock slave mode */ - if (!bit_clock_master) + if (!bit_clock_provider) mode |= BCM2835_I2S_CLKM; /* FSM selects bcm2835 frame sync slave mode */ - if (!frame_sync_master) + if (!frame_sync_provider) mode |= BCM2835_I2S_FSM; /* CLKI selects normal clocking mode, sampling on rising edge */ diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c index fca5a3f2eec5..9698f4531c90 100644 --- a/sound/soc/bcm/cygnus-ssp.c +++ b/sound/soc/bcm/cygnus-ssp.c @@ -848,12 +848,12 @@ static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) ssp_newcfg = 0; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: ssp_newcfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE); aio->is_slave = 1; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: ssp_newcfg &= ~BIT(I2S_OUT_CFGX_SLAVE_MODE); aio->is_slave = 0; break; diff --git a/sound/soc/cirrus/edb93xx.c b/sound/soc/cirrus/edb93xx.c index 7b6cdc9c8a23..385290202912 100644 --- a/sound/soc/cirrus/edb93xx.c +++ b/sound/soc/cirrus/edb93xx.c @@ -60,7 +60,7 @@ static struct snd_soc_dai_link edb93xx_dai = { .name = "CS4271", .stream_name = "CS4271 HiFi", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ops = &edb93xx_ops, SND_SOC_DAILINK_REG(hifi), }; diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c index 0d26550d0df8..06c315a4d20a 100644 --- a/sound/soc/cirrus/ep93xx-i2s.c +++ b/sound/soc/cirrus/ep93xx-i2s.c @@ -245,14 +245,14 @@ static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, return -EINVAL; } - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - /* CPU is master */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + /* CPU is provider */ clk_cfg |= EP93XX_I2S_CLKCFG_MASTER; break; - case SND_SOC_DAIFMT_CBM_CFM: - /* Codec is master */ + case SND_SOC_DAIFMT_CBP_CFP: + /* Codec is provider */ clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER; break; diff --git a/sound/soc/cirrus/snappercl15.c b/sound/soc/cirrus/snappercl15.c index c4b112921661..a286f5beeaeb 100644 --- a/sound/soc/cirrus/snappercl15.c +++ b/sound/soc/cirrus/snappercl15.c @@ -70,7 +70,7 @@ static struct snd_soc_dai_link snappercl15_dai = { .name = "tlv320aic23", .stream_name = "AIC23", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ops = &snappercl15_ops, SND_SOC_DAILINK_REG(aic23), }; diff --git a/sound/soc/codecs/88pm860x-codec.c b/sound/soc/codecs/88pm860x-codec.c index cac7e557edc8..c6043fa58c74 100644 --- a/sound/soc/codecs/88pm860x-codec.c +++ b/sound/soc/codecs/88pm860x-codec.c @@ -968,16 +968,16 @@ static int pm860x_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER; - /* set master/slave audio interface */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - case SND_SOC_DAIFMT_CBM_CFS: + /* set audio interface clocking */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + case SND_SOC_DAIFMT_CBP_CFC: if (pm860x->dir == PM860X_CLK_DIR_OUT) { inf |= PCM_INF2_MASTER; ret = 0; } break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: if (pm860x->dir == PM860X_CLK_DIR_IN) { inf &= ~PCM_INF2_MASTER; ret = 0; @@ -1072,15 +1072,15 @@ static int pm860x_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER; - /* set master/slave audio interface */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + /* set audio interface clocking */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: if (pm860x->dir == PM860X_CLK_DIR_OUT) inf |= PCM_INF2_MASTER; else return -EINVAL; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: if (pm860x->dir == PM860X_CLK_DIR_IN) inf &= ~PCM_INF2_MASTER; else diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 216cea04ad70..326f2d611ad4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -61,6 +61,8 @@ config SND_SOC_ALL_CODECS imply SND_SOC_CS35L34 imply SND_SOC_CS35L35 imply SND_SOC_CS35L36 + imply SND_SOC_CS35L41_SPI + imply SND_SOC_CS35L41_I2C imply SND_SOC_CS42L42 imply SND_SOC_CS42L51_I2C imply SND_SOC_CS42L52 @@ -115,6 +117,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_MAX98357A imply SND_SOC_MAX98371 imply SND_SOC_MAX98504 + imply SND_SOC_MAX98520 imply SND_SOC_MAX9867 imply SND_SOC_MAX98925 imply SND_SOC_MAX98926 @@ -136,6 +139,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_NAU8315 imply SND_SOC_NAU8540 imply SND_SOC_NAU8810 + imply SND_SOC_NAU8821 imply SND_SOC_NAU8822 imply SND_SOC_NAU8824 imply SND_SOC_NAU8825 @@ -180,6 +184,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_RT5677 imply SND_SOC_RT5682_I2C imply SND_SOC_RT5682_SDW + imply SND_SOC_RT5682S imply SND_SOC_RT700_SDW imply SND_SOC_RT711_SDW imply SND_SOC_RT711_SDCA_SDW @@ -187,6 +192,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_RT715_SDCA_SDW imply SND_SOC_RT1308_SDW imply SND_SOC_RT1316_SDW + imply SND_SOC_RT9120 imply SND_SOC_SDW_MOCKUP imply SND_SOC_SGTL5000 imply SND_SOC_SI476X @@ -330,6 +336,7 @@ config SND_SOC_WM_HUBS config SND_SOC_WM_ADSP tristate + select CS_DSP select SND_SOC_COMPRESS default y if SND_SOC_MADERA=y default y if SND_SOC_CS47L24=y @@ -602,6 +609,16 @@ config SND_SOC_CS35L36 tristate "Cirrus Logic CS35L36 CODEC" depends on I2C +config SND_SOC_CS35L41_SPI + tristate "Cirrus Logic CS35L41 CODEC (SPI)" + depends on SPI_MASTER + select REGMAP_SPI + +config SND_SOC_CS35L41_I2C + tristate "Cirrus Logic CS35L41 CODEC (I2C)" + depends on I2C + select REGMAP_I2C + config SND_SOC_CS42L42 tristate "Cirrus Logic CS42L42 CODEC" depends on I2C @@ -922,6 +939,17 @@ config SND_SOC_MAX98927 tristate "Maxim Integrated MAX98927 Speaker Amplifier" depends on I2C +config SND_SOC_MAX98520 + tristate "Maxim Integrated MAX98520 Speaker Amplifier" + depends on I2C + help + Enable support for Maxim Integrated MAX98520 audio + amplifier, which implements a tripler charge pump + based boost converter and supports sample rates of + 8KHz to 192KHz. + + To compile this driver as a module, choose M here. + config SND_SOC_MAX98373 tristate @@ -1249,6 +1277,10 @@ config SND_SOC_RT5682_SDW select SND_SOC_RT5682 select REGMAP_SOUNDWIRE +config SND_SOC_RT5682S + tristate + depends on I2C + config SND_SOC_RT700 tristate @@ -1288,6 +1320,15 @@ config SND_SOC_RT715_SDCA_SDW select REGMAP_SOUNDWIRE select REGMAP_SOUNDWIRE_MBQ +config SND_SOC_RT9120 + tristate "Richtek RT9120 Stereo Class-D Amplifier" + depends on I2C + select REGMAP_I2C + select GPIOLIB + help + Enable support for Richtek RT9120 20W, stereo, inductor-less, + high-efficiency Class-D audio amplifier. + config SND_SOC_SDW_MOCKUP tristate "SoundWire mockup codec" depends on EXPERT @@ -1905,6 +1946,10 @@ config SND_SOC_NAU8810 tristate "Nuvoton Technology Corporation NAU88C10 CODEC" depends on I2C +config SND_SOC_NAU8821 + tristate "Nuvoton Technology Corporation NAU88L21 CODEC" + depends on I2C + config SND_SOC_NAU8822 tristate "Nuvoton Technology Corporation NAU88C22 CODEC" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8dcea2c4604a..9acfbcbfc46d 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -54,6 +54,8 @@ snd-soc-cs35l33-objs := cs35l33.o snd-soc-cs35l34-objs := cs35l34.o snd-soc-cs35l35-objs := cs35l35.o snd-soc-cs35l36-objs := cs35l36.o +snd-soc-cs35l41-spi-objs := cs35l41-spi.o cs35l41.o cs35l41-tables.o +snd-soc-cs35l41-i2c-objs := cs35l41-i2c.o cs35l41.o cs35l41-tables.o snd-soc-cs42l42-objs := cs42l42.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o @@ -123,6 +125,7 @@ snd-soc-max9867-objs := max9867.o snd-soc-max98925-objs := max98925.o snd-soc-max98926-objs := max98926.o snd-soc-max98927-objs := max98927.o +snd-soc-max98520-objs := max98520.o snd-soc-max98373-objs := max98373.o snd-soc-max98373-i2c-objs := max98373-i2c.o snd-soc-max98373-sdw-objs := max98373-sdw.o @@ -141,6 +144,7 @@ snd-soc-mt6660-objs := mt6660.o snd-soc-nau8315-objs := nau8315.o snd-soc-nau8540-objs := nau8540.o snd-soc-nau8810-objs := nau8810.o +snd-soc-nau8821-objs := nau8821.o snd-soc-nau8822-objs := nau8822.o snd-soc-nau8824-objs := nau8824.o snd-soc-nau8825-objs := nau8825.o @@ -198,11 +202,13 @@ snd-soc-rt5677-spi-objs := rt5677-spi.o snd-soc-rt5682-objs := rt5682.o snd-soc-rt5682-sdw-objs := rt5682-sdw.o snd-soc-rt5682-i2c-objs := rt5682-i2c.o +snd-soc-rt5682s-objs := rt5682s.o snd-soc-rt700-objs := rt700.o rt700-sdw.o snd-soc-rt711-objs := rt711.o rt711-sdw.o snd-soc-rt711-sdca-objs := rt711-sdca.o rt711-sdca-sdw.o snd-soc-rt715-objs := rt715.o rt715-sdw.o snd-soc-rt715-sdca-objs := rt715-sdca.o rt715-sdca-sdw.o +snd-soc-rt9120-objs := rt9120.o snd-soc-sdw-mockup-objs := sdw-mockup.o snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o @@ -385,6 +391,8 @@ obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o obj-$(CONFIG_SND_SOC_CS35L35) += snd-soc-cs35l35.o obj-$(CONFIG_SND_SOC_CS35L36) += snd-soc-cs35l36.o +obj-$(CONFIG_SND_SOC_CS35L41_SPI) += snd-soc-cs35l41-spi.o +obj-$(CONFIG_SND_SOC_CS35L41_I2C) += snd-soc-cs35l41-i2c.o obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o @@ -450,6 +458,7 @@ obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o obj-$(CONFIG_SND_SOC_MAX98927) += snd-soc-max98927.o +obj-$(CONFIG_SND_SOC_MAX98520) += snd-soc-max98520.o obj-$(CONFIG_SND_SOC_MAX98373) += snd-soc-max98373.o obj-$(CONFIG_SND_SOC_MAX98373_I2C) += snd-soc-max98373-i2c.o obj-$(CONFIG_SND_SOC_MAX98373_SDW) += snd-soc-max98373-sdw.o @@ -468,6 +477,7 @@ obj-$(CONFIG_SND_SOC_MT6660) += snd-soc-mt6660.o obj-$(CONFIG_SND_SOC_NAU8315) += snd-soc-nau8315.o obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o +obj-$(CONFIG_SND_SOC_NAU8821) += snd-soc-nau8821.o obj-$(CONFIG_SND_SOC_NAU8822) += snd-soc-nau8822.o obj-$(CONFIG_SND_SOC_NAU8824) += snd-soc-nau8824.o obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o @@ -526,11 +536,13 @@ obj-$(CONFIG_SND_SOC_RT5677_SPI) += snd-soc-rt5677-spi.o obj-$(CONFIG_SND_SOC_RT5682) += snd-soc-rt5682.o obj-$(CONFIG_SND_SOC_RT5682_I2C) += snd-soc-rt5682-i2c.o obj-$(CONFIG_SND_SOC_RT5682_SDW) += snd-soc-rt5682-sdw.o +obj-$(CONFIG_SND_SOC_RT5682S) += snd-soc-rt5682s.o obj-$(CONFIG_SND_SOC_RT700) += snd-soc-rt700.o obj-$(CONFIG_SND_SOC_RT711) += snd-soc-rt711.o obj-$(CONFIG_SND_SOC_RT711_SDCA_SDW) += snd-soc-rt711-sdca.o obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o +obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o obj-$(CONFIG_SND_SOC_SDW_MOCKUP) += snd-soc-sdw-mockup.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 5525e1ccab76..aefafb0b7b97 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -2104,26 +2104,26 @@ static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) BIT(AB8500_DIGIFCONF3_IF0MASTER); val = 0; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & FRM master */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: dev_dbg(dai->component->dev, - "%s: IF0 Master-mode: AB8500 master.\n", __func__); + "%s: IF0 Master-mode: AB8500 provider.\n", __func__); val |= BIT(AB8500_DIGIFCONF3_IF0MASTER); break; - case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & FRM slave */ + case SND_SOC_DAIFMT_CBC_CFC: dev_dbg(dai->component->dev, - "%s: IF0 Master-mode: AB8500 slave.\n", __func__); + "%s: IF0 Master-mode: AB8500 consumer.\n", __func__); break; - case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & FRM master */ - case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + case SND_SOC_DAIFMT_CBC_CFP: + case SND_SOC_DAIFMT_CBP_CFC: dev_err(dai->component->dev, - "%s: ERROR: The device is either a master or a slave.\n", + "%s: ERROR: The device is either a provider or a consumer.\n", __func__); fallthrough; default: dev_err(dai->component->dev, - "%s: ERROR: Unsupporter master mask 0x%x\n", - __func__, fmt & SND_SOC_DAIFMT_MASTER_MASK); + "%s: ERROR: Unsupporter clocking mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK); return -EINVAL; } diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 08a5651bed9f..29e1689da67f 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -148,9 +148,9 @@ static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - /* ALCLK,ABCLK are both output, AD1836 can only be master */ - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + /* ALCLK,ABCLK are both output, AD1836 can only be provider */ + case SND_SOC_DAIFMT_CBP_CFP: break; default: return -EINVAL; diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index 278a55af158b..30b98b4267e1 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -243,22 +243,22 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, if (fmt & SND_SOC_DAIFMT_DSP_A) dac_fmt ^= AD193X_DAC_LEFT_HIGH; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: adc_fmt |= AD193X_ADC_LCR_MASTER; adc_fmt |= AD193X_ADC_BCLK_MASTER; dac_fmt |= AD193X_DAC_LCR_MASTER; dac_fmt |= AD193X_DAC_BCLK_MASTER; break; - case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ + case SND_SOC_DAIFMT_CBC_CFP: adc_fmt |= AD193X_ADC_LCR_MASTER; dac_fmt |= AD193X_DAC_LCR_MASTER; break; - case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + case SND_SOC_DAIFMT_CBP_CFC: adc_fmt |= AD193X_ADC_BCLK_MASTER; dac_fmt |= AD193X_DAC_BCLK_MASTER; break; - case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; diff --git a/sound/soc/codecs/adau1372.c b/sound/soc/codecs/adau1372.c index 6811a8b3866d..1faa4c426365 100644 --- a/sound/soc/codecs/adau1372.c +++ b/sound/soc/codecs/adau1372.c @@ -30,7 +30,7 @@ struct adau1372 { void (*switch_mode)(struct device *dev); bool use_pll; bool enabled; - bool master; + bool clock_provider; struct snd_pcm_hw_constraint_list rate_constraints; unsigned int slot_width; @@ -578,13 +578,13 @@ static int adau1372_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) unsigned int sai0 = 0, sai1 = 0; bool invert_lrclk = false; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - adau1372->master = true; + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + adau1372->clock_provider = true; sai1 |= ADAU1372_SAI1_MS; break; - case SND_SOC_DAIFMT_CBS_CFS: - adau1372->master = false; + case SND_SOC_DAIFMT_CBC_CFC: + adau1372->clock_provider = false; break; default: return -EINVAL; @@ -714,7 +714,7 @@ static int adau1372_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, break; case 4: sai0 = ADAU1372_SAI0_SAI_TDM4; - if (adau1372->master) + if (adau1372->clock_provider) adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM4_MASTER; else adau1372->rate_constraints.mask = ADAU1372_RATE_MASK_TDM4; diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c index 9887aa6f0be5..46128aaceae9 100644 --- a/sound/soc/codecs/adau1373.c +++ b/sound/soc/codecs/adau1373.c @@ -28,7 +28,7 @@ struct adau1373_dai { unsigned int clk_src; unsigned int sysclk; bool enable_src; - bool master; + bool clock_provider; }; struct adau1373 { @@ -827,7 +827,7 @@ static int adau1373_check_aif_clk(struct snd_soc_dapm_widget *source, dai = sink->name[3] - '1'; - if (!adau1373->dais[dai].master) + if (!adau1373->dais[dai].clock_provider) return 0; if (adau1373->dais[dai].clk_src == ADAU1373_CLK_SRC_PLL1) @@ -1102,14 +1102,14 @@ static int adau1373_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id]; unsigned int ctrl; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: ctrl = ADAU1373_DAI_MASTER; - adau1373_dai->master = true; + adau1373_dai->clock_provider = true; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: ctrl = 0; - adau1373_dai->master = false; + adau1373_dai->clock_provider = false; break; default: return -EINVAL; diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index 5ce74697564a..c5bf461c0b7e 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -482,13 +482,13 @@ static int adau1701_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int serictl = 0x00, seroctl = 0x00; bool invert_lrclk; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: /* master, 64-bits per sample, 1 frame per sample */ seroctl |= ADAU1701_SEROCTL_MASTER | ADAU1701_SEROCTL_OBF16 | ADAU1701_SEROCTL_OLF1024; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index 8aae7ab74091..af05463af4ac 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -556,12 +556,12 @@ static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai, unsigned int ctrl0_mask; int lrclk_pol; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: ctrl0 = ADAU17X1_SERIAL_PORT0_MASTER; adau->master = true; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: ctrl0 = 0; adau->master = false; break; diff --git a/sound/soc/codecs/adau1977.c b/sound/soc/codecs/adau1977.c index e347a48131d1..5fcbdf2ec313 100644 --- a/sound/soc/codecs/adau1977.c +++ b/sound/soc/codecs/adau1977.c @@ -124,10 +124,10 @@ struct adau1977 { struct device *dev; void (*switch_mode)(struct device *dev); - unsigned int max_master_fs; + unsigned int max_clock_provider_fs; unsigned int slot_width; bool enabled; - bool master; + bool clock_provider; }; static const struct reg_default adau1977_reg_defaults[] = { @@ -330,7 +330,7 @@ static int adau1977_hw_params(struct snd_pcm_substream *substream, ctrl0_mask |= ADAU1977_SAI_CTRL0_FMT_MASK; } - if (adau1977->master) { + if (adau1977->clock_provider) { switch (params_width(params)) { case 16: ctrl1 = ADAU1977_SAI_CTRL1_DATA_WIDTH_16BIT; @@ -504,7 +504,7 @@ static int adau1977_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, if (slots == 0) { /* 0 = No fixed slot width */ adau1977->slot_width = 0; - adau1977->max_master_fs = 192000; + adau1977->max_clock_provider_fs = 192000; return regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_CTRL0, ADAU1977_SAI_CTRL0_SAI_MASK, ADAU1977_SAI_CTRL0_SAI_I2S); @@ -533,7 +533,7 @@ static int adau1977_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, break; case 24: /* We can only generate 16 bit or 32 bit wide slots */ - if (adau1977->master) + if (adau1977->clock_provider) return -EINVAL; ctrl1 = ADAU1977_SAI_CTRL1_SLOT_WIDTH_24; break; @@ -593,8 +593,8 @@ static int adau1977_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, adau1977->slot_width = width; - /* In master mode the maximum bitclock is 24.576 MHz */ - adau1977->max_master_fs = min(192000, 24576000 / width / slots); + /* In clock provider mode the maximum bitclock is 24.576 MHz */ + adau1977->max_clock_provider_fs = min(192000, 24576000 / width / slots); return 0; } @@ -620,13 +620,13 @@ static int adau1977_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) bool invert_lrclk; int ret; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - adau1977->master = false; + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + adau1977->clock_provider = false; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: ctrl1 |= ADAU1977_SAI_CTRL1_MASTER; - adau1977->master = true; + adau1977->clock_provider = true; break; default: return -EINVAL; @@ -714,9 +714,10 @@ static int adau1977_startup(struct snd_pcm_substream *substream, snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &adau1977->constraints); - if (adau1977->master) + if (adau1977->clock_provider) snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, 8000, adau1977->max_master_fs); + SNDRV_PCM_HW_PARAM_RATE, 8000, + adau1977->max_clock_provider_fs); if (formats != 0) snd_pcm_hw_constraint_mask64(substream->runtime, @@ -913,7 +914,7 @@ int adau1977_probe(struct device *dev, struct regmap *regmap, adau1977->type = type; adau1977->regmap = regmap; adau1977->switch_mode = switch_mode; - adau1977->max_master_fs = 192000; + adau1977->max_clock_provider_fs = 192000; adau1977->constraints.list = adau1977_rates; adau1977->constraints.count = ARRAY_SIZE(adau1977_rates); diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c index 75a649108106..90f3a5e9e31f 100644 --- a/sound/soc/codecs/adav80x.c +++ b/sound/soc/codecs/adav80x.c @@ -369,12 +369,12 @@ static int adav80x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) unsigned int capture = 0x00; unsigned int playback = 0x00; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: capture |= ADAV80X_CAPTURE_MODE_MASTER; playback |= ADAV80X_PLAYBACK_MODE_MASTER; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index 979cfb165eed..dc4747c77a7a 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -81,8 +81,8 @@ static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - /* This device can only be slave */ - if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + /* This device can only be consumer */ + if ((format & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) return -EINVAL; ret = regmap_update_bits(ak4104->regmap, AK4104_REG_CONTROL1, diff --git a/sound/soc/codecs/ak4118.c b/sound/soc/codecs/ak4118.c index 5d46ae85566c..e0a6451851e8 100644 --- a/sound/soc/codecs/ak4118.c +++ b/sound/soc/codecs/ak4118.c @@ -151,8 +151,8 @@ static const struct snd_soc_dapm_route ak4118_dapm_routes[] = { }; -static int ak4118_set_dai_fmt_master(struct ak4118_priv *ak4118, - unsigned int format) +static int ak4118_set_dai_fmt_provider(struct ak4118_priv *ak4118, + unsigned int format) { int dif; @@ -173,8 +173,8 @@ static int ak4118_set_dai_fmt_master(struct ak4118_priv *ak4118, return dif; } -static int ak4118_set_dai_fmt_slave(struct ak4118_priv *ak4118, - unsigned int format) +static int ak4118_set_dai_fmt_consumer(struct ak4118_priv *ak4118, + unsigned int format) { int dif; @@ -201,14 +201,12 @@ static int ak4118_set_dai_fmt(struct snd_soc_dai *dai, int dif; int ret = 0; - switch (format & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - /* component is master */ - dif = ak4118_set_dai_fmt_master(ak4118, format); + switch (format & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + dif = ak4118_set_dai_fmt_provider(ak4118, format); break; - case SND_SOC_DAIFMT_CBS_CFS: - /*component is slave */ - dif = ak4118_set_dai_fmt_slave(ak4118, format); + case SND_SOC_DAIFMT_CBC_CFC: + dif = ak4118_set_dai_fmt_consumer(ak4118, format); break; default: ret = -ENOTSUPP; diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c index 29eb78702bf3..baa9ff5d0ce5 100644 --- a/sound/soc/codecs/ak4458.c +++ b/sound/soc/codecs/ak4458.c @@ -464,14 +464,14 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); int ret; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: /* Consumer Mode */ break; - case SND_SOC_DAIFMT_CBM_CFM: /* Master Mode is not supported */ - case SND_SOC_DAIFMT_CBS_CFM: - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBP_CFP: /* Provider Mode is not supported */ + case SND_SOC_DAIFMT_CBC_CFP: + case SND_SOC_DAIFMT_CBP_CFC: default: - dev_err(component->dev, "Master mode unsupported\n"); + dev_err(component->dev, "Clock provider mode unsupported\n"); return -EINVAL; } diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index c49c58eeb476..c284dcc5af76 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -392,13 +392,13 @@ static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) data = MCKO | PMPLL; /* use MCKO */ bcko = 0; - /* set master/slave audio interface */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + /* set clocking for audio interface */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: data |= MS; bcko = BCKO_64; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c index eb435235b5a3..e9d1251c4265 100644 --- a/sound/soc/codecs/ak4671.c +++ b/sound/soc/codecs/ak4671.c @@ -520,11 +520,11 @@ static int ak4671_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) /* set master/slave audio interface */ mode = snd_soc_component_read(component, AK4671_PLL_MODE_SELECT1); - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: mode |= AK4671_M_S; break; - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBP_CFC: mode &= ~(AK4671_M_S); break; default: diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c index 37d4600b6f2c..c94cfde3e4a8 100644 --- a/sound/soc/codecs/ak5558.c +++ b/sound/soc/codecs/ak5558.c @@ -198,13 +198,13 @@ static int ak5558_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) struct snd_soc_component *component = dai->component; u8 format; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: break; - case SND_SOC_DAIFMT_CBS_CFM: - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBC_CFP: + case SND_SOC_DAIFMT_CBP_CFC: default: dev_err(dai->dev, "Clock mode unsupported"); return -EINVAL; diff --git a/sound/soc/codecs/alc5623.c b/sound/soc/codecs/alc5623.c index 54f489837162..b10357a6d655 100644 --- a/sound/soc/codecs/alc5623.c +++ b/sound/soc/codecs/alc5623.c @@ -641,12 +641,12 @@ static int alc5623_set_dai_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_component *component = codec_dai->component; u16 iface = 0; - /* set master/slave audio interface */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + /* set audio interface clocking */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: iface = ALC5623_DAI_SDP_MASTER_MODE; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: iface = ALC5623_DAI_SDP_SLAVE_MODE; break; default: diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c index 79813882a955..6d7af3736a91 100644 --- a/sound/soc/codecs/alc5632.c +++ b/sound/soc/codecs/alc5632.c @@ -815,12 +815,12 @@ static int alc5632_set_dai_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_component *component = codec_dai->component; u16 iface = 0; - /* set master/slave audio interface */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + /* set audio interface clocking */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: iface = ALC5632_DAI_SDP_MASTER_MODE; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: iface = ALC5632_DAI_SDP_SLAVE_MODE; break; default: diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index 05bbacd0d174..598e09024e23 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1168,15 +1168,15 @@ static int cpcap_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai, /* * "HiFi Playback" should always be configured as - * SND_SOC_DAIFMT_CBM_CFM - codec clk & frm master + * SND_SOC_DAIFMT_CBP_CFP - codec clk & frm provider * SND_SOC_DAIFMT_I2S - I2S mode */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: val &= ~BIT(CPCAP_BIT_SMB_ST_DAC); break; default: - dev_err(dev, "HiFi dai fmt failed: CPCAP should be master"); + dev_err(dev, "HiFi dai fmt failed: CPCAP should be provider"); return -EINVAL; } @@ -1318,15 +1318,15 @@ static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, /* * "Voice Playback" and "Voice Capture" should always be - * configured as SND_SOC_DAIFMT_CBM_CFM - codec clk & frm - * master + * configured as SND_SOC_DAIFMT_CBP_CFP - codec clk & frm + * provider */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: val &= ~BIT(CPCAP_BIT_SMB_CDC); break; default: - dev_err(component->dev, "Voice dai fmt failed: CPCAP should be the master"); + dev_err(component->dev, "Voice dai fmt failed: CPCAP should be the provider"); val &= ~BIT(CPCAP_BIT_SMB_CDC); break; } diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c index a201d652aca2..9b92e1a0d1a3 100644 --- a/sound/soc/codecs/cros_ec_codec.c +++ b/sound/soc/codecs/cros_ec_codec.c @@ -283,8 +283,8 @@ static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) struct ec_param_ec_codec_i2s_rx p; enum ec_codec_i2s_rx_daifmt daifmt; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; diff --git a/sound/soc/codecs/cs35l41-i2c.c b/sound/soc/codecs/cs35l41-i2c.c new file mode 100644 index 000000000000..d5fa8d2c4a70 --- /dev/null +++ b/sound/soc/codecs/cs35l41-i2c.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs35l41-i2c.c -- CS35l41 I2C driver +// +// Copyright 2017-2021 Cirrus Logic, Inc. +// +// Author: David Rhodes <david.rhodes@cirrus.com> + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/cs35l41.h> +#include "cs35l41.h" + +static struct regmap_config cs35l41_regmap_i2c = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = CS35L41_REGSTRIDE, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = CS35L41_LASTREG, + .reg_defaults = cs35l41_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l41_reg), + .volatile_reg = cs35l41_volatile_reg, + .readable_reg = cs35l41_readable_reg, + .precious_reg = cs35l41_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct i2c_device_id cs35l41_id_i2c[] = { + { "cs35l40", 0 }, + { "cs35l41", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l41_id_i2c); + +static int cs35l41_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs35l41_private *cs35l41; + struct device *dev = &client->dev; + struct cs35l41_platform_data *pdata = dev_get_platdata(dev); + const struct regmap_config *regmap_config = &cs35l41_regmap_i2c; + int ret; + + cs35l41 = devm_kzalloc(dev, sizeof(struct cs35l41_private), GFP_KERNEL); + + if (!cs35l41) + return -ENOMEM; + + cs35l41->dev = dev; + cs35l41->irq = client->irq; + + i2c_set_clientdata(client, cs35l41); + cs35l41->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(cs35l41->regmap)) { + ret = PTR_ERR(cs35l41->regmap); + dev_err(cs35l41->dev, "Failed to allocate register map: %d\n", ret); + return ret; + } + + return cs35l41_probe(cs35l41, pdata); +} + +static int cs35l41_i2c_remove(struct i2c_client *client) +{ + struct cs35l41_private *cs35l41 = i2c_get_clientdata(client); + + cs35l41_remove(cs35l41); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id cs35l41_of_match[] = { + { .compatible = "cirrus,cs35l40" }, + { .compatible = "cirrus,cs35l41" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l41_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cs35l41_acpi_match[] = { + { "CSC3541", 0 }, /* Cirrus Logic PnP ID + part ID */ + {}, +}; +MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match); +#endif + +static struct i2c_driver cs35l41_i2c_driver = { + .driver = { + .name = "cs35l41", + .of_match_table = of_match_ptr(cs35l41_of_match), + .acpi_match_table = ACPI_PTR(cs35l41_acpi_match), + }, + .id_table = cs35l41_id_i2c, + .probe = cs35l41_i2c_probe, + .remove = cs35l41_i2c_remove, +}; + +module_i2c_driver(cs35l41_i2c_driver); + +MODULE_DESCRIPTION("I2C CS35L41 driver"); +MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <david.rhodes@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l41-spi.c b/sound/soc/codecs/cs35l41-spi.c new file mode 100644 index 000000000000..90a921f726c3 --- /dev/null +++ b/sound/soc/codecs/cs35l41-spi.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs35l41-spi.c -- CS35l41 SPI driver +// +// Copyright 2017-2021 Cirrus Logic, Inc. +// +// Author: David Rhodes <david.rhodes@cirrus.com> + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> + +#include <sound/cs35l41.h> +#include "cs35l41.h" + +static struct regmap_config cs35l41_regmap_spi = { + .reg_bits = 32, + .val_bits = 32, + .pad_bits = 16, + .reg_stride = CS35L41_REGSTRIDE, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = CS35L41_LASTREG, + .reg_defaults = cs35l41_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l41_reg), + .volatile_reg = cs35l41_volatile_reg, + .readable_reg = cs35l41_readable_reg, + .precious_reg = cs35l41_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct spi_device_id cs35l41_id_spi[] = { + { "cs35l40", 0 }, + { "cs35l41", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(spi, cs35l41_id_spi); + +static void cs35l41_spi_otp_setup(struct cs35l41_private *cs35l41, + bool is_pre_setup, unsigned int *freq) +{ + struct spi_device *spi; + u32 orig_spi_freq; + + spi = to_spi_device(cs35l41->dev); + + if (!spi) { + dev_err(cs35l41->dev, "%s: No SPI device\n", __func__); + return; + } + + if (is_pre_setup) { + orig_spi_freq = spi->max_speed_hz; + if (orig_spi_freq > CS35L41_SPI_MAX_FREQ_OTP) { + spi->max_speed_hz = CS35L41_SPI_MAX_FREQ_OTP; + spi_setup(spi); + } + *freq = orig_spi_freq; + } else { + if (spi->max_speed_hz != *freq) { + spi->max_speed_hz = *freq; + spi_setup(spi); + } + } +} + +static int cs35l41_spi_probe(struct spi_device *spi) +{ + const struct regmap_config *regmap_config = &cs35l41_regmap_spi; + struct cs35l41_platform_data *pdata = dev_get_platdata(&spi->dev); + struct cs35l41_private *cs35l41; + int ret; + + cs35l41 = devm_kzalloc(&spi->dev, sizeof(struct cs35l41_private), GFP_KERNEL); + if (!cs35l41) + return -ENOMEM; + + spi_set_drvdata(spi, cs35l41); + cs35l41->regmap = devm_regmap_init_spi(spi, regmap_config); + if (IS_ERR(cs35l41->regmap)) { + ret = PTR_ERR(cs35l41->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", ret); + return ret; + } + + cs35l41->dev = &spi->dev; + cs35l41->irq = spi->irq; + cs35l41->otp_setup = cs35l41_spi_otp_setup; + + return cs35l41_probe(cs35l41, pdata); +} + +static int cs35l41_spi_remove(struct spi_device *spi) +{ + struct cs35l41_private *cs35l41 = spi_get_drvdata(spi); + + cs35l41_remove(cs35l41); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id cs35l41_of_match[] = { + { .compatible = "cirrus,cs35l40" }, + { .compatible = "cirrus,cs35l41" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l41_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cs35l41_acpi_match[] = { + { "CSC3541", 0 }, /* Cirrus Logic PnP ID + part ID */ + {}, +}; +MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match); +#endif + +static struct spi_driver cs35l41_spi_driver = { + .driver = { + .name = "cs35l41", + .of_match_table = of_match_ptr(cs35l41_of_match), + .acpi_match_table = ACPI_PTR(cs35l41_acpi_match), + }, + .id_table = cs35l41_id_spi, + .probe = cs35l41_spi_probe, + .remove = cs35l41_spi_remove, +}; + +module_spi_driver(cs35l41_spi_driver); + +MODULE_DESCRIPTION("SPI CS35L41 driver"); +MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <david.rhodes@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l41-tables.c b/sound/soc/codecs/cs35l41-tables.c new file mode 100644 index 000000000000..964e530afa27 --- /dev/null +++ b/sound/soc/codecs/cs35l41-tables.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs35l41-tables.c -- CS35L41 ALSA SoC audio driver +// +// Copyright 2017-2021 Cirrus Logic, Inc. +// +// Author: David Rhodes <david.rhodes@cirrus.com> + +#include "cs35l41.h" + +const struct reg_default cs35l41_reg[CS35L41_MAX_CACHE_REG] = { + { CS35L41_PWR_CTRL1, 0x00000000 }, + { CS35L41_PWR_CTRL3, 0x01000010 }, + { CS35L41_GPIO_PAD_CONTROL, 0x00000000 }, + { CS35L41_SP_ENABLES, 0x00000000 }, + { CS35L41_SP_RATE_CTRL, 0x00000028 }, + { CS35L41_SP_FORMAT, 0x18180200 }, + { CS35L41_SP_HIZ_CTRL, 0x00000002 }, + { CS35L41_SP_FRAME_TX_SLOT, 0x03020100 }, + { CS35L41_SP_FRAME_RX_SLOT, 0x00000100 }, + { CS35L41_SP_TX_WL, 0x00000018 }, + { CS35L41_SP_RX_WL, 0x00000018 }, + { CS35L41_DAC_PCM1_SRC, 0x00000008 }, + { CS35L41_ASP_TX1_SRC, 0x00000018 }, + { CS35L41_ASP_TX2_SRC, 0x00000019 }, + { CS35L41_ASP_TX3_SRC, 0x00000020 }, + { CS35L41_ASP_TX4_SRC, 0x00000021 }, + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, + { CS35L41_DSP1_RX2_SRC, 0x00000009 }, + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, + { CS35L41_DSP1_RX5_SRC, 0x00000020 }, + { CS35L41_DSP1_RX6_SRC, 0x00000021 }, + { CS35L41_DSP1_RX7_SRC, 0x0000003A }, + { CS35L41_DSP1_RX8_SRC, 0x00000001 }, + { CS35L41_NGATE1_SRC, 0x00000008 }, + { CS35L41_NGATE2_SRC, 0x00000009 }, + { CS35L41_AMP_DIG_VOL_CTRL, 0x00008000 }, + { CS35L41_CLASSH_CFG, 0x000B0405 }, + { CS35L41_WKFET_CFG, 0x00000111 }, + { CS35L41_NG_CFG, 0x00000033 }, + { CS35L41_AMP_GAIN_CTRL, 0x00000273 }, + { CS35L41_GPIO1_CTRL1, 0xE1000001 }, + { CS35L41_GPIO2_CTRL1, 0xE1000001 }, + { CS35L41_MIXER_NGATE_CFG, 0x00000000 }, + { CS35L41_MIXER_NGATE_CH1_CFG, 0x00000303 }, + { CS35L41_MIXER_NGATE_CH2_CFG, 0x00000303 }, +}; + +bool cs35l41_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L41_DEVID: + case CS35L41_REVID: + case CS35L41_FABID: + case CS35L41_RELID: + case CS35L41_OTPID: + case CS35L41_TEST_KEY_CTL: + case CS35L41_USER_KEY_CTL: + case CS35L41_OTP_CTRL0: + case CS35L41_OTP_CTRL3: + case CS35L41_OTP_CTRL4: + case CS35L41_OTP_CTRL5: + case CS35L41_OTP_CTRL6: + case CS35L41_OTP_CTRL7: + case CS35L41_OTP_CTRL8: + case CS35L41_PWR_CTRL1: + case CS35L41_PWR_CTRL2: + case CS35L41_PWR_CTRL3: + case CS35L41_CTRL_OVRRIDE: + case CS35L41_AMP_OUT_MUTE: + case CS35L41_PROTECT_REL_ERR_IGN: + case CS35L41_GPIO_PAD_CONTROL: + case CS35L41_JTAG_CONTROL: + case CS35L41_PLL_CLK_CTRL: + case CS35L41_DSP_CLK_CTRL: + case CS35L41_GLOBAL_CLK_CTRL: + case CS35L41_DATA_FS_SEL: + case CS35L41_MDSYNC_EN: + case CS35L41_MDSYNC_TX_ID: + case CS35L41_MDSYNC_PWR_CTRL: + case CS35L41_MDSYNC_DATA_TX: + case CS35L41_MDSYNC_TX_STATUS: + case CS35L41_MDSYNC_DATA_RX: + case CS35L41_MDSYNC_RX_STATUS: + case CS35L41_MDSYNC_ERR_STATUS: + case CS35L41_MDSYNC_SYNC_PTE2: + case CS35L41_MDSYNC_SYNC_PTE3: + case CS35L41_MDSYNC_SYNC_MSM_STATUS: + case CS35L41_BSTCVRT_VCTRL1: + case CS35L41_BSTCVRT_VCTRL2: + case CS35L41_BSTCVRT_PEAK_CUR: + case CS35L41_BSTCVRT_SFT_RAMP: + case CS35L41_BSTCVRT_COEFF: + case CS35L41_BSTCVRT_SLOPE_LBST: + case CS35L41_BSTCVRT_SW_FREQ: + case CS35L41_BSTCVRT_DCM_CTRL: + case CS35L41_BSTCVRT_DCM_MODE_FORCE: + case CS35L41_BSTCVRT_OVERVOLT_CTRL: + case CS35L41_VI_VOL_POL: + case CS35L41_DTEMP_WARN_THLD: + case CS35L41_DTEMP_CFG: + case CS35L41_DTEMP_EN: + case CS35L41_VPVBST_FS_SEL: + case CS35L41_SP_ENABLES: + case CS35L41_SP_RATE_CTRL: + case CS35L41_SP_FORMAT: + case CS35L41_SP_HIZ_CTRL: + case CS35L41_SP_FRAME_TX_SLOT: + case CS35L41_SP_FRAME_RX_SLOT: + case CS35L41_SP_TX_WL: + case CS35L41_SP_RX_WL: + case CS35L41_DAC_PCM1_SRC: + case CS35L41_ASP_TX1_SRC: + case CS35L41_ASP_TX2_SRC: + case CS35L41_ASP_TX3_SRC: + case CS35L41_ASP_TX4_SRC: + case CS35L41_DSP1_RX1_SRC: + case CS35L41_DSP1_RX2_SRC: + case CS35L41_DSP1_RX3_SRC: + case CS35L41_DSP1_RX4_SRC: + case CS35L41_DSP1_RX5_SRC: + case CS35L41_DSP1_RX6_SRC: + case CS35L41_DSP1_RX7_SRC: + case CS35L41_DSP1_RX8_SRC: + case CS35L41_NGATE1_SRC: + case CS35L41_NGATE2_SRC: + case CS35L41_AMP_DIG_VOL_CTRL: + case CS35L41_VPBR_CFG: + case CS35L41_VBBR_CFG: + case CS35L41_VPBR_STATUS: + case CS35L41_VBBR_STATUS: + case CS35L41_OVERTEMP_CFG: + case CS35L41_AMP_ERR_VOL: + case CS35L41_VOL_STATUS_TO_DSP: + case CS35L41_CLASSH_CFG: + case CS35L41_WKFET_CFG: + case CS35L41_NG_CFG: + case CS35L41_AMP_GAIN_CTRL: + case CS35L41_DAC_MSM_CFG: + case CS35L41_IRQ1_CFG: + case CS35L41_IRQ1_STATUS: + case CS35L41_IRQ1_STATUS1: + case CS35L41_IRQ1_STATUS2: + case CS35L41_IRQ1_STATUS3: + case CS35L41_IRQ1_STATUS4: + case CS35L41_IRQ1_RAW_STATUS1: + case CS35L41_IRQ1_RAW_STATUS2: + case CS35L41_IRQ1_RAW_STATUS3: + case CS35L41_IRQ1_RAW_STATUS4: + case CS35L41_IRQ1_MASK1: + case CS35L41_IRQ1_MASK2: + case CS35L41_IRQ1_MASK3: + case CS35L41_IRQ1_MASK4: + case CS35L41_IRQ1_FRC1: + case CS35L41_IRQ1_FRC2: + case CS35L41_IRQ1_FRC3: + case CS35L41_IRQ1_FRC4: + case CS35L41_IRQ1_EDGE1: + case CS35L41_IRQ1_EDGE4: + case CS35L41_IRQ1_POL1: + case CS35L41_IRQ1_POL2: + case CS35L41_IRQ1_POL3: + case CS35L41_IRQ1_POL4: + case CS35L41_IRQ1_DB3: + case CS35L41_IRQ2_CFG: + case CS35L41_IRQ2_STATUS: + case CS35L41_IRQ2_STATUS1: + case CS35L41_IRQ2_STATUS2: + case CS35L41_IRQ2_STATUS3: + case CS35L41_IRQ2_STATUS4: + case CS35L41_IRQ2_RAW_STATUS1: + case CS35L41_IRQ2_RAW_STATUS2: + case CS35L41_IRQ2_RAW_STATUS3: + case CS35L41_IRQ2_RAW_STATUS4: + case CS35L41_IRQ2_MASK1: + case CS35L41_IRQ2_MASK2: + case CS35L41_IRQ2_MASK3: + case CS35L41_IRQ2_MASK4: + case CS35L41_IRQ2_FRC1: + case CS35L41_IRQ2_FRC2: + case CS35L41_IRQ2_FRC3: + case CS35L41_IRQ2_FRC4: + case CS35L41_IRQ2_EDGE1: + case CS35L41_IRQ2_EDGE4: + case CS35L41_IRQ2_POL1: + case CS35L41_IRQ2_POL2: + case CS35L41_IRQ2_POL3: + case CS35L41_IRQ2_POL4: + case CS35L41_IRQ2_DB3: + case CS35L41_GPIO_STATUS1: + case CS35L41_GPIO1_CTRL1: + case CS35L41_GPIO2_CTRL1: + case CS35L41_MIXER_NGATE_CFG: + case CS35L41_MIXER_NGATE_CH1_CFG: + case CS35L41_MIXER_NGATE_CH2_CFG: + case CS35L41_DSP_MBOX_1 ... CS35L41_DSP_VIRT2_MBOX_8: + case CS35L41_CLOCK_DETECT_1: + case CS35L41_DIE_STS1: + case CS35L41_DIE_STS2: + case CS35L41_TEMP_CAL1: + case CS35L41_TEMP_CAL2: + case CS35L41_OTP_TRIM_1: + case CS35L41_OTP_TRIM_2: + case CS35L41_OTP_TRIM_3: + case CS35L41_OTP_TRIM_4: + case CS35L41_OTP_TRIM_5: + case CS35L41_OTP_TRIM_6: + case CS35L41_OTP_TRIM_7: + case CS35L41_OTP_TRIM_8: + case CS35L41_OTP_TRIM_9: + case CS35L41_OTP_TRIM_10: + case CS35L41_OTP_TRIM_11: + case CS35L41_OTP_TRIM_12: + case CS35L41_OTP_TRIM_13: + case CS35L41_OTP_TRIM_14: + case CS35L41_OTP_TRIM_15: + case CS35L41_OTP_TRIM_16: + case CS35L41_OTP_TRIM_17: + case CS35L41_OTP_TRIM_18: + case CS35L41_OTP_TRIM_19: + case CS35L41_OTP_TRIM_20: + case CS35L41_OTP_TRIM_21: + case CS35L41_OTP_TRIM_22: + case CS35L41_OTP_TRIM_23: + case CS35L41_OTP_TRIM_24: + case CS35L41_OTP_TRIM_25: + case CS35L41_OTP_TRIM_26: + case CS35L41_OTP_TRIM_27: + case CS35L41_OTP_TRIM_28: + case CS35L41_OTP_TRIM_29: + case CS35L41_OTP_TRIM_30: + case CS35L41_OTP_TRIM_31: + case CS35L41_OTP_TRIM_32: + case CS35L41_OTP_TRIM_33: + case CS35L41_OTP_TRIM_34: + case CS35L41_OTP_TRIM_35: + case CS35L41_OTP_TRIM_36: + case CS35L41_OTP_MEM0 ... CS35L41_OTP_MEM31: + /*test regs*/ + case CS35L41_PLL_OVR: + case CS35L41_BST_TEST_DUTY: + case CS35L41_DIGPWM_IOCTRL: + return true; + default: + return false; + } +} + +bool cs35l41_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L41_OTP_MEM0 ... CS35L41_OTP_MEM31: + return true; + default: + return false; + } +} + +bool cs35l41_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L41_DEVID: + case CS35L41_SFT_RESET: + case CS35L41_FABID: + case CS35L41_REVID: + case CS35L41_DTEMP_EN: + case CS35L41_IRQ1_STATUS: + case CS35L41_IRQ1_STATUS1: + case CS35L41_IRQ1_STATUS2: + case CS35L41_IRQ1_STATUS3: + case CS35L41_IRQ1_STATUS4: + case CS35L41_IRQ1_RAW_STATUS1: + case CS35L41_IRQ1_RAW_STATUS2: + case CS35L41_IRQ1_RAW_STATUS3: + case CS35L41_IRQ1_RAW_STATUS4: + case CS35L41_IRQ1_FRC1: + case CS35L41_IRQ1_FRC2: + case CS35L41_IRQ1_FRC3: + case CS35L41_IRQ1_FRC4: + case CS35L41_IRQ1_EDGE1: + case CS35L41_IRQ1_EDGE4: + case CS35L41_IRQ1_POL1: + case CS35L41_IRQ1_POL2: + case CS35L41_IRQ1_POL3: + case CS35L41_IRQ1_POL4: + case CS35L41_IRQ1_DB3: + case CS35L41_IRQ2_STATUS: + case CS35L41_IRQ2_STATUS1: + case CS35L41_IRQ2_STATUS2: + case CS35L41_IRQ2_STATUS3: + case CS35L41_IRQ2_STATUS4: + case CS35L41_IRQ2_RAW_STATUS1: + case CS35L41_IRQ2_RAW_STATUS2: + case CS35L41_IRQ2_RAW_STATUS3: + case CS35L41_IRQ2_RAW_STATUS4: + case CS35L41_IRQ2_FRC1: + case CS35L41_IRQ2_FRC2: + case CS35L41_IRQ2_FRC3: + case CS35L41_IRQ2_FRC4: + case CS35L41_IRQ2_EDGE1: + case CS35L41_IRQ2_EDGE4: + case CS35L41_IRQ2_POL1: + case CS35L41_IRQ2_POL2: + case CS35L41_IRQ2_POL3: + case CS35L41_IRQ2_POL4: + case CS35L41_IRQ2_DB3: + case CS35L41_GPIO_STATUS1: + case CS35L41_OTP_TRIM_1: + case CS35L41_OTP_TRIM_2: + case CS35L41_OTP_TRIM_3: + case CS35L41_OTP_TRIM_4: + case CS35L41_OTP_TRIM_5: + case CS35L41_OTP_TRIM_6: + case CS35L41_OTP_TRIM_7: + case CS35L41_OTP_TRIM_8: + case CS35L41_OTP_TRIM_9: + case CS35L41_OTP_TRIM_10: + case CS35L41_OTP_TRIM_11: + case CS35L41_OTP_TRIM_12: + case CS35L41_OTP_TRIM_13: + case CS35L41_OTP_TRIM_14: + case CS35L41_OTP_TRIM_15: + case CS35L41_OTP_TRIM_16: + case CS35L41_OTP_TRIM_17: + case CS35L41_OTP_TRIM_18: + case CS35L41_OTP_TRIM_19: + case CS35L41_OTP_TRIM_20: + case CS35L41_OTP_TRIM_21: + case CS35L41_OTP_TRIM_22: + case CS35L41_OTP_TRIM_23: + case CS35L41_OTP_TRIM_24: + case CS35L41_OTP_TRIM_25: + case CS35L41_OTP_TRIM_26: + case CS35L41_OTP_TRIM_27: + case CS35L41_OTP_TRIM_28: + case CS35L41_OTP_TRIM_29: + case CS35L41_OTP_TRIM_30: + case CS35L41_OTP_TRIM_31: + case CS35L41_OTP_TRIM_32: + case CS35L41_OTP_TRIM_33: + case CS35L41_OTP_TRIM_34: + case CS35L41_OTP_TRIM_35: + case CS35L41_OTP_TRIM_36: + case CS35L41_OTP_MEM0 ... CS35L41_OTP_MEM31: + return true; + default: + return false; + } +} + +static const struct cs35l41_otp_packed_element_t otp_map_1[CS35L41_NUM_OTP_ELEM] = { + /* addr shift size */ + { 0x00002030, 0, 4 }, /*TRIM_OSC_FREQ_TRIM*/ + { 0x00002030, 7, 1 }, /*TRIM_OSC_TRIM_DONE*/ + { 0x0000208c, 24, 6 }, /*TST_DIGREG_VREF_TRIM*/ + { 0x00002090, 14, 4 }, /*TST_REF_TRIM*/ + { 0x00002090, 10, 4 }, /*TST_REF_TEMPCO_TRIM*/ + { 0x0000300C, 11, 4 }, /*PLL_LDOA_TST_VREF_TRIM*/ + { 0x0000394C, 23, 2 }, /*BST_ATEST_CM_VOFF*/ + { 0x00003950, 0, 7 }, /*BST_ATRIM_IADC_OFFSET*/ + { 0x00003950, 8, 7 }, /*BST_ATRIM_IADC_GAIN1*/ + { 0x00003950, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET1*/ + { 0x00003950, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN1*/ + { 0x00003954, 0, 7 }, /*BST_ATRIM_IADC_OFFSET2*/ + { 0x00003954, 8, 7 }, /*BST_ATRIM_IADC_GAIN2*/ + { 0x00003954, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET2*/ + { 0x00003954, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN2*/ + { 0x00003958, 0, 7 }, /*BST_ATRIM_IADC_OFFSET3*/ + { 0x00003958, 8, 7 }, /*BST_ATRIM_IADC_GAIN3*/ + { 0x00003958, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET3*/ + { 0x00003958, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN3*/ + { 0x0000395C, 0, 7 }, /*BST_ATRIM_IADC_OFFSET4*/ + { 0x0000395C, 8, 7 }, /*BST_ATRIM_IADC_GAIN4*/ + { 0x0000395C, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET4*/ + { 0x0000395C, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN4*/ + { 0x0000416C, 0, 8 }, /*VMON_GAIN_OTP_VAL*/ + { 0x00004160, 0, 7 }, /*VMON_OFFSET_OTP_VAL*/ + { 0x0000416C, 8, 8 }, /*IMON_GAIN_OTP_VAL*/ + { 0x00004160, 16, 10 }, /*IMON_OFFSET_OTP_VAL*/ + { 0x0000416C, 16, 12 }, /*VMON_CM_GAIN_OTP_VAL*/ + { 0x0000416C, 28, 1 }, /*VMON_CM_GAIN_SIGN_OTP_VAL*/ + { 0x00004170, 0, 6 }, /*IMON_CAL_TEMPCO_OTP_VAL*/ + { 0x00004170, 6, 1 }, /*IMON_CAL_TEMPCO_SIGN_OTP*/ + { 0x00004170, 8, 6 }, /*IMON_CAL_TEMPCO2_OTP_VAL*/ + { 0x00004170, 14, 1 }, /*IMON_CAL_TEMPCO2_DN_UPB_OTP_VAL*/ + { 0x00004170, 16, 9 }, /*IMON_CAL_TEMPCO_TBASE_OTP_VAL*/ + { 0x00004360, 0, 5 }, /*TEMP_GAIN_OTP_VAL*/ + { 0x00004360, 6, 9 }, /*TEMP_OFFSET_OTP_VAL*/ + { 0x00004448, 0, 8 }, /*VP_SARADC_OFFSET*/ + { 0x00004448, 8, 8 }, /*VP_GAIN_INDEX*/ + { 0x00004448, 16, 8 }, /*VBST_SARADC_OFFSET*/ + { 0x00004448, 24, 8 }, /*VBST_GAIN_INDEX*/ + { 0x0000444C, 0, 3 }, /*ANA_SELINVREF*/ + { 0x00006E30, 0, 5 }, /*GAIN_ERR_COEFF_0*/ + { 0x00006E30, 8, 5 }, /*GAIN_ERR_COEFF_1*/ + { 0x00006E30, 16, 5 }, /*GAIN_ERR_COEFF_2*/ + { 0x00006E30, 24, 5 }, /*GAIN_ERR_COEFF_3*/ + { 0x00006E34, 0, 5 }, /*GAIN_ERR_COEFF_4*/ + { 0x00006E34, 8, 5 }, /*GAIN_ERR_COEFF_5*/ + { 0x00006E34, 16, 5 }, /*GAIN_ERR_COEFF_6*/ + { 0x00006E34, 24, 5 }, /*GAIN_ERR_COEFF_7*/ + { 0x00006E38, 0, 5 }, /*GAIN_ERR_COEFF_8*/ + { 0x00006E38, 8, 5 }, /*GAIN_ERR_COEFF_9*/ + { 0x00006E38, 16, 5 }, /*GAIN_ERR_COEFF_10*/ + { 0x00006E38, 24, 5 }, /*GAIN_ERR_COEFF_11*/ + { 0x00006E3C, 0, 5 }, /*GAIN_ERR_COEFF_12*/ + { 0x00006E3C, 8, 5 }, /*GAIN_ERR_COEFF_13*/ + { 0x00006E3C, 16, 5 }, /*GAIN_ERR_COEFF_14*/ + { 0x00006E3C, 24, 5 }, /*GAIN_ERR_COEFF_15*/ + { 0x00006E40, 0, 5 }, /*GAIN_ERR_COEFF_16*/ + { 0x00006E40, 8, 5 }, /*GAIN_ERR_COEFF_17*/ + { 0x00006E40, 16, 5 }, /*GAIN_ERR_COEFF_18*/ + { 0x00006E40, 24, 5 }, /*GAIN_ERR_COEFF_19*/ + { 0x00006E44, 0, 5 }, /*GAIN_ERR_COEFF_20*/ + { 0x00006E48, 0, 10 }, /*VOFF_GAIN_0*/ + { 0x00006E48, 10, 10 }, /*VOFF_GAIN_1*/ + { 0x00006E48, 20, 10 }, /*VOFF_GAIN_2*/ + { 0x00006E4C, 0, 10 }, /*VOFF_GAIN_3*/ + { 0x00006E4C, 10, 10 }, /*VOFF_GAIN_4*/ + { 0x00006E4C, 20, 10 }, /*VOFF_GAIN_5*/ + { 0x00006E50, 0, 10 }, /*VOFF_GAIN_6*/ + { 0x00006E50, 10, 10 }, /*VOFF_GAIN_7*/ + { 0x00006E50, 20, 10 }, /*VOFF_GAIN_8*/ + { 0x00006E54, 0, 10 }, /*VOFF_GAIN_9*/ + { 0x00006E54, 10, 10 }, /*VOFF_GAIN_10*/ + { 0x00006E54, 20, 10 }, /*VOFF_GAIN_11*/ + { 0x00006E58, 0, 10 }, /*VOFF_GAIN_12*/ + { 0x00006E58, 10, 10 }, /*VOFF_GAIN_13*/ + { 0x00006E58, 20, 10 }, /*VOFF_GAIN_14*/ + { 0x00006E5C, 0, 10 }, /*VOFF_GAIN_15*/ + { 0x00006E5C, 10, 10 }, /*VOFF_GAIN_16*/ + { 0x00006E5C, 20, 10 }, /*VOFF_GAIN_17*/ + { 0x00006E60, 0, 10 }, /*VOFF_GAIN_18*/ + { 0x00006E60, 10, 10 }, /*VOFF_GAIN_19*/ + { 0x00006E60, 20, 10 }, /*VOFF_GAIN_20*/ + { 0x00006E64, 0, 10 }, /*VOFF_INT1*/ + { 0x00007418, 7, 5 }, /*DS_SPK_INT1_CAP_TRIM*/ + { 0x0000741C, 0, 5 }, /*DS_SPK_INT2_CAP_TRIM*/ + { 0x0000741C, 11, 4 }, /*DS_SPK_LPF_CAP_TRIM*/ + { 0x0000741C, 19, 4 }, /*DS_SPK_QUAN_CAP_TRIM*/ + { 0x00007434, 17, 1 }, /*FORCE_CAL*/ + { 0x00007434, 18, 7 }, /*CAL_OVERRIDE*/ + { 0x00007068, 0, 9 }, /*MODIX*/ + { 0x0000410C, 7, 1 }, /*VIMON_DLY_NOT_COMB*/ + { 0x0000400C, 0, 7 }, /*VIMON_DLY*/ + { 0x00000000, 0, 1 }, /*extra bit*/ + { 0x00017040, 0, 8 }, /*X_COORDINATE*/ + { 0x00017040, 8, 8 }, /*Y_COORDINATE*/ + { 0x00017040, 16, 8 }, /*WAFER_ID*/ + { 0x00017040, 24, 8 }, /*DVS*/ + { 0x00017044, 0, 24 }, /*LOT_NUMBER*/ +}; + +static const struct cs35l41_otp_packed_element_t otp_map_2[CS35L41_NUM_OTP_ELEM] = { + /* addr shift size */ + { 0x00002030, 0, 4 }, /*TRIM_OSC_FREQ_TRIM*/ + { 0x00002030, 7, 1 }, /*TRIM_OSC_TRIM_DONE*/ + { 0x0000208c, 24, 6 }, /*TST_DIGREG_VREF_TRIM*/ + { 0x00002090, 14, 4 }, /*TST_REF_TRIM*/ + { 0x00002090, 10, 4 }, /*TST_REF_TEMPCO_TRIM*/ + { 0x0000300C, 11, 4 }, /*PLL_LDOA_TST_VREF_TRIM*/ + { 0x0000394C, 23, 2 }, /*BST_ATEST_CM_VOFF*/ + { 0x00003950, 0, 7 }, /*BST_ATRIM_IADC_OFFSET*/ + { 0x00003950, 8, 7 }, /*BST_ATRIM_IADC_GAIN1*/ + { 0x00003950, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET1*/ + { 0x00003950, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN1*/ + { 0x00003954, 0, 7 }, /*BST_ATRIM_IADC_OFFSET2*/ + { 0x00003954, 8, 7 }, /*BST_ATRIM_IADC_GAIN2*/ + { 0x00003954, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET2*/ + { 0x00003954, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN2*/ + { 0x00003958, 0, 7 }, /*BST_ATRIM_IADC_OFFSET3*/ + { 0x00003958, 8, 7 }, /*BST_ATRIM_IADC_GAIN3*/ + { 0x00003958, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET3*/ + { 0x00003958, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN3*/ + { 0x0000395C, 0, 7 }, /*BST_ATRIM_IADC_OFFSET4*/ + { 0x0000395C, 8, 7 }, /*BST_ATRIM_IADC_GAIN4*/ + { 0x0000395C, 16, 8 }, /*BST_ATRIM_IPKCOMP_OFFSET4*/ + { 0x0000395C, 24, 8 }, /*BST_ATRIM_IPKCOMP_GAIN4*/ + { 0x0000416C, 0, 8 }, /*VMON_GAIN_OTP_VAL*/ + { 0x00004160, 0, 7 }, /*VMON_OFFSET_OTP_VAL*/ + { 0x0000416C, 8, 8 }, /*IMON_GAIN_OTP_VAL*/ + { 0x00004160, 16, 10 }, /*IMON_OFFSET_OTP_VAL*/ + { 0x0000416C, 16, 12 }, /*VMON_CM_GAIN_OTP_VAL*/ + { 0x0000416C, 28, 1 }, /*VMON_CM_GAIN_SIGN_OTP_VAL*/ + { 0x00004170, 0, 6 }, /*IMON_CAL_TEMPCO_OTP_VAL*/ + { 0x00004170, 6, 1 }, /*IMON_CAL_TEMPCO_SIGN_OTP*/ + { 0x00004170, 8, 6 }, /*IMON_CAL_TEMPCO2_OTP_VAL*/ + { 0x00004170, 14, 1 }, /*IMON_CAL_TEMPCO2_DN_UPB_OTP_VAL*/ + { 0x00004170, 16, 9 }, /*IMON_CAL_TEMPCO_TBASE_OTP_VAL*/ + { 0x00004360, 0, 5 }, /*TEMP_GAIN_OTP_VAL*/ + { 0x00004360, 6, 9 }, /*TEMP_OFFSET_OTP_VAL*/ + { 0x00004448, 0, 8 }, /*VP_SARADC_OFFSET*/ + { 0x00004448, 8, 8 }, /*VP_GAIN_INDEX*/ + { 0x00004448, 16, 8 }, /*VBST_SARADC_OFFSET*/ + { 0x00004448, 24, 8 }, /*VBST_GAIN_INDEX*/ + { 0x0000444C, 0, 3 }, /*ANA_SELINVREF*/ + { 0x00006E30, 0, 5 }, /*GAIN_ERR_COEFF_0*/ + { 0x00006E30, 8, 5 }, /*GAIN_ERR_COEFF_1*/ + { 0x00006E30, 16, 5 }, /*GAIN_ERR_COEFF_2*/ + { 0x00006E30, 24, 5 }, /*GAIN_ERR_COEFF_3*/ + { 0x00006E34, 0, 5 }, /*GAIN_ERR_COEFF_4*/ + { 0x00006E34, 8, 5 }, /*GAIN_ERR_COEFF_5*/ + { 0x00006E34, 16, 5 }, /*GAIN_ERR_COEFF_6*/ + { 0x00006E34, 24, 5 }, /*GAIN_ERR_COEFF_7*/ + { 0x00006E38, 0, 5 }, /*GAIN_ERR_COEFF_8*/ + { 0x00006E38, 8, 5 }, /*GAIN_ERR_COEFF_9*/ + { 0x00006E38, 16, 5 }, /*GAIN_ERR_COEFF_10*/ + { 0x00006E38, 24, 5 }, /*GAIN_ERR_COEFF_11*/ + { 0x00006E3C, 0, 5 }, /*GAIN_ERR_COEFF_12*/ + { 0x00006E3C, 8, 5 }, /*GAIN_ERR_COEFF_13*/ + { 0x00006E3C, 16, 5 }, /*GAIN_ERR_COEFF_14*/ + { 0x00006E3C, 24, 5 }, /*GAIN_ERR_COEFF_15*/ + { 0x00006E40, 0, 5 }, /*GAIN_ERR_COEFF_16*/ + { 0x00006E40, 8, 5 }, /*GAIN_ERR_COEFF_17*/ + { 0x00006E40, 16, 5 }, /*GAIN_ERR_COEFF_18*/ + { 0x00006E40, 24, 5 }, /*GAIN_ERR_COEFF_19*/ + { 0x00006E44, 0, 5 }, /*GAIN_ERR_COEFF_20*/ + { 0x00006E48, 0, 10 }, /*VOFF_GAIN_0*/ + { 0x00006E48, 10, 10 }, /*VOFF_GAIN_1*/ + { 0x00006E48, 20, 10 }, /*VOFF_GAIN_2*/ + { 0x00006E4C, 0, 10 }, /*VOFF_GAIN_3*/ + { 0x00006E4C, 10, 10 }, /*VOFF_GAIN_4*/ + { 0x00006E4C, 20, 10 }, /*VOFF_GAIN_5*/ + { 0x00006E50, 0, 10 }, /*VOFF_GAIN_6*/ + { 0x00006E50, 10, 10 }, /*VOFF_GAIN_7*/ + { 0x00006E50, 20, 10 }, /*VOFF_GAIN_8*/ + { 0x00006E54, 0, 10 }, /*VOFF_GAIN_9*/ + { 0x00006E54, 10, 10 }, /*VOFF_GAIN_10*/ + { 0x00006E54, 20, 10 }, /*VOFF_GAIN_11*/ + { 0x00006E58, 0, 10 }, /*VOFF_GAIN_12*/ + { 0x00006E58, 10, 10 }, /*VOFF_GAIN_13*/ + { 0x00006E58, 20, 10 }, /*VOFF_GAIN_14*/ + { 0x00006E5C, 0, 10 }, /*VOFF_GAIN_15*/ + { 0x00006E5C, 10, 10 }, /*VOFF_GAIN_16*/ + { 0x00006E5C, 20, 10 }, /*VOFF_GAIN_17*/ + { 0x00006E60, 0, 10 }, /*VOFF_GAIN_18*/ + { 0x00006E60, 10, 10 }, /*VOFF_GAIN_19*/ + { 0x00006E60, 20, 10 }, /*VOFF_GAIN_20*/ + { 0x00006E64, 0, 10 }, /*VOFF_INT1*/ + { 0x00007418, 7, 5 }, /*DS_SPK_INT1_CAP_TRIM*/ + { 0x0000741C, 0, 5 }, /*DS_SPK_INT2_CAP_TRIM*/ + { 0x0000741C, 11, 4 }, /*DS_SPK_LPF_CAP_TRIM*/ + { 0x0000741C, 19, 4 }, /*DS_SPK_QUAN_CAP_TRIM*/ + { 0x00007434, 17, 1 }, /*FORCE_CAL*/ + { 0x00007434, 18, 7 }, /*CAL_OVERRIDE*/ + { 0x00007068, 0, 9 }, /*MODIX*/ + { 0x0000410C, 7, 1 }, /*VIMON_DLY_NOT_COMB*/ + { 0x0000400C, 0, 7 }, /*VIMON_DLY*/ + { 0x00004000, 11, 1 }, /*VMON_POL*/ + { 0x00017040, 0, 8 }, /*X_COORDINATE*/ + { 0x00017040, 8, 8 }, /*Y_COORDINATE*/ + { 0x00017040, 16, 8 }, /*WAFER_ID*/ + { 0x00017040, 24, 8 }, /*DVS*/ + { 0x00017044, 0, 24 }, /*LOT_NUMBER*/ +}; + +const struct cs35l41_otp_map_element_t cs35l41_otp_map_map[CS35L41_NUM_OTP_MAPS] = { + { + .id = 0x01, + .map = otp_map_1, + .num_elements = CS35L41_NUM_OTP_ELEM, + .bit_offset = 16, + .word_offset = 2, + }, + { + .id = 0x02, + .map = otp_map_2, + .num_elements = CS35L41_NUM_OTP_ELEM, + .bit_offset = 16, + .word_offset = 2, + }, + { + .id = 0x03, + .map = otp_map_2, + .num_elements = CS35L41_NUM_OTP_ELEM, + .bit_offset = 16, + .word_offset = 2, + }, + { + .id = 0x06, + .map = otp_map_2, + .num_elements = CS35L41_NUM_OTP_ELEM, + .bit_offset = 16, + .word_offset = 2, + }, + { + .id = 0x08, + .map = otp_map_1, + .num_elements = CS35L41_NUM_OTP_ELEM, + .bit_offset = 16, + .word_offset = 2, + }, +}; diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c new file mode 100644 index 000000000000..94ed21d7676f --- /dev/null +++ b/sound/soc/codecs/cs35l41.c @@ -0,0 +1,1445 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs35l41.c -- CS35l41 ALSA SoC audio driver +// +// Copyright 2017-2021 Cirrus Logic, Inc. +// +// Author: David Rhodes <david.rhodes@cirrus.com> + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of_device.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +#include "cs35l41.h" + +static const char * const cs35l41_supplies[CS35L41_NUM_SUPPLIES] = { + "VA", + "VP", +}; + +struct cs35l41_pll_sysclk_config { + int freq; + int clk_cfg; +}; + +static const struct cs35l41_pll_sysclk_config cs35l41_pll_sysclk[] = { + { 32768, 0x00 }, + { 8000, 0x01 }, + { 11025, 0x02 }, + { 12000, 0x03 }, + { 16000, 0x04 }, + { 22050, 0x05 }, + { 24000, 0x06 }, + { 32000, 0x07 }, + { 44100, 0x08 }, + { 48000, 0x09 }, + { 88200, 0x0A }, + { 96000, 0x0B }, + { 128000, 0x0C }, + { 176400, 0x0D }, + { 192000, 0x0E }, + { 256000, 0x0F }, + { 352800, 0x10 }, + { 384000, 0x11 }, + { 512000, 0x12 }, + { 705600, 0x13 }, + { 750000, 0x14 }, + { 768000, 0x15 }, + { 1000000, 0x16 }, + { 1024000, 0x17 }, + { 1200000, 0x18 }, + { 1411200, 0x19 }, + { 1500000, 0x1A }, + { 1536000, 0x1B }, + { 2000000, 0x1C }, + { 2048000, 0x1D }, + { 2400000, 0x1E }, + { 2822400, 0x1F }, + { 3000000, 0x20 }, + { 3072000, 0x21 }, + { 3200000, 0x22 }, + { 4000000, 0x23 }, + { 4096000, 0x24 }, + { 4800000, 0x25 }, + { 5644800, 0x26 }, + { 6000000, 0x27 }, + { 6144000, 0x28 }, + { 6250000, 0x29 }, + { 6400000, 0x2A }, + { 6500000, 0x2B }, + { 6750000, 0x2C }, + { 7526400, 0x2D }, + { 8000000, 0x2E }, + { 8192000, 0x2F }, + { 9600000, 0x30 }, + { 11289600, 0x31 }, + { 12000000, 0x32 }, + { 12288000, 0x33 }, + { 12500000, 0x34 }, + { 12800000, 0x35 }, + { 13000000, 0x36 }, + { 13500000, 0x37 }, + { 19200000, 0x38 }, + { 22579200, 0x39 }, + { 24000000, 0x3A }, + { 24576000, 0x3B }, + { 25000000, 0x3C }, + { 25600000, 0x3D }, + { 26000000, 0x3E }, + { 27000000, 0x3F }, +}; + +struct cs35l41_fs_mon_config { + int freq; + unsigned int fs1; + unsigned int fs2; +}; + +static const struct cs35l41_fs_mon_config cs35l41_fs_mon[] = { + { 32768, 2254, 3754 }, + { 8000, 9220, 15364 }, + { 11025, 6148, 10244 }, + { 12000, 6148, 10244 }, + { 16000, 4612, 7684 }, + { 22050, 3076, 5124 }, + { 24000, 3076, 5124 }, + { 32000, 2308, 3844 }, + { 44100, 1540, 2564 }, + { 48000, 1540, 2564 }, + { 88200, 772, 1284 }, + { 96000, 772, 1284 }, + { 128000, 580, 964 }, + { 176400, 388, 644 }, + { 192000, 388, 644 }, + { 256000, 292, 484 }, + { 352800, 196, 324 }, + { 384000, 196, 324 }, + { 512000, 148, 244 }, + { 705600, 100, 164 }, + { 750000, 100, 164 }, + { 768000, 100, 164 }, + { 1000000, 76, 124 }, + { 1024000, 76, 124 }, + { 1200000, 64, 104 }, + { 1411200, 52, 84 }, + { 1500000, 52, 84 }, + { 1536000, 52, 84 }, + { 2000000, 40, 64 }, + { 2048000, 40, 64 }, + { 2400000, 34, 54 }, + { 2822400, 28, 44 }, + { 3000000, 28, 44 }, + { 3072000, 28, 44 }, + { 3200000, 27, 42 }, + { 4000000, 22, 34 }, + { 4096000, 22, 34 }, + { 4800000, 19, 29 }, + { 5644800, 16, 24 }, + { 6000000, 16, 24 }, + { 6144000, 16, 24 }, +}; + +static const unsigned char cs35l41_bst_k1_table[4][5] = { + { 0x24, 0x32, 0x32, 0x4F, 0x57 }, + { 0x24, 0x32, 0x32, 0x4F, 0x57 }, + { 0x40, 0x32, 0x32, 0x4F, 0x57 }, + { 0x40, 0x32, 0x32, 0x4F, 0x57 } +}; + +static const unsigned char cs35l41_bst_k2_table[4][5] = { + { 0x24, 0x49, 0x66, 0xA3, 0xEA }, + { 0x24, 0x49, 0x66, 0xA3, 0xEA }, + { 0x48, 0x49, 0x66, 0xA3, 0xEA }, + { 0x48, 0x49, 0x66, 0xA3, 0xEA } +}; + +static const unsigned char cs35l41_bst_slope_table[4] = { + 0x75, 0x6B, 0x3B, 0x28 +}; + +static int cs35l41_get_fs_mon_config_index(int freq) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l41_fs_mon); i++) { + if (cs35l41_fs_mon[i].freq == freq) + return i; + } + + return -EINVAL; +} + +static const DECLARE_TLV_DB_RANGE(dig_vol_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + 1, 913, TLV_DB_MINMAX_ITEM(-10200, 1200)); +static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1); + +static const struct snd_kcontrol_new dre_ctrl = + SOC_DAPM_SINGLE("Switch", CS35L41_PWR_CTRL3, 20, 1, 0); + +static const char * const cs35l41_pcm_sftramp_text[] = { + "Off", ".5ms", "1ms", "2ms", "4ms", "8ms", "15ms", "30ms" +}; + +static SOC_ENUM_SINGLE_DECL(pcm_sft_ramp, + CS35L41_AMP_DIG_VOL_CTRL, 0, + cs35l41_pcm_sftramp_text); + +static const char * const cs35l41_pcm_source_texts[] = {"ASP", "DSP"}; +static const unsigned int cs35l41_pcm_source_values[] = {0x08, 0x32}; +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_pcm_source_enum, + CS35L41_DAC_PCM1_SRC, + 0, CS35L41_ASP_SOURCE_MASK, + cs35l41_pcm_source_texts, + cs35l41_pcm_source_values); + +static const struct snd_kcontrol_new pcm_source_mux = + SOC_DAPM_ENUM("PCM Source", cs35l41_pcm_source_enum); + +static const char * const cs35l41_tx_input_texts[] = { + "Zero", "ASPRX1", "ASPRX2", "VMON", "IMON", + "VPMON", "VBSTMON", "DSPTX1", "DSPTX2" +}; + +static const unsigned int cs35l41_tx_input_values[] = { + 0x00, CS35L41_INPUT_SRC_ASPRX1, CS35L41_INPUT_SRC_ASPRX2, + CS35L41_INPUT_SRC_VMON, CS35L41_INPUT_SRC_IMON, CS35L41_INPUT_SRC_VPMON, + CS35L41_INPUT_SRC_VBSTMON, CS35L41_INPUT_DSP_TX1, CS35L41_INPUT_DSP_TX2 +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx1_enum, + CS35L41_ASP_TX1_SRC, + 0, CS35L41_ASP_SOURCE_MASK, + cs35l41_tx_input_texts, + cs35l41_tx_input_values); + +static const struct snd_kcontrol_new asp_tx1_mux = + SOC_DAPM_ENUM("ASPTX1 SRC", cs35l41_asptx1_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx2_enum, + CS35L41_ASP_TX2_SRC, + 0, CS35L41_ASP_SOURCE_MASK, + cs35l41_tx_input_texts, + cs35l41_tx_input_values); + +static const struct snd_kcontrol_new asp_tx2_mux = + SOC_DAPM_ENUM("ASPTX2 SRC", cs35l41_asptx2_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx3_enum, + CS35L41_ASP_TX3_SRC, + 0, CS35L41_ASP_SOURCE_MASK, + cs35l41_tx_input_texts, + cs35l41_tx_input_values); + +static const struct snd_kcontrol_new asp_tx3_mux = + SOC_DAPM_ENUM("ASPTX3 SRC", cs35l41_asptx3_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx4_enum, + CS35L41_ASP_TX4_SRC, + 0, CS35L41_ASP_SOURCE_MASK, + cs35l41_tx_input_texts, + cs35l41_tx_input_values); + +static const struct snd_kcontrol_new asp_tx4_mux = + SOC_DAPM_ENUM("ASPTX4 SRC", cs35l41_asptx4_enum); + +static const struct snd_kcontrol_new cs35l41_aud_controls[] = { + SOC_SINGLE_SX_TLV("Digital PCM Volume", CS35L41_AMP_DIG_VOL_CTRL, + 3, 0x4CF, 0x391, dig_vol_tlv), + SOC_SINGLE_TLV("Analog PCM Volume", CS35L41_AMP_GAIN_CTRL, 5, 0x14, 0, + amp_gain_tlv), + SOC_ENUM("PCM Soft Ramp", pcm_sft_ramp), + SOC_SINGLE("HW Noise Gate Enable", CS35L41_NG_CFG, 8, 63, 0), + SOC_SINGLE("HW Noise Gate Delay", CS35L41_NG_CFG, 4, 7, 0), + SOC_SINGLE("HW Noise Gate Threshold", CS35L41_NG_CFG, 0, 7, 0), + SOC_SINGLE("Aux Noise Gate CH1 Enable", + CS35L41_MIXER_NGATE_CH1_CFG, 16, 1, 0), + SOC_SINGLE("Aux Noise Gate CH1 Entry Delay", + CS35L41_MIXER_NGATE_CH1_CFG, 8, 15, 0), + SOC_SINGLE("Aux Noise Gate CH1 Threshold", + CS35L41_MIXER_NGATE_CH1_CFG, 0, 7, 0), + SOC_SINGLE("Aux Noise Gate CH2 Entry Delay", + CS35L41_MIXER_NGATE_CH2_CFG, 8, 15, 0), + SOC_SINGLE("Aux Noise Gate CH2 Enable", + CS35L41_MIXER_NGATE_CH2_CFG, 16, 1, 0), + SOC_SINGLE("Aux Noise Gate CH2 Threshold", + CS35L41_MIXER_NGATE_CH2_CFG, 0, 7, 0), + SOC_SINGLE("SCLK Force", CS35L41_SP_FORMAT, CS35L41_SCLK_FRC_SHIFT, 1, 0), + SOC_SINGLE("LRCLK Force", CS35L41_SP_FORMAT, CS35L41_LRCLK_FRC_SHIFT, 1, 0), + SOC_SINGLE("Invert Class D", CS35L41_AMP_DIG_VOL_CTRL, + CS35L41_AMP_INV_PCM_SHIFT, 1, 0), + SOC_SINGLE("Amp Gain ZC", CS35L41_AMP_GAIN_CTRL, + CS35L41_AMP_GAIN_ZC_SHIFT, 1, 0), +}; + +static const struct cs35l41_otp_map_element_t *cs35l41_find_otp_map(u32 otp_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l41_otp_map_map); i++) { + if (cs35l41_otp_map_map[i].id == otp_id) + return &cs35l41_otp_map_map[i]; + } + + return NULL; +} + +static int cs35l41_otp_unpack(void *data) +{ + const struct cs35l41_otp_map_element_t *otp_map_match; + const struct cs35l41_otp_packed_element_t *otp_map; + struct cs35l41_private *cs35l41 = data; + int bit_offset, word_offset, ret, i; + unsigned int orig_spi_freq; + unsigned int bit_sum = 8; + u32 otp_val, otp_id_reg; + u32 *otp_mem; + + otp_mem = kmalloc_array(CS35L41_OTP_SIZE_WORDS, sizeof(*otp_mem), GFP_KERNEL); + if (!otp_mem) + return -ENOMEM; + + ret = regmap_read(cs35l41->regmap, CS35L41_OTPID, &otp_id_reg); + if (ret < 0) { + dev_err(cs35l41->dev, "Read OTP ID failed: %d\n", ret); + goto err_otp_unpack; + } + + otp_map_match = cs35l41_find_otp_map(otp_id_reg); + + if (!otp_map_match) { + dev_err(cs35l41->dev, "OTP Map matching ID %d not found\n", + otp_id_reg); + ret = -EINVAL; + goto err_otp_unpack; + } + + if (cs35l41->otp_setup) + cs35l41->otp_setup(cs35l41, true, &orig_spi_freq); + + ret = regmap_bulk_read(cs35l41->regmap, CS35L41_OTP_MEM0, otp_mem, + CS35L41_OTP_SIZE_WORDS); + if (ret < 0) { + dev_err(cs35l41->dev, "Read OTP Mem failed: %d\n", ret); + goto err_otp_unpack; + } + + if (cs35l41->otp_setup) + cs35l41->otp_setup(cs35l41, false, &orig_spi_freq); + + otp_map = otp_map_match->map; + + bit_offset = otp_map_match->bit_offset; + word_offset = otp_map_match->word_offset; + + ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000055); + if (ret < 0) { + dev_err(cs35l41->dev, "Write Unlock key failed 1/2: %d\n", ret); + goto err_otp_unpack; + } + ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000AA); + if (ret < 0) { + dev_err(cs35l41->dev, "Write Unlock key failed 2/2: %d\n", ret); + goto err_otp_unpack; + } + + for (i = 0; i < otp_map_match->num_elements; i++) { + dev_dbg(cs35l41->dev, + "bitoffset= %d, word_offset=%d, bit_sum mod 32=%d\n", + bit_offset, word_offset, bit_sum % 32); + if (bit_offset + otp_map[i].size - 1 >= 32) { + otp_val = (otp_mem[word_offset] & + GENMASK(31, bit_offset)) >> + bit_offset; + otp_val |= (otp_mem[++word_offset] & + GENMASK(bit_offset + + otp_map[i].size - 33, 0)) << + (32 - bit_offset); + bit_offset += otp_map[i].size - 32; + } else { + otp_val = (otp_mem[word_offset] & + GENMASK(bit_offset + otp_map[i].size - 1, + bit_offset)) >> bit_offset; + bit_offset += otp_map[i].size; + } + bit_sum += otp_map[i].size; + + if (bit_offset == 32) { + bit_offset = 0; + word_offset++; + } + + if (otp_map[i].reg != 0) { + ret = regmap_update_bits(cs35l41->regmap, + otp_map[i].reg, + GENMASK(otp_map[i].shift + + otp_map[i].size - 1, + otp_map[i].shift), + otp_val << otp_map[i].shift); + if (ret < 0) { + dev_err(cs35l41->dev, "Write OTP val failed: %d\n", + ret); + goto err_otp_unpack; + } + } + } + + ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000CC); + if (ret < 0) { + dev_err(cs35l41->dev, "Write Lock key failed 1/2: %d\n", ret); + goto err_otp_unpack; + } + ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000033); + if (ret < 0) { + dev_err(cs35l41->dev, "Write Lock key failed 2/2: %d\n", ret); + goto err_otp_unpack; + } + ret = 0; + +err_otp_unpack: + kfree(otp_mem); + return ret; +} + +static irqreturn_t cs35l41_irq(int irq, void *data) +{ + struct cs35l41_private *cs35l41 = data; + unsigned int status[4] = { 0, 0, 0, 0 }; + unsigned int masks[4] = { 0, 0, 0, 0 }; + int ret = IRQ_NONE; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(status); i++) { + regmap_read(cs35l41->regmap, + CS35L41_IRQ1_STATUS1 + (i * CS35L41_REGSTRIDE), + &status[i]); + regmap_read(cs35l41->regmap, + CS35L41_IRQ1_MASK1 + (i * CS35L41_REGSTRIDE), + &masks[i]); + } + + /* Check to see if unmasked bits are active */ + if (!(status[0] & ~masks[0]) && !(status[1] & ~masks[1]) && + !(status[2] & ~masks[2]) && !(status[3] & ~masks[3])) + return IRQ_NONE; + + if (status[3] & CS35L41_OTP_BOOT_DONE) { + regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK4, + CS35L41_OTP_BOOT_DONE, CS35L41_OTP_BOOT_DONE); + } + + /* + * The following interrupts require a + * protection release cycle to get the + * speaker out of Safe-Mode. + */ + if (status[0] & CS35L41_AMP_SHORT_ERR) { + dev_crit_ratelimited(cs35l41->dev, "Amp short error\n"); + regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + CS35L41_AMP_SHORT_ERR); + regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_AMP_SHORT_ERR_RLS, + CS35L41_AMP_SHORT_ERR_RLS); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_AMP_SHORT_ERR_RLS, 0); + ret = IRQ_HANDLED; + } + + if (status[0] & CS35L41_TEMP_WARN) { + dev_crit_ratelimited(cs35l41->dev, "Over temperature warning\n"); + regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + CS35L41_TEMP_WARN); + regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_TEMP_WARN_ERR_RLS, + CS35L41_TEMP_WARN_ERR_RLS); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_TEMP_WARN_ERR_RLS, 0); + ret = IRQ_HANDLED; + } + + if (status[0] & CS35L41_TEMP_ERR) { + dev_crit_ratelimited(cs35l41->dev, "Over temperature error\n"); + regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + CS35L41_TEMP_ERR); + regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_TEMP_ERR_RLS, + CS35L41_TEMP_ERR_RLS); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_TEMP_ERR_RLS, 0); + ret = IRQ_HANDLED; + } + + if (status[0] & CS35L41_BST_OVP_ERR) { + dev_crit_ratelimited(cs35l41->dev, "VBST Over Voltage error\n"); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_BST_EN_MASK, 0); + regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + CS35L41_BST_OVP_ERR); + regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_BST_OVP_ERR_RLS, + CS35L41_BST_OVP_ERR_RLS); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_BST_OVP_ERR_RLS, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_BST_EN_MASK, + CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT); + ret = IRQ_HANDLED; + } + + if (status[0] & CS35L41_BST_DCM_UVP_ERR) { + dev_crit_ratelimited(cs35l41->dev, "DCM VBST Under Voltage Error\n"); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_BST_EN_MASK, 0); + regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + CS35L41_BST_DCM_UVP_ERR); + regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_BST_UVP_ERR_RLS, + CS35L41_BST_UVP_ERR_RLS); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_BST_UVP_ERR_RLS, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_BST_EN_MASK, + CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT); + ret = IRQ_HANDLED; + } + + if (status[0] & CS35L41_BST_SHORT_ERR) { + dev_crit_ratelimited(cs35l41->dev, "LBST error: powering off!\n"); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_BST_EN_MASK, 0); + regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + CS35L41_BST_SHORT_ERR); + regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_BST_SHORT_ERR_RLS, + CS35L41_BST_SHORT_ERR_RLS); + regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, + CS35L41_BST_SHORT_ERR_RLS, 0); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_BST_EN_MASK, + CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT); + ret = IRQ_HANDLED; + } + + return ret; +} + +static const struct reg_sequence cs35l41_pup_patch[] = { + { 0x00000040, 0x00000055 }, + { 0x00000040, 0x000000AA }, + { 0x00002084, 0x002F1AA0 }, + { 0x00000040, 0x000000CC }, + { 0x00000040, 0x00000033 }, +}; + +static const struct reg_sequence cs35l41_pdn_patch[] = { + { 0x00000040, 0x00000055 }, + { 0x00000040, 0x000000AA }, + { 0x00002084, 0x002F1AA3 }, + { 0x00000040, 0x000000CC }, + { 0x00000040, 0x00000033 }, +}; + +static int cs35l41_main_amp_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 cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); + unsigned int val; + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_multi_reg_write_bypassed(cs35l41->regmap, + cs35l41_pup_patch, + ARRAY_SIZE(cs35l41_pup_patch)); + + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL1, + CS35L41_GLOBAL_EN_MASK, + 1 << CS35L41_GLOBAL_EN_SHIFT); + + usleep_range(1000, 1100); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL1, + CS35L41_GLOBAL_EN_MASK, 0); + + ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + val, val & CS35L41_PDN_DONE_MASK, + 1000, 100000); + if (ret) + dev_warn(cs35l41->dev, "PDN failed: %d\n", ret); + + regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, + CS35L41_PDN_DONE_MASK); + + regmap_multi_reg_write_bypassed(cs35l41->regmap, + cs35l41_pdn_patch, + ARRAY_SIZE(cs35l41_pdn_patch)); + break; + default: + dev_err(cs35l41->dev, "Invalid event = 0x%x\n", event); + ret = -EINVAL; + } + + return ret; +} + +static const struct snd_soc_dapm_widget cs35l41_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("SPK"), + + SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, CS35L41_SP_ENABLES, 16, 0), + SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, CS35L41_SP_ENABLES, 17, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX1", NULL, 0, CS35L41_SP_ENABLES, 0, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX2", NULL, 0, CS35L41_SP_ENABLES, 1, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX3", NULL, 0, CS35L41_SP_ENABLES, 2, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX4", NULL, 0, CS35L41_SP_ENABLES, 3, 0), + + SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L41_PWR_CTRL2, 12, 0), + SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L41_PWR_CTRL2, 13, 0), + SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L41_PWR_CTRL2, 8, 0), + SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L41_PWR_CTRL2, 9, 0), + SND_SOC_DAPM_ADC("TEMPMON ADC", NULL, CS35L41_PWR_CTRL2, 10, 0), + SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L41_PWR_CTRL3, 4, 0), + + SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L41_PWR_CTRL2, 0, 0, NULL, 0, + cs35l41_main_amp_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_INPUT("VP"), + SND_SOC_DAPM_INPUT("VBST"), + SND_SOC_DAPM_INPUT("ISENSE"), + SND_SOC_DAPM_INPUT("VSENSE"), + SND_SOC_DAPM_INPUT("TEMP"), + + SND_SOC_DAPM_MUX("ASP TX1 Source", SND_SOC_NOPM, 0, 0, &asp_tx1_mux), + SND_SOC_DAPM_MUX("ASP TX2 Source", SND_SOC_NOPM, 0, 0, &asp_tx2_mux), + SND_SOC_DAPM_MUX("ASP TX3 Source", SND_SOC_NOPM, 0, 0, &asp_tx3_mux), + SND_SOC_DAPM_MUX("ASP TX4 Source", SND_SOC_NOPM, 0, 0, &asp_tx4_mux), + SND_SOC_DAPM_MUX("PCM Source", SND_SOC_NOPM, 0, 0, &pcm_source_mux), + SND_SOC_DAPM_SWITCH("DRE", SND_SOC_NOPM, 0, 0, &dre_ctrl), +}; + +static const struct snd_soc_dapm_route cs35l41_audio_map[] = { + {"ASP TX1 Source", "VMON", "VMON ADC"}, + {"ASP TX1 Source", "IMON", "IMON ADC"}, + {"ASP TX1 Source", "VPMON", "VPMON ADC"}, + {"ASP TX1 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX1 Source", "ASPRX1", "ASPRX1" }, + {"ASP TX1 Source", "ASPRX2", "ASPRX2" }, + {"ASP TX2 Source", "VMON", "VMON ADC"}, + {"ASP TX2 Source", "IMON", "IMON ADC"}, + {"ASP TX2 Source", "VPMON", "VPMON ADC"}, + {"ASP TX2 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX2 Source", "ASPRX1", "ASPRX1" }, + {"ASP TX2 Source", "ASPRX2", "ASPRX2" }, + {"ASP TX3 Source", "VMON", "VMON ADC"}, + {"ASP TX3 Source", "IMON", "IMON ADC"}, + {"ASP TX3 Source", "VPMON", "VPMON ADC"}, + {"ASP TX3 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX3 Source", "ASPRX1", "ASPRX1" }, + {"ASP TX3 Source", "ASPRX2", "ASPRX2" }, + {"ASP TX4 Source", "VMON", "VMON ADC"}, + {"ASP TX4 Source", "IMON", "IMON ADC"}, + {"ASP TX4 Source", "VPMON", "VPMON ADC"}, + {"ASP TX4 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX4 Source", "ASPRX1", "ASPRX1" }, + {"ASP TX4 Source", "ASPRX2", "ASPRX2" }, + {"ASPTX1", NULL, "ASP TX1 Source"}, + {"ASPTX2", NULL, "ASP TX2 Source"}, + {"ASPTX3", NULL, "ASP TX3 Source"}, + {"ASPTX4", NULL, "ASP TX4 Source"}, + {"AMP Capture", NULL, "ASPTX1"}, + {"AMP Capture", NULL, "ASPTX2"}, + {"AMP Capture", NULL, "ASPTX3"}, + {"AMP Capture", NULL, "ASPTX4"}, + + {"VMON ADC", NULL, "VSENSE"}, + {"IMON ADC", NULL, "ISENSE"}, + {"VPMON ADC", NULL, "VP"}, + {"TEMPMON ADC", NULL, "TEMP"}, + {"VBSTMON ADC", NULL, "VBST"}, + + {"ASPRX1", NULL, "AMP Playback"}, + {"ASPRX2", NULL, "AMP Playback"}, + {"DRE", "Switch", "CLASS H"}, + {"Main AMP", NULL, "CLASS H"}, + {"Main AMP", NULL, "DRE"}, + {"SPK", NULL, "Main AMP"}, + + {"PCM Source", "ASP", "ASPRX1"}, + {"CLASS H", NULL, "PCM Source"}, +}; + +static int cs35l41_set_channel_map(struct snd_soc_dai *dai, unsigned int tx_num, + unsigned int *tx_slot, unsigned int rx_num, + unsigned int *rx_slot) +{ + struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); + unsigned int val, mask; + int i; + + if (tx_num > 4 || rx_num > 2) + return -EINVAL; + + val = 0; + mask = 0; + for (i = 0; i < rx_num; i++) { + dev_dbg(cs35l41->dev, "rx slot %d position = %d\n", i, rx_slot[i]); + val |= rx_slot[i] << (i * 8); + mask |= 0x3F << (i * 8); + } + regmap_update_bits(cs35l41->regmap, CS35L41_SP_FRAME_RX_SLOT, mask, val); + + val = 0; + mask = 0; + for (i = 0; i < tx_num; i++) { + dev_dbg(cs35l41->dev, "tx slot %d position = %d\n", i, tx_slot[i]); + val |= tx_slot[i] << (i * 8); + mask |= 0x3F << (i * 8); + } + regmap_update_bits(cs35l41->regmap, CS35L41_SP_FRAME_TX_SLOT, mask, val); + + return 0; +} + +static int cs35l41_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); + unsigned int daifmt = 0; + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + daifmt |= CS35L41_SCLK_MSTR_MASK | CS35L41_LRCLK_MSTR_MASK; + break; + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: + dev_warn(cs35l41->dev, "Mixed provider/consumer mode unsupported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + break; + case SND_SOC_DAIFMT_I2S: + daifmt |= 2 << CS35L41_ASP_FMT_SHIFT; + break; + default: + dev_warn(cs35l41->dev, "Invalid or unsupported DAI format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + daifmt |= CS35L41_LRCLK_INV_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + daifmt |= CS35L41_SCLK_INV_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + daifmt |= CS35L41_LRCLK_INV_MASK | CS35L41_SCLK_INV_MASK; + break; + case SND_SOC_DAIFMT_NB_NF: + break; + default: + dev_warn(cs35l41->dev, "Invalid DAI clock INV\n"); + return -EINVAL; + } + + return regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT, + CS35L41_SCLK_MSTR_MASK | CS35L41_LRCLK_MSTR_MASK | + CS35L41_ASP_FMT_MASK | CS35L41_LRCLK_INV_MASK | + CS35L41_SCLK_INV_MASK, daifmt); +} + +struct cs35l41_global_fs_config { + int rate; + int fs_cfg; +}; + +static const struct cs35l41_global_fs_config cs35l41_fs_rates[] = { + { 12000, 0x01 }, + { 24000, 0x02 }, + { 48000, 0x03 }, + { 96000, 0x04 }, + { 192000, 0x05 }, + { 11025, 0x09 }, + { 22050, 0x0A }, + { 44100, 0x0B }, + { 88200, 0x0C }, + { 176400, 0x0D }, + { 8000, 0x11 }, + { 16000, 0x12 }, + { 32000, 0x13 }, +}; + +static int cs35l41_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); + unsigned int rate = params_rate(params); + u8 asp_wl; + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l41_fs_rates); i++) { + if (rate == cs35l41_fs_rates[i].rate) + break; + } + + if (i >= ARRAY_SIZE(cs35l41_fs_rates)) { + dev_err(cs35l41->dev, "Unsupported rate: %u\n", rate); + return -EINVAL; + } + + asp_wl = params_width(params); + + if (i < ARRAY_SIZE(cs35l41_fs_rates)) + regmap_update_bits(cs35l41->regmap, CS35L41_GLOBAL_CLK_CTRL, + CS35L41_GLOBAL_FS_MASK, + cs35l41_fs_rates[i].fs_cfg << CS35L41_GLOBAL_FS_SHIFT); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT, + CS35L41_ASP_WIDTH_RX_MASK, + asp_wl << CS35L41_ASP_WIDTH_RX_SHIFT); + regmap_update_bits(cs35l41->regmap, CS35L41_SP_RX_WL, + CS35L41_ASP_RX_WL_MASK, + asp_wl << CS35L41_ASP_RX_WL_SHIFT); + } else { + regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT, + CS35L41_ASP_WIDTH_TX_MASK, + asp_wl << CS35L41_ASP_WIDTH_TX_SHIFT); + regmap_update_bits(cs35l41->regmap, CS35L41_SP_TX_WL, + CS35L41_ASP_TX_WL_MASK, + asp_wl << CS35L41_ASP_TX_WL_SHIFT); + } + + return 0; +} + +static int cs35l41_get_clk_config(int freq) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l41_pll_sysclk); i++) { + if (cs35l41_pll_sysclk[i].freq == freq) + return cs35l41_pll_sysclk[i].clk_cfg; + } + + return -EINVAL; +} + +static const unsigned int cs35l41_src_rates[] = { + 8000, 12000, 11025, 16000, 22050, 24000, 32000, + 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l41_constraints = { + .count = ARRAY_SIZE(cs35l41_src_rates), + .list = cs35l41_src_rates, +}; + +static int cs35l41_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (substream->runtime) + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l41_constraints); + return 0; +} + +static int cs35l41_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, + unsigned int freq, int dir) +{ + struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(component); + int extclk_cfg, clksrc; + + switch (clk_id) { + case CS35L41_CLKID_SCLK: + clksrc = CS35L41_PLLSRC_SCLK; + break; + case CS35L41_CLKID_LRCLK: + clksrc = CS35L41_PLLSRC_LRCLK; + break; + case CS35L41_CLKID_MCLK: + clksrc = CS35L41_PLLSRC_MCLK; + break; + default: + dev_err(cs35l41->dev, "Invalid CLK Config\n"); + return -EINVAL; + } + + extclk_cfg = cs35l41_get_clk_config(freq); + + if (extclk_cfg < 0) { + dev_err(cs35l41->dev, "Invalid CLK Config: %d, freq: %u\n", + extclk_cfg, freq); + return -EINVAL; + } + + regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, + CS35L41_PLL_OPENLOOP_MASK, + 1 << CS35L41_PLL_OPENLOOP_SHIFT); + regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, + CS35L41_REFCLK_FREQ_MASK, + extclk_cfg << CS35L41_REFCLK_FREQ_SHIFT); + regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, + CS35L41_PLL_CLK_EN_MASK, + 0 << CS35L41_PLL_CLK_EN_SHIFT); + regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, + CS35L41_PLL_CLK_SEL_MASK, clksrc); + regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, + CS35L41_PLL_OPENLOOP_MASK, + 0 << CS35L41_PLL_OPENLOOP_SHIFT); + regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, + CS35L41_PLL_CLK_EN_MASK, + 1 << CS35L41_PLL_CLK_EN_SHIFT); + + return 0; +} + +static int cs35l41_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); + unsigned int fs1_val; + unsigned int fs2_val; + unsigned int val; + int fsindex; + + fsindex = cs35l41_get_fs_mon_config_index(freq); + if (fsindex < 0) { + dev_err(cs35l41->dev, "Invalid CLK Config freq: %u\n", freq); + return -EINVAL; + } + + dev_dbg(cs35l41->dev, "Set DAI sysclk %d\n", freq); + + if (freq <= 6144000) { + /* Use the lookup table */ + fs1_val = cs35l41_fs_mon[fsindex].fs1; + fs2_val = cs35l41_fs_mon[fsindex].fs2; + } else { + /* Use hard-coded values */ + fs1_val = 0x10; + fs2_val = 0x24; + } + + val = fs1_val; + val |= (fs2_val << CS35L41_FS2_WINDOW_SHIFT) & CS35L41_FS2_WINDOW_MASK; + regmap_write(cs35l41->regmap, CS35L41_TST_FS_MON0, val); + + return 0; +} + +static int cs35l41_boost_config(struct cs35l41_private *cs35l41, + int boost_ind, int boost_cap, int boost_ipk) +{ + unsigned char bst_lbst_val, bst_cbst_range, bst_ipk_scaled; + struct regmap *regmap = cs35l41->regmap; + struct device *dev = cs35l41->dev; + int ret; + + switch (boost_ind) { + case 1000: /* 1.0 uH */ + bst_lbst_val = 0; + break; + case 1200: /* 1.2 uH */ + bst_lbst_val = 1; + break; + case 1500: /* 1.5 uH */ + bst_lbst_val = 2; + break; + case 2200: /* 2.2 uH */ + bst_lbst_val = 3; + break; + default: + dev_err(dev, "Invalid boost inductor value: %d nH\n", boost_ind); + return -EINVAL; + } + + switch (boost_cap) { + case 0 ... 19: + bst_cbst_range = 0; + break; + case 20 ... 50: + bst_cbst_range = 1; + break; + case 51 ... 100: + bst_cbst_range = 2; + break; + case 101 ... 200: + bst_cbst_range = 3; + break; + default: /* 201 uF and greater */ + bst_cbst_range = 4; + } + + ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_COEFF, + CS35L41_BST_K1_MASK | CS35L41_BST_K2_MASK, + cs35l41_bst_k1_table[bst_lbst_val][bst_cbst_range] + << CS35L41_BST_K1_SHIFT | + cs35l41_bst_k2_table[bst_lbst_val][bst_cbst_range] + << CS35L41_BST_K2_SHIFT); + if (ret) { + dev_err(dev, "Failed to write boost coefficients: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_SLOPE_LBST, + CS35L41_BST_SLOPE_MASK | CS35L41_BST_LBST_VAL_MASK, + cs35l41_bst_slope_table[bst_lbst_val] + << CS35L41_BST_SLOPE_SHIFT | + bst_lbst_val << CS35L41_BST_LBST_VAL_SHIFT); + if (ret) { + dev_err(dev, "Failed to write boost slope/inductor value: %d\n", ret); + return ret; + } + + if (boost_ipk < 1600 || boost_ipk > 4500) { + dev_err(dev, "Invalid boost inductor peak current: %d mA\n", + boost_ipk); + return -EINVAL; + } + bst_ipk_scaled = ((boost_ipk - 1600) / 50) + 0x10; + + ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_PEAK_CUR, + CS35L41_BST_IPK_MASK, + bst_ipk_scaled << CS35L41_BST_IPK_SHIFT); + if (ret) { + dev_err(dev, "Failed to write boost inductor peak current: %d\n", ret); + return ret; + } + + return 0; +} + +static int cs35l41_set_pdata(struct cs35l41_private *cs35l41) +{ + int ret; + + /* Set Platform Data */ + /* Required */ + if (cs35l41->pdata.bst_ipk && + cs35l41->pdata.bst_ind && cs35l41->pdata.bst_cap) { + ret = cs35l41_boost_config(cs35l41, cs35l41->pdata.bst_ind, + cs35l41->pdata.bst_cap, + cs35l41->pdata.bst_ipk); + if (ret) { + dev_err(cs35l41->dev, "Error in Boost DT config: %d\n", ret); + return ret; + } + } else { + dev_err(cs35l41->dev, "Incomplete Boost component DT config\n"); + return -EINVAL; + } + + /* Optional */ + if (cs35l41->pdata.dout_hiz <= CS35L41_ASP_DOUT_HIZ_MASK && + cs35l41->pdata.dout_hiz >= 0) + regmap_update_bits(cs35l41->regmap, CS35L41_SP_HIZ_CTRL, + CS35L41_ASP_DOUT_HIZ_MASK, + cs35l41->pdata.dout_hiz); + + return 0; +} + +static int cs35l41_irq_gpio_config(struct cs35l41_private *cs35l41) +{ + struct cs35l41_irq_cfg *irq_gpio_cfg1 = &cs35l41->pdata.irq_config1; + struct cs35l41_irq_cfg *irq_gpio_cfg2 = &cs35l41->pdata.irq_config2; + int irq_pol = IRQF_TRIGGER_NONE; + + regmap_update_bits(cs35l41->regmap, CS35L41_GPIO1_CTRL1, + CS35L41_GPIO_POL_MASK | CS35L41_GPIO_DIR_MASK, + irq_gpio_cfg1->irq_pol_inv << CS35L41_GPIO_POL_SHIFT | + !irq_gpio_cfg1->irq_out_en << CS35L41_GPIO_DIR_SHIFT); + + regmap_update_bits(cs35l41->regmap, CS35L41_GPIO2_CTRL1, + CS35L41_GPIO_POL_MASK | CS35L41_GPIO_DIR_MASK, + irq_gpio_cfg1->irq_pol_inv << CS35L41_GPIO_POL_SHIFT | + !irq_gpio_cfg1->irq_out_en << CS35L41_GPIO_DIR_SHIFT); + + regmap_update_bits(cs35l41->regmap, CS35L41_GPIO_PAD_CONTROL, + CS35L41_GPIO1_CTRL_MASK | CS35L41_GPIO2_CTRL_MASK, + irq_gpio_cfg1->irq_src_sel << CS35L41_GPIO1_CTRL_SHIFT | + irq_gpio_cfg2->irq_src_sel << CS35L41_GPIO2_CTRL_SHIFT); + + if ((irq_gpio_cfg2->irq_src_sel == + (CS35L41_GPIO_CTRL_ACTV_LO | CS35L41_VALID_PDATA)) || + (irq_gpio_cfg2->irq_src_sel == + (CS35L41_GPIO_CTRL_OPEN_INT | CS35L41_VALID_PDATA))) + irq_pol = IRQF_TRIGGER_LOW; + else if (irq_gpio_cfg2->irq_src_sel == + (CS35L41_GPIO_CTRL_ACTV_HI | CS35L41_VALID_PDATA)) + irq_pol = IRQF_TRIGGER_HIGH; + + return irq_pol; +} + +static const struct snd_soc_dai_ops cs35l41_ops = { + .startup = cs35l41_pcm_startup, + .set_fmt = cs35l41_set_dai_fmt, + .hw_params = cs35l41_pcm_hw_params, + .set_sysclk = cs35l41_dai_set_sysclk, + .set_channel_map = cs35l41_set_channel_map, +}; + +static struct snd_soc_dai_driver cs35l41_dai[] = { + { + .name = "cs35l41-pcm", + .id = 0, + .playback = { + .stream_name = "AMP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L41_RX_FORMATS, + }, + .capture = { + .stream_name = "AMP Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L41_TX_FORMATS, + }, + .ops = &cs35l41_ops, + .symmetric_rate = 1, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_cs35l41 = { + .name = "cs35l41-codec", + + .dapm_widgets = cs35l41_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l41_dapm_widgets), + .dapm_routes = cs35l41_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l41_audio_map), + + .controls = cs35l41_aud_controls, + .num_controls = ARRAY_SIZE(cs35l41_aud_controls), + .set_sysclk = cs35l41_component_set_sysclk, +}; + +static int cs35l41_handle_pdata(struct device *dev, + struct cs35l41_platform_data *pdata, + struct cs35l41_private *cs35l41) +{ + struct cs35l41_irq_cfg *irq_gpio1_config = &pdata->irq_config1; + struct cs35l41_irq_cfg *irq_gpio2_config = &pdata->irq_config2; + unsigned int val; + int ret; + + ret = device_property_read_u32(dev, "cirrus,boost-peak-milliamp", &val); + if (ret >= 0) + pdata->bst_ipk = val; + + ret = device_property_read_u32(dev, "cirrus,boost-ind-nanohenry", &val); + if (ret >= 0) + pdata->bst_ind = val; + + ret = device_property_read_u32(dev, "cirrus,boost-cap-microfarad", &val); + if (ret >= 0) + pdata->bst_cap = val; + + ret = device_property_read_u32(dev, "cirrus,asp-sdout-hiz", &val); + if (ret >= 0) + pdata->dout_hiz = val; + else + pdata->dout_hiz = -1; + + /* GPIO1 Pin Config */ + irq_gpio1_config->irq_pol_inv = device_property_read_bool(dev, + "cirrus,gpio1-polarity-invert"); + irq_gpio1_config->irq_out_en = device_property_read_bool(dev, + "cirrus,gpio1-output-enable"); + ret = device_property_read_u32(dev, "cirrus,gpio1-src-select", + &val); + if (ret >= 0) + irq_gpio1_config->irq_src_sel = val | CS35L41_VALID_PDATA; + + /* GPIO2 Pin Config */ + irq_gpio2_config->irq_pol_inv = device_property_read_bool(dev, + "cirrus,gpio2-polarity-invert"); + irq_gpio2_config->irq_out_en = device_property_read_bool(dev, + "cirrus,gpio2-output-enable"); + ret = device_property_read_u32(dev, "cirrus,gpio2-src-select", + &val); + if (ret >= 0) + irq_gpio2_config->irq_src_sel = val | CS35L41_VALID_PDATA; + + return 0; +} + +static const struct reg_sequence cs35l41_reva0_errata_patch[] = { + { 0x00000040, 0x00005555 }, + { 0x00000040, 0x0000AAAA }, + { 0x00003854, 0x05180240 }, + { CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 }, + { 0x00004310, 0x00000000 }, + { CS35L41_VPVBST_FS_SEL, 0x00000000 }, + { CS35L41_OTP_TRIM_30, 0x9091A1C8 }, + { 0x00003014, 0x0200EE0E }, + { CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 }, + { 0x00000054, 0x00000004 }, + { CS35L41_IRQ1_DB3, 0x00000000 }, + { CS35L41_IRQ2_DB3, 0x00000000 }, + { CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 }, + { CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 }, + { 0x00000040, 0x0000CCCC }, + { 0x00000040, 0x00003333 }, +}; + +static const struct reg_sequence cs35l41_revb0_errata_patch[] = { + { 0x00000040, 0x00005555 }, + { 0x00000040, 0x0000AAAA }, + { CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 }, + { 0x00004310, 0x00000000 }, + { CS35L41_VPVBST_FS_SEL, 0x00000000 }, + { CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 }, + { CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 }, + { CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 }, + { 0x00000040, 0x0000CCCC }, + { 0x00000040, 0x00003333 }, +}; + +static const struct reg_sequence cs35l41_revb2_errata_patch[] = { + { 0x00000040, 0x00005555 }, + { 0x00000040, 0x0000AAAA }, + { CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 }, + { 0x00004310, 0x00000000 }, + { CS35L41_VPVBST_FS_SEL, 0x00000000 }, + { CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 }, + { CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 }, + { CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 }, + { 0x00000040, 0x0000CCCC }, + { 0x00000040, 0x00003333 }, +}; + +int cs35l41_probe(struct cs35l41_private *cs35l41, + struct cs35l41_platform_data *pdata) +{ + u32 regid, reg_revid, i, mtl_revid, int_status, chipid_match; + int irq_pol = 0; + int ret; + + if (pdata) { + cs35l41->pdata = *pdata; + } else { + ret = cs35l41_handle_pdata(cs35l41->dev, &cs35l41->pdata, cs35l41); + if (ret != 0) + return ret; + } + + for (i = 0; i < CS35L41_NUM_SUPPLIES; i++) + cs35l41->supplies[i].supply = cs35l41_supplies[i]; + + ret = devm_regulator_bulk_get(cs35l41->dev, CS35L41_NUM_SUPPLIES, + cs35l41->supplies); + if (ret != 0) { + dev_err(cs35l41->dev, "Failed to request core supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); + if (ret != 0) { + dev_err(cs35l41->dev, "Failed to enable core supplies: %d\n", ret); + return ret; + } + + /* returning NULL can be an option if in stereo mode */ + cs35l41->reset_gpio = devm_gpiod_get_optional(cs35l41->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs35l41->reset_gpio)) { + ret = PTR_ERR(cs35l41->reset_gpio); + cs35l41->reset_gpio = NULL; + if (ret == -EBUSY) { + dev_info(cs35l41->dev, + "Reset line busy, assuming shared reset\n"); + } else { + dev_err(cs35l41->dev, + "Failed to get reset GPIO: %d\n", ret); + goto err; + } + } + if (cs35l41->reset_gpio) { + /* satisfy minimum reset pulse width spec */ + usleep_range(2000, 2100); + gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); + } + + usleep_range(2000, 2100); + + ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, + int_status, int_status & CS35L41_OTP_BOOT_DONE, + 1000, 100000); + if (ret) { + dev_err(cs35l41->dev, + "Failed waiting for OTP_BOOT_DONE: %d\n", ret); + goto err; + } + + regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); + if (int_status & CS35L41_OTP_BOOT_ERR) { + dev_err(cs35l41->dev, "OTP Boot error\n"); + ret = -EINVAL; + goto err; + } + + ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); + if (ret < 0) { + dev_err(cs35l41->dev, "Get Device ID failed: %d\n", ret); + goto err; + } + + ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid); + if (ret < 0) { + dev_err(cs35l41->dev, "Get Revision ID failed: %d\n", ret); + goto err; + } + + mtl_revid = reg_revid & CS35L41_MTLREVID_MASK; + + /* CS35L41 will have even MTLREVID + * CS35L41R will have odd MTLREVID + */ + chipid_match = (mtl_revid % 2) ? CS35L41R_CHIP_ID : CS35L41_CHIP_ID; + if (regid != chipid_match) { + dev_err(cs35l41->dev, "CS35L41 Device ID (%X). Expected ID %X\n", + regid, chipid_match); + ret = -ENODEV; + goto err; + } + + switch (reg_revid) { + case CS35L41_REVID_A0: + ret = regmap_register_patch(cs35l41->regmap, + cs35l41_reva0_errata_patch, + ARRAY_SIZE(cs35l41_reva0_errata_patch)); + if (ret < 0) { + dev_err(cs35l41->dev, + "Failed to apply A0 errata patch: %d\n", ret); + goto err; + } + break; + case CS35L41_REVID_B0: + ret = regmap_register_patch(cs35l41->regmap, + cs35l41_revb0_errata_patch, + ARRAY_SIZE(cs35l41_revb0_errata_patch)); + if (ret < 0) { + dev_err(cs35l41->dev, + "Failed to apply B0 errata patch: %d\n", ret); + goto err; + } + break; + case CS35L41_REVID_B2: + ret = regmap_register_patch(cs35l41->regmap, + cs35l41_revb2_errata_patch, + ARRAY_SIZE(cs35l41_revb2_errata_patch)); + if (ret < 0) { + dev_err(cs35l41->dev, + "Failed to apply B2 errata patch: %d\n", ret); + goto err; + } + break; + } + + irq_pol = cs35l41_irq_gpio_config(cs35l41); + + /* Set interrupt masks for critical errors */ + regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, + CS35L41_INT1_MASK_DEFAULT); + + ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL, cs35l41_irq, + IRQF_ONESHOT | IRQF_SHARED | irq_pol, + "cs35l41", cs35l41); + + /* CS35L41 needs INT for PDN_DONE */ + if (ret != 0) { + dev_err(cs35l41->dev, "Failed to request IRQ: %d\n", ret); + goto err; + } + + ret = cs35l41_otp_unpack(cs35l41); + if (ret < 0) { + dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret); + goto err; + } + + ret = regmap_write(cs35l41->regmap, CS35L41_DSP1_CCM_CORE_CTRL, 0); + if (ret < 0) { + dev_err(cs35l41->dev, "Write CCM_CORE_CTRL failed: %d\n", ret); + goto err; + } + + ret = regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_AMP_EN_MASK, 0); + if (ret < 0) { + dev_err(cs35l41->dev, "Write CS35L41_PWR_CTRL2 failed: %d\n", ret); + goto err; + } + + ret = regmap_update_bits(cs35l41->regmap, CS35L41_AMP_GAIN_CTRL, + CS35L41_AMP_GAIN_PCM_MASK, 0); + if (ret < 0) { + dev_err(cs35l41->dev, "Write CS35L41_AMP_GAIN_CTRL failed: %d\n", ret); + goto err; + } + + ret = cs35l41_set_pdata(cs35l41); + if (ret < 0) { + dev_err(cs35l41->dev, "Set pdata failed: %d\n", ret); + goto err; + } + + ret = devm_snd_soc_register_component(cs35l41->dev, + &soc_component_dev_cs35l41, + cs35l41_dai, ARRAY_SIZE(cs35l41_dai)); + if (ret < 0) { + dev_err(cs35l41->dev, "Register codec failed: %d\n", ret); + goto err; + } + + dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", + regid, reg_revid); + + return 0; + +err: + regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); + + return ret; +} + +void cs35l41_remove(struct cs35l41_private *cs35l41) +{ + regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF); + regulator_bulk_disable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); +} + +MODULE_DESCRIPTION("ASoC CS35L41 driver"); +MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <david.rhodes@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l41.h b/sound/soc/codecs/cs35l41.h new file mode 100644 index 000000000000..6cffe8a55beb --- /dev/null +++ b/sound/soc/codecs/cs35l41.h @@ -0,0 +1,775 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * cs35l41.h -- CS35L41 ALSA SoC audio driver + * + * Copyright 2017-2021 Cirrus Logic, Inc. + * + * Author: David Rhodes <david.rhodes@cirrus.com> + */ + +#ifndef __CS35L41_H__ +#define __CS35L41_H__ + +#include <linux/gpio/consumer.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <sound/core.h> +#include <sound/cs35l41.h> + +#define CS35L41_FIRSTREG 0x00000000 +#define CS35L41_LASTREG 0x03804FE8 +#define CS35L41_DEVID 0x00000000 +#define CS35L41_REVID 0x00000004 +#define CS35L41_FABID 0x00000008 +#define CS35L41_RELID 0x0000000C +#define CS35L41_OTPID 0x00000010 +#define CS35L41_SFT_RESET 0x00000020 +#define CS35L41_TEST_KEY_CTL 0x00000040 +#define CS35L41_USER_KEY_CTL 0x00000044 +#define CS35L41_OTP_MEM0 0x00000400 +#define CS35L41_OTP_MEM31 0x0000047C +#define CS35L41_OTP_CTRL0 0x00000500 +#define CS35L41_OTP_CTRL1 0x00000504 +#define CS35L41_OTP_CTRL3 0x00000508 +#define CS35L41_OTP_CTRL4 0x0000050C +#define CS35L41_OTP_CTRL5 0x00000510 +#define CS35L41_OTP_CTRL6 0x00000514 +#define CS35L41_OTP_CTRL7 0x00000518 +#define CS35L41_OTP_CTRL8 0x0000051C +#define CS35L41_PWR_CTRL1 0x00002014 +#define CS35L41_PWR_CTRL2 0x00002018 +#define CS35L41_PWR_CTRL3 0x0000201C +#define CS35L41_CTRL_OVRRIDE 0x00002020 +#define CS35L41_AMP_OUT_MUTE 0x00002024 +#define CS35L41_PROTECT_REL_ERR_IGN 0x00002034 +#define CS35L41_GPIO_PAD_CONTROL 0x0000242C +#define CS35L41_JTAG_CONTROL 0x00002438 +#define CS35L41_PLL_CLK_CTRL 0x00002C04 +#define CS35L41_DSP_CLK_CTRL 0x00002C08 +#define CS35L41_GLOBAL_CLK_CTRL 0x00002C0C +#define CS35L41_DATA_FS_SEL 0x00002C10 +#define CS35L41_TST_FS_MON0 0x00002D10 +#define CS35L41_MDSYNC_EN 0x00003400 +#define CS35L41_MDSYNC_TX_ID 0x00003408 +#define CS35L41_MDSYNC_PWR_CTRL 0x0000340C +#define CS35L41_MDSYNC_DATA_TX 0x00003410 +#define CS35L41_MDSYNC_TX_STATUS 0x00003414 +#define CS35L41_MDSYNC_DATA_RX 0x0000341C +#define CS35L41_MDSYNC_RX_STATUS 0x00003420 +#define CS35L41_MDSYNC_ERR_STATUS 0x00003424 +#define CS35L41_MDSYNC_SYNC_PTE2 0x00003528 +#define CS35L41_MDSYNC_SYNC_PTE3 0x0000352C +#define CS35L41_MDSYNC_SYNC_MSM_STATUS 0x0000353C +#define CS35L41_BSTCVRT_VCTRL1 0x00003800 +#define CS35L41_BSTCVRT_VCTRL2 0x00003804 +#define CS35L41_BSTCVRT_PEAK_CUR 0x00003808 +#define CS35L41_BSTCVRT_SFT_RAMP 0x0000380C +#define CS35L41_BSTCVRT_COEFF 0x00003810 +#define CS35L41_BSTCVRT_SLOPE_LBST 0x00003814 +#define CS35L41_BSTCVRT_SW_FREQ 0x00003818 +#define CS35L41_BSTCVRT_DCM_CTRL 0x0000381C +#define CS35L41_BSTCVRT_DCM_MODE_FORCE 0x00003820 +#define CS35L41_BSTCVRT_OVERVOLT_CTRL 0x00003830 +#define CS35L41_VI_VOL_POL 0x00004000 +#define CS35L41_VIMON_SPKMON_RESYNC 0x00004100 +#define CS35L41_DTEMP_WARN_THLD 0x00004220 +#define CS35L41_DTEMP_CFG 0x00004224 +#define CS35L41_DTEMP_EN 0x00004308 +#define CS35L41_VPVBST_FS_SEL 0x00004400 +#define CS35L41_SP_ENABLES 0x00004800 +#define CS35L41_SP_RATE_CTRL 0x00004804 +#define CS35L41_SP_FORMAT 0x00004808 +#define CS35L41_SP_HIZ_CTRL 0x0000480C +#define CS35L41_SP_FRAME_TX_SLOT 0x00004810 +#define CS35L41_SP_FRAME_RX_SLOT 0x00004820 +#define CS35L41_SP_TX_WL 0x00004830 +#define CS35L41_SP_RX_WL 0x00004840 +#define CS35L41_ASP_CONTROL4 0x00004854 +#define CS35L41_DAC_PCM1_SRC 0x00004C00 +#define CS35L41_ASP_TX1_SRC 0x00004C20 +#define CS35L41_ASP_TX2_SRC 0x00004C24 +#define CS35L41_ASP_TX3_SRC 0x00004C28 +#define CS35L41_ASP_TX4_SRC 0x00004C2C +#define CS35L41_DSP1_RX1_SRC 0x00004C40 +#define CS35L41_DSP1_RX2_SRC 0x00004C44 +#define CS35L41_DSP1_RX3_SRC 0x00004C48 +#define CS35L41_DSP1_RX4_SRC 0x00004C4C +#define CS35L41_DSP1_RX5_SRC 0x00004C50 +#define CS35L41_DSP1_RX6_SRC 0x00004C54 +#define CS35L41_DSP1_RX7_SRC 0x00004C58 +#define CS35L41_DSP1_RX8_SRC 0x00004C5C +#define CS35L41_NGATE1_SRC 0x00004C60 +#define CS35L41_NGATE2_SRC 0x00004C64 +#define CS35L41_AMP_DIG_VOL_CTRL 0x00006000 +#define CS35L41_VPBR_CFG 0x00006404 +#define CS35L41_VBBR_CFG 0x00006408 +#define CS35L41_VPBR_STATUS 0x0000640C +#define CS35L41_VBBR_STATUS 0x00006410 +#define CS35L41_OVERTEMP_CFG 0x00006414 +#define CS35L41_AMP_ERR_VOL 0x00006418 +#define CS35L41_VOL_STATUS_TO_DSP 0x00006450 +#define CS35L41_CLASSH_CFG 0x00006800 +#define CS35L41_WKFET_CFG 0x00006804 +#define CS35L41_NG_CFG 0x00006808 +#define CS35L41_AMP_GAIN_CTRL 0x00006C04 +#define CS35L41_DAC_MSM_CFG 0x00007400 +#define CS35L41_IRQ1_CFG 0x00010000 +#define CS35L41_IRQ1_STATUS 0x00010004 +#define CS35L41_IRQ1_STATUS1 0x00010010 +#define CS35L41_IRQ1_STATUS2 0x00010014 +#define CS35L41_IRQ1_STATUS3 0x00010018 +#define CS35L41_IRQ1_STATUS4 0x0001001C +#define CS35L41_IRQ1_RAW_STATUS1 0x00010090 +#define CS35L41_IRQ1_RAW_STATUS2 0x00010094 +#define CS35L41_IRQ1_RAW_STATUS3 0x00010098 +#define CS35L41_IRQ1_RAW_STATUS4 0x0001009C +#define CS35L41_IRQ1_MASK1 0x00010110 +#define CS35L41_IRQ1_MASK2 0x00010114 +#define CS35L41_IRQ1_MASK3 0x00010118 +#define CS35L41_IRQ1_MASK4 0x0001011C +#define CS35L41_IRQ1_FRC1 0x00010190 +#define CS35L41_IRQ1_FRC2 0x00010194 +#define CS35L41_IRQ1_FRC3 0x00010198 +#define CS35L41_IRQ1_FRC4 0x0001019C +#define CS35L41_IRQ1_EDGE1 0x00010210 +#define CS35L41_IRQ1_EDGE4 0x0001021C +#define CS35L41_IRQ1_POL1 0x00010290 +#define CS35L41_IRQ1_POL2 0x00010294 +#define CS35L41_IRQ1_POL3 0x00010298 +#define CS35L41_IRQ1_POL4 0x0001029C +#define CS35L41_IRQ1_DB3 0x00010318 +#define CS35L41_IRQ2_CFG 0x00010800 +#define CS35L41_IRQ2_STATUS 0x00010804 +#define CS35L41_IRQ2_STATUS1 0x00010810 +#define CS35L41_IRQ2_STATUS2 0x00010814 +#define CS35L41_IRQ2_STATUS3 0x00010818 +#define CS35L41_IRQ2_STATUS4 0x0001081C +#define CS35L41_IRQ2_RAW_STATUS1 0x00010890 +#define CS35L41_IRQ2_RAW_STATUS2 0x00010894 +#define CS35L41_IRQ2_RAW_STATUS3 0x00010898 +#define CS35L41_IRQ2_RAW_STATUS4 0x0001089C +#define CS35L41_IRQ2_MASK1 0x00010910 +#define CS35L41_IRQ2_MASK2 0x00010914 +#define CS35L41_IRQ2_MASK3 0x00010918 +#define CS35L41_IRQ2_MASK4 0x0001091C +#define CS35L41_IRQ2_FRC1 0x00010990 +#define CS35L41_IRQ2_FRC2 0x00010994 +#define CS35L41_IRQ2_FRC3 0x00010998 +#define CS35L41_IRQ2_FRC4 0x0001099C +#define CS35L41_IRQ2_EDGE1 0x00010A10 +#define CS35L41_IRQ2_EDGE4 0x00010A1C +#define CS35L41_IRQ2_POL1 0x00010A90 +#define CS35L41_IRQ2_POL2 0x00010A94 +#define CS35L41_IRQ2_POL3 0x00010A98 +#define CS35L41_IRQ2_POL4 0x00010A9C +#define CS35L41_IRQ2_DB3 0x00010B18 +#define CS35L41_GPIO_STATUS1 0x00011000 +#define CS35L41_GPIO1_CTRL1 0x00011008 +#define CS35L41_GPIO2_CTRL1 0x0001100C +#define CS35L41_MIXER_NGATE_CFG 0x00012000 +#define CS35L41_MIXER_NGATE_CH1_CFG 0x00012004 +#define CS35L41_MIXER_NGATE_CH2_CFG 0x00012008 +#define CS35L41_DSP_MBOX_1 0x00013000 +#define CS35L41_DSP_MBOX_2 0x00013004 +#define CS35L41_DSP_MBOX_3 0x00013008 +#define CS35L41_DSP_MBOX_4 0x0001300C +#define CS35L41_DSP_MBOX_5 0x00013010 +#define CS35L41_DSP_MBOX_6 0x00013014 +#define CS35L41_DSP_MBOX_7 0x00013018 +#define CS35L41_DSP_MBOX_8 0x0001301C +#define CS35L41_DSP_VIRT1_MBOX_1 0x00013020 +#define CS35L41_DSP_VIRT1_MBOX_2 0x00013024 +#define CS35L41_DSP_VIRT1_MBOX_3 0x00013028 +#define CS35L41_DSP_VIRT1_MBOX_4 0x0001302C +#define CS35L41_DSP_VIRT1_MBOX_5 0x00013030 +#define CS35L41_DSP_VIRT1_MBOX_6 0x00013034 +#define CS35L41_DSP_VIRT1_MBOX_7 0x00013038 +#define CS35L41_DSP_VIRT1_MBOX_8 0x0001303C +#define CS35L41_DSP_VIRT2_MBOX_1 0x00013040 +#define CS35L41_DSP_VIRT2_MBOX_2 0x00013044 +#define CS35L41_DSP_VIRT2_MBOX_3 0x00013048 +#define CS35L41_DSP_VIRT2_MBOX_4 0x0001304C +#define CS35L41_DSP_VIRT2_MBOX_5 0x00013050 +#define CS35L41_DSP_VIRT2_MBOX_6 0x00013054 +#define CS35L41_DSP_VIRT2_MBOX_7 0x00013058 +#define CS35L41_DSP_VIRT2_MBOX_8 0x0001305C +#define CS35L41_CLOCK_DETECT_1 0x00014000 +#define CS35L41_TIMER1_CONTROL 0x00015000 +#define CS35L41_TIMER1_COUNT_PRESET 0x00015004 +#define CS35L41_TIMER1_START_STOP 0x0001500C +#define CS35L41_TIMER1_STATUS 0x00015010 +#define CS35L41_TIMER1_COUNT_READBACK 0x00015014 +#define CS35L41_TIMER1_DSP_CLK_CFG 0x00015018 +#define CS35L41_TIMER1_DSP_CLK_STATUS 0x0001501C +#define CS35L41_TIMER2_CONTROL 0x00015100 +#define CS35L41_TIMER2_COUNT_PRESET 0x00015104 +#define CS35L41_TIMER2_START_STOP 0x0001510C +#define CS35L41_TIMER2_STATUS 0x00015110 +#define CS35L41_TIMER2_COUNT_READBACK 0x00015114 +#define CS35L41_TIMER2_DSP_CLK_CFG 0x00015118 +#define CS35L41_TIMER2_DSP_CLK_STATUS 0x0001511C +#define CS35L41_DFT_JTAG_CONTROL 0x00016000 +#define CS35L41_DIE_STS1 0x00017040 +#define CS35L41_DIE_STS2 0x00017044 +#define CS35L41_TEMP_CAL1 0x00017048 +#define CS35L41_TEMP_CAL2 0x0001704C +#define CS35L41_DSP1_XMEM_PACK_0 0x02000000 +#define CS35L41_DSP1_XMEM_PACK_3068 0x02002FF0 +#define CS35L41_DSP1_XMEM_UNPACK32_0 0x02400000 +#define CS35L41_DSP1_XMEM_UNPACK32_2046 0x02401FF8 +#define CS35L41_DSP1_TIMESTAMP_COUNT 0x025C0800 +#define CS35L41_DSP1_SYS_ID 0x025E0000 +#define CS35L41_DSP1_SYS_VERSION 0x025E0004 +#define CS35L41_DSP1_SYS_CORE_ID 0x025E0008 +#define CS35L41_DSP1_SYS_AHB_ADDR 0x025E000C +#define CS35L41_DSP1_SYS_XSRAM_SIZE 0x025E0010 +#define CS35L41_DSP1_SYS_YSRAM_SIZE 0x025E0018 +#define CS35L41_DSP1_SYS_PSRAM_SIZE 0x025E0020 +#define CS35L41_DSP1_SYS_PM_BOOT_SIZE 0x025E0028 +#define CS35L41_DSP1_SYS_FEATURES 0x025E002C +#define CS35L41_DSP1_SYS_FIR_FILTERS 0x025E0030 +#define CS35L41_DSP1_SYS_LMS_FILTERS 0x025E0034 +#define CS35L41_DSP1_SYS_XM_BANK_SIZE 0x025E0038 +#define CS35L41_DSP1_SYS_YM_BANK_SIZE 0x025E003C +#define CS35L41_DSP1_SYS_PM_BANK_SIZE 0x025E0040 +#define CS35L41_DSP1_AHBM_WIN0_CTRL0 0x025E2000 +#define CS35L41_DSP1_AHBM_WIN0_CTRL1 0x025E2004 +#define CS35L41_DSP1_AHBM_WIN1_CTRL0 0x025E2008 +#define CS35L41_DSP1_AHBM_WIN1_CTRL1 0x025E200C +#define CS35L41_DSP1_AHBM_WIN2_CTRL0 0x025E2010 +#define CS35L41_DSP1_AHBM_WIN2_CTRL1 0x025E2014 +#define CS35L41_DSP1_AHBM_WIN3_CTRL0 0x025E2018 +#define CS35L41_DSP1_AHBM_WIN3_CTRL1 0x025E201C +#define CS35L41_DSP1_AHBM_WIN4_CTRL0 0x025E2020 +#define CS35L41_DSP1_AHBM_WIN4_CTRL1 0x025E2024 +#define CS35L41_DSP1_AHBM_WIN5_CTRL0 0x025E2028 +#define CS35L41_DSP1_AHBM_WIN5_CTRL1 0x025E202C +#define CS35L41_DSP1_AHBM_WIN6_CTRL0 0x025E2030 +#define CS35L41_DSP1_AHBM_WIN6_CTRL1 0x025E2034 +#define CS35L41_DSP1_AHBM_WIN7_CTRL0 0x025E2038 +#define CS35L41_DSP1_AHBM_WIN7_CTRL1 0x025E203C +#define CS35L41_DSP1_AHBM_WIN_DBG_CTRL0 0x025E2040 +#define CS35L41_DSP1_AHBM_WIN_DBG_CTRL1 0x025E2044 +#define CS35L41_DSP1_XMEM_UNPACK24_0 0x02800000 +#define CS35L41_DSP1_XMEM_UNPACK24_4093 0x02803FF4 +#define CS35L41_DSP1_CTRL_BASE 0x02B80000 +#define CS35L41_DSP1_CORE_SOFT_RESET 0x02B80010 +#define CS35L41_DSP1_DEBUG 0x02B80040 +#define CS35L41_DSP1_TIMER_CTRL 0x02B80048 +#define CS35L41_DSP1_STREAM_ARB_CTRL 0x02B80050 +#define CS35L41_DSP1_RX1_RATE 0x02B80080 +#define CS35L41_DSP1_RX2_RATE 0x02B80088 +#define CS35L41_DSP1_RX3_RATE 0x02B80090 +#define CS35L41_DSP1_RX4_RATE 0x02B80098 +#define CS35L41_DSP1_RX5_RATE 0x02B800A0 +#define CS35L41_DSP1_RX6_RATE 0x02B800A8 +#define CS35L41_DSP1_RX7_RATE 0x02B800B0 +#define CS35L41_DSP1_RX8_RATE 0x02B800B8 +#define CS35L41_DSP1_TX1_RATE 0x02B80280 +#define CS35L41_DSP1_TX2_RATE 0x02B80288 +#define CS35L41_DSP1_TX3_RATE 0x02B80290 +#define CS35L41_DSP1_TX4_RATE 0x02B80298 +#define CS35L41_DSP1_TX5_RATE 0x02B802A0 +#define CS35L41_DSP1_TX6_RATE 0x02B802A8 +#define CS35L41_DSP1_TX7_RATE 0x02B802B0 +#define CS35L41_DSP1_TX8_RATE 0x02B802B8 +#define CS35L41_DSP1_NMI_CTRL1 0x02B80480 +#define CS35L41_DSP1_NMI_CTRL2 0x02B80488 +#define CS35L41_DSP1_NMI_CTRL3 0x02B80490 +#define CS35L41_DSP1_NMI_CTRL4 0x02B80498 +#define CS35L41_DSP1_NMI_CTRL5 0x02B804A0 +#define CS35L41_DSP1_NMI_CTRL6 0x02B804A8 +#define CS35L41_DSP1_NMI_CTRL7 0x02B804B0 +#define CS35L41_DSP1_NMI_CTRL8 0x02B804B8 +#define CS35L41_DSP1_RESUME_CTRL 0x02B80500 +#define CS35L41_DSP1_IRQ1_CTRL 0x02B80508 +#define CS35L41_DSP1_IRQ2_CTRL 0x02B80510 +#define CS35L41_DSP1_IRQ3_CTRL 0x02B80518 +#define CS35L41_DSP1_IRQ4_CTRL 0x02B80520 +#define CS35L41_DSP1_IRQ5_CTRL 0x02B80528 +#define CS35L41_DSP1_IRQ6_CTRL 0x02B80530 +#define CS35L41_DSP1_IRQ7_CTRL 0x02B80538 +#define CS35L41_DSP1_IRQ8_CTRL 0x02B80540 +#define CS35L41_DSP1_IRQ9_CTRL 0x02B80548 +#define CS35L41_DSP1_IRQ10_CTRL 0x02B80550 +#define CS35L41_DSP1_IRQ11_CTRL 0x02B80558 +#define CS35L41_DSP1_IRQ12_CTRL 0x02B80560 +#define CS35L41_DSP1_IRQ13_CTRL 0x02B80568 +#define CS35L41_DSP1_IRQ14_CTRL 0x02B80570 +#define CS35L41_DSP1_IRQ15_CTRL 0x02B80578 +#define CS35L41_DSP1_IRQ16_CTRL 0x02B80580 +#define CS35L41_DSP1_IRQ17_CTRL 0x02B80588 +#define CS35L41_DSP1_IRQ18_CTRL 0x02B80590 +#define CS35L41_DSP1_IRQ19_CTRL 0x02B80598 +#define CS35L41_DSP1_IRQ20_CTRL 0x02B805A0 +#define CS35L41_DSP1_IRQ21_CTRL 0x02B805A8 +#define CS35L41_DSP1_IRQ22_CTRL 0x02B805B0 +#define CS35L41_DSP1_IRQ23_CTRL 0x02B805B8 +#define CS35L41_DSP1_SCRATCH1 0x02B805C0 +#define CS35L41_DSP1_SCRATCH2 0x02B805C8 +#define CS35L41_DSP1_SCRATCH3 0x02B805D0 +#define CS35L41_DSP1_SCRATCH4 0x02B805D8 +#define CS35L41_DSP1_CCM_CORE_CTRL 0x02BC1000 +#define CS35L41_DSP1_CCM_CLK_OVERRIDE 0x02BC1008 +#define CS35L41_DSP1_XM_MSTR_EN 0x02BC2000 +#define CS35L41_DSP1_XM_CORE_PRI 0x02BC2008 +#define CS35L41_DSP1_XM_AHB_PACK_PL_PRI 0x02BC2010 +#define CS35L41_DSP1_XM_AHB_UP_PL_PRI 0x02BC2018 +#define CS35L41_DSP1_XM_ACCEL_PL0_PRI 0x02BC2020 +#define CS35L41_DSP1_XM_NPL0_PRI 0x02BC2078 +#define CS35L41_DSP1_YM_MSTR_EN 0x02BC20C0 +#define CS35L41_DSP1_YM_CORE_PRI 0x02BC20C8 +#define CS35L41_DSP1_YM_AHB_PACK_PL_PRI 0x02BC20D0 +#define CS35L41_DSP1_YM_AHB_UP_PL_PRI 0x02BC20D8 +#define CS35L41_DSP1_YM_ACCEL_PL0_PRI 0x02BC20E0 +#define CS35L41_DSP1_YM_NPL0_PRI 0x02BC2138 +#define CS35L41_DSP1_PM_MSTR_EN 0x02BC2180 +#define CS35L41_DSP1_PM_PATCH0_ADDR 0x02BC2188 +#define CS35L41_DSP1_PM_PATCH0_EN 0x02BC218C +#define CS35L41_DSP1_PM_PATCH0_DATA_LO 0x02BC2190 +#define CS35L41_DSP1_PM_PATCH0_DATA_HI 0x02BC2194 +#define CS35L41_DSP1_PM_PATCH1_ADDR 0x02BC2198 +#define CS35L41_DSP1_PM_PATCH1_EN 0x02BC219C +#define CS35L41_DSP1_PM_PATCH1_DATA_LO 0x02BC21A0 +#define CS35L41_DSP1_PM_PATCH1_DATA_HI 0x02BC21A4 +#define CS35L41_DSP1_PM_PATCH2_ADDR 0x02BC21A8 +#define CS35L41_DSP1_PM_PATCH2_EN 0x02BC21AC +#define CS35L41_DSP1_PM_PATCH2_DATA_LO 0x02BC21B0 +#define CS35L41_DSP1_PM_PATCH2_DATA_HI 0x02BC21B4 +#define CS35L41_DSP1_PM_PATCH3_ADDR 0x02BC21B8 +#define CS35L41_DSP1_PM_PATCH3_EN 0x02BC21BC +#define CS35L41_DSP1_PM_PATCH3_DATA_LO 0x02BC21C0 +#define CS35L41_DSP1_PM_PATCH3_DATA_HI 0x02BC21C4 +#define CS35L41_DSP1_PM_PATCH4_ADDR 0x02BC21C8 +#define CS35L41_DSP1_PM_PATCH4_EN 0x02BC21CC +#define CS35L41_DSP1_PM_PATCH4_DATA_LO 0x02BC21D0 +#define CS35L41_DSP1_PM_PATCH4_DATA_HI 0x02BC21D4 +#define CS35L41_DSP1_PM_PATCH5_ADDR 0x02BC21D8 +#define CS35L41_DSP1_PM_PATCH5_EN 0x02BC21DC +#define CS35L41_DSP1_PM_PATCH5_DATA_LO 0x02BC21E0 +#define CS35L41_DSP1_PM_PATCH5_DATA_HI 0x02BC21E4 +#define CS35L41_DSP1_PM_PATCH6_ADDR 0x02BC21E8 +#define CS35L41_DSP1_PM_PATCH6_EN 0x02BC21EC +#define CS35L41_DSP1_PM_PATCH6_DATA_LO 0x02BC21F0 +#define CS35L41_DSP1_PM_PATCH6_DATA_HI 0x02BC21F4 +#define CS35L41_DSP1_PM_PATCH7_ADDR 0x02BC21F8 +#define CS35L41_DSP1_PM_PATCH7_EN 0x02BC21FC +#define CS35L41_DSP1_PM_PATCH7_DATA_LO 0x02BC2200 +#define CS35L41_DSP1_PM_PATCH7_DATA_HI 0x02BC2204 +#define CS35L41_DSP1_MPU_XM_ACCESS0 0x02BC3000 +#define CS35L41_DSP1_MPU_YM_ACCESS0 0x02BC3004 +#define CS35L41_DSP1_MPU_WNDW_ACCESS0 0x02BC3008 +#define CS35L41_DSP1_MPU_XREG_ACCESS0 0x02BC300C +#define CS35L41_DSP1_MPU_YREG_ACCESS0 0x02BC3014 +#define CS35L41_DSP1_MPU_XM_ACCESS1 0x02BC3018 +#define CS35L41_DSP1_MPU_YM_ACCESS1 0x02BC301C +#define CS35L41_DSP1_MPU_WNDW_ACCESS1 0x02BC3020 +#define CS35L41_DSP1_MPU_XREG_ACCESS1 0x02BC3024 +#define CS35L41_DSP1_MPU_YREG_ACCESS1 0x02BC302C +#define CS35L41_DSP1_MPU_XM_ACCESS2 0x02BC3030 +#define CS35L41_DSP1_MPU_YM_ACCESS2 0x02BC3034 +#define CS35L41_DSP1_MPU_WNDW_ACCESS2 0x02BC3038 +#define CS35L41_DSP1_MPU_XREG_ACCESS2 0x02BC303C +#define CS35L41_DSP1_MPU_YREG_ACCESS2 0x02BC3044 +#define CS35L41_DSP1_MPU_XM_ACCESS3 0x02BC3048 +#define CS35L41_DSP1_MPU_YM_ACCESS3 0x02BC304C +#define CS35L41_DSP1_MPU_WNDW_ACCESS3 0x02BC3050 +#define CS35L41_DSP1_MPU_XREG_ACCESS3 0x02BC3054 +#define CS35L41_DSP1_MPU_YREG_ACCESS3 0x02BC305C +#define CS35L41_DSP1_MPU_XM_VIO_ADDR 0x02BC3100 +#define CS35L41_DSP1_MPU_XM_VIO_STATUS 0x02BC3104 +#define CS35L41_DSP1_MPU_YM_VIO_ADDR 0x02BC3108 +#define CS35L41_DSP1_MPU_YM_VIO_STATUS 0x02BC310C +#define CS35L41_DSP1_MPU_PM_VIO_ADDR 0x02BC3110 +#define CS35L41_DSP1_MPU_PM_VIO_STATUS 0x02BC3114 +#define CS35L41_DSP1_MPU_LOCK_CONFIG 0x02BC3140 +#define CS35L41_DSP1_MPU_WDT_RST_CTRL 0x02BC3180 +#define CS35L41_DSP1_STRMARB_MSTR0_CFG0 0x02BC5000 +#define CS35L41_DSP1_STRMARB_MSTR0_CFG1 0x02BC5004 +#define CS35L41_DSP1_STRMARB_MSTR0_CFG2 0x02BC5008 +#define CS35L41_DSP1_STRMARB_MSTR1_CFG0 0x02BC5010 +#define CS35L41_DSP1_STRMARB_MSTR1_CFG1 0x02BC5014 +#define CS35L41_DSP1_STRMARB_MSTR1_CFG2 0x02BC5018 +#define CS35L41_DSP1_STRMARB_MSTR2_CFG0 0x02BC5020 +#define CS35L41_DSP1_STRMARB_MSTR2_CFG1 0x02BC5024 +#define CS35L41_DSP1_STRMARB_MSTR2_CFG2 0x02BC5028 +#define CS35L41_DSP1_STRMARB_MSTR3_CFG0 0x02BC5030 +#define CS35L41_DSP1_STRMARB_MSTR3_CFG1 0x02BC5034 +#define CS35L41_DSP1_STRMARB_MSTR3_CFG2 0x02BC5038 +#define CS35L41_DSP1_STRMARB_MSTR4_CFG0 0x02BC5040 +#define CS35L41_DSP1_STRMARB_MSTR4_CFG1 0x02BC5044 +#define CS35L41_DSP1_STRMARB_MSTR4_CFG2 0x02BC5048 +#define CS35L41_DSP1_STRMARB_MSTR5_CFG0 0x02BC5050 +#define CS35L41_DSP1_STRMARB_MSTR5_CFG1 0x02BC5054 +#define CS35L41_DSP1_STRMARB_MSTR5_CFG2 0x02BC5058 +#define CS35L41_DSP1_STRMARB_MSTR6_CFG0 0x02BC5060 +#define CS35L41_DSP1_STRMARB_MSTR6_CFG1 0x02BC5064 +#define CS35L41_DSP1_STRMARB_MSTR6_CFG2 0x02BC5068 +#define CS35L41_DSP1_STRMARB_MSTR7_CFG0 0x02BC5070 +#define CS35L41_DSP1_STRMARB_MSTR7_CFG1 0x02BC5074 +#define CS35L41_DSP1_STRMARB_MSTR7_CFG2 0x02BC5078 +#define CS35L41_DSP1_STRMARB_TX0_CFG0 0x02BC5200 +#define CS35L41_DSP1_STRMARB_TX0_CFG1 0x02BC5204 +#define CS35L41_DSP1_STRMARB_TX1_CFG0 0x02BC5208 +#define CS35L41_DSP1_STRMARB_TX1_CFG1 0x02BC520C +#define CS35L41_DSP1_STRMARB_TX2_CFG0 0x02BC5210 +#define CS35L41_DSP1_STRMARB_TX2_CFG1 0x02BC5214 +#define CS35L41_DSP1_STRMARB_TX3_CFG0 0x02BC5218 +#define CS35L41_DSP1_STRMARB_TX3_CFG1 0x02BC521C +#define CS35L41_DSP1_STRMARB_TX4_CFG0 0x02BC5220 +#define CS35L41_DSP1_STRMARB_TX4_CFG1 0x02BC5224 +#define CS35L41_DSP1_STRMARB_TX5_CFG0 0x02BC5228 +#define CS35L41_DSP1_STRMARB_TX5_CFG1 0x02BC522C +#define CS35L41_DSP1_STRMARB_TX6_CFG0 0x02BC5230 +#define CS35L41_DSP1_STRMARB_TX6_CFG1 0x02BC5234 +#define CS35L41_DSP1_STRMARB_TX7_CFG0 0x02BC5238 +#define CS35L41_DSP1_STRMARB_TX7_CFG1 0x02BC523C +#define CS35L41_DSP1_STRMARB_RX0_CFG0 0x02BC5400 +#define CS35L41_DSP1_STRMARB_RX0_CFG1 0x02BC5404 +#define CS35L41_DSP1_STRMARB_RX1_CFG0 0x02BC5408 +#define CS35L41_DSP1_STRMARB_RX1_CFG1 0x02BC540C +#define CS35L41_DSP1_STRMARB_RX2_CFG0 0x02BC5410 +#define CS35L41_DSP1_STRMARB_RX2_CFG1 0x02BC5414 +#define CS35L41_DSP1_STRMARB_RX3_CFG0 0x02BC5418 +#define CS35L41_DSP1_STRMARB_RX3_CFG1 0x02BC541C +#define CS35L41_DSP1_STRMARB_RX4_CFG0 0x02BC5420 +#define CS35L41_DSP1_STRMARB_RX4_CFG1 0x02BC5424 +#define CS35L41_DSP1_STRMARB_RX5_CFG0 0x02BC5428 +#define CS35L41_DSP1_STRMARB_RX5_CFG1 0x02BC542C +#define CS35L41_DSP1_STRMARB_RX6_CFG0 0x02BC5430 +#define CS35L41_DSP1_STRMARB_RX6_CFG1 0x02BC5434 +#define CS35L41_DSP1_STRMARB_RX7_CFG0 0x02BC5438 +#define CS35L41_DSP1_STRMARB_RX7_CFG1 0x02BC543C +#define CS35L41_DSP1_STRMARB_IRQ0_CFG0 0x02BC5600 +#define CS35L41_DSP1_STRMARB_IRQ0_CFG1 0x02BC5604 +#define CS35L41_DSP1_STRMARB_IRQ0_CFG2 0x02BC5608 +#define CS35L41_DSP1_STRMARB_IRQ1_CFG0 0x02BC5610 +#define CS35L41_DSP1_STRMARB_IRQ1_CFG1 0x02BC5614 +#define CS35L41_DSP1_STRMARB_IRQ1_CFG2 0x02BC5618 +#define CS35L41_DSP1_STRMARB_IRQ2_CFG0 0x02BC5620 +#define CS35L41_DSP1_STRMARB_IRQ2_CFG1 0x02BC5624 +#define CS35L41_DSP1_STRMARB_IRQ2_CFG2 0x02BC5628 +#define CS35L41_DSP1_STRMARB_IRQ3_CFG0 0x02BC5630 +#define CS35L41_DSP1_STRMARB_IRQ3_CFG1 0x02BC5634 +#define CS35L41_DSP1_STRMARB_IRQ3_CFG2 0x02BC5638 +#define CS35L41_DSP1_STRMARB_IRQ4_CFG0 0x02BC5640 +#define CS35L41_DSP1_STRMARB_IRQ4_CFG1 0x02BC5644 +#define CS35L41_DSP1_STRMARB_IRQ4_CFG2 0x02BC5648 +#define CS35L41_DSP1_STRMARB_IRQ5_CFG0 0x02BC5650 +#define CS35L41_DSP1_STRMARB_IRQ5_CFG1 0x02BC5654 +#define CS35L41_DSP1_STRMARB_IRQ5_CFG2 0x02BC5658 +#define CS35L41_DSP1_STRMARB_IRQ6_CFG0 0x02BC5660 +#define CS35L41_DSP1_STRMARB_IRQ6_CFG1 0x02BC5664 +#define CS35L41_DSP1_STRMARB_IRQ6_CFG2 0x02BC5668 +#define CS35L41_DSP1_STRMARB_IRQ7_CFG0 0x02BC5670 +#define CS35L41_DSP1_STRMARB_IRQ7_CFG1 0x02BC5674 +#define CS35L41_DSP1_STRMARB_IRQ7_CFG2 0x02BC5678 +#define CS35L41_DSP1_STRMARB_RESYNC_MSK 0x02BC5A00 +#define CS35L41_DSP1_STRMARB_ERR_STATUS 0x02BC5A08 +#define CS35L41_DSP1_INTPCTL_RES_STATIC 0x02BC6000 +#define CS35L41_DSP1_INTPCTL_RES_DYN 0x02BC6004 +#define CS35L41_DSP1_INTPCTL_NMI_CTRL 0x02BC6008 +#define CS35L41_DSP1_INTPCTL_IRQ_INV 0x02BC6010 +#define CS35L41_DSP1_INTPCTL_IRQ_MODE 0x02BC6014 +#define CS35L41_DSP1_INTPCTL_IRQ_EN 0x02BC6018 +#define CS35L41_DSP1_INTPCTL_IRQ_MSK 0x02BC601C +#define CS35L41_DSP1_INTPCTL_IRQ_FLUSH 0x02BC6020 +#define CS35L41_DSP1_INTPCTL_IRQ_MSKCLR 0x02BC6024 +#define CS35L41_DSP1_INTPCTL_IRQ_FRC 0x02BC6028 +#define CS35L41_DSP1_INTPCTL_IRQ_MSKSET 0x02BC602C +#define CS35L41_DSP1_INTPCTL_IRQ_ERR 0x02BC6030 +#define CS35L41_DSP1_INTPCTL_IRQ_PEND 0x02BC6034 +#define CS35L41_DSP1_INTPCTL_IRQ_GEN 0x02BC6038 +#define CS35L41_DSP1_INTPCTL_TESTBITS 0x02BC6040 +#define CS35L41_DSP1_WDT_CONTROL 0x02BC7000 +#define CS35L41_DSP1_WDT_STATUS 0x02BC7008 +#define CS35L41_DSP1_YMEM_PACK_0 0x02C00000 +#define CS35L41_DSP1_YMEM_PACK_1532 0x02C017F0 +#define CS35L41_DSP1_YMEM_UNPACK32_0 0x03000000 +#define CS35L41_DSP1_YMEM_UNPACK32_1022 0x03000FF8 +#define CS35L41_DSP1_YMEM_UNPACK24_0 0x03400000 +#define CS35L41_DSP1_YMEM_UNPACK24_2045 0x03401FF4 +#define CS35L41_DSP1_PMEM_0 0x03800000 +#define CS35L41_DSP1_PMEM_5114 0x03804FE8 + +/*test regs for emulation bringup*/ +#define CS35L41_PLL_OVR 0x00003018 +#define CS35L41_BST_TEST_DUTY 0x00003900 +#define CS35L41_DIGPWM_IOCTRL 0x0000706C + +/*registers populated by OTP*/ +#define CS35L41_OTP_TRIM_1 0x0000208c +#define CS35L41_OTP_TRIM_2 0x00002090 +#define CS35L41_OTP_TRIM_3 0x00003010 +#define CS35L41_OTP_TRIM_4 0x0000300C +#define CS35L41_OTP_TRIM_5 0x0000394C +#define CS35L41_OTP_TRIM_6 0x00003950 +#define CS35L41_OTP_TRIM_7 0x00003954 +#define CS35L41_OTP_TRIM_8 0x00003958 +#define CS35L41_OTP_TRIM_9 0x0000395C +#define CS35L41_OTP_TRIM_10 0x0000416C +#define CS35L41_OTP_TRIM_11 0x00004160 +#define CS35L41_OTP_TRIM_12 0x00004170 +#define CS35L41_OTP_TRIM_13 0x00004360 +#define CS35L41_OTP_TRIM_14 0x00004448 +#define CS35L41_OTP_TRIM_15 0x0000444C +#define CS35L41_OTP_TRIM_16 0x00006E30 +#define CS35L41_OTP_TRIM_17 0x00006E34 +#define CS35L41_OTP_TRIM_18 0x00006E38 +#define CS35L41_OTP_TRIM_19 0x00006E3C +#define CS35L41_OTP_TRIM_20 0x00006E40 +#define CS35L41_OTP_TRIM_21 0x00006E44 +#define CS35L41_OTP_TRIM_22 0x00006E48 +#define CS35L41_OTP_TRIM_23 0x00006E4C +#define CS35L41_OTP_TRIM_24 0x00006E50 +#define CS35L41_OTP_TRIM_25 0x00006E54 +#define CS35L41_OTP_TRIM_26 0x00006E58 +#define CS35L41_OTP_TRIM_27 0x00006E5C +#define CS35L41_OTP_TRIM_28 0x00006E60 +#define CS35L41_OTP_TRIM_29 0x00006E64 +#define CS35L41_OTP_TRIM_30 0x00007418 +#define CS35L41_OTP_TRIM_31 0x0000741C +#define CS35L41_OTP_TRIM_32 0x00007434 +#define CS35L41_OTP_TRIM_33 0x00007068 +#define CS35L41_OTP_TRIM_34 0x0000410C +#define CS35L41_OTP_TRIM_35 0x0000400C +#define CS35L41_OTP_TRIM_36 0x00002030 + +#define CS35L41_MAX_CACHE_REG 36 +#define CS35L41_OTP_SIZE_WORDS 32 +#define CS35L41_NUM_OTP_ELEM 100 +#define CS35L41_NUM_OTP_MAPS 5 + +#define CS35L41_VALID_PDATA 0x80000000 +#define CS35L41_NUM_SUPPLIES 2 + +#define CS35L41_SCLK_MSTR_MASK 0x10 +#define CS35L41_SCLK_MSTR_SHIFT 4 +#define CS35L41_LRCLK_MSTR_MASK 0x01 +#define CS35L41_LRCLK_MSTR_SHIFT 0 +#define CS35L41_SCLK_INV_MASK 0x40 +#define CS35L41_SCLK_INV_SHIFT 6 +#define CS35L41_LRCLK_INV_MASK 0x04 +#define CS35L41_LRCLK_INV_SHIFT 2 +#define CS35L41_SCLK_FRC_MASK 0x20 +#define CS35L41_SCLK_FRC_SHIFT 5 +#define CS35L41_LRCLK_FRC_MASK 0x02 +#define CS35L41_LRCLK_FRC_SHIFT 1 + +#define CS35L41_AMP_GAIN_PCM_MASK 0x3E0 +#define CS35L41_AMP_GAIN_ZC_MASK 0x0400 +#define CS35L41_AMP_GAIN_ZC_SHIFT 10 + +#define CS35L41_BST_CTL_MASK 0xFF +#define CS35L41_BST_CTL_SEL_MASK 0x03 +#define CS35L41_BST_CTL_SEL_REG 0x00 +#define CS35L41_BST_CTL_SEL_CLASSH 0x01 +#define CS35L41_BST_IPK_MASK 0x7F +#define CS35L41_BST_IPK_SHIFT 0 +#define CS35L41_BST_LIM_MASK 0x4 +#define CS35L41_BST_LIM_SHIFT 2 +#define CS35L41_BST_K1_MASK 0x000000FF +#define CS35L41_BST_K1_SHIFT 0 +#define CS35L41_BST_K2_MASK 0x0000FF00 +#define CS35L41_BST_K2_SHIFT 8 +#define CS35L41_BST_SLOPE_MASK 0x0000FF00 +#define CS35L41_BST_SLOPE_SHIFT 8 +#define CS35L41_BST_LBST_VAL_MASK 0x00000003 +#define CS35L41_BST_LBST_VAL_SHIFT 0 + +#define CS35L41_TEMP_THLD_MASK 0x03 +#define CS35L41_VMON_IMON_VOL_MASK 0x07FF07FF +#define CS35L41_PDM_MODE_MASK 0x01 +#define CS35L41_PDM_MODE_SHIFT 0 + +#define CS35L41_CH_MEM_DEPTH_MASK 0x07 +#define CS35L41_CH_MEM_DEPTH_SHIFT 0 +#define CS35L41_CH_HDRM_CTL_MASK 0x007F0000 +#define CS35L41_CH_HDRM_CTL_SHIFT 16 +#define CS35L41_CH_REL_RATE_MASK 0xFF00 +#define CS35L41_CH_REL_RATE_SHIFT 8 +#define CS35L41_CH_WKFET_DLY_MASK 0x001C +#define CS35L41_CH_WKFET_DLY_SHIFT 2 +#define CS35L41_CH_WKFET_THLD_MASK 0x0F00 +#define CS35L41_CH_WKFET_THLD_SHIFT 8 + +#define CS35L41_HW_NG_SEL_MASK 0x3F00 +#define CS35L41_HW_NG_SEL_SHIFT 8 +#define CS35L41_HW_NG_DLY_MASK 0x0070 +#define CS35L41_HW_NG_DLY_SHIFT 4 +#define CS35L41_HW_NG_THLD_MASK 0x0007 +#define CS35L41_HW_NG_THLD_SHIFT 0 + +#define CS35L41_DSP_NG_ENABLE_MASK 0x00010000 +#define CS35L41_DSP_NG_ENABLE_SHIFT 16 +#define CS35L41_DSP_NG_THLD_MASK 0x7 +#define CS35L41_DSP_NG_THLD_SHIFT 0 +#define CS35L41_DSP_NG_DELAY_MASK 0x0F00 +#define CS35L41_DSP_NG_DELAY_SHIFT 8 + +#define CS35L41_ASP_FMT_MASK 0x0700 +#define CS35L41_ASP_FMT_SHIFT 8 +#define CS35L41_ASP_DOUT_HIZ_MASK 0x03 +#define CS35L41_ASP_DOUT_HIZ_SHIFT 0 +#define CS35L41_ASP_WIDTH_16 0x10 +#define CS35L41_ASP_WIDTH_24 0x18 +#define CS35L41_ASP_WIDTH_32 0x20 +#define CS35L41_ASP_WIDTH_TX_MASK 0xFF0000 +#define CS35L41_ASP_WIDTH_TX_SHIFT 16 +#define CS35L41_ASP_WIDTH_RX_MASK 0xFF000000 +#define CS35L41_ASP_WIDTH_RX_SHIFT 24 +#define CS35L41_ASP_RX1_SLOT_MASK 0x3F +#define CS35L41_ASP_RX1_SLOT_SHIFT 0 +#define CS35L41_ASP_RX2_SLOT_MASK 0x3F00 +#define CS35L41_ASP_RX2_SLOT_SHIFT 8 +#define CS35L41_ASP_RX_WL_MASK 0x3F +#define CS35L41_ASP_TX_WL_MASK 0x3F +#define CS35L41_ASP_RX_WL_SHIFT 0 +#define CS35L41_ASP_TX_WL_SHIFT 0 +#define CS35L41_ASP_SOURCE_MASK 0x7F + +#define CS35L41_INPUT_SRC_ASPRX1 0x08 +#define CS35L41_INPUT_SRC_ASPRX2 0x09 +#define CS35L41_INPUT_SRC_VMON 0x18 +#define CS35L41_INPUT_SRC_IMON 0x19 +#define CS35L41_INPUT_SRC_CLASSH 0x21 +#define CS35L41_INPUT_SRC_VPMON 0x28 +#define CS35L41_INPUT_SRC_VBSTMON 0x29 +#define CS35L41_INPUT_SRC_TEMPMON 0x3A +#define CS35L41_INPUT_SRC_RSVD 0x3B +#define CS35L41_INPUT_DSP_TX1 0x32 +#define CS35L41_INPUT_DSP_TX2 0x33 + +#define CS35L41_PLL_CLK_SEL_MASK 0x07 +#define CS35L41_PLL_CLK_SEL_SHIFT 0 +#define CS35L41_PLL_CLK_EN_MASK 0x10 +#define CS35L41_PLL_CLK_EN_SHIFT 4 +#define CS35L41_PLL_OPENLOOP_MASK 0x0800 +#define CS35L41_PLL_OPENLOOP_SHIFT 11 +#define CS35L41_PLLSRC_SCLK 0 +#define CS35L41_PLLSRC_LRCLK 1 +#define CS35L41_PLLSRC_SELF 3 +#define CS35L41_PLLSRC_PDMCLK 4 +#define CS35L41_PLLSRC_MCLK 5 +#define CS35L41_PLLSRC_SWIRE 7 +#define CS35L41_REFCLK_FREQ_MASK 0x7E0 +#define CS35L41_REFCLK_FREQ_SHIFT 5 + +#define CS35L41_GLOBAL_FS_MASK 0x1F +#define CS35L41_GLOBAL_FS_SHIFT 0 + +#define CS35L41_GLOBAL_EN_MASK 0x01 +#define CS35L41_GLOBAL_EN_SHIFT 0 +#define CS35L41_BST_EN_MASK 0x0030 +#define CS35L41_BST_EN_SHIFT 4 +#define CS35L41_BST_EN_DEFAULT 0x2 +#define CS35L41_AMP_EN_SHIFT 0 +#define CS35L41_AMP_EN_MASK 1 + +#define CS35L41_PDN_DONE_MASK 0x00800000 +#define CS35L41_PDN_DONE_SHIFT 23 +#define CS35L41_PUP_DONE_MASK 0x01000000 +#define CS35L41_PUP_DONE_SHIFT 24 + +#define CS35L36_PUP_DONE_IRQ_UNMASK 0x5F +#define CS35L36_PUP_DONE_IRQ_MASK 0xBF + +#define CS35L41_AMP_SHORT_ERR 0x80000000 +#define CS35L41_BST_SHORT_ERR 0x0100 +#define CS35L41_TEMP_WARN 0x8000 +#define CS35L41_TEMP_ERR 0x00020000 +#define CS35L41_BST_OVP_ERR 0x40 +#define CS35L41_BST_DCM_UVP_ERR 0x80 +#define CS35L41_OTP_BOOT_DONE 0x02 +#define CS35L41_PLL_UNLOCK 0x10 +#define CS35L41_OTP_BOOT_ERR 0x80000000 + +#define CS35L41_AMP_SHORT_ERR_RLS 0x02 +#define CS35L41_BST_SHORT_ERR_RLS 0x04 +#define CS35L41_BST_OVP_ERR_RLS 0x08 +#define CS35L41_BST_UVP_ERR_RLS 0x10 +#define CS35L41_TEMP_WARN_ERR_RLS 0x20 +#define CS35L41_TEMP_ERR_RLS 0x40 + +#define CS35L41_INT1_MASK_DEFAULT 0x7FFCFE3F +#define CS35L41_INT1_UNMASK_PUP 0xFEFFFFFF +#define CS35L41_INT1_UNMASK_PDN 0xFF7FFFFF + +#define CS35L41_GPIO_DIR_MASK 0x80000000 +#define CS35L41_GPIO_DIR_SHIFT 31 +#define CS35L41_GPIO1_CTRL_MASK 0x00030000 +#define CS35L41_GPIO1_CTRL_SHIFT 16 +#define CS35L41_GPIO2_CTRL_MASK 0x07000000 +#define CS35L41_GPIO2_CTRL_SHIFT 24 +#define CS35L41_GPIO_CTRL_OPEN_INT 2 +#define CS35L41_GPIO_CTRL_ACTV_LO 4 +#define CS35L41_GPIO_CTRL_ACTV_HI 5 +#define CS35L41_GPIO_POL_MASK 0x1000 +#define CS35L41_GPIO_POL_SHIFT 12 + +#define CS35L41_AMP_INV_PCM_SHIFT 14 +#define CS35L41_AMP_INV_PCM_MASK BIT(CS35L41_AMP_INV_PCM_SHIFT) +#define CS35L41_AMP_PCM_VOL_SHIFT 3 +#define CS35L41_AMP_PCM_VOL_MASK (0x7FF << 3) +#define CS35L41_AMP_PCM_VOL_MUTE 0x4CF + +#define CS35L41_CHIP_ID 0x35a40 +#define CS35L41R_CHIP_ID 0x35b40 +#define CS35L41_MTLREVID_MASK 0x0F +#define CS35L41_REVID_A0 0xA0 +#define CS35L41_REVID_B0 0xB0 +#define CS35L41_REVID_B2 0xB2 + +#define CS35L41_HALO_CORE_RESET 0x00000200 + +#define CS35L41_FS1_WINDOW_MASK 0x000007FF +#define CS35L41_FS2_WINDOW_MASK 0x00FFF800 +#define CS35L41_FS2_WINDOW_SHIFT 12 + +#define CS35L41_SPI_MAX_FREQ_OTP 4000000 + +#define CS35L41_RX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +#define CS35L41_TX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +bool cs35l41_readable_reg(struct device *dev, unsigned int reg); +bool cs35l41_precious_reg(struct device *dev, unsigned int reg); +bool cs35l41_volatile_reg(struct device *dev, unsigned int reg); + +struct cs35l41_otp_packed_element_t { + u32 reg; + u8 shift; + u8 size; +}; + +struct cs35l41_otp_map_element_t { + u32 id; + u32 num_elements; + const struct cs35l41_otp_packed_element_t *map; + u32 bit_offset; + u32 word_offset; +}; + +extern const struct reg_default cs35l41_reg[CS35L41_MAX_CACHE_REG]; +extern const struct cs35l41_otp_map_element_t + cs35l41_otp_map_map[CS35L41_NUM_OTP_MAPS]; + +#define CS35L41_REGSTRIDE 4 + +struct cs35l41_private { + struct snd_soc_codec *codec; + struct cs35l41_platform_data pdata; + struct device *dev; + struct regmap *regmap; + struct regulator_bulk_data supplies[CS35L41_NUM_SUPPLIES]; + int irq; + /* GPIO for /RST */ + struct gpio_desc *reset_gpio; + void (*otp_setup)(struct cs35l41_private *cs35l41, bool is_pre_setup, + unsigned int *freq); +}; + +int cs35l41_probe(struct cs35l41_private *cs35l41, + struct cs35l41_platform_data *pdata); +void cs35l41_remove(struct cs35l41_private *cs35l41); + +#endif /*__CS35L41_H__*/ diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 9a463ab54bdd..27a1c4c73074 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -25,7 +25,6 @@ #include <linux/regulator/consumer.h> #include <linux/gpio/consumer.h> #include <linux/of_device.h> -#include <linux/pm_runtime.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -41,7 +40,6 @@ static const struct reg_default cs42l42_reg_defaults[] = { { CS42L42_FRZ_CTL, 0x00 }, { CS42L42_SRC_CTL, 0x10 }, - { CS42L42_MCLK_STATUS, 0x02 }, { CS42L42_MCLK_CTL, 0x02 }, { CS42L42_SFTRAMP_RATE, 0xA4 }, { CS42L42_I2C_DEBOUNCE, 0x88 }, @@ -53,15 +51,12 @@ static const struct reg_default cs42l42_reg_defaults[] = { { CS42L42_RSENSE_CTL1, 0x40 }, { CS42L42_RSENSE_CTL2, 0x00 }, { CS42L42_OSC_SWITCH, 0x00 }, - { CS42L42_OSC_SWITCH_STATUS, 0x05 }, { CS42L42_RSENSE_CTL3, 0x1B }, { CS42L42_TSENSE_CTL, 0x1B }, { CS42L42_TSRS_INT_DISABLE, 0x00 }, - { CS42L42_TRSENSE_STATUS, 0x00 }, { CS42L42_HSDET_CTL1, 0x77 }, { CS42L42_HSDET_CTL2, 0x00 }, { CS42L42_HS_SWITCH_CTL, 0xF3 }, - { CS42L42_HS_DET_STATUS, 0x00 }, { CS42L42_HS_CLAMP_DISABLE, 0x00 }, { CS42L42_MCLK_SRC_SEL, 0x00 }, { CS42L42_SPDIF_CLK_CFG, 0x00 }, @@ -75,25 +70,13 @@ static const struct reg_default cs42l42_reg_defaults[] = { { CS42L42_IN_ASRC_CLK, 0x00 }, { CS42L42_OUT_ASRC_CLK, 0x00 }, { CS42L42_PLL_DIV_CFG1, 0x00 }, - { CS42L42_ADC_OVFL_STATUS, 0x00 }, - { CS42L42_MIXER_STATUS, 0x00 }, - { CS42L42_SRC_STATUS, 0x00 }, - { CS42L42_ASP_RX_STATUS, 0x00 }, - { CS42L42_ASP_TX_STATUS, 0x00 }, - { CS42L42_CODEC_STATUS, 0x00 }, - { CS42L42_DET_INT_STATUS1, 0x00 }, - { CS42L42_DET_INT_STATUS2, 0x00 }, - { CS42L42_SRCPL_INT_STATUS, 0x00 }, - { CS42L42_VPMON_STATUS, 0x00 }, - { CS42L42_PLL_LOCK_STATUS, 0x00 }, - { CS42L42_TSRS_PLUG_STATUS, 0x00 }, { CS42L42_ADC_OVFL_INT_MASK, 0x01 }, { CS42L42_MIXER_INT_MASK, 0x0F }, { CS42L42_SRC_INT_MASK, 0x0F }, { CS42L42_ASP_RX_INT_MASK, 0x1F }, { CS42L42_ASP_TX_INT_MASK, 0x0F }, { CS42L42_CODEC_INT_MASK, 0x03 }, - { CS42L42_SRCPL_INT_MASK, 0xFF }, + { CS42L42_SRCPL_INT_MASK, 0x7F }, { CS42L42_VPMON_INT_MASK, 0x01 }, { CS42L42_PLL_LOCK_INT_MASK, 0x01 }, { CS42L42_TSRS_PLUG_INT_MASK, 0x0F }, @@ -105,8 +88,6 @@ static const struct reg_default cs42l42_reg_defaults[] = { { CS42L42_PLL_CTL3, 0x10 }, { CS42L42_PLL_CAL_RATIO, 0x80 }, { CS42L42_PLL_CTL4, 0x03 }, - { CS42L42_LOAD_DET_RCSTAT, 0x00 }, - { CS42L42_LOAD_DET_DONE, 0x00 }, { CS42L42_LOAD_DET_EN, 0x00 }, { CS42L42_HSBIAS_SC_AUTOCTL, 0x03 }, { CS42L42_WAKE_CTL, 0xC0 }, @@ -115,8 +96,6 @@ static const struct reg_default cs42l42_reg_defaults[] = { { CS42L42_MISC_DET_CTL, 0x03 }, { CS42L42_MIC_DET_CTL1, 0x1F }, { CS42L42_MIC_DET_CTL2, 0x2F }, - { CS42L42_DET_STATUS1, 0x00 }, - { CS42L42_DET_STATUS2, 0x00 }, { CS42L42_DET_INT1_MASK, 0xE0 }, { CS42L42_DET_INT2_MASK, 0xFF }, { CS42L42_HS_BIAS_CTL, 0xC2 }, @@ -130,7 +109,7 @@ static const struct reg_default cs42l42_reg_defaults[] = { { CS42L42_MIXER_CHA_VOL, 0x3F }, { CS42L42_MIXER_ADC_VOL, 0x3F }, { CS42L42_MIXER_CHB_VOL, 0x3F }, - { CS42L42_EQ_COEF_IN0, 0x22 }, + { CS42L42_EQ_COEF_IN0, 0x00 }, { CS42L42_EQ_COEF_IN1, 0x00 }, { CS42L42_EQ_COEF_IN2, 0x00 }, { CS42L42_EQ_COEF_IN3, 0x00 }, @@ -182,7 +161,6 @@ static const struct reg_default cs42l42_reg_defaults[] = { { CS42L42_ASP_RX_DAI1_CH2_AP_RES, 0x03 }, { CS42L42_ASP_RX_DAI1_CH2_BIT_MSB, 0x00 }, { CS42L42_ASP_RX_DAI1_CH2_BIT_LSB, 0x00 }, - { CS42L42_SUB_REVID, 0x03 }, }; static bool cs42l42_readable_register(struct device *dev, unsigned int reg) @@ -351,6 +329,7 @@ static bool cs42l42_volatile_register(struct device *dev, unsigned int reg) case CS42L42_DEVID_CD: case CS42L42_DEVID_E: case CS42L42_MCLK_STATUS: + case CS42L42_OSC_SWITCH_STATUS: case CS42L42_TRSENSE_STATUS: case CS42L42_HS_DET_STATUS: case CS42L42_ADC_OVFL_STATUS: @@ -455,10 +434,36 @@ static const struct snd_kcontrol_new cs42l42_snd_controls[] = { 0x3f, 1, mixer_tlv) }; +static int cs42l42_hp_adc_ev(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 cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + cs42l42->hp_adc_up_pending = true; + break; + case SND_SOC_DAPM_POST_PMU: + /* Only need one delay if HP and ADC are both powering-up */ + if (cs42l42->hp_adc_up_pending) { + usleep_range(CS42L42_HP_ADC_EN_TIME_US, + CS42L42_HP_ADC_EN_TIME_US + 1000); + cs42l42->hp_adc_up_pending = false; + } + break; + default: + break; + } + + return 0; +} + static const struct snd_soc_dapm_widget cs42l42_dapm_widgets[] = { /* Playback Path */ SND_SOC_DAPM_OUTPUT("HP"), - SND_SOC_DAPM_DAC("DAC", NULL, CS42L42_PWR_CTL1, CS42L42_HP_PDN_SHIFT, 1), + SND_SOC_DAPM_DAC_E("DAC", NULL, CS42L42_PWR_CTL1, CS42L42_HP_PDN_SHIFT, 1, + cs42l42_hp_adc_ev, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_MIXER("MIXER", CS42L42_PWR_CTL1, CS42L42_MIXER_PDN_SHIFT, 1, NULL, 0), SND_SOC_DAPM_AIF_IN("SDIN1", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("SDIN2", NULL, 1, SND_SOC_NOPM, 0, 0), @@ -468,7 +473,8 @@ static const struct snd_soc_dapm_widget cs42l42_dapm_widgets[] = { /* Capture Path */ SND_SOC_DAPM_INPUT("HS"), - SND_SOC_DAPM_ADC("ADC", NULL, CS42L42_PWR_CTL1, CS42L42_ADC_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC_E("ADC", NULL, CS42L42_PWR_CTL1, CS42L42_ADC_PDN_SHIFT, 1, + cs42l42_hp_adc_ev, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_AIF_OUT("SDOUT1", NULL, 0, CS42L42_ASP_TX_CH_EN, CS42L42_ASP_TX0_CH1_SHIFT, 0), SND_SOC_DAPM_AIF_OUT("SDOUT2", NULL, 1, CS42L42_ASP_TX_CH_EN, CS42L42_ASP_TX0_CH2_SHIFT, 0), @@ -517,26 +523,10 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ cs42l42->jack = jk; - regmap_update_bits(cs42l42->regmap, CS42L42_TSRS_PLUG_INT_MASK, - CS42L42_RS_PLUG_MASK | CS42L42_RS_UNPLUG_MASK | - CS42L42_TS_PLUG_MASK | CS42L42_TS_UNPLUG_MASK, - (1 << CS42L42_RS_PLUG_SHIFT) | (1 << CS42L42_RS_UNPLUG_SHIFT) | - (0 << CS42L42_TS_PLUG_SHIFT) | (0 << CS42L42_TS_UNPLUG_SHIFT)); - - return 0; -} - -static int cs42l42_component_probe(struct snd_soc_component *component) -{ - struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); - - cs42l42->component = component; - return 0; } static const struct snd_soc_component_driver soc_component_dev_cs42l42 = { - .probe = cs42l42_component_probe, .set_jack = cs42l42_set_jack, .dapm_widgets = cs42l42_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(cs42l42_dapm_widgets), @@ -569,7 +559,6 @@ static const struct reg_sequence cs42l42_to_osc_seq[] = { struct cs42l42_pll_params { u32 sclk; - u8 mclk_div; u8 mclk_src_sel; u8 sclk_prediv; u8 pll_div_int; @@ -586,24 +575,24 @@ struct cs42l42_pll_params { * Table 4-5 from the Datasheet */ static const struct cs42l42_pll_params pll_ratio_table[] = { - { 1411200, 0, 1, 0x00, 0x80, 0x000000, 0x03, 0x10, 11289600, 128, 2}, - { 1536000, 0, 1, 0x00, 0x7D, 0x000000, 0x03, 0x10, 12000000, 125, 2}, - { 2304000, 0, 1, 0x00, 0x55, 0xC00000, 0x02, 0x10, 12288000, 85, 2}, - { 2400000, 0, 1, 0x00, 0x50, 0x000000, 0x03, 0x10, 12000000, 80, 2}, - { 2822400, 0, 1, 0x00, 0x40, 0x000000, 0x03, 0x10, 11289600, 128, 1}, - { 3000000, 0, 1, 0x00, 0x40, 0x000000, 0x03, 0x10, 12000000, 128, 1}, - { 3072000, 0, 1, 0x00, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125, 1}, - { 4000000, 0, 1, 0x00, 0x30, 0x800000, 0x03, 0x10, 12000000, 96, 1}, - { 4096000, 0, 1, 0x00, 0x2E, 0xE00000, 0x03, 0x10, 12000000, 94, 1}, - { 5644800, 0, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 11289600, 128, 1}, - { 6000000, 0, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 12000000, 128, 1}, - { 6144000, 0, 1, 0x01, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125, 1}, - { 11289600, 0, 0, 0, 0, 0, 0, 0, 11289600, 0, 1}, - { 12000000, 0, 0, 0, 0, 0, 0, 0, 12000000, 0, 1}, - { 12288000, 0, 0, 0, 0, 0, 0, 0, 12288000, 0, 1}, - { 22579200, 1, 0, 0, 0, 0, 0, 0, 22579200, 0, 1}, - { 24000000, 1, 0, 0, 0, 0, 0, 0, 24000000, 0, 1}, - { 24576000, 1, 0, 0, 0, 0, 0, 0, 24576000, 0, 1} + { 1411200, 1, 0x00, 0x80, 0x000000, 0x03, 0x10, 11289600, 128, 2}, + { 1536000, 1, 0x00, 0x7D, 0x000000, 0x03, 0x10, 12000000, 125, 2}, + { 2304000, 1, 0x00, 0x55, 0xC00000, 0x02, 0x10, 12288000, 85, 2}, + { 2400000, 1, 0x00, 0x50, 0x000000, 0x03, 0x10, 12000000, 80, 2}, + { 2822400, 1, 0x00, 0x40, 0x000000, 0x03, 0x10, 11289600, 128, 1}, + { 3000000, 1, 0x00, 0x40, 0x000000, 0x03, 0x10, 12000000, 128, 1}, + { 3072000, 1, 0x00, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125, 1}, + { 4000000, 1, 0x00, 0x30, 0x800000, 0x03, 0x10, 12000000, 96, 1}, + { 4096000, 1, 0x00, 0x2E, 0xE00000, 0x03, 0x10, 12000000, 94, 1}, + { 5644800, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 11289600, 128, 1}, + { 6000000, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 12000000, 128, 1}, + { 6144000, 1, 0x01, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125, 1}, + { 11289600, 0, 0, 0, 0, 0, 0, 11289600, 0, 1}, + { 12000000, 0, 0, 0, 0, 0, 0, 12000000, 0, 1}, + { 12288000, 0, 0, 0, 0, 0, 0, 12288000, 0, 1}, + { 22579200, 1, 0x03, 0x40, 0x000000, 0x03, 0x10, 11289600, 128, 1}, + { 24000000, 1, 0x03, 0x40, 0x000000, 0x03, 0x10, 12000000, 128, 1}, + { 24576000, 1, 0x03, 0x40, 0x000000, 0x03, 0x10, 12288000, 128, 1} }; static int cs42l42_pll_config(struct snd_soc_component *component) @@ -618,6 +607,14 @@ static int cs42l42_pll_config(struct snd_soc_component *component) else clk = cs42l42->sclk; + /* Don't reconfigure if there is an audio stream running */ + if (cs42l42->stream_use) { + if (pll_ratio_table[cs42l42->pll_config].sclk == clk) + return 0; + else + return -EBUSY; + } + for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) { if (pll_ratio_table[i].sclk == clk) { cs42l42->pll_config = i; @@ -631,10 +628,6 @@ static int cs42l42_pll_config(struct snd_soc_component *component) 24000000)) << CS42L42_INTERNAL_FS_SHIFT); - snd_soc_component_update_bits(component, CS42L42_MCLK_SRC_SEL, - CS42L42_MCLKDIV_MASK, - (pll_ratio_table[i].mclk_div << - CS42L42_MCLKDIV_SHIFT)); /* Set up the LRCLK */ fsync = clk / cs42l42->srate; if (((fsync * cs42l42->srate) != clk) @@ -668,22 +661,6 @@ static int cs42l42_pll_config(struct snd_soc_component *component) CS42L42_FSYNC_PULSE_WIDTH_MASK, CS42L42_FRAC1_VAL(fsync - 1) << CS42L42_FSYNC_PULSE_WIDTH_SHIFT); - /* Set the sample rates (96k or lower) */ - snd_soc_component_update_bits(component, CS42L42_FS_RATE_EN, - CS42L42_FS_EN_MASK, - (CS42L42_FS_EN_IASRC_96K | - CS42L42_FS_EN_OASRC_96K) << - CS42L42_FS_EN_SHIFT); - /* Set the input/output internal MCLK clock ~12 MHz */ - snd_soc_component_update_bits(component, CS42L42_IN_ASRC_CLK, - CS42L42_CLK_IASRC_SEL_MASK, - CS42L42_CLK_IASRC_SEL_12 << - CS42L42_CLK_IASRC_SEL_SHIFT); - snd_soc_component_update_bits(component, - CS42L42_OUT_ASRC_CLK, - CS42L42_CLK_OASRC_SEL_MASK, - CS42L42_CLK_OASRC_SEL_12 << - CS42L42_CLK_OASRC_SEL_SHIFT); if (pll_ratio_table[i].mclk_src_sel == 0) { /* Pass the clock straight through */ snd_soc_component_update_bits(component, @@ -746,6 +723,39 @@ static int cs42l42_pll_config(struct snd_soc_component *component) return -EINVAL; } +static void cs42l42_src_config(struct snd_soc_component *component, unsigned int sample_rate) +{ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + unsigned int fs; + + /* Don't reconfigure if there is an audio stream running */ + if (cs42l42->stream_use) + return; + + /* SRC MCLK must be as close as possible to 125 * sample rate */ + if (sample_rate <= 48000) + fs = CS42L42_CLK_IASRC_SEL_6; + else + fs = CS42L42_CLK_IASRC_SEL_12; + + /* Set the sample rates (96k or lower) */ + snd_soc_component_update_bits(component, + CS42L42_FS_RATE_EN, + CS42L42_FS_EN_MASK, + (CS42L42_FS_EN_IASRC_96K | + CS42L42_FS_EN_OASRC_96K) << + CS42L42_FS_EN_SHIFT); + + snd_soc_component_update_bits(component, + CS42L42_IN_ASRC_CLK, + CS42L42_CLK_IASRC_SEL_MASK, + fs << CS42L42_CLK_IASRC_SEL_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_OUT_ASRC_CLK, + CS42L42_CLK_OASRC_SEL_MASK, + fs << CS42L42_CLK_OASRC_SEL_SHIFT); +} + static int cs42l42_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_component *component = codec_dai->component; @@ -824,7 +834,7 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s /* Machine driver has not set a SCLK, limit bottom end to 44.1 kHz */ return snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_RATE, - 44100, 192000); + 44100, 96000); } static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, @@ -836,6 +846,7 @@ static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, unsigned int channels = params_channels(params); unsigned int width = (params_width(params) / 8) - 1; unsigned int val = 0; + int ret; cs42l42->srate = params_rate(params); cs42l42->bclk = snd_soc_params_to_bclk(params); @@ -851,13 +862,12 @@ static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, if (params_width(params) == 24) cs42l42->bclk = (cs42l42->bclk / 3) * 4; - switch(substream->stream) { + switch (substream->stream) { case SNDRV_PCM_STREAM_CAPTURE: - if (channels == 2) { - val |= CS42L42_ASP_TX_CH2_AP_MASK; - val |= width << CS42L42_ASP_TX_CH2_RES_SHIFT; - } - val |= width << CS42L42_ASP_TX_CH1_RES_SHIFT; + /* channel 2 on high LRCLK */ + val = CS42L42_ASP_TX_CH2_AP_MASK | + (width << CS42L42_ASP_TX_CH2_RES_SHIFT) | + (width << CS42L42_ASP_TX_CH1_RES_SHIFT); snd_soc_component_update_bits(component, CS42L42_ASP_TX_CH_AP_RES, CS42L42_ASP_TX_CH1_AP_MASK | CS42L42_ASP_TX_CH2_AP_MASK | @@ -890,7 +900,13 @@ static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, break; } - return cs42l42_pll_config(component); + ret = cs42l42_pll_config(component); + if (ret) + return ret; + + cs42l42_src_config(component, params_rate(params)); + + return 0; } static int cs42l42_set_sysclk(struct snd_soc_dai *dai, @@ -934,7 +950,7 @@ static int cs42l42_mute_stream(struct snd_soc_dai *dai, int mute, int stream) CS42L42_HP_ANA_BMUTE_MASK); cs42l42->stream_use &= ~(1 << stream); - if(!cs42l42->stream_use) { + if (!cs42l42->stream_use) { /* * Switch to the internal oscillator. * SCLK must remain running until after this clock switch. @@ -1005,7 +1021,7 @@ static int cs42l42_mute_stream(struct snd_soc_dai *dai, int mute, int stream) #define CS42L42_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ SNDRV_PCM_FMTBIT_S24_LE |\ - SNDRV_PCM_FMTBIT_S32_LE ) + SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops cs42l42_ops = { .startup = cs42l42_dai_startup, @@ -1021,14 +1037,14 @@ static struct snd_soc_dai_driver cs42l42_dai = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, + .rates = SNDRV_PCM_RATE_8000_96000, .formats = CS42L42_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, + .rates = SNDRV_PCM_RATE_8000_96000, .formats = CS42L42_FORMATS, }, .symmetric_rate = 1, @@ -1036,11 +1052,121 @@ static struct snd_soc_dai_driver cs42l42_dai = { .ops = &cs42l42_ops, }; +static void cs42l42_manual_hs_type_detect(struct cs42l42_private *cs42l42) +{ + unsigned int hs_det_status; + unsigned int hs_det_comp1; + unsigned int hs_det_comp2; + unsigned int hs_det_sw; + + /* Set hs detect to manual, active mode */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL2, + CS42L42_HSDET_CTRL_MASK | + CS42L42_HSDET_SET_MASK | + CS42L42_HSBIAS_REF_MASK | + CS42L42_HSDET_AUTO_TIME_MASK, + (1 << CS42L42_HSDET_CTRL_SHIFT) | + (0 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (0 << CS42L42_HSDET_AUTO_TIME_SHIFT)); + + /* Configure HS DET comparator reference levels. */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL1, + CS42L42_HSDET_COMP1_LVL_MASK | + CS42L42_HSDET_COMP2_LVL_MASK, + (CS42L42_HSDET_COMP1_LVL_VAL << CS42L42_HSDET_COMP1_LVL_SHIFT) | + (CS42L42_HSDET_COMP2_LVL_VAL << CS42L42_HSDET_COMP2_LVL_SHIFT)); + + /* Open the SW_HSB_HS3 switch and close SW_HSB_HS4 for a Type 1 headset. */ + regmap_write(cs42l42->regmap, CS42L42_HS_SWITCH_CTL, CS42L42_HSDET_SW_COMP1); + + msleep(100); + + regmap_read(cs42l42->regmap, CS42L42_HS_DET_STATUS, &hs_det_status); + + hs_det_comp1 = (hs_det_status & CS42L42_HSDET_COMP1_OUT_MASK) >> + CS42L42_HSDET_COMP1_OUT_SHIFT; + hs_det_comp2 = (hs_det_status & CS42L42_HSDET_COMP2_OUT_MASK) >> + CS42L42_HSDET_COMP2_OUT_SHIFT; + + /* Close the SW_HSB_HS3 switch for a Type 2 headset. */ + regmap_write(cs42l42->regmap, CS42L42_HS_SWITCH_CTL, CS42L42_HSDET_SW_COMP2); + + msleep(100); + + regmap_read(cs42l42->regmap, CS42L42_HS_DET_STATUS, &hs_det_status); + + hs_det_comp1 |= ((hs_det_status & CS42L42_HSDET_COMP1_OUT_MASK) >> + CS42L42_HSDET_COMP1_OUT_SHIFT) << 1; + hs_det_comp2 |= ((hs_det_status & CS42L42_HSDET_COMP2_OUT_MASK) >> + CS42L42_HSDET_COMP2_OUT_SHIFT) << 1; + + /* Use Comparator 1 with 1.25V Threshold. */ + switch (hs_det_comp1) { + case CS42L42_HSDET_COMP_TYPE1: + cs42l42->hs_type = CS42L42_PLUG_CTIA; + hs_det_sw = CS42L42_HSDET_SW_TYPE1; + break; + case CS42L42_HSDET_COMP_TYPE2: + cs42l42->hs_type = CS42L42_PLUG_OMTP; + hs_det_sw = CS42L42_HSDET_SW_TYPE2; + break; + default: + /* Fallback to Comparator 2 with 1.75V Threshold. */ + switch (hs_det_comp2) { + case CS42L42_HSDET_COMP_TYPE1: + cs42l42->hs_type = CS42L42_PLUG_CTIA; + hs_det_sw = CS42L42_HSDET_SW_TYPE1; + break; + case CS42L42_HSDET_COMP_TYPE2: + cs42l42->hs_type = CS42L42_PLUG_OMTP; + hs_det_sw = CS42L42_HSDET_SW_TYPE2; + break; + case CS42L42_HSDET_COMP_TYPE3: + cs42l42->hs_type = CS42L42_PLUG_HEADPHONE; + hs_det_sw = CS42L42_HSDET_SW_TYPE3; + break; + default: + cs42l42->hs_type = CS42L42_PLUG_INVALID; + hs_det_sw = CS42L42_HSDET_SW_TYPE4; + break; + } + } + + /* Set Switches */ + regmap_write(cs42l42->regmap, CS42L42_HS_SWITCH_CTL, hs_det_sw); + + /* Set HSDET mode to Manual—Disabled */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL2, + CS42L42_HSDET_CTRL_MASK | + CS42L42_HSDET_SET_MASK | + CS42L42_HSBIAS_REF_MASK | + CS42L42_HSDET_AUTO_TIME_MASK, + (0 << CS42L42_HSDET_CTRL_SHIFT) | + (0 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (0 << CS42L42_HSDET_AUTO_TIME_SHIFT)); + + /* Configure HS DET comparator reference levels. */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL1, + CS42L42_HSDET_COMP1_LVL_MASK | + CS42L42_HSDET_COMP2_LVL_MASK, + (CS42L42_HSDET_COMP1_LVL_DEFAULT << CS42L42_HSDET_COMP1_LVL_SHIFT) | + (CS42L42_HSDET_COMP2_LVL_DEFAULT << CS42L42_HSDET_COMP2_LVL_SHIFT)); +} + static void cs42l42_process_hs_type_detect(struct cs42l42_private *cs42l42) { unsigned int hs_det_status; unsigned int int_status; + /* Read and save the hs detection result */ + regmap_read(cs42l42->regmap, CS42L42_HS_DET_STATUS, &hs_det_status); + /* Mask the auto detect interrupt */ regmap_update_bits(cs42l42->regmap, CS42L42_CODEC_INT_MASK, @@ -1049,6 +1175,10 @@ static void cs42l42_process_hs_type_detect(struct cs42l42_private *cs42l42) (1 << CS42L42_PDN_DONE_SHIFT) | (1 << CS42L42_HSDET_AUTO_DONE_SHIFT)); + + cs42l42->hs_type = (hs_det_status & CS42L42_HSDET_TYPE_MASK) >> + CS42L42_HSDET_TYPE_SHIFT; + /* Set hs detect to automatic, disabled mode */ regmap_update_bits(cs42l42->regmap, CS42L42_HSDET_CTL2, @@ -1061,11 +1191,15 @@ static void cs42l42_process_hs_type_detect(struct cs42l42_private *cs42l42) (0 << CS42L42_HSBIAS_REF_SHIFT) | (3 << CS42L42_HSDET_AUTO_TIME_SHIFT)); - /* Read and save the hs detection result */ - regmap_read(cs42l42->regmap, CS42L42_HS_DET_STATUS, &hs_det_status); - - cs42l42->hs_type = (hs_det_status & CS42L42_HSDET_TYPE_MASK) >> - CS42L42_HSDET_TYPE_SHIFT; + /* Run Manual detection if auto detect has not found a headset. + * We Re-Run with Manual Detection if the original detection was invalid or headphones, + * to ensure that a headset mic is detected in all cases. + */ + if (cs42l42->hs_type == CS42L42_PLUG_INVALID || + cs42l42->hs_type == CS42L42_PLUG_HEADPHONE) { + dev_dbg(cs42l42->dev, "Running Manual Detection Fallback\n"); + cs42l42_manual_hs_type_detect(cs42l42); + } /* Set up button detection */ if ((cs42l42->hs_type == CS42L42_PLUG_CTIA) || @@ -1362,19 +1496,19 @@ static int cs42l42_handle_button_press(struct cs42l42_private *cs42l42) switch (bias_level) { case 1: /* Function C button press */ bias_level = SND_JACK_BTN_2; - dev_dbg(cs42l42->component->dev, "Function C button press\n"); + dev_dbg(cs42l42->dev, "Function C button press\n"); break; case 2: /* Function B button press */ bias_level = SND_JACK_BTN_1; - dev_dbg(cs42l42->component->dev, "Function B button press\n"); + dev_dbg(cs42l42->dev, "Function B button press\n"); break; case 3: /* Function D button press */ bias_level = SND_JACK_BTN_3; - dev_dbg(cs42l42->component->dev, "Function D button press\n"); + dev_dbg(cs42l42->dev, "Function D button press\n"); break; case 4: /* Function A button press */ bias_level = SND_JACK_BTN_0; - dev_dbg(cs42l42->component->dev, "Function A button press\n"); + dev_dbg(cs42l42->dev, "Function A button press\n"); break; default: bias_level = 0; @@ -1448,7 +1582,6 @@ static const struct cs42l42_irq_params irq_params_table[] = { static irqreturn_t cs42l42_irq_thread(int irq, void *data) { struct cs42l42_private *cs42l42 = (struct cs42l42_private *)data; - struct snd_soc_component *component = cs42l42->component; unsigned int stickies[12]; unsigned int masks[12]; unsigned int current_plug_status; @@ -1482,7 +1615,7 @@ static irqreturn_t cs42l42_irq_thread(int irq, void *data) if ((~masks[5]) & irq_params_table[5].mask) { if (stickies[5] & CS42L42_HSDET_AUTO_DONE_MASK) { cs42l42_process_hs_type_detect(cs42l42); - switch(cs42l42->hs_type){ + switch (cs42l42->hs_type) { case CS42L42_PLUG_CTIA: case CS42L42_PLUG_OMTP: snd_soc_jack_report(cs42l42->jack, SND_JACK_HEADSET, @@ -1495,7 +1628,7 @@ static irqreturn_t cs42l42_irq_thread(int irq, void *data) default: break; } - dev_dbg(component->dev, "Auto detect done (%d)\n", cs42l42->hs_type); + dev_dbg(cs42l42->dev, "Auto detect done (%d)\n", cs42l42->hs_type); } } @@ -1514,7 +1647,7 @@ static irqreturn_t cs42l42_irq_thread(int irq, void *data) cs42l42->plug_state = CS42L42_TS_UNPLUG; cs42l42_cancel_hs_type_detect(cs42l42); - switch(cs42l42->hs_type){ + switch (cs42l42->hs_type) { case CS42L42_PLUG_CTIA: case CS42L42_PLUG_OMTP: snd_soc_jack_report(cs42l42->jack, 0, SND_JACK_HEADSET); @@ -1529,7 +1662,7 @@ static irqreturn_t cs42l42_irq_thread(int irq, void *data) SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3); - dev_dbg(component->dev, "Unplug event\n"); + dev_dbg(cs42l42->dev, "Unplug event\n"); } break; @@ -1545,7 +1678,7 @@ static irqreturn_t cs42l42_irq_thread(int irq, void *data) CS42L42_M_HSBIAS_HIZ_MASK)) { if (current_button_status & CS42L42_M_DETECT_TF_MASK) { - dev_dbg(component->dev, "Button released\n"); + dev_dbg(cs42l42->dev, "Button released\n"); report = 0; } else if (current_button_status & CS42L42_M_DETECT_FT_MASK) { report = cs42l42_handle_button_press(cs42l42); @@ -1658,8 +1791,8 @@ static void cs42l42_set_interrupt_masks(struct cs42l42_private *cs42l42) CS42L42_TS_UNPLUG_MASK, (1 << CS42L42_RS_PLUG_SHIFT) | (1 << CS42L42_RS_UNPLUG_SHIFT) | - (1 << CS42L42_TS_PLUG_SHIFT) | - (1 << CS42L42_TS_UNPLUG_SHIFT)); + (0 << CS42L42_TS_PLUG_SHIFT) | + (0 << CS42L42_TS_UNPLUG_SHIFT)); } static void cs42l42_setup_hs_type_detect(struct cs42l42_private *cs42l42) @@ -1685,12 +1818,15 @@ static void cs42l42_setup_hs_type_detect(struct cs42l42_private *cs42l42) (1 << CS42L42_HS_CLAMP_DISABLE_SHIFT)); /* Enable the tip sense circuit */ + regmap_update_bits(cs42l42->regmap, CS42L42_TSENSE_CTL, + CS42L42_TS_INV_MASK, CS42L42_TS_INV_MASK); + regmap_update_bits(cs42l42->regmap, CS42L42_TIPSENSE_CTL, CS42L42_TIP_SENSE_CTRL_MASK | CS42L42_TIP_SENSE_INV_MASK | CS42L42_TIP_SENSE_DEBOUNCE_MASK, (3 << CS42L42_TIP_SENSE_CTRL_SHIFT) | - (0 << CS42L42_TIP_SENSE_INV_SHIFT) | + (!cs42l42->ts_inv << CS42L42_TIP_SENSE_INV_SHIFT) | (2 << CS42L42_TIP_SENSE_DEBOUNCE_SHIFT)); /* Save the initial status of the tip sense */ @@ -1734,10 +1870,6 @@ static int cs42l42_handle_device_data(struct device *dev, cs42l42->ts_inv = CS42L42_TS_INV_DIS; } - regmap_update_bits(cs42l42->regmap, CS42L42_TSENSE_CTL, - CS42L42_TS_INV_MASK, - (cs42l42->ts_inv << CS42L42_TS_INV_SHIFT)); - ret = device_property_read_u32(dev, "cirrus,ts-dbnc-rise", &val); if (!ret) { switch (val) { @@ -1899,6 +2031,7 @@ static int cs42l42_i2c_probe(struct i2c_client *i2c_client, if (!cs42l42) return -ENOMEM; + cs42l42->dev = &i2c_client->dev; i2c_set_clientdata(i2c_client, cs42l42); cs42l42->regmap = devm_regmap_init_i2c(i2c_client, &cs42l42_regmap); @@ -1933,7 +2066,7 @@ static int cs42l42_i2c_probe(struct i2c_client *i2c_client, "reset", GPIOD_OUT_LOW); if (IS_ERR(cs42l42->reset_gpio)) { ret = PTR_ERR(cs42l42->reset_gpio); - goto err_disable; + goto err_disable_noreset; } if (cs42l42->reset_gpio) { @@ -1942,16 +2075,20 @@ static int cs42l42_i2c_probe(struct i2c_client *i2c_client, } usleep_range(CS42L42_BOOT_TIME_US, CS42L42_BOOT_TIME_US * 2); - /* Request IRQ */ - ret = devm_request_threaded_irq(&i2c_client->dev, - i2c_client->irq, - NULL, cs42l42_irq_thread, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "cs42l42", cs42l42); - - if (ret != 0) - dev_err(&i2c_client->dev, - "Failed to request IRQ: %d\n", ret); + /* Request IRQ if one was specified */ + if (i2c_client->irq) { + ret = request_threaded_irq(i2c_client->irq, + NULL, cs42l42_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs42l42", cs42l42); + if (ret == -EPROBE_DEFER) { + goto err_disable_noirq; + } else if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request IRQ: %d\n", ret); + goto err_disable_noirq; + } + } /* initialize codec */ devid = cirrus_read_device_id(cs42l42->regmap, CS42L42_DEVID_AB); @@ -1972,7 +2109,7 @@ static int cs42l42_i2c_probe(struct i2c_client *i2c_client, ret = regmap_read(cs42l42->regmap, CS42L42_REVID, ®); if (ret < 0) { dev_err(&i2c_client->dev, "Get Revision ID failed\n"); - goto err_disable; + goto err_shutdown; } dev_info(&i2c_client->dev, @@ -1997,7 +2134,7 @@ static int cs42l42_i2c_probe(struct i2c_client *i2c_client, ret = cs42l42_handle_device_data(&i2c_client->dev, cs42l42); if (ret != 0) - goto err_disable; + goto err_shutdown; /* Setup headset detection */ cs42l42_setup_hs_type_detect(cs42l42); @@ -2009,10 +2146,22 @@ static int cs42l42_i2c_probe(struct i2c_client *i2c_client, ret = devm_snd_soc_register_component(&i2c_client->dev, &soc_component_dev_cs42l42, &cs42l42_dai, 1); if (ret < 0) - goto err_disable; + goto err_shutdown; + return 0; +err_shutdown: + regmap_write(cs42l42->regmap, CS42L42_CODEC_INT_MASK, 0xff); + regmap_write(cs42l42->regmap, CS42L42_TSRS_PLUG_INT_MASK, 0xff); + regmap_write(cs42l42->regmap, CS42L42_PWR_CTL1, 0xff); + err_disable: + if (i2c_client->irq) + free_irq(i2c_client->irq, cs42l42); + +err_disable_noirq: + gpiod_set_value_cansleep(cs42l42->reset_gpio, 0); +err_disable_noreset: regulator_bulk_disable(ARRAY_SIZE(cs42l42->supplies), cs42l42->supplies); return ret; @@ -2022,59 +2171,22 @@ static int cs42l42_i2c_remove(struct i2c_client *i2c_client) { struct cs42l42_private *cs42l42 = i2c_get_clientdata(i2c_client); - devm_free_irq(&i2c_client->dev, i2c_client->irq, cs42l42); - pm_runtime_suspend(&i2c_client->dev); - pm_runtime_disable(&i2c_client->dev); + if (i2c_client->irq) + free_irq(i2c_client->irq, cs42l42); - return 0; -} - -#ifdef CONFIG_PM -static int cs42l42_runtime_suspend(struct device *dev) -{ - struct cs42l42_private *cs42l42 = dev_get_drvdata(dev); - - regcache_cache_only(cs42l42->regmap, true); - regcache_mark_dirty(cs42l42->regmap); + /* + * The driver might not have control of reset and power supplies, + * so ensure that the chip internals are powered down. + */ + regmap_write(cs42l42->regmap, CS42L42_CODEC_INT_MASK, 0xff); + regmap_write(cs42l42->regmap, CS42L42_TSRS_PLUG_INT_MASK, 0xff); + regmap_write(cs42l42->regmap, CS42L42_PWR_CTL1, 0xff); - /* Hold down reset */ gpiod_set_value_cansleep(cs42l42->reset_gpio, 0); - - /* remove power */ - regulator_bulk_disable(ARRAY_SIZE(cs42l42->supplies), - cs42l42->supplies); - - return 0; -} - -static int cs42l42_runtime_resume(struct device *dev) -{ - struct cs42l42_private *cs42l42 = dev_get_drvdata(dev); - int ret; - - /* Enable power */ - ret = regulator_bulk_enable(ARRAY_SIZE(cs42l42->supplies), - cs42l42->supplies); - if (ret != 0) { - dev_err(dev, "Failed to enable supplies: %d\n", - ret); - return ret; - } - - gpiod_set_value_cansleep(cs42l42->reset_gpio, 1); - usleep_range(CS42L42_BOOT_TIME_US, CS42L42_BOOT_TIME_US * 2); - - regcache_cache_only(cs42l42->regmap, false); - regcache_sync(cs42l42->regmap); + regulator_bulk_disable(ARRAY_SIZE(cs42l42->supplies), cs42l42->supplies); return 0; } -#endif - -static const struct dev_pm_ops cs42l42_runtime_pm = { - SET_RUNTIME_PM_OPS(cs42l42_runtime_suspend, cs42l42_runtime_resume, - NULL) -}; #ifdef CONFIG_OF static const struct of_device_id cs42l42_of_match[] = { @@ -2102,7 +2214,6 @@ MODULE_DEVICE_TABLE(i2c, cs42l42_id); static struct i2c_driver cs42l42_i2c_driver = { .driver = { .name = "cs42l42", - .pm = &cs42l42_runtime_pm, .of_match_table = of_match_ptr(cs42l42_of_match), .acpi_match_table = ACPI_PTR(cs42l42_acpi_match), }, diff --git a/sound/soc/codecs/cs42l42.h b/sound/soc/codecs/cs42l42.h index 8734f6828f3e..f45bcc9a3a62 100644 --- a/sound/soc/codecs/cs42l42.h +++ b/sound/soc/codecs/cs42l42.h @@ -188,6 +188,11 @@ #define CS42L42_HSDET_COMP2_LVL_SHIFT 4 #define CS42L42_HSDET_COMP2_LVL_MASK (15 << CS42L42_HSDET_COMP2_LVL_SHIFT) +#define CS42L42_HSDET_COMP1_LVL_VAL 12 /* 1.25V Comparator */ +#define CS42L42_HSDET_COMP2_LVL_VAL 2 /* 1.75V Comparator */ +#define CS42L42_HSDET_COMP1_LVL_DEFAULT 7 /* 1V Comparator */ +#define CS42L42_HSDET_COMP2_LVL_DEFAULT 7 /* 2V Comparator */ + #define CS42L42_HSDET_CTL2 (CS42L42_PAGE_11 + 0x20) #define CS42L42_HSDET_AUTO_TIME_SHIFT 0 #define CS42L42_HSDET_AUTO_TIME_MASK (3 << CS42L42_HSDET_AUTO_TIME_SHIFT) @@ -228,6 +233,60 @@ #define CS42L42_PLUG_HEADPHONE 2 #define CS42L42_PLUG_INVALID 3 +#define CS42L42_HSDET_SW_COMP1 ((0 << CS42L42_SW_GNDHS_HS4_SHIFT) | \ + (1 << CS42L42_SW_GNDHS_HS3_SHIFT) | \ + (1 << CS42L42_SW_HSB_HS4_SHIFT) | \ + (0 << CS42L42_SW_HSB_HS3_SHIFT) | \ + (0 << CS42L42_SW_HSB_FILT_HS4_SHIFT) | \ + (1 << CS42L42_SW_HSB_FILT_HS3_SHIFT) | \ + (0 << CS42L42_SW_REF_HS4_SHIFT) | \ + (1 << CS42L42_SW_REF_HS3_SHIFT)) +#define CS42L42_HSDET_SW_COMP2 ((1 << CS42L42_SW_GNDHS_HS4_SHIFT) | \ + (0 << CS42L42_SW_GNDHS_HS3_SHIFT) | \ + (0 << CS42L42_SW_HSB_HS4_SHIFT) | \ + (1 << CS42L42_SW_HSB_HS3_SHIFT) | \ + (1 << CS42L42_SW_HSB_FILT_HS4_SHIFT) | \ + (0 << CS42L42_SW_HSB_FILT_HS3_SHIFT) | \ + (1 << CS42L42_SW_REF_HS4_SHIFT) | \ + (0 << CS42L42_SW_REF_HS3_SHIFT)) +#define CS42L42_HSDET_SW_TYPE1 ((0 << CS42L42_SW_GNDHS_HS4_SHIFT) | \ + (1 << CS42L42_SW_GNDHS_HS3_SHIFT) | \ + (1 << CS42L42_SW_HSB_HS4_SHIFT) | \ + (0 << CS42L42_SW_HSB_HS3_SHIFT) | \ + (0 << CS42L42_SW_HSB_FILT_HS4_SHIFT) | \ + (1 << CS42L42_SW_HSB_FILT_HS3_SHIFT) | \ + (0 << CS42L42_SW_REF_HS4_SHIFT) | \ + (1 << CS42L42_SW_REF_HS3_SHIFT)) +#define CS42L42_HSDET_SW_TYPE2 ((1 << CS42L42_SW_GNDHS_HS4_SHIFT) | \ + (0 << CS42L42_SW_GNDHS_HS3_SHIFT) | \ + (0 << CS42L42_SW_HSB_HS4_SHIFT) | \ + (1 << CS42L42_SW_HSB_HS3_SHIFT) | \ + (1 << CS42L42_SW_HSB_FILT_HS4_SHIFT) | \ + (0 << CS42L42_SW_HSB_FILT_HS3_SHIFT) | \ + (1 << CS42L42_SW_REF_HS4_SHIFT) | \ + (0 << CS42L42_SW_REF_HS3_SHIFT)) +#define CS42L42_HSDET_SW_TYPE3 ((1 << CS42L42_SW_GNDHS_HS4_SHIFT) | \ + (1 << CS42L42_SW_GNDHS_HS3_SHIFT) | \ + (0 << CS42L42_SW_HSB_HS4_SHIFT) | \ + (0 << CS42L42_SW_HSB_HS3_SHIFT) | \ + (1 << CS42L42_SW_HSB_FILT_HS4_SHIFT) | \ + (1 << CS42L42_SW_HSB_FILT_HS3_SHIFT) | \ + (1 << CS42L42_SW_REF_HS4_SHIFT) | \ + (1 << CS42L42_SW_REF_HS3_SHIFT)) +#define CS42L42_HSDET_SW_TYPE4 ((0 << CS42L42_SW_GNDHS_HS4_SHIFT) | \ + (1 << CS42L42_SW_GNDHS_HS3_SHIFT) | \ + (1 << CS42L42_SW_HSB_HS4_SHIFT) | \ + (0 << CS42L42_SW_HSB_HS3_SHIFT) | \ + (0 << CS42L42_SW_HSB_FILT_HS4_SHIFT) | \ + (1 << CS42L42_SW_HSB_FILT_HS3_SHIFT) | \ + (0 << CS42L42_SW_REF_HS4_SHIFT) | \ + (1 << CS42L42_SW_REF_HS3_SHIFT)) + +#define CS42L42_HSDET_COMP_TYPE1 1 +#define CS42L42_HSDET_COMP_TYPE2 2 +#define CS42L42_HSDET_COMP_TYPE3 0 +#define CS42L42_HSDET_COMP_TYPE4 3 + #define CS42L42_HS_CLAMP_DISABLE (CS42L42_PAGE_11 + 0x29) #define CS42L42_HS_CLAMP_DISABLE_SHIFT 0 #define CS42L42_HS_CLAMP_DISABLE_MASK (1 << CS42L42_HS_CLAMP_DISABLE_SHIFT) @@ -288,6 +347,7 @@ #define CS42L42_IN_ASRC_CLK (CS42L42_PAGE_12 + 0x0A) #define CS42L42_CLK_IASRC_SEL_SHIFT 0 #define CS42L42_CLK_IASRC_SEL_MASK (1 << CS42L42_CLK_IASRC_SEL_SHIFT) +#define CS42L42_CLK_IASRC_SEL_6 0 #define CS42L42_CLK_IASRC_SEL_12 1 #define CS42L42_OUT_ASRC_CLK (CS42L42_PAGE_12 + 0x0B) @@ -761,6 +821,7 @@ #define CS42L42_CLOCK_SWITCH_DELAY_US 150 #define CS42L42_PLL_LOCK_POLL_US 250 #define CS42L42_PLL_LOCK_TIMEOUT_US 1250 +#define CS42L42_HP_ADC_EN_TIME_US 20000 static const char *const cs42l42_supply_names[CS42L42_NUM_SUPPLIES] = { "VA", @@ -772,7 +833,7 @@ static const char *const cs42l42_supply_names[CS42L42_NUM_SUPPLIES] = { struct cs42l42_private { struct regmap *regmap; - struct snd_soc_component *component; + struct device *dev; struct regulator_bulk_data supplies[CS42L42_NUM_SUPPLIES]; struct gpio_desc *reset_gpio; struct completion pdn_done; @@ -794,6 +855,7 @@ struct cs42l42_private { u8 hs_bias_ramp_time; u8 hs_bias_sense_en; u8 stream_use; + bool hp_adc_up_pending; }; #endif /* __CS42L42_H__ */ diff --git a/sound/soc/codecs/cs47l15.c b/sound/soc/codecs/cs47l15.c index 1ee83160b83f..391fd7da331f 100644 --- a/sound/soc/codecs/cs47l15.c +++ b/sound/soc/codecs/cs47l15.c @@ -45,7 +45,7 @@ struct cs47l15 { bool in1_lp_mode; }; -static const struct wm_adsp_region cs47l15_dsp1_regions[] = { +static const struct cs_dsp_region cs47l15_dsp1_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x080000 }, { .type = WMFW_ADSP2_ZM, .base = 0x0e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x0a0000 }, @@ -1402,18 +1402,18 @@ static int cs47l15_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "Failed to set DSP IRQ wake: %d\n", ret); cs47l15->core.adsp[0].part = "cs47l15"; - cs47l15->core.adsp[0].num = 1; - cs47l15->core.adsp[0].type = WMFW_ADSP2; - cs47l15->core.adsp[0].rev = 2; - cs47l15->core.adsp[0].dev = madera->dev; - cs47l15->core.adsp[0].regmap = madera->regmap_32bit; - - cs47l15->core.adsp[0].base = MADERA_DSP1_CONFIG_1; - cs47l15->core.adsp[0].mem = cs47l15_dsp1_regions; - cs47l15->core.adsp[0].num_mems = ARRAY_SIZE(cs47l15_dsp1_regions); - - cs47l15->core.adsp[0].lock_regions = - WM_ADSP2_REGION_1 | WM_ADSP2_REGION_2 | WM_ADSP2_REGION_3; + cs47l15->core.adsp[0].cs_dsp.num = 1; + cs47l15->core.adsp[0].cs_dsp.type = WMFW_ADSP2; + cs47l15->core.adsp[0].cs_dsp.rev = 2; + cs47l15->core.adsp[0].cs_dsp.dev = madera->dev; + cs47l15->core.adsp[0].cs_dsp.regmap = madera->regmap_32bit; + + cs47l15->core.adsp[0].cs_dsp.base = MADERA_DSP1_CONFIG_1; + cs47l15->core.adsp[0].cs_dsp.mem = cs47l15_dsp1_regions; + cs47l15->core.adsp[0].cs_dsp.num_mems = ARRAY_SIZE(cs47l15_dsp1_regions); + + cs47l15->core.adsp[0].cs_dsp.lock_regions = + CS_ADSP2_REGION_1 | CS_ADSP2_REGION_2 | CS_ADSP2_REGION_3; ret = wm_adsp2_init(&cs47l15->core.adsp[0]); if (ret != 0) diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c index 6b6d08816024..6356f81aafc5 100644 --- a/sound/soc/codecs/cs47l24.c +++ b/sound/soc/codecs/cs47l24.c @@ -37,21 +37,21 @@ struct cs47l24_priv { struct arizona_fll fll[2]; }; -static const struct wm_adsp_region cs47l24_dsp2_regions[] = { +static const struct cs_dsp_region cs47l24_dsp2_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x200000 }, { .type = WMFW_ADSP2_ZM, .base = 0x280000 }, { .type = WMFW_ADSP2_XM, .base = 0x290000 }, { .type = WMFW_ADSP2_YM, .base = 0x2a8000 }, }; -static const struct wm_adsp_region cs47l24_dsp3_regions[] = { +static const struct cs_dsp_region cs47l24_dsp3_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x300000 }, { .type = WMFW_ADSP2_ZM, .base = 0x380000 }, { .type = WMFW_ADSP2_XM, .base = 0x390000 }, { .type = WMFW_ADSP2_YM, .base = 0x3a8000 }, }; -static const struct wm_adsp_region *cs47l24_dsp_regions[] = { +static const struct cs_dsp_region *cs47l24_dsp_regions[] = { cs47l24_dsp2_regions, cs47l24_dsp3_regions, }; @@ -1234,15 +1234,15 @@ static int cs47l24_probe(struct platform_device *pdev) for (i = 1; i <= 2; i++) { cs47l24->core.adsp[i].part = "cs47l24"; - cs47l24->core.adsp[i].num = i + 1; - cs47l24->core.adsp[i].type = WMFW_ADSP2; - cs47l24->core.adsp[i].dev = arizona->dev; - cs47l24->core.adsp[i].regmap = arizona->regmap; + cs47l24->core.adsp[i].cs_dsp.num = i + 1; + cs47l24->core.adsp[i].cs_dsp.type = WMFW_ADSP2; + cs47l24->core.adsp[i].cs_dsp.dev = arizona->dev; + cs47l24->core.adsp[i].cs_dsp.regmap = arizona->regmap; - cs47l24->core.adsp[i].base = ARIZONA_DSP1_CONTROL_1 + + cs47l24->core.adsp[i].cs_dsp.base = ARIZONA_DSP1_CONTROL_1 + (0x100 * i); - cs47l24->core.adsp[i].mem = cs47l24_dsp_regions[i - 1]; - cs47l24->core.adsp[i].num_mems = + cs47l24->core.adsp[i].cs_dsp.mem = cs47l24_dsp_regions[i - 1]; + cs47l24->core.adsp[i].cs_dsp.num_mems = ARRAY_SIZE(cs47l24_dsp2_regions); ret = wm_adsp2_init(&cs47l24->core.adsp[i]); diff --git a/sound/soc/codecs/cs47l35.c b/sound/soc/codecs/cs47l35.c index 3f04a2a74521..db2f844b8b17 100644 --- a/sound/soc/codecs/cs47l35.c +++ b/sound/soc/codecs/cs47l35.c @@ -37,28 +37,28 @@ struct cs47l35 { struct madera_fll fll; }; -static const struct wm_adsp_region cs47l35_dsp1_regions[] = { +static const struct cs_dsp_region cs47l35_dsp1_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x080000 }, { .type = WMFW_ADSP2_ZM, .base = 0x0e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x0a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x0c0000 }, }; -static const struct wm_adsp_region cs47l35_dsp2_regions[] = { +static const struct cs_dsp_region cs47l35_dsp2_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x100000 }, { .type = WMFW_ADSP2_ZM, .base = 0x160000 }, { .type = WMFW_ADSP2_XM, .base = 0x120000 }, { .type = WMFW_ADSP2_YM, .base = 0x140000 }, }; -static const struct wm_adsp_region cs47l35_dsp3_regions[] = { +static const struct cs_dsp_region cs47l35_dsp3_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x180000 }, { .type = WMFW_ADSP2_ZM, .base = 0x1e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x1a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x1c0000 }, }; -static const struct wm_adsp_region *cs47l35_dsp_regions[] = { +static const struct cs_dsp_region *cs47l35_dsp_regions[] = { cs47l35_dsp1_regions, cs47l35_dsp2_regions, cs47l35_dsp3_regions, @@ -1686,15 +1686,15 @@ static int cs47l35_probe(struct platform_device *pdev) for (i = 0; i < CS47L35_NUM_ADSP; i++) { cs47l35->core.adsp[i].part = "cs47l35"; - cs47l35->core.adsp[i].num = i + 1; - cs47l35->core.adsp[i].type = WMFW_ADSP2; - cs47l35->core.adsp[i].rev = 1; - cs47l35->core.adsp[i].dev = madera->dev; - cs47l35->core.adsp[i].regmap = madera->regmap_32bit; - - cs47l35->core.adsp[i].base = wm_adsp2_control_bases[i]; - cs47l35->core.adsp[i].mem = cs47l35_dsp_regions[i]; - cs47l35->core.adsp[i].num_mems = + cs47l35->core.adsp[i].cs_dsp.num = i + 1; + cs47l35->core.adsp[i].cs_dsp.type = WMFW_ADSP2; + cs47l35->core.adsp[i].cs_dsp.rev = 1; + cs47l35->core.adsp[i].cs_dsp.dev = madera->dev; + cs47l35->core.adsp[i].cs_dsp.regmap = madera->regmap_32bit; + + cs47l35->core.adsp[i].cs_dsp.base = wm_adsp2_control_bases[i]; + cs47l35->core.adsp[i].cs_dsp.mem = cs47l35_dsp_regions[i]; + cs47l35->core.adsp[i].cs_dsp.num_mems = ARRAY_SIZE(cs47l35_dsp1_regions); ret = wm_adsp2_init(&cs47l35->core.adsp[i]); diff --git a/sound/soc/codecs/cs47l85.c b/sound/soc/codecs/cs47l85.c index 748a180870bc..d4fedc5ad516 100644 --- a/sound/soc/codecs/cs47l85.c +++ b/sound/soc/codecs/cs47l85.c @@ -37,56 +37,56 @@ struct cs47l85 { struct madera_fll fll[3]; }; -static const struct wm_adsp_region cs47l85_dsp1_regions[] = { +static const struct cs_dsp_region cs47l85_dsp1_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x080000 }, { .type = WMFW_ADSP2_ZM, .base = 0x0e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x0a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x0c0000 }, }; -static const struct wm_adsp_region cs47l85_dsp2_regions[] = { +static const struct cs_dsp_region cs47l85_dsp2_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x100000 }, { .type = WMFW_ADSP2_ZM, .base = 0x160000 }, { .type = WMFW_ADSP2_XM, .base = 0x120000 }, { .type = WMFW_ADSP2_YM, .base = 0x140000 }, }; -static const struct wm_adsp_region cs47l85_dsp3_regions[] = { +static const struct cs_dsp_region cs47l85_dsp3_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x180000 }, { .type = WMFW_ADSP2_ZM, .base = 0x1e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x1a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x1c0000 }, }; -static const struct wm_adsp_region cs47l85_dsp4_regions[] = { +static const struct cs_dsp_region cs47l85_dsp4_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x200000 }, { .type = WMFW_ADSP2_ZM, .base = 0x260000 }, { .type = WMFW_ADSP2_XM, .base = 0x220000 }, { .type = WMFW_ADSP2_YM, .base = 0x240000 }, }; -static const struct wm_adsp_region cs47l85_dsp5_regions[] = { +static const struct cs_dsp_region cs47l85_dsp5_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x280000 }, { .type = WMFW_ADSP2_ZM, .base = 0x2e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x2a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x2c0000 }, }; -static const struct wm_adsp_region cs47l85_dsp6_regions[] = { +static const struct cs_dsp_region cs47l85_dsp6_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x300000 }, { .type = WMFW_ADSP2_ZM, .base = 0x360000 }, { .type = WMFW_ADSP2_XM, .base = 0x320000 }, { .type = WMFW_ADSP2_YM, .base = 0x340000 }, }; -static const struct wm_adsp_region cs47l85_dsp7_regions[] = { +static const struct cs_dsp_region cs47l85_dsp7_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x380000 }, { .type = WMFW_ADSP2_ZM, .base = 0x3e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x3a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x3c0000 }, }; -static const struct wm_adsp_region *cs47l85_dsp_regions[] = { +static const struct cs_dsp_region *cs47l85_dsp_regions[] = { cs47l85_dsp1_regions, cs47l85_dsp2_regions, cs47l85_dsp3_regions, @@ -2632,15 +2632,15 @@ static int cs47l85_probe(struct platform_device *pdev) for (i = 0; i < CS47L85_NUM_ADSP; i++) { cs47l85->core.adsp[i].part = "cs47l85"; - cs47l85->core.adsp[i].num = i + 1; - cs47l85->core.adsp[i].type = WMFW_ADSP2; - cs47l85->core.adsp[i].rev = 1; - cs47l85->core.adsp[i].dev = madera->dev; - cs47l85->core.adsp[i].regmap = madera->regmap_32bit; - - cs47l85->core.adsp[i].base = wm_adsp2_control_bases[i]; - cs47l85->core.adsp[i].mem = cs47l85_dsp_regions[i]; - cs47l85->core.adsp[i].num_mems = + cs47l85->core.adsp[i].cs_dsp.num = i + 1; + cs47l85->core.adsp[i].cs_dsp.type = WMFW_ADSP2; + cs47l85->core.adsp[i].cs_dsp.rev = 1; + cs47l85->core.adsp[i].cs_dsp.dev = madera->dev; + cs47l85->core.adsp[i].cs_dsp.regmap = madera->regmap_32bit; + + cs47l85->core.adsp[i].cs_dsp.base = wm_adsp2_control_bases[i]; + cs47l85->core.adsp[i].cs_dsp.mem = cs47l85_dsp_regions[i]; + cs47l85->core.adsp[i].cs_dsp.num_mems = ARRAY_SIZE(cs47l85_dsp1_regions); ret = wm_adsp2_init(&cs47l85->core.adsp[i]); diff --git a/sound/soc/codecs/cs47l90.c b/sound/soc/codecs/cs47l90.c index d2911c014b86..5aec937a2462 100644 --- a/sound/soc/codecs/cs47l90.c +++ b/sound/soc/codecs/cs47l90.c @@ -37,56 +37,56 @@ struct cs47l90 { struct madera_fll fll[3]; }; -static const struct wm_adsp_region cs47l90_dsp1_regions[] = { +static const struct cs_dsp_region cs47l90_dsp1_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x080000 }, { .type = WMFW_ADSP2_ZM, .base = 0x0e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x0a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x0c0000 }, }; -static const struct wm_adsp_region cs47l90_dsp2_regions[] = { +static const struct cs_dsp_region cs47l90_dsp2_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x100000 }, { .type = WMFW_ADSP2_ZM, .base = 0x160000 }, { .type = WMFW_ADSP2_XM, .base = 0x120000 }, { .type = WMFW_ADSP2_YM, .base = 0x140000 }, }; -static const struct wm_adsp_region cs47l90_dsp3_regions[] = { +static const struct cs_dsp_region cs47l90_dsp3_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x180000 }, { .type = WMFW_ADSP2_ZM, .base = 0x1e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x1a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x1c0000 }, }; -static const struct wm_adsp_region cs47l90_dsp4_regions[] = { +static const struct cs_dsp_region cs47l90_dsp4_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x200000 }, { .type = WMFW_ADSP2_ZM, .base = 0x260000 }, { .type = WMFW_ADSP2_XM, .base = 0x220000 }, { .type = WMFW_ADSP2_YM, .base = 0x240000 }, }; -static const struct wm_adsp_region cs47l90_dsp5_regions[] = { +static const struct cs_dsp_region cs47l90_dsp5_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x280000 }, { .type = WMFW_ADSP2_ZM, .base = 0x2e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x2a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x2c0000 }, }; -static const struct wm_adsp_region cs47l90_dsp6_regions[] = { +static const struct cs_dsp_region cs47l90_dsp6_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x300000 }, { .type = WMFW_ADSP2_ZM, .base = 0x360000 }, { .type = WMFW_ADSP2_XM, .base = 0x320000 }, { .type = WMFW_ADSP2_YM, .base = 0x340000 }, }; -static const struct wm_adsp_region cs47l90_dsp7_regions[] = { +static const struct cs_dsp_region cs47l90_dsp7_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x380000 }, { .type = WMFW_ADSP2_ZM, .base = 0x3e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x3a0000 }, { .type = WMFW_ADSP2_YM, .base = 0x3c0000 }, }; -static const struct wm_adsp_region *cs47l90_dsp_regions[] = { +static const struct cs_dsp_region *cs47l90_dsp_regions[] = { cs47l90_dsp1_regions, cs47l90_dsp2_regions, cs47l90_dsp3_regions, @@ -2543,18 +2543,18 @@ static int cs47l90_probe(struct platform_device *pdev) for (i = 0; i < CS47L90_NUM_ADSP; i++) { cs47l90->core.adsp[i].part = "cs47l90"; - cs47l90->core.adsp[i].num = i + 1; - cs47l90->core.adsp[i].type = WMFW_ADSP2; - cs47l90->core.adsp[i].rev = 2; - cs47l90->core.adsp[i].dev = madera->dev; - cs47l90->core.adsp[i].regmap = madera->regmap_32bit; - - cs47l90->core.adsp[i].base = cs47l90_dsp_control_bases[i]; - cs47l90->core.adsp[i].mem = cs47l90_dsp_regions[i]; - cs47l90->core.adsp[i].num_mems = + cs47l90->core.adsp[i].cs_dsp.num = i + 1; + cs47l90->core.adsp[i].cs_dsp.type = WMFW_ADSP2; + cs47l90->core.adsp[i].cs_dsp.rev = 2; + cs47l90->core.adsp[i].cs_dsp.dev = madera->dev; + cs47l90->core.adsp[i].cs_dsp.regmap = madera->regmap_32bit; + + cs47l90->core.adsp[i].cs_dsp.base = cs47l90_dsp_control_bases[i]; + cs47l90->core.adsp[i].cs_dsp.mem = cs47l90_dsp_regions[i]; + cs47l90->core.adsp[i].cs_dsp.num_mems = ARRAY_SIZE(cs47l90_dsp1_regions); - cs47l90->core.adsp[i].lock_regions = WM_ADSP2_REGION_1_9; + cs47l90->core.adsp[i].cs_dsp.lock_regions = CS_ADSP2_REGION_1_9; ret = wm_adsp2_init(&cs47l90->core.adsp[i]); diff --git a/sound/soc/codecs/cs47l92.c b/sound/soc/codecs/cs47l92.c index 1a0280416d92..a1b8dcdb9f7b 100644 --- a/sound/soc/codecs/cs47l92.c +++ b/sound/soc/codecs/cs47l92.c @@ -37,7 +37,7 @@ struct cs47l92 { struct madera_fll fll[2]; }; -static const struct wm_adsp_region cs47l92_dsp1_regions[] = { +static const struct cs_dsp_region cs47l92_dsp1_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x080000 }, { .type = WMFW_ADSP2_ZM, .base = 0x0e0000 }, { .type = WMFW_ADSP2_XM, .base = 0x0a0000 }, @@ -2002,17 +2002,17 @@ static int cs47l92_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "Failed to set DSP IRQ wake: %d\n", ret); cs47l92->core.adsp[0].part = "cs47l92"; - cs47l92->core.adsp[0].num = 1; - cs47l92->core.adsp[0].type = WMFW_ADSP2; - cs47l92->core.adsp[0].rev = 2; - cs47l92->core.adsp[0].dev = madera->dev; - cs47l92->core.adsp[0].regmap = madera->regmap_32bit; + cs47l92->core.adsp[0].cs_dsp.num = 1; + cs47l92->core.adsp[0].cs_dsp.type = WMFW_ADSP2; + cs47l92->core.adsp[0].cs_dsp.rev = 2; + cs47l92->core.adsp[0].cs_dsp.dev = madera->dev; + cs47l92->core.adsp[0].cs_dsp.regmap = madera->regmap_32bit; - cs47l92->core.adsp[0].base = MADERA_DSP1_CONFIG_1; - cs47l92->core.adsp[0].mem = cs47l92_dsp1_regions; - cs47l92->core.adsp[0].num_mems = ARRAY_SIZE(cs47l92_dsp1_regions); + cs47l92->core.adsp[0].cs_dsp.base = MADERA_DSP1_CONFIG_1; + cs47l92->core.adsp[0].cs_dsp.mem = cs47l92_dsp1_regions; + cs47l92->core.adsp[0].cs_dsp.num_mems = ARRAY_SIZE(cs47l92_dsp1_regions); - cs47l92->core.adsp[0].lock_regions = WM_ADSP2_REGION_1_9; + cs47l92->core.adsp[0].cs_dsp.lock_regions = CS_ADSP2_REGION_1_9; ret = wm_adsp2_init(&cs47l92->core.adsp[0]); if (ret != 0) diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 067757d1d70a..8f30a3ea8bfe 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -811,12 +811,9 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client, mutex_init(&es8316->lock); ret = devm_request_threaded_irq(dev, es8316->irq, NULL, es8316_irq, - IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQF_NO_AUTOEN, "es8316", es8316); - if (ret == 0) { - /* Gets re-enabled by es8316_set_jack() */ - disable_irq(es8316->irq); - } else { + if (ret) { dev_warn(dev, "Failed to get IRQ %d: %d\n", es8316->irq, ret); es8316->irq = -ENXIO; } @@ -843,6 +840,7 @@ MODULE_DEVICE_TABLE(of, es8316_of_match); #ifdef CONFIG_ACPI static const struct acpi_device_id es8316_acpi_match[] = { {"ESSX8316", 0}, + {"ESSX8336", 0}, {}, }; MODULE_DEVICE_TABLE(acpi, es8316_acpi_match); diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c index 196b06898eeb..2bed5cf229be 100644 --- a/sound/soc/codecs/lpass-rx-macro.c +++ b/sound/soc/codecs/lpass-rx-macro.c @@ -3531,7 +3531,7 @@ static int rx_macro_probe(struct platform_device *pdev) rx->clks[3].id = "npl"; rx->clks[4].id = "fsgen"; - ret = devm_clk_bulk_get(dev, RX_NUM_CLKS_MAX, rx->clks); + ret = devm_clk_bulk_get_optional(dev, RX_NUM_CLKS_MAX, rx->clks); if (ret) { dev_err(dev, "Error getting RX Clocks (%d)\n", ret); return ret; @@ -3577,6 +3577,7 @@ static int rx_macro_remove(struct platform_device *pdev) } static const struct of_device_id rx_macro_dt_match[] = { + { .compatible = "qcom,sc7280-lpass-rx-macro" }, { .compatible = "qcom,sm8250-lpass-rx-macro" }, { } }; diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c index 27a0d5defd27..a4c0a155af56 100644 --- a/sound/soc/codecs/lpass-tx-macro.c +++ b/sound/soc/codecs/lpass-tx-macro.c @@ -272,7 +272,7 @@ struct tx_macro { static const DECLARE_TLV_DB_SCALE(digital_gain, -8400, 100, -8400); -static const struct reg_default tx_defaults[] = { +static struct reg_default tx_defaults[] = { /* TX Macro */ { CDC_TX_CLK_RST_CTRL_MCLK_CONTROL, 0x00 }, { CDC_TX_CLK_RST_CTRL_FS_CNT_CONTROL, 0x00 }, @@ -1674,6 +1674,9 @@ static int tx_macro_component_probe(struct snd_soc_component *comp) snd_soc_component_update_bits(comp, CDC_TX0_TX_PATH_SEC7, 0x3F, 0x0A); + /* Enable swr mic0 and mic1 clock */ + snd_soc_component_update_bits(comp, CDC_TX_TOP_CSR_SWR_AMIC0_CTL, 0xFF, 0x00); + snd_soc_component_update_bits(comp, CDC_TX_TOP_CSR_SWR_AMIC1_CTL, 0xFF, 0x00); return 0; } @@ -1778,9 +1781,10 @@ static const struct snd_soc_component_driver tx_macro_component_drv = { static int tx_macro_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct tx_macro *tx; void __iomem *base; - int ret; + int ret, reg; tx = devm_kzalloc(dev, sizeof(*tx), GFP_KERNEL); if (!tx) @@ -1792,7 +1796,7 @@ static int tx_macro_probe(struct platform_device *pdev) tx->clks[3].id = "npl"; tx->clks[4].id = "fsgen"; - ret = devm_clk_bulk_get(dev, TX_NUM_CLKS_MAX, tx->clks); + ret = devm_clk_bulk_get_optional(dev, TX_NUM_CLKS_MAX, tx->clks); if (ret) { dev_err(dev, "Error getting RX Clocks (%d)\n", ret); return ret; @@ -1802,6 +1806,20 @@ static int tx_macro_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); + /* Update defaults for lpass sc7280 */ + if (of_device_is_compatible(np, "qcom,sc7280-lpass-tx-macro")) { + for (reg = 0; reg < ARRAY_SIZE(tx_defaults); reg++) { + switch (tx_defaults[reg].reg) { + case CDC_TX_TOP_CSR_SWR_AMIC0_CTL: + case CDC_TX_TOP_CSR_SWR_AMIC1_CTL: + tx_defaults[reg].def = 0x0E; + break; + default: + break; + } + } + } + tx->regmap = devm_regmap_init_mmio(dev, base, &tx_regmap_config); dev_set_drvdata(dev, tx); @@ -1843,6 +1861,7 @@ static int tx_macro_remove(struct platform_device *pdev) } static const struct of_device_id tx_macro_dt_match[] = { + { .compatible = "qcom,sc7280-lpass-tx-macro" }, { .compatible = "qcom,sm8250-lpass-tx-macro" }, { } }; diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c index 56c93f4465c9..11147e35689b 100644 --- a/sound/soc/codecs/lpass-va-macro.c +++ b/sound/soc/codecs/lpass-va-macro.c @@ -1408,7 +1408,7 @@ static int va_macro_probe(struct platform_device *pdev) va->clks[1].id = "dcodec"; va->clks[2].id = "mclk"; - ret = devm_clk_bulk_get(dev, VA_NUM_CLKS_MAX, va->clks); + ret = devm_clk_bulk_get_optional(dev, VA_NUM_CLKS_MAX, va->clks); if (ret) { dev_err(dev, "Error getting VA Clocks (%d)\n", ret); return ret; @@ -1472,6 +1472,7 @@ static int va_macro_remove(struct platform_device *pdev) } static const struct of_device_id va_macro_dt_match[] = { + { .compatible = "qcom,sc7280-lpass-va-macro" }, { .compatible = "qcom,sm8250-lpass-va-macro" }, {} }; diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index d3ac318fd6b6..75baf8eb7029 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -2445,6 +2445,7 @@ static int wsa_macro_remove(struct platform_device *pdev) } static const struct of_device_id wsa_macro_dt_match[] = { + {.compatible = "qcom,sc7280-lpass-wsa-macro"}, {.compatible = "qcom,sm8250-lpass-wsa-macro"}, {} }; diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c index f4ed7e04673f..272041c6236a 100644 --- a/sound/soc/codecs/madera.c +++ b/sound/soc/codecs/madera.c @@ -905,7 +905,7 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, */ mutex_lock(&priv->rate_lock); - if (!madera_can_change_grp_rate(priv, priv->adsp[adsp_num].base)) { + if (!madera_can_change_grp_rate(priv, priv->adsp[adsp_num].cs_dsp.base)) { dev_warn(priv->madera->dev, "Cannot change '%s' while in use by active audio paths\n", kcontrol->id.name); @@ -964,7 +964,7 @@ static int madera_write_adsp_clk_setting(struct madera_priv *priv, unsigned int mask = MADERA_DSP_RATE_MASK; int ret; - val = priv->adsp_rate_cache[dsp->num - 1] << MADERA_DSP_RATE_SHIFT; + val = priv->adsp_rate_cache[dsp->cs_dsp.num - 1] << MADERA_DSP_RATE_SHIFT; switch (priv->madera->type) { case CS47L35: @@ -978,15 +978,15 @@ static int madera_write_adsp_clk_setting(struct madera_priv *priv, /* Configure exact dsp frequency */ dev_dbg(priv->madera->dev, "Set DSP frequency to 0x%x\n", freq); - ret = regmap_write(dsp->regmap, - dsp->base + MADERA_DSP_CONFIG_2_OFFS, freq); + ret = regmap_write(dsp->cs_dsp.regmap, + dsp->cs_dsp.base + MADERA_DSP_CONFIG_2_OFFS, freq); if (ret) goto err; break; } - ret = regmap_update_bits(dsp->regmap, - dsp->base + MADERA_DSP_CONFIG_1_OFFS, + ret = regmap_update_bits(dsp->cs_dsp.regmap, + dsp->cs_dsp.base + MADERA_DSP_CONFIG_1_OFFS, mask, val); if (ret) goto err; @@ -996,7 +996,7 @@ static int madera_write_adsp_clk_setting(struct madera_priv *priv, return 0; err: - dev_err(dsp->dev, "Failed to set DSP%d clock: %d\n", dsp->num, ret); + dev_err(dsp->cs_dsp.dev, "Failed to set DSP%d clock: %d\n", dsp->cs_dsp.num, ret); return ret; } @@ -1018,7 +1018,7 @@ int madera_set_adsp_clk(struct madera_priv *priv, int dsp_num, * changes are locked out by the domain_group_ref reference count. */ - ret = regmap_read(dsp->regmap, dsp->base, &cur); + ret = regmap_read(dsp->cs_dsp.regmap, dsp->cs_dsp.base, &cur); if (ret) { dev_err(madera->dev, "Failed to read current DSP rate: %d\n", ret); @@ -1027,7 +1027,7 @@ int madera_set_adsp_clk(struct madera_priv *priv, int dsp_num, cur &= MADERA_DSP_RATE_MASK; - new = priv->adsp_rate_cache[dsp->num - 1] << MADERA_DSP_RATE_SHIFT; + new = priv->adsp_rate_cache[dsp->cs_dsp.num - 1] << MADERA_DSP_RATE_SHIFT; if (new == cur) { dev_dbg(madera->dev, "DSP rate not changed\n"); diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c index b392567c2b3e..d1882cbc9381 100644 --- a/sound/soc/codecs/max98390.c +++ b/sound/soc/codecs/max98390.c @@ -1021,7 +1021,7 @@ static int max98390_i2c_probe(struct i2c_client *i2c, int reg = 0; struct max98390_priv *max98390 = NULL; - struct i2c_adapter *adapter = to_i2c_adapter(i2c->dev.parent); + struct i2c_adapter *adapter = i2c->adapter; ret = i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE diff --git a/sound/soc/codecs/max98520.c b/sound/soc/codecs/max98520.c new file mode 100644 index 000000000000..bb8649cd421c --- /dev/null +++ b/sound/soc/codecs/max98520.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021, Maxim Integrated + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/cdev.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <sound/tlv.h> +#include "max98520.h" + +static struct reg_default max98520_reg[] = { + {MAX98520_R2000_SW_RESET, 0x00}, + {MAX98520_R2001_STATUS_1, 0x00}, + {MAX98520_R2002_STATUS_2, 0x00}, + {MAX98520_R2020_THERM_WARN_THRESH, 0x46}, + {MAX98520_R2021_THERM_SHDN_THRESH, 0x64}, + {MAX98520_R2022_THERM_HYSTERESIS, 0x02}, + {MAX98520_R2023_THERM_FOLDBACK_SET, 0x31}, + {MAX98520_R2027_THERM_FOLDBACK_EN, 0x01}, + {MAX98520_R2030_CLK_MON_CTRL, 0x00}, + {MAX98520_R2037_ERR_MON_CTRL, 0x01}, + {MAX98520_R2040_PCM_MODE_CFG, 0xC0}, + {MAX98520_R2041_PCM_CLK_SETUP, 0x04}, + {MAX98520_R2042_PCM_SR_SETUP, 0x08}, + {MAX98520_R2043_PCM_RX_SRC1, 0x00}, + {MAX98520_R2044_PCM_RX_SRC2, 0x00}, + {MAX98520_R204F_PCM_RX_EN, 0x00}, + {MAX98520_R2090_AMP_VOL_CTRL, 0x00}, + {MAX98520_R2091_AMP_PATH_GAIN, 0x03}, + {MAX98520_R2092_AMP_DSP_CFG, 0x02}, + {MAX98520_R2094_SSM_CFG, 0x01}, + {MAX98520_R2095_AMP_CFG, 0xF0}, + {MAX98520_R209F_AMP_EN, 0x00}, + {MAX98520_R20B0_ADC_SR, 0x00}, + {MAX98520_R20B1_ADC_RESOLUTION, 0x00}, + {MAX98520_R20B2_ADC_PVDD0_CFG, 0x02}, + {MAX98520_R20B3_ADC_THERMAL_CFG, 0x02}, + {MAX98520_R20B4_ADC_READBACK_CTRL, 0x00}, + {MAX98520_R20B5_ADC_READBACK_UPDATE, 0x00}, + {MAX98520_R20B6_ADC_PVDD_READBACK_MSB, 0x00}, + {MAX98520_R20B7_ADC_PVDD_READBACK_LSB, 0x00}, + {MAX98520_R20B8_ADC_TEMP_READBACK_MSB, 0x00}, + {MAX98520_R20B9_ADC_TEMP_READBACK_LSB, 0x00}, + {MAX98520_R20BA_ADC_LOW_PVDD_READBACK_MSB, 0xFF}, + {MAX98520_R20BB_ADC_LOW_READBACK_LSB, 0x01}, + {MAX98520_R20BC_ADC_HIGH_TEMP_READBACK_MSB, 0x00}, + {MAX98520_R20BD_ADC_HIGH_TEMP_READBACK_LSB, 0x00}, + {MAX98520_R20CF_MEAS_ADC_CFG, 0x00}, + {MAX98520_R20D0_DHT_CFG1, 0x00}, + {MAX98520_R20D1_LIMITER_CFG1, 0x08}, + {MAX98520_R20D2_LIMITER_CFG2, 0x00}, + {MAX98520_R20D3_DHT_CFG2, 0x14}, + {MAX98520_R20D4_DHT_CFG3, 0x02}, + {MAX98520_R20D5_DHT_CFG4, 0x04}, + {MAX98520_R20D6_DHT_HYSTERESIS_CFG, 0x07}, + {MAX98520_R20D8_DHT_EN, 0x00}, + {MAX98520_R210E_AUTO_RESTART_BEHAVIOR, 0x00}, + {MAX98520_R210F_GLOBAL_EN, 0x00}, + {MAX98520_R21FF_REVISION_ID, 0x00}, +}; + +static int max98520_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98520_priv *max98520 = + snd_soc_component_get_drvdata(component); + unsigned int format = 0; + unsigned int invert = 0; + + dev_dbg(component->dev, "%s: fmt 0x%08X\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + invert = MAX98520_PCM_MODE_CFG_PCM_BCLKEDGE; + break; + default: + dev_err(component->dev, "DAI invert mode unsupported\n"); + return -EINVAL; + } + + regmap_update_bits(max98520->regmap, + MAX98520_R2041_PCM_CLK_SETUP, + MAX98520_PCM_MODE_CFG_PCM_BCLKEDGE, + invert); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = MAX98520_PCM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = MAX98520_PCM_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_DSP_A: + format = MAX98520_PCM_FORMAT_TDM_MODE1; + break; + case SND_SOC_DAIFMT_DSP_B: + format = MAX98520_PCM_FORMAT_TDM_MODE0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(max98520->regmap, + MAX98520_R2040_PCM_MODE_CFG, + MAX98520_PCM_MODE_CFG_FORMAT_MASK, + format << MAX98520_PCM_MODE_CFG_FORMAT_SHIFT); + + return 0; +} + +/* BCLKs per LRCLK */ +static const int bclk_sel_table[] = { + 32, 48, 64, 96, 128, 192, 256, 384, 512, 320, +}; + +static int max98520_get_bclk_sel(int bclk) +{ + int i; + /* match BCLKs per LRCLK */ + for (i = 0; i < ARRAY_SIZE(bclk_sel_table); i++) { + if (bclk_sel_table[i] == bclk) + return i + 2; + } + return 0; +} + +static int max98520_set_clock(struct snd_soc_component *component, + struct snd_pcm_hw_params *params) +{ + struct max98520_priv *max98520 = + snd_soc_component_get_drvdata(component); + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98520->ch_size; + int value; + + if (!max98520->tdm_mode) { + /* BCLK configuration */ + value = max98520_get_bclk_sel(blr_clk_ratio); + if (!value) { + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + return -EINVAL; + } + + regmap_update_bits(max98520->regmap, + MAX98520_R2041_PCM_CLK_SETUP, + MAX98520_PCM_CLK_SETUP_BSEL_MASK, + value); + } + dev_dbg(component->dev, "%s tdm_mode:%d out\n", __func__, max98520->tdm_mode); + return 0; +} + +static int max98520_dai_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; + struct max98520_priv *max98520 = + snd_soc_component_get_drvdata(component); + unsigned int sampling_rate = 0; + unsigned int chan_sz = 0; + + /* pcm mode configuration */ + switch (snd_pcm_format_width(params_format(params))) { + case 16: + chan_sz = MAX98520_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98520_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98520_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + goto err; + } + + max98520->ch_size = snd_pcm_format_width(params_format(params)); + + regmap_update_bits(max98520->regmap, + MAX98520_R2040_PCM_MODE_CFG, + MAX98520_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + dev_dbg(component->dev, "format supported %d", + params_format(params)); + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 8000: + sampling_rate = MAX98520_PCM_SR_8000; + break; + case 11025: + sampling_rate = MAX98520_PCM_SR_11025; + break; + case 12000: + sampling_rate = MAX98520_PCM_SR_12000; + break; + case 16000: + sampling_rate = MAX98520_PCM_SR_16000; + break; + case 22050: + sampling_rate = MAX98520_PCM_SR_22050; + break; + case 24000: + sampling_rate = MAX98520_PCM_SR_24000; + break; + case 32000: + sampling_rate = MAX98520_PCM_SR_32000; + break; + case 44100: + sampling_rate = MAX98520_PCM_SR_44100; + break; + case 48000: + sampling_rate = MAX98520_PCM_SR_48000; + break; + case 88200: + sampling_rate = MAX98520_PCM_SR_88200; + break; + case 96000: + sampling_rate = MAX98520_PCM_SR_96000; + break; + case 176400: + sampling_rate = MAX98520_PCM_SR_176400; + break; + case 192000: + sampling_rate = MAX98520_PCM_SR_192000; + break; + default: + dev_err(component->dev, "rate %d not supported\n", + params_rate(params)); + goto err; + } + + dev_dbg(component->dev, " %s ch_size: %d, sampling rate : %d out\n", __func__, + snd_pcm_format_width(params_format(params)), params_rate(params)); + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98520->regmap, + MAX98520_R2042_PCM_SR_SETUP, + MAX98520_PCM_SR_MASK, + sampling_rate); + + return max98520_set_clock(component, params); +err: + dev_dbg(component->dev, "%s out error", __func__); + return -EINVAL; +} + +static int max98520_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct max98520_priv *max98520 = + snd_soc_component_get_drvdata(component); + int bsel; + unsigned int chan_sz = 0; + + if (!tx_mask && !rx_mask && !slots && !slot_width) + max98520->tdm_mode = false; + else + max98520->tdm_mode = true; + + /* BCLK configuration */ + bsel = max98520_get_bclk_sel(slots * slot_width); + if (bsel == 0) { + dev_err(component->dev, "BCLK %d not supported\n", + slots * slot_width); + return -EINVAL; + } + + regmap_update_bits(max98520->regmap, + MAX98520_R2041_PCM_CLK_SETUP, + MAX98520_PCM_CLK_SETUP_BSEL_MASK, + bsel); + + /* Channel size configuration */ + switch (slot_width) { + case 16: + chan_sz = MAX98520_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98520_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98520_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + slot_width); + return -EINVAL; + } + + regmap_update_bits(max98520->regmap, + MAX98520_R2040_PCM_MODE_CFG, + MAX98520_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + /* Rx slot configuration */ + regmap_update_bits(max98520->regmap, + MAX98520_R2044_PCM_RX_SRC2, + MAX98520_PCM_DMIX_CH0_SRC_MASK, + rx_mask); + regmap_update_bits(max98520->regmap, + MAX98520_R2044_PCM_RX_SRC2, + MAX98520_PCM_DMIX_CH1_SRC_MASK, + rx_mask << MAX98520_PCM_DMIX_CH1_SHIFT); + + return 0; +} + +#define MAX98520_RATES SNDRV_PCM_RATE_8000_192000 + +#define MAX98520_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops max98520_dai_ops = { + .set_fmt = max98520_dai_set_fmt, + .hw_params = max98520_dai_hw_params, + .set_tdm_slot = max98520_dai_tdm_slot, +}; + +static int max98520_dac_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 max98520_priv *max98520 = + snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + dev_dbg(component->dev, " AMP ON\n"); + + regmap_write(max98520->regmap, MAX98520_R209F_AMP_EN, 1); + regmap_write(max98520->regmap, MAX98520_R210F_GLOBAL_EN, 1); + usleep_range(30000, 31000); + break; + case SND_SOC_DAPM_POST_PMD: + dev_dbg(component->dev, " AMP OFF\n"); + + regmap_write(max98520->regmap, MAX98520_R210F_GLOBAL_EN, 0); + regmap_write(max98520->regmap, MAX98520_R209F_AMP_EN, 0); + usleep_range(30000, 31000); + break; + default: + return 0; + } + return 0; +} + +static const char * const max98520_switch_text[] = { + "Left", "Right", "LeftRight"}; + +static const struct soc_enum dai_sel_enum = + SOC_ENUM_SINGLE(MAX98520_R2043_PCM_RX_SRC1, + 0, 3, max98520_switch_text); + +static const struct snd_kcontrol_new max98520_dai_controls = + SOC_DAPM_ENUM("DAI Sel", dai_sel_enum); + +static const struct snd_kcontrol_new max98520_left_input_mixer_controls[] = { + SOC_DAPM_SINGLE("PCM_INPUT_CH0", MAX98520_R2044_PCM_RX_SRC2, 0, 0x0, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH1", MAX98520_R2044_PCM_RX_SRC2, 0, 0x1, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH2", MAX98520_R2044_PCM_RX_SRC2, 0, 0x2, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH3", MAX98520_R2044_PCM_RX_SRC2, 0, 0x3, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH4", MAX98520_R2044_PCM_RX_SRC2, 0, 0x4, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH5", MAX98520_R2044_PCM_RX_SRC2, 0, 0x5, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH6", MAX98520_R2044_PCM_RX_SRC2, 0, 0x6, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH7", MAX98520_R2044_PCM_RX_SRC2, 0, 0x7, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH8", MAX98520_R2044_PCM_RX_SRC2, 0, 0x8, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH9", MAX98520_R2044_PCM_RX_SRC2, 0, 0x9, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH10", MAX98520_R2044_PCM_RX_SRC2, 0, 0xa, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH11", MAX98520_R2044_PCM_RX_SRC2, 0, 0xb, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH12", MAX98520_R2044_PCM_RX_SRC2, 0, 0xc, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH13", MAX98520_R2044_PCM_RX_SRC2, 0, 0xd, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH14", MAX98520_R2044_PCM_RX_SRC2, 0, 0xe, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH15", MAX98520_R2044_PCM_RX_SRC2, 0, 0xf, 0), +}; + +static const struct snd_kcontrol_new max98520_right_input_mixer_controls[] = { + SOC_DAPM_SINGLE("PCM_INPUT_CH0", MAX98520_R2044_PCM_RX_SRC2, 4, 0x0, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH1", MAX98520_R2044_PCM_RX_SRC2, 4, 0x1, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH2", MAX98520_R2044_PCM_RX_SRC2, 4, 0x2, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH3", MAX98520_R2044_PCM_RX_SRC2, 4, 0x3, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH4", MAX98520_R2044_PCM_RX_SRC2, 4, 0x4, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH5", MAX98520_R2044_PCM_RX_SRC2, 4, 0x5, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH6", MAX98520_R2044_PCM_RX_SRC2, 4, 0x6, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH7", MAX98520_R2044_PCM_RX_SRC2, 4, 0x7, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH8", MAX98520_R2044_PCM_RX_SRC2, 4, 0x8, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH9", MAX98520_R2044_PCM_RX_SRC2, 4, 0x9, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH10", MAX98520_R2044_PCM_RX_SRC2, 4, 0xa, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH11", MAX98520_R2044_PCM_RX_SRC2, 4, 0xb, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH12", MAX98520_R2044_PCM_RX_SRC2, 4, 0xc, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH13", MAX98520_R2044_PCM_RX_SRC2, 4, 0xd, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH14", MAX98520_R2044_PCM_RX_SRC2, 4, 0xe, 0), + SOC_DAPM_SINGLE("PCM_INPUT_CH15", MAX98520_R2044_PCM_RX_SRC2, 4, 0xf, 0), +}; + +static const struct snd_soc_dapm_widget max98520_dapm_widgets[] = { + SND_SOC_DAPM_DAC_E("Amp Enable", "HiFi Playback", + SND_SOC_NOPM, 0, 0, max98520_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("DAI Sel Mux", SND_SOC_NOPM, 0, 0, &max98520_dai_controls), + SND_SOC_DAPM_OUTPUT("BE_OUT"), + /* Left Input Selection */ + SND_SOC_DAPM_MIXER("Left Input Selection", SND_SOC_NOPM, 0, 0, + &max98520_left_input_mixer_controls[0], + ARRAY_SIZE(max98520_left_input_mixer_controls)), + /* Right Input Selection */ + SND_SOC_DAPM_MIXER("Right Input Selection", SND_SOC_NOPM, 0, 0, + &max98520_right_input_mixer_controls[0], + ARRAY_SIZE(max98520_right_input_mixer_controls)), +}; + +static const DECLARE_TLV_DB_SCALE(max98520_digital_tlv, -6300, 50, 1); +static const DECLARE_TLV_DB_SCALE(max98520_spk_tlv, -600, 300, 0); + +static const DECLARE_TLV_DB_RANGE(max98520_dht_lim_thresh_tlv, + 0, 15, TLV_DB_SCALE_ITEM(-1500, 100, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98520_dht_hysteresis_tlv, + 0, 3, TLV_DB_SCALE_ITEM(100, 100, 0), + 4, 7, TLV_DB_SCALE_ITEM(600, 200, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98520_dht_rotation_point_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-1500, 300, 0), + 2, 4, TLV_DB_SCALE_ITEM(-1000, 200, 0), + 5, 10, TLV_DB_SCALE_ITEM(-500, 100, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98520_dht_supply_hr_tlv, + 0, 16, TLV_DB_SCALE_ITEM(-2000, 250, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98520_dht_max_atten_tlv, + 1, 20, TLV_DB_SCALE_ITEM(-2000, 100, 0), +); + +static const char * const max98520_dht_attack_rate_text[] = { + "20us", "40us", "80us", "160us", "320us", "640us", + "1.28ms", "2.56ms", "5.12ms", "10.24ms", "20.48ms", "40.96ms", + "81.92ms", "163.84ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98520_dht_attack_rate_enum, + MAX98520_R20D4_DHT_CFG3, 0, + max98520_dht_attack_rate_text); + +static const char * const max98520_dht_release_rate_text[] = { + "2ms", "4ms", "8ms", "16ms", "32ms", "64ms", "128ms", "256ms", "512ms", + "1.024s", "2.048s", "4.096s", "8.192s", "16.384s" +}; + +static SOC_ENUM_SINGLE_DECL(max98520_dht_release_rate_enum, + MAX98520_R20D5_DHT_CFG4, 0, + max98520_dht_release_rate_text); + +static bool max98520_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98520_R2000_SW_RESET: + case MAX98520_R2027_THERM_FOLDBACK_EN: + case MAX98520_R2030_CLK_MON_CTRL: + case MAX98520_R2037_ERR_MON_CTRL: + case MAX98520_R204F_PCM_RX_EN: + case MAX98520_R209F_AMP_EN: + case MAX98520_R20CF_MEAS_ADC_CFG: + case MAX98520_R20D8_DHT_EN: + case MAX98520_R21FF_REVISION_ID: + case MAX98520_R2001_STATUS_1... MAX98520_R2002_STATUS_2: + case MAX98520_R2020_THERM_WARN_THRESH... MAX98520_R2023_THERM_FOLDBACK_SET: + case MAX98520_R2040_PCM_MODE_CFG... MAX98520_R2044_PCM_RX_SRC2: + case MAX98520_R2090_AMP_VOL_CTRL... MAX98520_R2092_AMP_DSP_CFG: + case MAX98520_R2094_SSM_CFG... MAX98520_R2095_AMP_CFG: + case MAX98520_R20B0_ADC_SR... MAX98520_R20BD_ADC_HIGH_TEMP_READBACK_LSB: + case MAX98520_R20D0_DHT_CFG1... MAX98520_R20D6_DHT_HYSTERESIS_CFG: + case MAX98520_R210E_AUTO_RESTART_BEHAVIOR... MAX98520_R210F_GLOBAL_EN: + case MAX98520_R2161_BOOST_TM1... MAX98520_R2163_BOOST_TM3: + return true; + default: + return false; + } +}; + +static bool max98520_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98520_R210F_GLOBAL_EN: + case MAX98520_R21FF_REVISION_ID: + case MAX98520_R2000_SW_RESET: + case MAX98520_R2001_STATUS_1 ... MAX98520_R2002_STATUS_2: + case MAX98520_R20B4_ADC_READBACK_CTRL + ... MAX98520_R20BD_ADC_HIGH_TEMP_READBACK_LSB: + return true; + default: + return false; + } +} + +static const struct snd_kcontrol_new max98520_snd_controls[] = { +/* Volume */ +SOC_SINGLE_TLV("Digital Volume", MAX98520_R2090_AMP_VOL_CTRL, + 0, 0x7F, 1, max98520_digital_tlv), +SOC_SINGLE_TLV("Speaker Volume", MAX98520_R2091_AMP_PATH_GAIN, + 0, 0x5, 0, max98520_spk_tlv), +/* Volume Ramp Up/Down Enable*/ +SOC_SINGLE("Ramp Up Switch", MAX98520_R2092_AMP_DSP_CFG, + MAX98520_DSP_SPK_VOL_RMPUP_SHIFT, 1, 0), +SOC_SINGLE("Ramp Down Switch", MAX98520_R2092_AMP_DSP_CFG, + MAX98520_DSP_SPK_VOL_RMPDN_SHIFT, 1, 0), +/* Clock Monitor Enable */ +SOC_SINGLE("CLK Monitor Switch", MAX98520_R2037_ERR_MON_CTRL, + MAX98520_CTRL_CMON_EN_SHIFT, 1, 0), +/* Clock Monitor Config */ +SOC_SINGLE("CLKMON Autorestart Switch", MAX98520_R2030_CLK_MON_CTRL, + MAX98520_CMON_AUTORESTART_SHIFT, 1, 0), +/* Dither Enable */ +SOC_SINGLE("Dither Switch", MAX98520_R2092_AMP_DSP_CFG, + MAX98520_DSP_SPK_DITH_EN_SHIFT, 1, 0), +/* DC Blocker Enable */ +SOC_SINGLE("DC Blocker Switch", MAX98520_R2092_AMP_DSP_CFG, + MAX98520_DSP_SPK_DCBLK_EN_SHIFT, 1, 0), +/* Speaker Safe Mode Enable */ +SOC_SINGLE("Speaker Safemode Switch", MAX98520_R2092_AMP_DSP_CFG, + MAX98520_DSP_SPK_SAFE_EN_SHIFT, 1, 0), +/* AMP SSM Enable */ +SOC_SINGLE("CP Bypass Switch", MAX98520_R2094_SSM_CFG, + MAX98520_SSM_RCVR_MODE_SHIFT, 1, 0), +/* Dynamic Headroom Tracking */ +SOC_SINGLE("DHT Switch", MAX98520_R20D8_DHT_EN, 0, 1, 0), +SOC_SINGLE("DHT Limiter Mode", MAX98520_R20D2_LIMITER_CFG2, + MAX98520_DHT_LIMITER_MODE_SHIFT, 1, 0), +SOC_SINGLE("DHT Hysteresis Switch", MAX98520_R20D6_DHT_HYSTERESIS_CFG, + MAX98520_DHT_HYSTERESIS_SWITCH_SHIFT, 1, 0), +SOC_SINGLE_TLV("DHT Rot Pnt", MAX98520_R20D0_DHT_CFG1, + MAX98520_DHT_VROT_PNT_SHIFT, 10, 1, max98520_dht_rotation_point_tlv), +SOC_SINGLE_TLV("DHT Supply Headroom", MAX98520_R20D1_LIMITER_CFG1, + MAX98520_DHT_SUPPLY_HR_SHIFT, 16, 0, max98520_dht_supply_hr_tlv), +SOC_SINGLE_TLV("DHT Limiter Threshold", MAX98520_R20D2_LIMITER_CFG2, + MAX98520_DHT_LIMITER_THRESHOLD_SHIFT, 0xF, 1, max98520_dht_lim_thresh_tlv), +SOC_SINGLE_TLV("DHT Max Attenuation", MAX98520_R20D3_DHT_CFG2, + MAX98520_DHT_MAX_ATTEN_SHIFT, 20, 1, max98520_dht_max_atten_tlv), +SOC_SINGLE_TLV("DHT Hysteresis", MAX98520_R20D6_DHT_HYSTERESIS_CFG, + MAX98520_DHT_HYSTERESIS_SHIFT, 0x7, 0, max98520_dht_hysteresis_tlv), +SOC_ENUM("DHT Attack Rate", max98520_dht_attack_rate_enum), +SOC_ENUM("DHT Release Rate", max98520_dht_release_rate_enum), +/* ADC configuration */ +SOC_SINGLE("ADC PVDD CH Switch", MAX98520_R20CF_MEAS_ADC_CFG, 0, 1, 0), +SOC_SINGLE("ADC PVDD FLT Switch", MAX98520_R20B2_ADC_PVDD0_CFG, MAX98520_FLT_EN_SHIFT, 1, 0), +SOC_SINGLE("ADC TEMP FLT Switch", MAX98520_R20B3_ADC_THERMAL_CFG, MAX98520_FLT_EN_SHIFT, 1, 0), +SOC_SINGLE("ADC PVDD MSB", MAX98520_R20B6_ADC_PVDD_READBACK_MSB, 0, 0xFF, 0), +SOC_SINGLE("ADC PVDD LSB", MAX98520_R20B7_ADC_PVDD_READBACK_LSB, 0, 0x01, 0), +SOC_SINGLE("ADC TEMP MSB", MAX98520_R20B8_ADC_TEMP_READBACK_MSB, 0, 0xFF, 0), +SOC_SINGLE("ADC TEMP LSB", MAX98520_R20B9_ADC_TEMP_READBACK_LSB, 0, 0x01, 0), +}; + +static const struct snd_soc_dapm_route max98520_audio_map[] = { + /* Plabyack */ + {"DAI Sel Mux", "Left", "Amp Enable"}, + {"DAI Sel Mux", "Right", "Amp Enable"}, + {"DAI Sel Mux", "LeftRight", "Amp Enable"}, + {"BE_OUT", NULL, "DAI Sel Mux"}, +}; + +static struct snd_soc_dai_driver max98520_dai[] = { + { + .name = "max98520-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98520_RATES, + .formats = MAX98520_FORMATS, + }, + .ops = &max98520_dai_ops, + } + +}; + +static int max98520_probe(struct snd_soc_component *component) +{ + struct max98520_priv *max98520 = + snd_soc_component_get_drvdata(component); + + /* Software Reset */ + regmap_write(max98520->regmap, MAX98520_R2000_SW_RESET, 1); + + /* L/R mono mix configuration : "DAI Sel" for 0x2043 */ + regmap_write(max98520->regmap, MAX98520_R2043_PCM_RX_SRC1, 0x2); + + /* PCM input channles configuration : "Left Input Selection" for 0x2044 */ + /* PCM input channles configuration : "Right Input Selection" for 0x2044 */ + regmap_write(max98520->regmap, MAX98520_R2044_PCM_RX_SRC2, 0x10); + + /* Enable DC blocker */ + regmap_update_bits(max98520->regmap, MAX98520_R2092_AMP_DSP_CFG, 1, 1); + /* Enable Clock Monitor Auto-restart */ + regmap_write(max98520->regmap, MAX98520_R2030_CLK_MON_CTRL, 0x1); + + /* set Rx Enable */ + regmap_update_bits(max98520->regmap, + MAX98520_R204F_PCM_RX_EN, + MAX98520_PCM_RX_EN_MASK, + 1); + + return 0; +} + +static int __maybe_unused max98520_suspend(struct device *dev) +{ + struct max98520_priv *max98520 = dev_get_drvdata(dev); + + regcache_cache_only(max98520->regmap, true); + regcache_mark_dirty(max98520->regmap); + return 0; +} + +static int __maybe_unused max98520_resume(struct device *dev) +{ + struct max98520_priv *max98520 = dev_get_drvdata(dev); + + regcache_cache_only(max98520->regmap, false); + regmap_write(max98520->regmap, MAX98520_R2000_SW_RESET, 1); + regcache_sync(max98520->regmap); + return 0; +} + +static const struct dev_pm_ops max98520_pm = { + SET_SYSTEM_SLEEP_PM_OPS(max98520_suspend, max98520_resume) +}; + +static const struct snd_soc_component_driver soc_codec_dev_max98520 = { + .probe = max98520_probe, + .controls = max98520_snd_controls, + .num_controls = ARRAY_SIZE(max98520_snd_controls), + .dapm_widgets = max98520_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98520_dapm_widgets), + .dapm_routes = max98520_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98520_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config max98520_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98520_R21FF_REVISION_ID, + .reg_defaults = max98520_reg, + .num_reg_defaults = ARRAY_SIZE(max98520_reg), + .readable_reg = max98520_readable_register, + .volatile_reg = max98520_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static void max98520_power_on(struct max98520_priv *max98520, bool poweron) +{ + if (max98520->reset_gpio) + gpiod_set_value_cansleep(max98520->reset_gpio, !poweron); +} + +static int max98520_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + int ret; + int reg = 0; + struct max98520_priv *max98520; + struct i2c_adapter *adapter = to_i2c_adapter(i2c->dev.parent); + + ret = i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA); + if (!ret) { + dev_err(&i2c->dev, "I2C check functionality failed\n"); + return -ENXIO; + } + + max98520 = devm_kzalloc(&i2c->dev, sizeof(*max98520), GFP_KERNEL); + + if (!max98520) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98520); + + /* regmap initialization */ + max98520->regmap = devm_regmap_init_i2c(i2c, &max98520_regmap); + if (IS_ERR(max98520->regmap)) { + ret = PTR_ERR(max98520->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + /* Power on device */ + max98520->reset_gpio = devm_gpiod_get_optional(&i2c->dev, "reset", GPIOD_OUT_HIGH); + if (max98520->reset_gpio) { + if (IS_ERR(max98520->reset_gpio)) { + ret = PTR_ERR(max98520->reset_gpio); + dev_err(&i2c->dev, "Unable to request GPIO pin: %d.\n", ret); + return ret; + } + + max98520_power_on(max98520, 1); + } + + /* Check Revision ID */ + ret = regmap_read(max98520->regmap, MAX98520_R21FF_REVISION_ID, ®); + if (ret < 0) { + dev_err(&i2c->dev, + "Failed to read: 0x%02X\n", MAX98520_R21FF_REVISION_ID); + return ret; + } + dev_info(&i2c->dev, "MAX98520 revisionID: 0x%02X\n", reg); + + /* codec registration */ + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_codec_dev_max98520, + max98520_dai, ARRAY_SIZE(max98520_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id max98520_i2c_id[] = { + { "max98520", 0}, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max98520_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id max98520_of_match[] = { + { .compatible = "maxim,max98520", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98520_of_match); +#endif + +static struct i2c_driver max98520_i2c_driver = { + .driver = { + .name = "max98520", + .of_match_table = of_match_ptr(max98520_of_match), + .pm = &max98520_pm, + }, + .probe = max98520_i2c_probe, + .id_table = max98520_i2c_id, +}; + +module_i2c_driver(max98520_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98520 driver"); +MODULE_AUTHOR("George Song <george.song@maximintegrated.com>"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/max98520.h b/sound/soc/codecs/max98520.h new file mode 100644 index 000000000000..89a95c25afcf --- /dev/null +++ b/sound/soc/codecs/max98520.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021, Maxim Integrated. + */ + +#ifndef _MAX98520_H +#define _MAX98520_H + +#define MAX98520_R2000_SW_RESET 0x2000 +#define MAX98520_R2001_STATUS_1 0x2001 +#define MAX98520_R2002_STATUS_2 0x2002 +#define MAX98520_R2020_THERM_WARN_THRESH 0x2020 +#define MAX98520_R2021_THERM_SHDN_THRESH 0x2021 +#define MAX98520_R2022_THERM_HYSTERESIS 0x2022 +#define MAX98520_R2023_THERM_FOLDBACK_SET 0x2023 +#define MAX98520_R2027_THERM_FOLDBACK_EN 0x2027 +#define MAX98520_R2030_CLK_MON_CTRL 0x2030 +#define MAX98520_R2037_ERR_MON_CTRL 0x2037 +#define MAX98520_R2040_PCM_MODE_CFG 0x2040 +#define MAX98520_R2041_PCM_CLK_SETUP 0x2041 +#define MAX98520_R2042_PCM_SR_SETUP 0x2042 +#define MAX98520_R2043_PCM_RX_SRC1 0x2043 +#define MAX98520_R2044_PCM_RX_SRC2 0x2044 +#define MAX98520_R204F_PCM_RX_EN 0x204F +#define MAX98520_R2090_AMP_VOL_CTRL 0x2090 +#define MAX98520_R2091_AMP_PATH_GAIN 0x2091 +#define MAX98520_R2092_AMP_DSP_CFG 0x2092 +#define MAX98520_R2094_SSM_CFG 0x2094 +#define MAX98520_R2095_AMP_CFG 0x2095 +#define MAX98520_R209F_AMP_EN 0x209F +#define MAX98520_R20B0_ADC_SR 0x20B0 +#define MAX98520_R20B1_ADC_RESOLUTION 0x20B1 +#define MAX98520_R20B2_ADC_PVDD0_CFG 0x20B2 +#define MAX98520_R20B3_ADC_THERMAL_CFG 0x20B3 +#define MAX98520_R20B4_ADC_READBACK_CTRL 0x20B4 +#define MAX98520_R20B5_ADC_READBACK_UPDATE 0x20B5 +#define MAX98520_R20B6_ADC_PVDD_READBACK_MSB 0x20B6 +#define MAX98520_R20B7_ADC_PVDD_READBACK_LSB 0x20B7 +#define MAX98520_R20B8_ADC_TEMP_READBACK_MSB 0x20B8 +#define MAX98520_R20B9_ADC_TEMP_READBACK_LSB 0x20B9 +#define MAX98520_R20BA_ADC_LOW_PVDD_READBACK_MSB 0x20BA +#define MAX98520_R20BB_ADC_LOW_READBACK_LSB 0x20BB +#define MAX98520_R20BC_ADC_HIGH_TEMP_READBACK_MSB 0x20BC +#define MAX98520_R20BD_ADC_HIGH_TEMP_READBACK_LSB 0x20BD +#define MAX98520_R20CF_MEAS_ADC_CFG 0x20CF +#define MAX98520_R20D0_DHT_CFG1 0x20D0 +#define MAX98520_R20D1_LIMITER_CFG1 0x20D1 +#define MAX98520_R20D2_LIMITER_CFG2 0x20D2 +#define MAX98520_R20D3_DHT_CFG2 0x20D3 +#define MAX98520_R20D4_DHT_CFG3 0x20D4 +#define MAX98520_R20D5_DHT_CFG4 0x20D5 +#define MAX98520_R20D6_DHT_HYSTERESIS_CFG 0x20D6 +#define MAX98520_R20D8_DHT_EN 0x20D8 +#define MAX98520_R210E_AUTO_RESTART_BEHAVIOR 0x210E +#define MAX98520_R210F_GLOBAL_EN 0x210F +#define MAX98520_R2161_BOOST_TM1 0x2161 +#define MAX98520_R2162_BOOST_TM2 0x2162 +#define MAX98520_R2163_BOOST_TM3 0x2163 +#define MAX98520_R21FF_REVISION_ID 0x21FF + +/* MAX98520_R2030_CLK_MON_CTRL */ +#define MAX98520_CMON_AUTORESTART_SHIFT (0) + +/* MAX98520_R2037_ERR_MON_CTRL */ +#define MAX98520_CTRL_CMON_EN_SHIFT (0) + +/* MAX98520_R2040_PCM_MODE_CFG */ +#define MAX98520_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3) +#define MAX98520_PCM_MODE_CFG_FORMAT_SHIFT (3) +#define MAX98520_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 2) +#define MAX98520_PCM_FORMAT_I2S (0x0 << 3) +#define MAX98520_PCM_FORMAT_LJ (0x1 << 3) +#define MAX98520_PCM_FORMAT_TDM_MODE0 (0x3 << 3) +#define MAX98520_PCM_FORMAT_TDM_MODE1 (0x4 << 3) +#define MAX98520_PCM_FORMAT_TDM_MODE2 (0x5 << 3) +#define MAX98520_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6) +#define MAX98520_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6) +#define MAX98520_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6) +#define MAX98520_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6) + +/* MAX98520_R2041_PCM_CLK_SETUP */ +#define MAX98520_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 4) +#define MAX98520_PCM_CLK_SETUP_BSEL_MASK (0xF << 0) + +/* MAX98520_R2042_PCM_SR_SETUP */ +#define MAX98520_PCM_SR_SHIFT (0) +#define MAX98520_IVADC_SR_SHIFT (4) +#define MAX98520_PCM_SR_MASK (0xF << MAX98520_PCM_SR_SHIFT) +#define MAX98520_IVADC_SR_MASK (0xF << MAX98520_IVADC_SR_SHIFT) +#define MAX98520_PCM_SR_8000 (0x0) +#define MAX98520_PCM_SR_11025 (0x1) +#define MAX98520_PCM_SR_12000 (0x2) +#define MAX98520_PCM_SR_16000 (0x3) +#define MAX98520_PCM_SR_22050 (0x4) +#define MAX98520_PCM_SR_24000 (0x5) +#define MAX98520_PCM_SR_32000 (0x6) +#define MAX98520_PCM_SR_44100 (0x7) +#define MAX98520_PCM_SR_48000 (0x8) +#define MAX98520_PCM_SR_88200 (0x9) +#define MAX98520_PCM_SR_96000 (0xA) +#define MAX98520_PCM_SR_176400 (0xB) +#define MAX98520_PCM_SR_192000 (0xC) + +/* MAX98520_R2044_PCM_RX_SRC2 */ +#define MAX98520_PCM_DMIX_CH1_SHIFT (0xF << 0) +#define MAX98520_PCM_DMIX_CH0_SRC_MASK (0xF << 0) +#define MAX98520_PCM_DMIX_CH1_SRC_MASK (0xF << MAX98520_PCM_DMIX_CH1_SHIFT) + +/* MAX98520_R204F_PCM_RX_EN */ +#define MAX98520_PCM_RX_EN_MASK (0x1 << 0) +#define MAX98520_PCM_RX_BYP_EN_MASK (0x1 << 1) + +/* MAX98520_R2092_AMP_DSP_CFG */ +#define MAX98520_DSP_SPK_DCBLK_EN_SHIFT (0) +#define MAX98520_DSP_SPK_DITH_EN_SHIFT (1) +#define MAX98520_DSP_SPK_INVERT_SHIFT (2) +#define MAX98520_DSP_SPK_VOL_RMPUP_SHIFT (3) +#define MAX98520_DSP_SPK_VOL_RMPDN_SHIFT (4) +#define MAX98520_DSP_SPK_SAFE_EN_SHIFT (5) + +#define MAX98520_SPK_SAFE_EN_MASK (0x1 << MAX98520_DSP_SPK_SAFE_EN_SHIFT) + +/* MAX98520_R2094_SSM_CFG */ +#define MAX98520_SSM_EN_SHIFT (0) +#define MAX98520_SSM_MOD_SHIFT (1) +#define MAX98520_SSM_RCVR_MODE_SHIFT (3) + +/* MAX98520_R2095_AMP_CFG */ +#define MAX98520_CFG_DYN_MODE_SHIFT (4) +#define MAX98520_CFG_SPK_MODE_SHIFT (3) + +/* MAX98520_R20D0_DHT_CFG1 */ +#define MAX98520_DHT_VROT_PNT_SHIFT (0) + +/* MAX98520_R20D1_LIMITER_CFG1 */ +#define MAX98520_DHT_SUPPLY_HR_SHIFT (0) + +/* MAX98520_R20D2_DHT_CFG2 */ +#define MAX98520_DHT_LIMITER_MODE_SHIFT (0) +#define MAX98520_DHT_LIMITER_THRESHOLD_SHIFT (1) + +/* MAX98520_R20D3_DHT_CFG2 */ +#define MAX98520_DHT_MAX_ATTEN_SHIFT (0) + +/* MAX98520_R20D6_DHT_HYSTERESIS_CFG */ +#define MAX98520_DHT_HYSTERESIS_SWITCH_SHIFT (0) +#define MAX98520_DHT_HYSTERESIS_SHIFT (1) + +/* MAX98520_R20B2_ADC_PVDD0_CFG, MAX98520_R20B3_ADC_THERMAL_CFG */ +#define MAX98520_FLT_EN_SHIFT (4) + +struct max98520_priv { + struct regmap *regmap; + struct gpio_desc *reset_gpio; + unsigned int ch_size; + bool tdm_mode; +}; +#endif + diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c index 8b206ee77709..5ba5f876eab8 100644 --- a/sound/soc/codecs/max98927.c +++ b/sound/soc/codecs/max98927.c @@ -897,6 +897,19 @@ static int max98927_i2c_probe(struct i2c_client *i2c, "Failed to allocate regmap: %d\n", ret); return ret; } + + max98927->reset_gpio + = devm_gpiod_get_optional(&i2c->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(max98927->reset_gpio)) { + ret = PTR_ERR(max98927->reset_gpio); + return dev_err_probe(&i2c->dev, ret, "failed to request GPIO reset pin"); + } + + if (max98927->reset_gpio) { + gpiod_set_value_cansleep(max98927->reset_gpio, 0); + /* Wait for i2c port to be ready */ + usleep_range(5000, 6000); + } /* Check Revision ID */ ret = regmap_read(max98927->regmap, @@ -921,6 +934,17 @@ static int max98927_i2c_probe(struct i2c_client *i2c, return ret; } +static int max98927_i2c_remove(struct i2c_client *i2c) +{ + struct max98927_priv *max98927 = i2c_get_clientdata(i2c); + + if (max98927->reset_gpio) { + gpiod_set_value_cansleep(max98927->reset_gpio, 1); + } + + return 0; +} + static const struct i2c_device_id max98927_i2c_id[] = { { "max98927", 0}, { }, @@ -952,6 +976,7 @@ static struct i2c_driver max98927_i2c_driver = { .pm = &max98927_pm, }, .probe = max98927_i2c_probe, + .remove = max98927_i2c_remove, .id_table = max98927_i2c_id, }; diff --git a/sound/soc/codecs/max98927.h b/sound/soc/codecs/max98927.h index 05f495db914d..13f5066d7419 100644 --- a/sound/soc/codecs/max98927.h +++ b/sound/soc/codecs/max98927.h @@ -255,6 +255,7 @@ struct max98927_priv { struct regmap *regmap; struct snd_soc_component *component; struct max98927_pdata *pdata; + struct gpio_desc *reset_gpio; unsigned int spk_gain; unsigned int sysclk; unsigned int v_l_slot; diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index 2d6a4a29b850..f8532aa7e4aa 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -2697,7 +2697,7 @@ static int mt6359_codec_probe(struct snd_soc_component *cmpnt) static void mt6359_codec_remove(struct snd_soc_component *cmpnt) { - snd_soc_component_exit_regmap(cmpnt); + cmpnt->regmap = NULL; } static const DECLARE_TLV_DB_SCALE(hp_playback_tlv, -2200, 100, 0); diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c new file mode 100644 index 000000000000..2de818377484 --- /dev/null +++ b/sound/soc/codecs/nau8821.c @@ -0,0 +1,1714 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// nau8821.c -- Nuvoton NAU88L21 audio codec driver +// +// Copyright 2021 Nuvoton Technology Corp. +// Author: John Hsu <kchsu0@nuvoton.com> +// Co-author: Seven Lee <wtli@nuvoton.com> +// + +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/math64.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include "nau8821.h" + +#define NAU_FREF_MAX 13500000 +#define NAU_FVCO_MAX 100000000 +#define NAU_FVCO_MIN 90000000 + +/* the maximum frequency of CLK_ADC and CLK_DAC */ +#define CLK_DA_AD_MAX 6144000 + +static int nau8821_configure_sysclk(struct nau8821 *nau8821, + int clk_id, unsigned int freq); + +struct nau8821_fll { + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; +}; + +struct nau8821_fll_attr { + unsigned int param; + unsigned int val; +}; + +/* scaling for mclk from sysclk_src output */ +static const struct nau8821_fll_attr mclk_src_scaling[] = { + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, + { 48, 0xd }, + { 96, 0xe }, + { 5, 0xf }, +}; + +/* ratio for input clk freq */ +static const struct nau8821_fll_attr fll_ratio[] = { + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, +}; + +static const struct nau8821_fll_attr fll_pre_scalar[] = { + { 0, 0x0 }, + { 1, 0x1 }, + { 2, 0x2 }, + { 3, 0x3 }, +}; + +/* over sampling rate */ +struct nau8821_osr_attr { + unsigned int osr; + unsigned int clk_src; +}; + +static const struct nau8821_osr_attr osr_dac_sel[] = { + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 256, 0 }, /* OSR 256, SRC 1 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 0, 0 }, + { 32, 3 }, /* OSR 32, SRC 1/8 */ +}; + +static const struct nau8821_osr_attr osr_adc_sel[] = { + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ +}; + +struct nau8821_dmic_speed { + unsigned int param; + unsigned int val; +}; + +static const struct nau8821_dmic_speed dmic_speed_sel[] = { + { 0, 0x0 }, /*SPEED 1, SRC 1 */ + { 1, 0x1 }, /*SPEED 2, SRC 1/2 */ + { 2, 0x2 }, /*SPEED 4, SRC 1/4 */ + { 3, 0x3 }, /*SPEED 8, SRC 1/8 */ +}; + +static const struct reg_default nau8821_reg_defaults[] = { + { NAU8821_R01_ENA_CTRL, 0x00ff }, + { NAU8821_R03_CLK_DIVIDER, 0x0050 }, + { NAU8821_R04_FLL1, 0x0 }, + { NAU8821_R05_FLL2, 0x00bc }, + { NAU8821_R06_FLL3, 0x0008 }, + { NAU8821_R07_FLL4, 0x0010 }, + { NAU8821_R08_FLL5, 0x4000 }, + { NAU8821_R09_FLL6, 0x6900 }, + { NAU8821_R0A_FLL7, 0x0031 }, + { NAU8821_R0B_FLL8, 0x26e9 }, + { NAU8821_R0D_JACK_DET_CTRL, 0x0 }, + { NAU8821_R0F_INTERRUPT_MASK, 0x0 }, + { NAU8821_R12_INTERRUPT_DIS_CTRL, 0xffff }, + { NAU8821_R13_DMIC_CTRL, 0x0 }, + { NAU8821_R1A_GPIO12_CTRL, 0x0 }, + { NAU8821_R1B_TDM_CTRL, 0x0 }, + { NAU8821_R1C_I2S_PCM_CTRL1, 0x000a }, + { NAU8821_R1D_I2S_PCM_CTRL2, 0x8010 }, + { NAU8821_R1E_LEFT_TIME_SLOT, 0x0 }, + { NAU8821_R1F_RIGHT_TIME_SLOT, 0x0 }, + { NAU8821_R21_BIQ0_COF1, 0x0 }, + { NAU8821_R22_BIQ0_COF2, 0x0 }, + { NAU8821_R23_BIQ0_COF3, 0x0 }, + { NAU8821_R24_BIQ0_COF4, 0x0 }, + { NAU8821_R25_BIQ0_COF5, 0x0 }, + { NAU8821_R26_BIQ0_COF6, 0x0 }, + { NAU8821_R27_BIQ0_COF7, 0x0 }, + { NAU8821_R28_BIQ0_COF8, 0x0 }, + { NAU8821_R29_BIQ0_COF9, 0x0 }, + { NAU8821_R2A_BIQ0_COF10, 0x0 }, + { NAU8821_R2B_ADC_RATE, 0x0002 }, + { NAU8821_R2C_DAC_CTRL1, 0x0082 }, + { NAU8821_R2D_DAC_CTRL2, 0x0 }, + { NAU8821_R2F_DAC_DGAIN_CTRL, 0x0 }, + { NAU8821_R30_ADC_DGAIN_CTRL, 0x0 }, + { NAU8821_R31_MUTE_CTRL, 0x0 }, + { NAU8821_R32_HSVOL_CTRL, 0x0 }, + { NAU8821_R34_DACR_CTRL, 0xcfcf }, + { NAU8821_R35_ADC_DGAIN_CTRL1, 0xcfcf }, + { NAU8821_R36_ADC_DRC_KNEE_IP12, 0x1486 }, + { NAU8821_R37_ADC_DRC_KNEE_IP34, 0x0f12 }, + { NAU8821_R38_ADC_DRC_SLOPES, 0x25ff }, + { NAU8821_R39_ADC_DRC_ATKDCY, 0x3457 }, + { NAU8821_R3A_DAC_DRC_KNEE_IP12, 0x1486 }, + { NAU8821_R3B_DAC_DRC_KNEE_IP34, 0x0f12 }, + { NAU8821_R3C_DAC_DRC_SLOPES, 0x25f9 }, + { NAU8821_R3D_DAC_DRC_ATKDCY, 0x3457 }, + { NAU8821_R41_BIQ1_COF1, 0x0 }, + { NAU8821_R42_BIQ1_COF2, 0x0 }, + { NAU8821_R43_BIQ1_COF3, 0x0 }, + { NAU8821_R44_BIQ1_COF4, 0x0 }, + { NAU8821_R45_BIQ1_COF5, 0x0 }, + { NAU8821_R46_BIQ1_COF6, 0x0 }, + { NAU8821_R47_BIQ1_COF7, 0x0 }, + { NAU8821_R48_BIQ1_COF8, 0x0 }, + { NAU8821_R49_BIQ1_COF9, 0x0 }, + { NAU8821_R4A_BIQ1_COF10, 0x0 }, + { NAU8821_R4B_CLASSG_CTRL, 0x0 }, + { NAU8821_R4C_IMM_MODE_CTRL, 0x0 }, + { NAU8821_R4D_IMM_RMS_L, 0x0 }, + { NAU8821_R53_OTPDOUT_1, 0xaad8 }, + { NAU8821_R54_OTPDOUT_2, 0x0002 }, + { NAU8821_R55_MISC_CTRL, 0x0 }, + { NAU8821_R66_BIAS_ADJ, 0x0 }, + { NAU8821_R68_TRIM_SETTINGS, 0x0 }, + { NAU8821_R69_ANALOG_CONTROL_1, 0x0 }, + { NAU8821_R6A_ANALOG_CONTROL_2, 0x0 }, + { NAU8821_R6B_PGA_MUTE, 0x0 }, + { NAU8821_R71_ANALOG_ADC_1, 0x0011 }, + { NAU8821_R72_ANALOG_ADC_2, 0x0020 }, + { NAU8821_R73_RDAC, 0x0008 }, + { NAU8821_R74_MIC_BIAS, 0x0006 }, + { NAU8821_R76_BOOST, 0x0 }, + { NAU8821_R77_FEPGA, 0x0 }, + { NAU8821_R7E_PGA_GAIN, 0x0 }, + { NAU8821_R7F_POWER_UP_CONTROL, 0x0 }, + { NAU8821_R80_CHARGE_PUMP, 0x0 }, +}; + +static bool nau8821_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8821_R00_RESET ... NAU8821_R01_ENA_CTRL: + case NAU8821_R03_CLK_DIVIDER ... NAU8821_R0B_FLL8: + case NAU8821_R0D_JACK_DET_CTRL: + case NAU8821_R0F_INTERRUPT_MASK ... NAU8821_R13_DMIC_CTRL: + case NAU8821_R1A_GPIO12_CTRL ... NAU8821_R1F_RIGHT_TIME_SLOT: + case NAU8821_R21_BIQ0_COF1 ... NAU8821_R2D_DAC_CTRL2: + case NAU8821_R2F_DAC_DGAIN_CTRL ... NAU8821_R32_HSVOL_CTRL: + case NAU8821_R34_DACR_CTRL ... NAU8821_R3D_DAC_DRC_ATKDCY: + case NAU8821_R41_BIQ1_COF1 ... NAU8821_R4F_FUSE_CTRL3: + case NAU8821_R51_FUSE_CTRL1: + case NAU8821_R53_OTPDOUT_1 ... NAU8821_R55_MISC_CTRL: + case NAU8821_R58_I2C_DEVICE_ID ... NAU8821_R5A_SOFTWARE_RST: + case NAU8821_R66_BIAS_ADJ: + case NAU8821_R68_TRIM_SETTINGS ... NAU8821_R6B_PGA_MUTE: + case NAU8821_R71_ANALOG_ADC_1 ... NAU8821_R74_MIC_BIAS: + case NAU8821_R76_BOOST ... NAU8821_R77_FEPGA: + case NAU8821_R7E_PGA_GAIN ... NAU8821_R82_GENERAL_STATUS: + return true; + default: + return false; + } +} + +static bool nau8821_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8821_R00_RESET ... NAU8821_R01_ENA_CTRL: + case NAU8821_R03_CLK_DIVIDER ... NAU8821_R0B_FLL8: + case NAU8821_R0D_JACK_DET_CTRL: + case NAU8821_R0F_INTERRUPT_MASK: + case NAU8821_R11_INT_CLR_KEY_STATUS ... NAU8821_R13_DMIC_CTRL: + case NAU8821_R1A_GPIO12_CTRL ... NAU8821_R1F_RIGHT_TIME_SLOT: + case NAU8821_R21_BIQ0_COF1 ... NAU8821_R2D_DAC_CTRL2: + case NAU8821_R2F_DAC_DGAIN_CTRL ... NAU8821_R32_HSVOL_CTRL: + case NAU8821_R34_DACR_CTRL ... NAU8821_R3D_DAC_DRC_ATKDCY: + case NAU8821_R41_BIQ1_COF1 ... NAU8821_R4C_IMM_MODE_CTRL: + case NAU8821_R4E_FUSE_CTRL2 ... NAU8821_R4F_FUSE_CTRL3: + case NAU8821_R51_FUSE_CTRL1: + case NAU8821_R55_MISC_CTRL: + case NAU8821_R5A_SOFTWARE_RST: + case NAU8821_R66_BIAS_ADJ: + case NAU8821_R68_TRIM_SETTINGS ... NAU8821_R6B_PGA_MUTE: + case NAU8821_R71_ANALOG_ADC_1 ... NAU8821_R74_MIC_BIAS: + case NAU8821_R76_BOOST ... NAU8821_R77_FEPGA: + case NAU8821_R7E_PGA_GAIN ... NAU8821_R80_CHARGE_PUMP: + return true; + default: + return false; + } +} + +static bool nau8821_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8821_R00_RESET: + case NAU8821_R10_IRQ_STATUS ... NAU8821_R11_INT_CLR_KEY_STATUS: + case NAU8821_R21_BIQ0_COF1 ... NAU8821_R2A_BIQ0_COF10: + case NAU8821_R41_BIQ1_COF1 ... NAU8821_R4A_BIQ1_COF10: + case NAU8821_R4D_IMM_RMS_L: + case NAU8821_R53_OTPDOUT_1 ... NAU8821_R54_OTPDOUT_2: + case NAU8821_R58_I2C_DEVICE_ID ... NAU8821_R5A_SOFTWARE_RST: + case NAU8821_R81_CHARGE_PUMP_INPUT_READ ... NAU8821_R82_GENERAL_STATUS: + return true; + default: + return false; + } +} + +static int nau8821_biq_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + + if (!component->regmap) + return -EINVAL; + + regmap_raw_read(component->regmap, NAU8821_R21_BIQ0_COF1, + ucontrol->value.bytes.data, params->max); + + return 0; +} + +static int nau8821_biq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + void *data; + + if (!component->regmap) + return -EINVAL; + + data = kmemdup(ucontrol->value.bytes.data, + params->max, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + regmap_raw_write(component->regmap, NAU8821_R21_BIQ0_COF1, + data, params->max); + + kfree(data); + + return 0; +} + +static const char * const nau8821_adc_decimation[] = { + "32", "64", "128", "256" }; + +static const struct soc_enum nau8821_adc_decimation_enum = + SOC_ENUM_SINGLE(NAU8821_R2B_ADC_RATE, NAU8821_ADC_SYNC_DOWN_SFT, + ARRAY_SIZE(nau8821_adc_decimation), nau8821_adc_decimation); + +static const char * const nau8821_dac_oversampl[] = { + "64", "256", "128", "", "32" }; + +static const struct soc_enum nau8821_dac_oversampl_enum = + SOC_ENUM_SINGLE(NAU8821_R2C_DAC_CTRL1, NAU8821_DAC_OVERSAMPLE_SFT, + ARRAY_SIZE(nau8821_dac_oversampl), nau8821_dac_oversampl); + +static const DECLARE_TLV_DB_MINMAX_MUTE(adc_vol_tlv, -6600, 2400); +static const DECLARE_TLV_DB_MINMAX_MUTE(sidetone_vol_tlv, -4200, 0); +static const DECLARE_TLV_DB_MINMAX(hp_vol_tlv, -900, 0); +static const DECLARE_TLV_DB_SCALE(playback_vol_tlv, -6600, 50, 1); +static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600); +static const DECLARE_TLV_DB_MINMAX_MUTE(crosstalk_vol_tlv, -7000, 2400); + +static const struct snd_kcontrol_new nau8821_controls[] = { + SOC_DOUBLE_TLV("Mic Volume", NAU8821_R35_ADC_DGAIN_CTRL1, + NAU8821_ADCL_CH_VOL_SFT, NAU8821_ADCR_CH_VOL_SFT, + 0xff, 0, adc_vol_tlv), + SOC_DOUBLE_TLV("Headphone Bypass Volume", NAU8821_R30_ADC_DGAIN_CTRL, + 12, 8, 0x0f, 0, sidetone_vol_tlv), + SOC_DOUBLE_TLV("Headphone Volume", NAU8821_R32_HSVOL_CTRL, + NAU8821_HPL_VOL_SFT, NAU8821_HPR_VOL_SFT, 0x3, 1, hp_vol_tlv), + SOC_DOUBLE_TLV("Digital Playback Volume", NAU8821_R34_DACR_CTRL, + NAU8821_DACL_CH_VOL_SFT, NAU8821_DACR_CH_VOL_SFT, + 0xcf, 0, playback_vol_tlv), + SOC_DOUBLE_TLV("Frontend PGA Volume", NAU8821_R7E_PGA_GAIN, + NAU8821_PGA_GAIN_L_SFT, NAU8821_PGA_GAIN_R_SFT, + 37, 0, fepga_gain_tlv), + SOC_DOUBLE_TLV("Headphone Crosstalk Volume", + NAU8821_R2F_DAC_DGAIN_CTRL, + 0, 8, 0xff, 0, crosstalk_vol_tlv), + + SOC_ENUM("ADC Decimation Rate", nau8821_adc_decimation_enum), + SOC_ENUM("DAC Oversampling Rate", nau8821_dac_oversampl_enum), + SND_SOC_BYTES_EXT("BIQ Coefficients", 20, + nau8821_biq_coeff_get, nau8821_biq_coeff_put), + SOC_SINGLE("ADC Phase Switch", NAU8821_R1B_TDM_CTRL, + NAU8821_ADCPHS_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8821_dmic_mode_switch = + SOC_DAPM_SINGLE("Switch", NAU8821_R13_DMIC_CTRL, + NAU8821_DMIC_EN_SFT, 1, 0); + +static int dmic_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + int i, speed_selection = -1, clk_adc_src, clk_adc; + unsigned int clk_divider_r03; + + /* The DMIC clock is gotten from adc clock divided by + * CLK_DMIC_SRC (1, 2, 4, 8). The clock has to be equal or + * less than nau8821->dmic_clk_threshold. + */ + regmap_read(nau8821->regmap, NAU8821_R03_CLK_DIVIDER, + &clk_divider_r03); + clk_adc_src = (clk_divider_r03 & NAU8821_CLK_ADC_SRC_MASK) + >> NAU8821_CLK_ADC_SRC_SFT; + clk_adc = (nau8821->fs * 256) >> clk_adc_src; + + for (i = 0 ; i < 4 ; i++) + if ((clk_adc >> dmic_speed_sel[i].param) <= + nau8821->dmic_clk_threshold) { + speed_selection = dmic_speed_sel[i].val; + break; + } + if (i == 4) + return -EINVAL; + + dev_dbg(nau8821->dev, + "clk_adc=%d, dmic_clk_threshold = %d, param=%d, val = %d\n", + clk_adc, nau8821->dmic_clk_threshold, + dmic_speed_sel[i].param, dmic_speed_sel[i].val); + regmap_update_bits(nau8821->regmap, NAU8821_R13_DMIC_CTRL, + NAU8821_DMIC_SRC_MASK, + (speed_selection << NAU8821_DMIC_SRC_SFT)); + + return 0; +} + +static int nau8821_left_adc_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 nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(125); + regmap_update_bits(nau8821->regmap, NAU8821_R01_ENA_CTRL, + NAU8821_EN_ADCL, NAU8821_EN_ADCL); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8821->regmap, + NAU8821_R01_ENA_CTRL, NAU8821_EN_ADCL, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8821_right_adc_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 nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(125); + regmap_update_bits(nau8821->regmap, NAU8821_R01_ENA_CTRL, + NAU8821_EN_ADCR, NAU8821_EN_ADCR); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8821->regmap, + NAU8821_R01_ENA_CTRL, NAU8821_EN_ADCR, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8821_pump_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 nau8821 *nau8821 = + snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Prevent startup click by letting charge pump to ramp up */ + msleep(20); + regmap_update_bits(nau8821->regmap, NAU8821_R80_CHARGE_PUMP, + NAU8821_JAMNODCLOW, NAU8821_JAMNODCLOW); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(nau8821->regmap, NAU8821_R80_CHARGE_PUMP, + NAU8821_JAMNODCLOW, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8821_output_dac_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 nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Disables the TESTDAC to let DAC signal pass through. */ + regmap_update_bits(nau8821->regmap, NAU8821_R66_BIAS_ADJ, + NAU8821_BIAS_TESTDAC_EN, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8821->regmap, NAU8821_R66_BIAS_ADJ, + NAU8821_BIAS_TESTDAC_EN, NAU8821_BIAS_TESTDAC_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dapm_widget nau8821_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("MICBIAS", NAU8821_R74_MIC_BIAS, + NAU8821_MICBIAS_POWERUP_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC Clock", SND_SOC_NOPM, 0, 0, + dmic_clock_control, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC("ADCL Power", NULL, NAU8821_R72_ANALOG_ADC_2, + NAU8821_POWERUP_ADCL_SFT, 0), + SND_SOC_DAPM_ADC("ADCR Power", NULL, NAU8821_R72_ANALOG_ADC_2, + NAU8821_POWERUP_ADCR_SFT, 0), + SND_SOC_DAPM_PGA_S("Frontend PGA L", 1, NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_PGA_L_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Frontend PGA R", 1, NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_PGA_R_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADCL Digital path", 0, NAU8821_R01_ENA_CTRL, + NAU8821_EN_ADCL_SFT, 0, nau8821_left_adc_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADCR Digital path", 0, NAU8821_R01_ENA_CTRL, + NAU8821_EN_ADCR_SFT, 0, nau8821_right_adc_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("DMIC Enable", SND_SOC_NOPM, + 0, 0, &nau8821_dmic_mode_switch), + SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, NAU8821_R1D_I2S_PCM_CTRL2, + NAU8821_I2S_TRISTATE_SFT, 1), + SND_SOC_DAPM_AIF_IN("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_PGA_S("ADACL", 2, NAU8821_R73_RDAC, + NAU8821_DACL_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADACR", 2, NAU8821_R73_RDAC, + NAU8821_DACR_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADACL Clock", 3, NAU8821_R73_RDAC, + NAU8821_DACL_CLK_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADACR Clock", 3, NAU8821_R73_RDAC, + NAU8821_DACR_CLK_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DDACR", NULL, NAU8821_R01_ENA_CTRL, + NAU8821_EN_DACR_SFT, 0), + SND_SOC_DAPM_DAC("DDACL", NULL, NAU8821_R01_ENA_CTRL, + NAU8821_EN_DACL_SFT, 0), + SND_SOC_DAPM_PGA_S("HP amp L", 0, NAU8821_R4B_CLASSG_CTRL, + NAU8821_CLASSG_LDAC_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("HP amp R", 0, NAU8821_R4B_CLASSG_CTRL, + NAU8821_CLASSG_RDAC_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Charge Pump", 1, NAU8821_R80_CHARGE_PUMP, + NAU8821_CHANRGE_PUMP_EN_SFT, 0, nau8821_pump_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_S("Output Driver R Stage 1", 4, + NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_INTEG_R_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver L Stage 1", 4, + NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_INTEG_L_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver R Stage 2", 5, + NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_DRV_INSTG_R_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver L Stage 2", 5, + NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_DRV_INSTG_L_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver R Stage 3", 6, + NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_MAIN_DRV_R_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver L Stage 3", 6, + NAU8821_R7F_POWER_UP_CONTROL, + NAU8821_PUP_MAIN_DRV_L_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output DACL", 7, + NAU8821_R80_CHARGE_PUMP, NAU8821_POWER_DOWN_DACL_SFT, + 0, nau8821_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("Output DACR", 7, + NAU8821_R80_CHARGE_PUMP, NAU8821_POWER_DOWN_DACR_SFT, + 0, nau8821_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* HPOL/R are ungrounded by disabling 16 Ohm pull-downs on playback */ + SND_SOC_DAPM_PGA_S("HPOL Pulldown", 8, + NAU8821_R0D_JACK_DET_CTRL, + NAU8821_SPKR_DWN1L_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("HPOR Pulldown", 8, + NAU8821_R0D_JACK_DET_CTRL, + NAU8821_SPKR_DWN1R_SFT, 0, NULL, 0), + + /* High current HPOL/R boost driver */ + SND_SOC_DAPM_PGA_S("HP Boost Driver", 9, + NAU8821_R76_BOOST, NAU8821_HP_BOOST_DIS_SFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Class G", NAU8821_R4B_CLASSG_CTRL, + NAU8821_CLASSG_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + SND_SOC_DAPM_INPUT("DMIC"), + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route nau8821_dapm_routes[] = { + {"DMIC Enable", "Switch", "DMIC"}, + {"DMIC Enable", NULL, "DMIC Clock"}, + + {"Frontend PGA L", NULL, "MICL"}, + {"Frontend PGA R", NULL, "MICR"}, + {"Frontend PGA L", NULL, "MICBIAS"}, + {"Frontend PGA R", NULL, "MICBIAS"}, + + {"ADCL Power", NULL, "Frontend PGA L"}, + {"ADCR Power", NULL, "Frontend PGA R"}, + + {"ADCL Digital path", NULL, "ADCL Power"}, + {"ADCR Digital path", NULL, "ADCR Power"}, + {"ADCL Digital path", NULL, "DMIC Enable"}, + {"ADCR Digital path", NULL, "DMIC Enable"}, + + {"AIFTX", NULL, "ADCL Digital path"}, + {"AIFTX", NULL, "ADCR Digital path"}, + + {"DDACL", NULL, "AIFRX"}, + {"DDACR", NULL, "AIFRX"}, + + {"HP amp L", NULL, "DDACL"}, + {"HP amp R", NULL, "DDACR"}, + + {"Charge Pump", NULL, "HP amp L"}, + {"Charge Pump", NULL, "HP amp R"}, + + {"ADACL", NULL, "Charge Pump"}, + {"ADACR", NULL, "Charge Pump"}, + {"ADACL Clock", NULL, "ADACL"}, + {"ADACR Clock", NULL, "ADACR"}, + + {"Output Driver L Stage 1", NULL, "ADACL Clock"}, + {"Output Driver R Stage 1", NULL, "ADACR Clock"}, + {"Output Driver L Stage 2", NULL, "Output Driver L Stage 1"}, + {"Output Driver R Stage 2", NULL, "Output Driver R Stage 1"}, + {"Output Driver L Stage 3", NULL, "Output Driver L Stage 2"}, + {"Output Driver R Stage 3", NULL, "Output Driver R Stage 2"}, + {"Output DACL", NULL, "Output Driver L Stage 3"}, + {"Output DACR", NULL, "Output Driver R Stage 3"}, + + {"HPOL Pulldown", NULL, "Output DACL"}, + {"HPOR Pulldown", NULL, "Output DACR"}, + {"HP Boost Driver", NULL, "HPOL Pulldown"}, + {"HP Boost Driver", NULL, "HPOR Pulldown"}, + + {"Class G", NULL, "HP Boost Driver"}, + {"HPOL", NULL, "Class G"}, + {"HPOR", NULL, "Class G"}, +}; + +static int nau8821_clock_check(struct nau8821 *nau8821, + int stream, int rate, int osr) +{ + int osrate = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (osr >= ARRAY_SIZE(osr_dac_sel)) + return -EINVAL; + osrate = osr_dac_sel[osr].osr; + } else { + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + osrate = osr_adc_sel[osr].osr; + } + + if (!osrate || rate * osrate > CLK_DA_AD_MAX) { + dev_err(nau8821->dev, + "exceed the maximum frequency of CLK_ADC or CLK_DAC"); + return -EINVAL; + } + + return 0; +} + +static int nau8821_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; + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, osr, ctrl_val, bclk_fs, clk_div; + + nau8821->fs = params_rate(params); + /* CLK_DAC or CLK_ADC = OSR * FS + * DAC or ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_read(nau8821->regmap, NAU8821_R2C_DAC_CTRL1, &osr); + osr &= NAU8821_DAC_OVERSAMPLE_MASK; + if (nau8821_clock_check(nau8821, substream->stream, + nau8821->fs, osr)) { + return -EINVAL; + } + regmap_update_bits(nau8821->regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_DAC_SRC_MASK, + osr_dac_sel[osr].clk_src << NAU8821_CLK_DAC_SRC_SFT); + } else { + regmap_read(nau8821->regmap, NAU8821_R2B_ADC_RATE, &osr); + osr &= NAU8821_ADC_SYNC_DOWN_MASK; + if (nau8821_clock_check(nau8821, substream->stream, + nau8821->fs, osr)) { + return -EINVAL; + } + regmap_update_bits(nau8821->regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8821_CLK_ADC_SRC_SFT); + } + + /* make BCLK and LRC divde configuration if the codec as master. */ + regmap_read(nau8821->regmap, NAU8821_R1D_I2S_PCM_CTRL2, &ctrl_val); + if (ctrl_val & NAU8821_I2S_MS_MASTER) { + /* get the bclk and fs ratio */ + bclk_fs = snd_soc_params_to_bclk(params) / nau8821->fs; + if (bclk_fs <= 32) + clk_div = 3; + else if (bclk_fs <= 64) + clk_div = 2; + else if (bclk_fs <= 128) + clk_div = 1; + else { + return -EINVAL; + } + regmap_update_bits(nau8821->regmap, NAU8821_R1D_I2S_PCM_CTRL2, + NAU8821_I2S_LRC_DIV_MASK | NAU8821_I2S_BLK_DIV_MASK, + (clk_div << NAU8821_I2S_LRC_DIV_SFT) | clk_div); + } + + switch (params_width(params)) { + case 16: + val_len |= NAU8821_I2S_DL_16; + break; + case 20: + val_len |= NAU8821_I2S_DL_20; + break; + case 24: + val_len |= NAU8821_I2S_DL_24; + break; + case 32: + val_len |= NAU8821_I2S_DL_32; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8821->regmap, NAU8821_R1C_I2S_PCM_CTRL1, + NAU8821_I2S_DL_MASK, val_len); + + return 0; +} + +static int nau8821_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + ctrl2_val |= NAU8821_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8821_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8821_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8821_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8821_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8821_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8821_I2S_DF_PCM_AB; + ctrl1_val |= NAU8821_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8821->regmap, NAU8821_R1C_I2S_PCM_CTRL1, + NAU8821_I2S_DL_MASK | NAU8821_I2S_DF_MASK | + NAU8821_I2S_BP_MASK | NAU8821_I2S_PCMB_MASK, ctrl1_val); + regmap_update_bits(nau8821->regmap, NAU8821_R1D_I2S_PCM_CTRL2, + NAU8821_I2S_MS_MASK, ctrl2_val); + + return 0; +} + +static int nau8821_digital_mute(struct snd_soc_dai *dai, int mute, + int direction) +{ + struct snd_soc_component *component = dai->component; + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + + if (mute) + val = NAU8821_DAC_SOFT_MUTE; + + return regmap_update_bits(nau8821->regmap, + NAU8821_R31_MUTE_CTRL, NAU8821_DAC_SOFT_MUTE, val); +} + +static const struct snd_soc_dai_ops nau8821_dai_ops = { + .hw_params = nau8821_hw_params, + .set_fmt = nau8821_set_dai_fmt, + .mute_stream = nau8821_digital_mute, +}; + +#define NAU8821_RATES SNDRV_PCM_RATE_8000_192000 +#define NAU8821_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8821_dai = { + .name = NUVOTON_CODEC_DAI, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8821_RATES, + .formats = NAU8821_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8821_RATES, + .formats = NAU8821_FORMATS, + }, + .ops = &nau8821_dai_ops, +}; + + +static bool nau8821_is_jack_inserted(struct regmap *regmap) +{ + bool active_high, is_high; + int status, jkdet; + + regmap_read(regmap, NAU8821_R0D_JACK_DET_CTRL, &jkdet); + active_high = jkdet & NAU8821_JACK_POLARITY; + regmap_read(regmap, NAU8821_R82_GENERAL_STATUS, &status); + is_high = status & NAU8821_GPIO2_IN; + /* return jack connection status according to jack insertion logic + * active high or active low. + */ + return active_high == is_high; +} + +static void nau8821_int_status_clear_all(struct regmap *regmap) +{ + int active_irq, clear_irq, i; + + /* Reset the intrruption status from rightmost bit if the corres- + * ponding irq event occurs. + */ + regmap_read(regmap, NAU8821_R10_IRQ_STATUS, &active_irq); + for (i = 0; i < NAU8821_REG_DATA_LEN; i++) { + clear_irq = (0x1 << i); + if (active_irq & clear_irq) + regmap_write(regmap, + NAU8821_R11_INT_CLR_KEY_STATUS, clear_irq); + } +} + +static void nau8821_eject_jack(struct nau8821 *nau8821) +{ + struct snd_soc_dapm_context *dapm = nau8821->dapm; + struct regmap *regmap = nau8821->regmap; + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + + /* Detach 2kOhm Resistors from MICBIAS to MICGND */ + regmap_update_bits(regmap, NAU8821_R74_MIC_BIAS, + NAU8821_MICBIAS_JKR2, 0); + /* HPL/HPR short to ground */ + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_SPKR_DWN1R | NAU8821_SPKR_DWN1L, 0); + snd_soc_component_disable_pin(component, "MICBIAS"); + snd_soc_dapm_sync(dapm); + + /* Clear all interruption status */ + nau8821_int_status_clear_all(regmap); + + /* Enable the insertion interruption, disable the ejection inter- + * ruption, and then bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8821_R12_INTERRUPT_DIS_CTRL, + NAU8821_IRQ_EJECT_DIS | NAU8821_IRQ_INSERT_DIS, + NAU8821_IRQ_EJECT_DIS); + /* Mask unneeded IRQs: 1 - disable, 0 - enable */ + regmap_update_bits(regmap, NAU8821_R0F_INTERRUPT_MASK, + NAU8821_IRQ_EJECT_EN | NAU8821_IRQ_INSERT_EN, + NAU8821_IRQ_EJECT_EN); + + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_JACK_DET_DB_BYPASS, NAU8821_JACK_DET_DB_BYPASS); + + /* Close clock for jack type detection at manual mode */ + if (dapm->bias_level < SND_SOC_BIAS_PREPARE) + nau8821_configure_sysclk(nau8821, NAU8821_CLK_DIS, 0); + + /* Recover to normal channel input */ + regmap_update_bits(regmap, NAU8821_R2B_ADC_RATE, + NAU8821_ADC_R_SRC_EN, 0); +} + +static void nau8821_jdet_work(struct work_struct *work) +{ + struct nau8821 *nau8821 = + container_of(work, struct nau8821, jdet_work); + struct snd_soc_dapm_context *dapm = nau8821->dapm; + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct regmap *regmap = nau8821->regmap; + int jack_status_reg, mic_detected, event = 0, event_mask = 0; + + snd_soc_component_force_enable_pin(component, "MICBIAS"); + snd_soc_dapm_sync(dapm); + msleep(20); + + regmap_read(regmap, NAU8821_R58_I2C_DEVICE_ID, &jack_status_reg); + mic_detected = !(jack_status_reg & NAU8821_KEYDET); + if (mic_detected) { + dev_dbg(nau8821->dev, "Headset connected\n"); + event |= SND_JACK_HEADSET; + + /* 2kOhm Resistor from MICBIAS to MICGND1 */ + regmap_update_bits(regmap, NAU8821_R74_MIC_BIAS, + NAU8821_MICBIAS_JKR2, NAU8821_MICBIAS_JKR2); + /* Latch Right Channel Analog data + * input into the Right Channel Filter + */ + regmap_update_bits(regmap, NAU8821_R2B_ADC_RATE, + NAU8821_ADC_R_SRC_EN, NAU8821_ADC_R_SRC_EN); + } else { + dev_dbg(nau8821->dev, "Headphone connected\n"); + event |= SND_JACK_HEADPHONE; + snd_soc_component_disable_pin(component, "MICBIAS"); + snd_soc_dapm_sync(dapm); + } + event_mask |= SND_JACK_HEADSET; + snd_soc_jack_report(nau8821->jack, event, event_mask); +} + +/* Enable interruptions with internal clock. */ +static void nau8821_setup_inserted_irq(struct nau8821 *nau8821) +{ + struct regmap *regmap = nau8821->regmap; + + /* Enable internal VCO needed for interruptions */ + if (nau8821->dapm->bias_level < SND_SOC_BIAS_PREPARE) + nau8821_configure_sysclk(nau8821, NAU8821_CLK_INTERNAL, 0); + + /* Chip needs one FSCLK cycle in order to generate interruptions, + * as we cannot guarantee one will be provided by the system. Turning + * master mode on then off enables us to generate that FSCLK cycle + * with a minimum of contention on the clock bus. + */ + regmap_update_bits(regmap, NAU8821_R1D_I2S_PCM_CTRL2, + NAU8821_I2S_MS_MASK, NAU8821_I2S_MS_MASTER); + regmap_update_bits(regmap, NAU8821_R1D_I2S_PCM_CTRL2, + NAU8821_I2S_MS_MASK, NAU8821_I2S_MS_SLAVE); + + /* Not bypass de-bounce circuit */ + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_JACK_DET_DB_BYPASS, 0); + + regmap_update_bits(regmap, NAU8821_R0F_INTERRUPT_MASK, + NAU8821_IRQ_EJECT_EN, 0); + regmap_update_bits(regmap, NAU8821_R12_INTERRUPT_DIS_CTRL, + NAU8821_IRQ_EJECT_DIS, 0); +} + +static irqreturn_t nau8821_interrupt(int irq, void *data) +{ + struct nau8821 *nau8821 = (struct nau8821 *)data; + struct regmap *regmap = nau8821->regmap; + int active_irq, clear_irq = 0, event = 0, event_mask = 0; + + if (regmap_read(regmap, NAU8821_R10_IRQ_STATUS, &active_irq)) { + dev_err(nau8821->dev, "failed to read irq status\n"); + return IRQ_NONE; + } + + dev_dbg(nau8821->dev, "IRQ %d\n", active_irq); + + if ((active_irq & NAU8821_JACK_EJECT_IRQ_MASK) == + NAU8821_JACK_EJECT_DETECTED) { + regmap_update_bits(regmap, NAU8821_R71_ANALOG_ADC_1, + NAU8821_MICDET_MASK, NAU8821_MICDET_DIS); + nau8821_eject_jack(nau8821); + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8821_JACK_EJECT_IRQ_MASK; + } else if ((active_irq & NAU8821_JACK_INSERT_IRQ_MASK) == + NAU8821_JACK_INSERT_DETECTED) { + regmap_update_bits(regmap, NAU8821_R71_ANALOG_ADC_1, + NAU8821_MICDET_MASK, NAU8821_MICDET_EN); + if (nau8821_is_jack_inserted(regmap)) { + /* detect microphone and jack type */ + cancel_work_sync(&nau8821->jdet_work); + schedule_work(&nau8821->jdet_work); + /* Turn off insertion interruption at manual mode */ + regmap_update_bits(regmap, + NAU8821_R12_INTERRUPT_DIS_CTRL, + NAU8821_IRQ_INSERT_DIS, + NAU8821_IRQ_INSERT_DIS); + regmap_update_bits(regmap, + NAU8821_R0F_INTERRUPT_MASK, + NAU8821_IRQ_INSERT_EN, + NAU8821_IRQ_INSERT_EN); + nau8821_setup_inserted_irq(nau8821); + } else { + dev_warn(nau8821->dev, + "Inserted IRQ fired but not connected\n"); + nau8821_eject_jack(nau8821); + } + } + + if (!clear_irq) + clear_irq = active_irq; + /* clears the rightmost interruption */ + regmap_write(regmap, NAU8821_R11_INT_CLR_KEY_STATUS, clear_irq); + + if (event_mask) + snd_soc_jack_report(nau8821->jack, event, event_mask); + + return IRQ_HANDLED; +} + +static const struct regmap_config nau8821_regmap_config = { + .val_bits = NAU8821_REG_DATA_LEN, + .reg_bits = NAU8821_REG_ADDR_LEN, + + .max_register = NAU8821_REG_MAX, + .readable_reg = nau8821_readable_reg, + .writeable_reg = nau8821_writeable_reg, + .volatile_reg = nau8821_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8821_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8821_reg_defaults), +}; + +static int nau8821_component_probe(struct snd_soc_component *component) +{ + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + + nau8821->dapm = dapm; + + return 0; +} + +/** + * nau8821_calc_fll_param - Calculate FLL parameters. + * @fll_in: external clock provided to codec. + * @fs: sampling rate. + * @fll_param: Pointer to structure of FLL parameters. + * + * Calculate FLL parameters to configure codec. + * + * Returns 0 for success or negative error code. + */ +static int nau8821_calc_fll_param(unsigned int fll_in, + unsigned int fs, struct nau8821_fll *fll_param) +{ + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by + * dividing freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8821_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in >> fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 100MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256ULL * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 24-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 24, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 24) & 0x3ff; + fll_param->fll_frac = fvco & 0xffffff; + + return 0; +} + +static void nau8821_fll_apply(struct nau8821 *nau8821, + struct nau8821_fll *fll_param) +{ + struct regmap *regmap = nau8821->regmap; + + regmap_update_bits(regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_SRC_MASK | NAU8821_CLK_MCLK_SRC_MASK, + NAU8821_CLK_SRC_MCLK | fll_param->mclk_src); + /* Make DSP operate at high speed for better performance. */ + regmap_update_bits(regmap, NAU8821_R04_FLL1, + NAU8821_FLL_RATIO_MASK | NAU8821_ICTRL_LATCH_MASK, + fll_param->ratio | (0x6 << NAU8821_ICTRL_LATCH_SFT)); + /* FLL 24-bit fractional input */ + regmap_write(regmap, NAU8821_R0A_FLL7, + (fll_param->fll_frac >> 16) & 0xff); + regmap_write(regmap, NAU8821_R0B_FLL8, fll_param->fll_frac & 0xffff); + /* FLL 10-bit integer input */ + regmap_update_bits(regmap, NAU8821_R06_FLL3, + NAU8821_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(regmap, NAU8821_R07_FLL4, + NAU8821_HIGHBW_EN | NAU8821_FLL_REF_DIV_MASK, + NAU8821_HIGHBW_EN | + (fll_param->clk_ref_div << NAU8821_FLL_REF_DIV_SFT)); + /* select divided VCO input */ + regmap_update_bits(regmap, NAU8821_R08_FLL5, + NAU8821_FLL_CLK_SW_MASK, NAU8821_FLL_CLK_SW_REF); + /* Disable free-running mode */ + regmap_update_bits(regmap, + NAU8821_R09_FLL6, NAU8821_DCO_EN, 0); + if (fll_param->fll_frac) { + /* set FLL loop filter enable and cutoff frequency at 500Khz */ + regmap_update_bits(regmap, NAU8821_R08_FLL5, + NAU8821_FLL_PDB_DAC_EN | NAU8821_FLL_LOOP_FTR_EN | + NAU8821_FLL_FTR_SW_MASK, + NAU8821_FLL_PDB_DAC_EN | NAU8821_FLL_LOOP_FTR_EN | + NAU8821_FLL_FTR_SW_FILTER); + regmap_update_bits(regmap, NAU8821_R09_FLL6, + NAU8821_SDM_EN | NAU8821_CUTOFF500, + NAU8821_SDM_EN | NAU8821_CUTOFF500); + } else { + /* disable FLL loop filter and cutoff frequency */ + regmap_update_bits(regmap, NAU8821_R08_FLL5, + NAU8821_FLL_PDB_DAC_EN | NAU8821_FLL_LOOP_FTR_EN | + NAU8821_FLL_FTR_SW_MASK, NAU8821_FLL_FTR_SW_ACCU); + regmap_update_bits(regmap, NAU8821_R09_FLL6, + NAU8821_SDM_EN | NAU8821_CUTOFF500, 0); + } +} + +/** + * nau8821_set_fll - FLL configuration of nau8821 + * @component: codec component + * @pll_id: PLL requested + * @source: clock source + * @freq_in: frequency of input clock source + * @freq_out: must be 256*Fs in order to achieve the best performance + * + * The FLL function can select BCLK or MCLK as the input clock source. + * + * Returns 0 if the parameters have been applied successfully + * or negative error code. + */ +static int nau8821_set_fll(struct snd_soc_component *component, + int pll_id, int source, unsigned int freq_in, unsigned int freq_out) +{ + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + struct nau8821_fll fll_set_param, *fll_param = &fll_set_param; + int ret, fs; + + fs = freq_out >> 8; + ret = nau8821_calc_fll_param(freq_in, fs, fll_param); + if (ret) { + dev_err(nau8821->dev, + "Unsupported input clock %d to output clock %d\n", + freq_in, freq_out); + return ret; + } + dev_dbg(nau8821->dev, + "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param->mclk_src, fll_param->ratio, fll_param->fll_frac, + fll_param->fll_int, fll_param->clk_ref_div); + + nau8821_fll_apply(nau8821, fll_param); + mdelay(2); + regmap_update_bits(nau8821->regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_SRC_MASK, NAU8821_CLK_SRC_VCO); + + return 0; +} + +static void nau8821_configure_mclk_as_sysclk(struct regmap *regmap) +{ + regmap_update_bits(regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_SRC_MASK, NAU8821_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8821_R09_FLL6, + NAU8821_DCO_EN, 0); + /* Make DSP operate as default setting for power saving. */ + regmap_update_bits(regmap, NAU8821_R04_FLL1, + NAU8821_ICTRL_LATCH_MASK, 0); +} + +static int nau8821_configure_sysclk(struct nau8821 *nau8821, + int clk_id, unsigned int freq) +{ + struct regmap *regmap = nau8821->regmap; + + switch (clk_id) { + case NAU8821_CLK_DIS: + /* Clock provided externally and disable internal VCO clock */ + nau8821_configure_mclk_as_sysclk(regmap); + break; + case NAU8821_CLK_MCLK: + nau8821_configure_mclk_as_sysclk(regmap); + /* MCLK not changed by clock tree */ + regmap_update_bits(regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_MCLK_SRC_MASK, 0); + break; + case NAU8821_CLK_INTERNAL: + if (nau8821_is_jack_inserted(regmap)) { + regmap_update_bits(regmap, NAU8821_R09_FLL6, + NAU8821_DCO_EN, NAU8821_DCO_EN); + regmap_update_bits(regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_SRC_MASK, NAU8821_CLK_SRC_VCO); + /* Decrease the VCO frequency and make DSP operate + * as default setting for power saving. + */ + regmap_update_bits(regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_MCLK_SRC_MASK, 0xf); + regmap_update_bits(regmap, NAU8821_R04_FLL1, + NAU8821_ICTRL_LATCH_MASK | + NAU8821_FLL_RATIO_MASK, 0x10); + regmap_update_bits(regmap, NAU8821_R09_FLL6, + NAU8821_SDM_EN, NAU8821_SDM_EN); + } + break; + case NAU8821_CLK_FLL_MCLK: + /* Higher FLL reference input frequency can only set lower + * gain error, such as 0000 for input reference from MCLK + * 12.288Mhz. + */ + regmap_update_bits(regmap, NAU8821_R06_FLL3, + NAU8821_FLL_CLK_SRC_MASK | NAU8821_GAIN_ERR_MASK, + NAU8821_FLL_CLK_SRC_MCLK | 0); + break; + case NAU8821_CLK_FLL_BLK: + /* If FLL reference input is from low frequency source, + * higher error gain can apply such as 0xf which has + * the most sensitive gain error correction threshold, + * Therefore, FLL has the most accurate DCO to + * target frequency. + */ + regmap_update_bits(regmap, NAU8821_R06_FLL3, + NAU8821_FLL_CLK_SRC_MASK | NAU8821_GAIN_ERR_MASK, + NAU8821_FLL_CLK_SRC_BLK | + (0xf << NAU8821_GAIN_ERR_SFT)); + break; + case NAU8821_CLK_FLL_FS: + /* If FLL reference input is from low frequency source, + * higher error gain can apply such as 0xf which has + * the most sensitive gain error correction threshold, + * Therefore, FLL has the most accurate DCO to + * target frequency. + */ + regmap_update_bits(regmap, NAU8821_R06_FLL3, + NAU8821_FLL_CLK_SRC_MASK | NAU8821_GAIN_ERR_MASK, + NAU8821_FLL_CLK_SRC_FS | + (0xf << NAU8821_GAIN_ERR_SFT)); + break; + default: + dev_err(nau8821->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + nau8821->clk_id = clk_id; + dev_dbg(nau8821->dev, "Sysclk is %dHz and clock id is %d\n", freq, + nau8821->clk_id); + + return 0; +} + +static int nau8821_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + + return nau8821_configure_sysclk(nau8821, clk_id, freq); +} + +static int nau8821_resume_setup(struct nau8821 *nau8821) +{ + struct regmap *regmap = nau8821->regmap; + + /* Close clock when jack type detection at manual mode */ + nau8821_configure_sysclk(nau8821, NAU8821_CLK_DIS, 0); + if (nau8821->irq) { + /* Clear all interruption status */ + nau8821_int_status_clear_all(regmap); + + /* Enable both insertion and ejection interruptions, and then + * bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8821_R0F_INTERRUPT_MASK, + NAU8821_IRQ_EJECT_EN | NAU8821_IRQ_INSERT_EN, 0); + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_JACK_DET_DB_BYPASS, + NAU8821_JACK_DET_DB_BYPASS); + regmap_update_bits(regmap, NAU8821_R12_INTERRUPT_DIS_CTRL, + NAU8821_IRQ_INSERT_DIS | NAU8821_IRQ_EJECT_DIS, 0); + } + + return 0; +} + +static int nau8821_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8821->regmap; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + /* Setup codec configuration after resume */ + if (snd_soc_component_get_bias_level(component) == + SND_SOC_BIAS_OFF) + nau8821_resume_setup(nau8821); + break; + + case SND_SOC_BIAS_OFF: + /* HPL/HPR short to ground */ + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_SPKR_DWN1R | NAU8821_SPKR_DWN1L, 0); + if (nau8821->irq) { + /* Reset the configuration of jack type for detection. + * Detach 2kOhm Resistors from MICBIAS to MICGND1/2. + */ + regmap_update_bits(regmap, NAU8821_R74_MIC_BIAS, + NAU8821_MICBIAS_JKR2, 0); + /* Turn off all interruptions before system shutdown. + * Keep theinterruption quiet before resume + * setup completes. + */ + regmap_write(regmap, + NAU8821_R12_INTERRUPT_DIS_CTRL, 0xffff); + regmap_update_bits(regmap, NAU8821_R0F_INTERRUPT_MASK, + NAU8821_IRQ_EJECT_EN | NAU8821_IRQ_INSERT_EN, + NAU8821_IRQ_EJECT_EN | NAU8821_IRQ_INSERT_EN); + } + break; + default: + break; + } + + return 0; +} + +static int __maybe_unused nau8821_suspend(struct snd_soc_component *component) +{ + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + + if (nau8821->irq) + disable_irq(nau8821->irq); + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + /* Power down codec power; don't support button wakeup */ + snd_soc_component_disable_pin(component, "MICBIAS"); + snd_soc_dapm_sync(nau8821->dapm); + regcache_cache_only(nau8821->regmap, true); + regcache_mark_dirty(nau8821->regmap); + + return 0; +} + +static int __maybe_unused nau8821_resume(struct snd_soc_component *component) +{ + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(nau8821->regmap, false); + regcache_sync(nau8821->regmap); + if (nau8821->irq) + enable_irq(nau8821->irq); + + return 0; +} + +static const struct snd_soc_component_driver nau8821_component_driver = { + .probe = nau8821_component_probe, + .set_sysclk = nau8821_set_sysclk, + .set_pll = nau8821_set_fll, + .set_bias_level = nau8821_set_bias_level, + .suspend = nau8821_suspend, + .resume = nau8821_resume, + .controls = nau8821_controls, + .num_controls = ARRAY_SIZE(nau8821_controls), + .dapm_widgets = nau8821_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8821_dapm_widgets), + .dapm_routes = nau8821_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8821_dapm_routes), + .suspend_bias_off = 1, + .non_legacy_dai_naming = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +/** + * nau8821_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int nau8821_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); + int ret; + + nau8821->jack = jack; + /* Initiate jack detection work queue */ + INIT_WORK(&nau8821->jdet_work, nau8821_jdet_work); + ret = devm_request_threaded_irq(nau8821->dev, nau8821->irq, NULL, + nau8821_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "nau8821", nau8821); + if (ret) { + dev_err(nau8821->dev, "Cannot request irq %d (%d)\n", + nau8821->irq, ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(nau8821_enable_jack_detect); + +static void nau8821_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8821_R00_RESET, 0xffff); + regmap_write(regmap, NAU8821_R00_RESET, 0xffff); +} + +static void nau8821_print_device_properties(struct nau8821 *nau8821) +{ + struct device *dev = nau8821->dev; + + dev_dbg(dev, "jkdet-enable: %d\n", nau8821->jkdet_enable); + dev_dbg(dev, "jkdet-pull-enable: %d\n", nau8821->jkdet_pull_enable); + dev_dbg(dev, "jkdet-pull-up: %d\n", nau8821->jkdet_pull_up); + dev_dbg(dev, "jkdet-polarity: %d\n", nau8821->jkdet_polarity); + dev_dbg(dev, "micbias-voltage: %d\n", nau8821->micbias_voltage); + dev_dbg(dev, "vref-impedance: %d\n", nau8821->vref_impedance); + dev_dbg(dev, "jack-insert-debounce: %d\n", + nau8821->jack_insert_debounce); + dev_dbg(dev, "jack-eject-debounce: %d\n", + nau8821->jack_eject_debounce); + dev_dbg(dev, "dmic-clk-threshold: %d\n", + nau8821->dmic_clk_threshold); +} + +static int nau8821_read_device_properties(struct device *dev, + struct nau8821 *nau8821) +{ + int ret; + + nau8821->jkdet_enable = device_property_read_bool(dev, + "nuvoton,jkdet-enable"); + nau8821->jkdet_pull_enable = device_property_read_bool(dev, + "nuvoton,jkdet-pull-enable"); + nau8821->jkdet_pull_up = device_property_read_bool(dev, + "nuvoton,jkdet-pull-up"); + ret = device_property_read_u32(dev, "nuvoton,jkdet-polarity", + &nau8821->jkdet_polarity); + if (ret) + nau8821->jkdet_polarity = 1; + ret = device_property_read_u32(dev, "nuvoton,micbias-voltage", + &nau8821->micbias_voltage); + if (ret) + nau8821->micbias_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,vref-impedance", + &nau8821->vref_impedance); + if (ret) + nau8821->vref_impedance = 2; + ret = device_property_read_u32(dev, "nuvoton,jack-insert-debounce", + &nau8821->jack_insert_debounce); + if (ret) + nau8821->jack_insert_debounce = 7; + ret = device_property_read_u32(dev, "nuvoton,jack-eject-debounce", + &nau8821->jack_eject_debounce); + if (ret) + nau8821->jack_eject_debounce = 0; + ret = device_property_read_u32(dev, "nuvoton,dmic-clk-threshold", + &nau8821->dmic_clk_threshold); + if (ret) + nau8821->dmic_clk_threshold = 3072000; + + return 0; +} + +static void nau8821_init_regs(struct nau8821 *nau8821) +{ + struct regmap *regmap = nau8821->regmap; + + /* Enable Bias/Vmid */ + regmap_update_bits(regmap, NAU8821_R66_BIAS_ADJ, + NAU8821_BIAS_VMID, NAU8821_BIAS_VMID); + regmap_update_bits(regmap, NAU8821_R76_BOOST, + NAU8821_GLOBAL_BIAS_EN, NAU8821_GLOBAL_BIAS_EN); + /* VMID Tieoff setting and enable TESTDAC. + * This sets the analog DAC inputs to a '0' input signal to avoid + * any glitches due to power up transients in both the analog and + * digital DAC circuit. + */ + regmap_update_bits(regmap, NAU8821_R66_BIAS_ADJ, + NAU8821_BIAS_VMID_SEL_MASK | NAU8821_BIAS_TESTDAC_EN, + (nau8821->vref_impedance << NAU8821_BIAS_VMID_SEL_SFT) | + NAU8821_BIAS_TESTDAC_EN); + /* Disable short Frame Sync detection logic */ + regmap_update_bits(regmap, NAU8821_R1E_LEFT_TIME_SLOT, + NAU8821_DIS_FS_SHORT_DET, NAU8821_DIS_FS_SHORT_DET); + /* Disable Boost Driver, Automatic Short circuit protection enable */ + regmap_update_bits(regmap, NAU8821_R76_BOOST, + NAU8821_PRECHARGE_DIS | NAU8821_HP_BOOST_DIS | + NAU8821_HP_BOOST_G_DIS | NAU8821_SHORT_SHUTDOWN_EN, + NAU8821_PRECHARGE_DIS | NAU8821_HP_BOOST_DIS | + NAU8821_HP_BOOST_G_DIS | NAU8821_SHORT_SHUTDOWN_EN); + /* Class G timer 64ms */ + regmap_update_bits(regmap, NAU8821_R4B_CLASSG_CTRL, + NAU8821_CLASSG_TIMER_MASK, + 0x20 << NAU8821_CLASSG_TIMER_SFT); + /* Class AB bias current to 2x, DAC Capacitor enable MSB/LSB */ + regmap_update_bits(regmap, NAU8821_R6A_ANALOG_CONTROL_2, + NAU8821_HP_NON_CLASSG_CURRENT_2xADJ | + NAU8821_DAC_CAPACITOR_MSB | NAU8821_DAC_CAPACITOR_LSB, + NAU8821_HP_NON_CLASSG_CURRENT_2xADJ | + NAU8821_DAC_CAPACITOR_MSB | NAU8821_DAC_CAPACITOR_LSB); + /* Disable DACR/L power */ + regmap_update_bits(regmap, NAU8821_R80_CHARGE_PUMP, + NAU8821_POWER_DOWN_DACR | NAU8821_POWER_DOWN_DACL, 0); + /* DAC clock delay 2ns, VREF */ + regmap_update_bits(regmap, NAU8821_R73_RDAC, + NAU8821_DAC_CLK_DELAY_MASK | NAU8821_DAC_VREF_MASK, + (0x2 << NAU8821_DAC_CLK_DELAY_SFT) | + (0x3 << NAU8821_DAC_VREF_SFT)); + + regmap_update_bits(regmap, NAU8821_R74_MIC_BIAS, + NAU8821_MICBIAS_VOLTAGE_MASK, nau8821->micbias_voltage); + /* Default oversampling/decimations settings are unusable + * (audible hiss). Set it to something better. + */ + regmap_update_bits(regmap, NAU8821_R2B_ADC_RATE, + NAU8821_ADC_SYNC_DOWN_MASK, NAU8821_ADC_SYNC_DOWN_64); + regmap_update_bits(regmap, NAU8821_R2C_DAC_CTRL1, + NAU8821_DAC_OVERSAMPLE_MASK, NAU8821_DAC_OVERSAMPLE_64); +} + +static int nau8821_setup_irq(struct nau8821 *nau8821) +{ + struct regmap *regmap = nau8821->regmap; + + /* Jack detection */ + regmap_update_bits(regmap, NAU8821_R1A_GPIO12_CTRL, + NAU8821_JKDET_OUTPUT_EN, + nau8821->jkdet_enable ? 0 : NAU8821_JKDET_OUTPUT_EN); + regmap_update_bits(regmap, NAU8821_R1A_GPIO12_CTRL, + NAU8821_JKDET_PULL_EN, + nau8821->jkdet_pull_enable ? 0 : NAU8821_JKDET_PULL_EN); + regmap_update_bits(regmap, NAU8821_R1A_GPIO12_CTRL, + NAU8821_JKDET_PULL_UP, + nau8821->jkdet_pull_up ? NAU8821_JKDET_PULL_UP : 0); + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_JACK_POLARITY, + /* jkdet_polarity - 1 is for active-low */ + nau8821->jkdet_polarity ? 0 : NAU8821_JACK_POLARITY); + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_JACK_INSERT_DEBOUNCE_MASK, + nau8821->jack_insert_debounce << + NAU8821_JACK_INSERT_DEBOUNCE_SFT); + regmap_update_bits(regmap, NAU8821_R0D_JACK_DET_CTRL, + NAU8821_JACK_EJECT_DEBOUNCE_MASK, + nau8821->jack_eject_debounce << + NAU8821_JACK_EJECT_DEBOUNCE_SFT); + /* Pull up IRQ pin */ + regmap_update_bits(regmap, NAU8821_R0F_INTERRUPT_MASK, + NAU8821_IRQ_PIN_PULL_UP | NAU8821_IRQ_PIN_PULL_EN | + NAU8821_IRQ_OUTPUT_EN, NAU8821_IRQ_PIN_PULL_UP | + NAU8821_IRQ_PIN_PULL_EN | NAU8821_IRQ_OUTPUT_EN); + /* Disable interruption before codec initiation done */ + /* Mask unneeded IRQs: 1 - disable, 0 - enable */ + regmap_update_bits(regmap, NAU8821_R0F_INTERRUPT_MASK, 0x3f5, 0x3f5); + + return 0; +} + +static int nau8821_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8821 *nau8821 = dev_get_platdata(&i2c->dev); + int ret, value; + + if (!nau8821) { + nau8821 = devm_kzalloc(dev, sizeof(*nau8821), GFP_KERNEL); + if (!nau8821) + return -ENOMEM; + nau8821_read_device_properties(dev, nau8821); + } + i2c_set_clientdata(i2c, nau8821); + + nau8821->regmap = devm_regmap_init_i2c(i2c, &nau8821_regmap_config); + if (IS_ERR(nau8821->regmap)) + return PTR_ERR(nau8821->regmap); + + nau8821->dev = dev; + nau8821->irq = i2c->irq; + nau8821_print_device_properties(nau8821); + + nau8821_reset_chip(nau8821->regmap); + ret = regmap_read(nau8821->regmap, NAU8821_R58_I2C_DEVICE_ID, &value); + if (ret) { + dev_err(dev, "Failed to read device id (%d)\n", ret); + return ret; + } + nau8821_init_regs(nau8821); + + if (i2c->irq) + nau8821_setup_irq(nau8821); + + ret = devm_snd_soc_register_component(&i2c->dev, + &nau8821_component_driver, &nau8821_dai, 1); + + return ret; +} + +static int nau8821_i2c_remove(struct i2c_client *i2c_client) +{ + struct nau8821 *nau8821 = i2c_get_clientdata(i2c_client); + + devm_free_irq(nau8821->dev, nau8821->irq, nau8821); + + return 0; +} + +static const struct i2c_device_id nau8821_i2c_ids[] = { + { "nau8821", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8821_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id nau8821_of_ids[] = { + { .compatible = "nuvoton,nau8821", }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8821_of_ids); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id nau8821_acpi_match[] = { + { "NVTN2020", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, nau8821_acpi_match); +#endif + +static struct i2c_driver nau8821_driver = { + .driver = { + .name = "nau8821", + .of_match_table = of_match_ptr(nau8821_of_ids), + .acpi_match_table = ACPI_PTR(nau8821_acpi_match), + }, + .probe = nau8821_i2c_probe, + .remove = nau8821_i2c_remove, + .id_table = nau8821_i2c_ids, +}; +module_i2c_driver(nau8821_driver); + +MODULE_DESCRIPTION("ASoC nau8821 driver"); +MODULE_AUTHOR("John Hsu <kchsu0@nuvoton.com>"); +MODULE_AUTHOR("Seven Lee <wtli@nuvoton.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/nau8821.h b/sound/soc/codecs/nau8821.h new file mode 100644 index 000000000000..a92edfeb9d3a --- /dev/null +++ b/sound/soc/codecs/nau8821.h @@ -0,0 +1,533 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NAU88L21 ALSA SoC audio driver + * + * Copyright 2021 Nuvoton Technology Corp. + * Author: John Hsu <kchsu0@nuvoton.com> + * Co-author: Seven Lee <wtli@nuvoton.com> + */ + +#ifndef __NAU8821_H__ +#define __NAU8821_H__ + +#define NAU8821_R00_RESET 0x00 +#define NAU8821_R01_ENA_CTRL 0x01 +#define NAU8821_R03_CLK_DIVIDER 0x03 +#define NAU8821_R04_FLL1 0x04 +#define NAU8821_R05_FLL2 0x05 +#define NAU8821_R06_FLL3 0x06 +#define NAU8821_R07_FLL4 0x07 +#define NAU8821_R08_FLL5 0x08 +#define NAU8821_R09_FLL6 0x09 +#define NAU8821_R0A_FLL7 0x0a +#define NAU8821_R0B_FLL8 0x0b +#define NAU8821_R0D_JACK_DET_CTRL 0x0d +#define NAU8821_R0F_INTERRUPT_MASK 0x0f +#define NAU8821_R10_IRQ_STATUS 0x10 +#define NAU8821_R11_INT_CLR_KEY_STATUS 0x11 +#define NAU8821_R12_INTERRUPT_DIS_CTRL 0x12 +#define NAU8821_R13_DMIC_CTRL 0x13 +#define NAU8821_R1A_GPIO12_CTRL 0x1a +#define NAU8821_R1B_TDM_CTRL 0x1b +#define NAU8821_R1C_I2S_PCM_CTRL1 0x1c +#define NAU8821_R1D_I2S_PCM_CTRL2 0x1d +#define NAU8821_R1E_LEFT_TIME_SLOT 0x1e +#define NAU8821_R1F_RIGHT_TIME_SLOT 0x1f +#define NAU8821_R21_BIQ0_COF1 0x21 +#define NAU8821_R22_BIQ0_COF2 0x22 +#define NAU8821_R23_BIQ0_COF3 0x23 +#define NAU8821_R24_BIQ0_COF4 0x24 +#define NAU8821_R25_BIQ0_COF5 0x25 +#define NAU8821_R26_BIQ0_COF6 0x26 +#define NAU8821_R27_BIQ0_COF7 0x27 +#define NAU8821_R28_BIQ0_COF8 0x28 +#define NAU8821_R29_BIQ0_COF9 0x29 +#define NAU8821_R2A_BIQ0_COF10 0x2a +#define NAU8821_R2B_ADC_RATE 0x2b +#define NAU8821_R2C_DAC_CTRL1 0x2c +#define NAU8821_R2D_DAC_CTRL2 0x2d +#define NAU8821_R2F_DAC_DGAIN_CTRL 0x2f +#define NAU8821_R30_ADC_DGAIN_CTRL 0x30 +#define NAU8821_R31_MUTE_CTRL 0x31 +#define NAU8821_R32_HSVOL_CTRL 0x32 +#define NAU8821_R34_DACR_CTRL 0x34 +#define NAU8821_R35_ADC_DGAIN_CTRL1 0x35 +#define NAU8821_R36_ADC_DRC_KNEE_IP12 0x36 +#define NAU8821_R37_ADC_DRC_KNEE_IP34 0x37 +#define NAU8821_R38_ADC_DRC_SLOPES 0x38 +#define NAU8821_R39_ADC_DRC_ATKDCY 0x39 +#define NAU8821_R3A_DAC_DRC_KNEE_IP12 0x3a +#define NAU8821_R3B_DAC_DRC_KNEE_IP34 0x3b +#define NAU8821_R3C_DAC_DRC_SLOPES 0x3c +#define NAU8821_R3D_DAC_DRC_ATKDCY 0x3d +#define NAU8821_R41_BIQ1_COF1 0x41 +#define NAU8821_R42_BIQ1_COF2 0x42 +#define NAU8821_R43_BIQ1_COF3 0x43 +#define NAU8821_R44_BIQ1_COF4 0x44 +#define NAU8821_R45_BIQ1_COF5 0x45 +#define NAU8821_R46_BIQ1_COF6 0x46 +#define NAU8821_R47_BIQ1_COF7 0x47 +#define NAU8821_R48_BIQ1_COF8 0x48 +#define NAU8821_R49_BIQ1_COF9 0x49 +#define NAU8821_R4A_BIQ1_COF10 0x4a +#define NAU8821_R4B_CLASSG_CTRL 0x4b +#define NAU8821_R4C_IMM_MODE_CTRL 0x4c +#define NAU8821_R4D_IMM_RMS_L 0x4d +#define NAU8821_R4E_FUSE_CTRL2 0x4e +#define NAU8821_R4F_FUSE_CTRL3 0x4f +#define NAU8821_R51_FUSE_CTRL1 0x51 +#define NAU8821_R53_OTPDOUT_1 0x53 +#define NAU8821_R54_OTPDOUT_2 0x54 +#define NAU8821_R55_MISC_CTRL 0x55 +#define NAU8821_R58_I2C_DEVICE_ID 0x58 +#define NAU8821_R59_SARDOUT_RAM_STATUS 0x59 +#define NAU8821_R5A_SOFTWARE_RST 0x5a +#define NAU8821_R66_BIAS_ADJ 0x66 +#define NAU8821_R68_TRIM_SETTINGS 0x68 +#define NAU8821_R69_ANALOG_CONTROL_1 0x69 +#define NAU8821_R6A_ANALOG_CONTROL_2 0x6a +#define NAU8821_R6B_PGA_MUTE 0x6b +#define NAU8821_R71_ANALOG_ADC_1 0x71 +#define NAU8821_R72_ANALOG_ADC_2 0x72 +#define NAU8821_R73_RDAC 0x73 +#define NAU8821_R74_MIC_BIAS 0x74 +#define NAU8821_R76_BOOST 0x76 +#define NAU8821_R77_FEPGA 0x77 +#define NAU8821_R7E_PGA_GAIN 0x7e +#define NAU8821_R7F_POWER_UP_CONTROL 0x7f +#define NAU8821_R80_CHARGE_PUMP 0x80 +#define NAU8821_R81_CHARGE_PUMP_INPUT_READ 0x81 +#define NAU8821_R82_GENERAL_STATUS 0x82 +#define NAU8821_REG_MAX NAU8821_R82_GENERAL_STATUS +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8821_REG_ADDR_LEN 16 +#define NAU8821_REG_DATA_LEN 16 + +/* ENA_CTRL (0x01) */ +#define NAU8821_CLK_DAC_INV_SFT 14 +#define NAU8821_CLK_DAC_INV (0x1 << NAU8821_CLK_DAC_INV) +#define NAU8821_EN_DACR_SFT 11 +#define NAU8821_EN_DACR (0x1 << NAU8821_EN_DACR_SFT) +#define NAU8821_EN_DACL_SFT 10 +#define NAU8821_EN_DACL (0x1 << NAU8821_EN_DACL_SFT) +#define NAU8821_EN_ADCR_SFT 9 +#define NAU8821_EN_ADCR (0x1 << NAU8821_EN_ADCR_SFT) +#define NAU8821_EN_ADCL_SFT 8 +#define NAU8821_EN_ADCL (0x1 << NAU8821_EN_ADCL_SFT) +#define NAU8821_EN_ADC_CLK_SFT 7 +#define NAU8821_EN_ADC_CLK (0x1 << NAU8821_EN_ADC_CLK_SFT) +#define NAU8821_EN_DAC_CLK_SFT 6 +#define NAU8821_EN_DAC_CLK (0x1 << NAU8821_EN_DAC_CLK_SFT) +#define NAU8821_EN_I2S_CLK_SFT 4 +#define NAU8821_EN_I2S_CLK (0x1 << NAU8821_EN_I2S_CLK_SFT) +#define NAU8821_EN_DRC_CLK_SFT 0 +#define NAU8821_EN_DRC_CLK (0x1 << NAU8821_EN_DRC_CLK_SFT) + +/* CLK_DIVIDER (0x03) */ +#define NAU8821_CLK_SRC_SFT 15 +#define NAU8821_CLK_SRC_MASK (0x1 << NAU8821_CLK_SRC_SFT) +#define NAU8821_CLK_SRC_VCO (0x1 << NAU8821_CLK_SRC_SFT) +#define NAU8821_CLK_SRC_MCLK (0x0 << NAU8821_CLK_SRC_SFT) +#define NAU8821_CLK_CODEC_SRC_SFT 13 +#define NAU8821_CLK_CODEC_SRC_MASK (0x1 << NAU8821_CLK_CODEC_SRC_SFT) +#define NAU8821_CLK_CODEC_SRC_VCO (0x1 << NAU8821_CLK_CODEC_SRC_SFT) +#define NAU8821_CLK_CODEC_SRC_MCLK (0x0 << NAU8821_CLK_CODEC_SRC_SFT) +#define NAU8821_CLK_ADC_SRC_SFT 6 +#define NAU8821_CLK_ADC_SRC_MASK (0x3 << NAU8821_CLK_ADC_SRC_SFT) +#define NAU8821_CLK_DAC_SRC_SFT 4 +#define NAU8821_CLK_DAC_SRC_MASK (0x3 << NAU8821_CLK_DAC_SRC_SFT) +#define NAU8821_CLK_MCLK_SRC_MASK 0xf + +/* FLL1 (0x04) */ +#define NAU8821_ICTRL_LATCH_SFT 10 +#define NAU8821_ICTRL_LATCH_MASK (0x7 << NAU8821_ICTRL_LATCH_SFT) +#define NAU8821_FLL_RATIO_MASK 0x7f + +/* FLL3 (0x06) */ +#define NAU8821_GAIN_ERR_SFT 12 +#define NAU8821_GAIN_ERR_MASK (0xf << NAU8821_GAIN_ERR_SFT) +#define NAU8821_FLL_CLK_SRC_SFT 10 +#define NAU8821_FLL_CLK_SRC_MASK (0x3 << NAU8821_FLL_CLK_SRC_SFT) +#define NAU8821_FLL_CLK_SRC_FS (0x3 << NAU8821_FLL_CLK_SRC_SFT) +#define NAU8821_FLL_CLK_SRC_BLK (0x2 << NAU8821_FLL_CLK_SRC_SFT) +#define NAU8821_FLL_CLK_SRC_MCLK (0x0 << NAU8821_FLL_CLK_SRC_SFT) +#define NAU8821_FLL_INTEGER_MASK 0x3ff + +/* FLL4 (0x07) */ +#define NAU8821_HIGHBW_EN_SFT 15 +#define NAU8821_HIGHBW_EN (0x1 << NAU8821_HIGHBW_EN_SFT) +#define NAU8821_FLL_REF_DIV_SFT 10 +#define NAU8821_FLL_REF_DIV_MASK (0x3 << NAU8821_FLL_REF_DIV_SFT) + +/* FLL5 (0x08) */ +#define NAU8821_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8821_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8821_FLL_CLK_SW_SFT 13 +#define NAU8821_FLL_CLK_SW_MASK (0x1 << NAU8821_FLL_CLK_SW_SFT) +#define NAU8821_FLL_CLK_SW_N2 (0x1 << NAU8821_FLL_CLK_SW_SFT) +#define NAU8821_FLL_CLK_SW_REF (0x0 << NAU8821_FLL_CLK_SW_SFT) +#define NAU8821_FLL_FTR_SW_SFT 12 +#define NAU8821_FLL_FTR_SW_MASK (0x1 << NAU8821_FLL_FTR_SW_SFT) +#define NAU8821_FLL_FTR_SW_ACCU (0x1 << NAU8821_FLL_FTR_SW_SFT) +#define NAU8821_FLL_FTR_SW_FILTER (0x0 << NAU8821_FLL_FTR_SW_SFT) + +/* FLL6 (0x09) */ +#define NAU8821_DCO_EN (0x1 << 15) +#define NAU8821_SDM_EN (0x1 << 14) +#define NAU8821_CUTOFF500 (0x1 << 13) + +/* FLL7 (0x0a) */ +#define NAU8821_FLL_FRACH_MASK 0xff + +/* FLL8 (0x0b) */ +#define NAU8821_FLL_FRACL_MASK 0xffff + +/* JACK_DET_CTRL (0x0d) */ +/* 0 - open, 1 - short to GND */ +#define NAU8821_SPKR_DWN1R_SFT 15 +#define NAU8821_SPKR_DWN1R (0x1 << NAU8821_SPKR_DWN1R_SFT) +#define NAU8821_SPKR_DWN1L_SFT 14 +#define NAU8821_SPKR_DWN1L (0x1 << NAU8821_SPKR_DWN1L_SFT) +#define NAU8821_JACK_DET_RESTART (0x1 << 9) +#define NAU8821_JACK_DET_DB_BYPASS (0x1 << 8) +#define NAU8821_JACK_INSERT_DEBOUNCE_SFT 5 +#define NAU8821_JACK_INSERT_DEBOUNCE_MASK (0x7 << NAU8821_JACK_INSERT_DEBOUNCE_SFT) +#define NAU8821_JACK_EJECT_DEBOUNCE_SFT 2 +#define NAU8821_JACK_EJECT_DEBOUNCE_MASK (0x7 << NAU8821_JACK_EJECT_DEBOUNCE_SFT) +#define NAU8821_JACK_POLARITY (0x1 << 1) /* 0 - active low, 1 - active high */ + +/* INTERRUPT_MASK (0x0f) */ +#define NAU8821_IRQ_PIN_PULL_UP (0x1 << 14) +#define NAU8821_IRQ_PIN_PULL_EN (0x1 << 13) +#define NAU8821_IRQ_OUTPUT_EN (0x1 << 11) +#define NAU8821_IRQ_RMS_EN (0x1 << 8) +#define NAU8821_IRQ_KEY_RELEASE_EN (0x1 << 7) +#define NAU8821_IRQ_KEY_PRESS_EN (0x1 << 6) +#define NAU8821_IRQ_MIC_DET_EN (0x1 << 4) +#define NAU8821_IRQ_EJECT_EN (0x1 << 2) +#define NAU8821_IRQ_INSERT_EN 0x1 + +/* IRQ_STATUS (0x10) */ +#define NAU8821_SHORT_CIRCUIT_IRQ (0x1 << 9) +#define NAU8821_IMPEDANCE_MEAS_IRQ (0x1 << 8) +#define NAU8821_KEY_IRQ_SFT 6 +#define NAU8821_KEY_IRQ_MASK (0x3 << NAU8821_KEY_IRQ_SFT) +#define NAU8821_KEY_RELEASE_IRQ (0x2 << NAU8821_KEY_IRQ_SFT) +#define NAU8821_KEY_SHORT_PRESS_IRQ (0x1 << NAU8821_KEY_IRQ_SFT) +#define NAU8821_MIC_DETECT_IRQ (0x1 << 4) +#define NAU8821_JACK_EJECT_IRQ_MASK (0x3 << 2) +#define NAU8821_JACK_EJECT_DETECTED (0x1 << 2) +#define NAU8821_JACK_INSERT_IRQ_MASK 0x3 +#define NAU8821_JACK_INSERT_DETECTED 0x1 + +/* INTERRUPT_DIS_CTRL (0x12) */ +#define NAU8821_IRQ_KEY_RELEASE_DIS (0x1 << 7) +#define NAU8821_IRQ_KEY_PRESS_DIS (0x1 << 6) +#define NAU8821_IRQ_MIC_DIS (0x1 << 4) +#define NAU8821_IRQ_EJECT_DIS (0x1 << 2) +#define NAU8821_IRQ_INSERT_DIS 0x1 + +/* DMIC_CTRL (0x13) */ +#define NAU8821_DMIC_DS_SFT 7 +#define NAU8821_DMIC_DS_MASK (0x1 << NAU8821_DMIC_DS_SFT) +#define NAU8821_DMIC_DS_HIGH (0x1 << NAU8821_DMIC_DS_SFT) +#define NAU8821_DMIC_DS_LOW (0x0 << NAU8821_DMIC_DS_SFT) +#define NAU8821_DMIC_SRC_SFT 1 +#define NAU8821_DMIC_SRC_MASK (0x3 << NAU8821_DMIC_SRC_SFT) +#define NAU8821_CLK_DMIC_SRC (0x2 << NAU8821_DMIC_SRC_SFT) +#define NAU8821_DMIC_EN_SFT 0 + +/* GPIO12_CTRL (0x1a) */ +#define NAU8821_JKDET_PULL_UP (0x1 << 11) /* 0 - pull down, 1 - pull up */ +#define NAU8821_JKDET_PULL_EN (0x1 << 9) /* 0 - enable pull, 1 - disable */ +#define NAU8821_JKDET_OUTPUT_EN (0x1 << 8) /* 0 - enable input, 1 - enable output */ + +/* TDM_CTRL (0x1b) */ +#define NAU8821_TDM_EN_SFT 15 +#define NAU8821_TDM_EN (0x1 << NAU8821_TDM_EN_SFT) +#define NAU8821_ADCPHS_SFT 13 +#define NAU8821_DACL_CH_SFT 7 +#define NAU8821_DACL_CH_MASK (0x7 << NAU8821_DACL_CH_SFT) +#define NAU8821_DACR_CH_SFT 4 +#define NAU8821_DACR_CH_MASK (0x7 << NAU8821_DACR_CH_SFT) +#define NAU8821_ADCL_CH_SFT 2 +#define NAU8821_ADCL_CH_MASK (0x3 << NAU8821_ADCL_CH_SFT) +#define NAU8821_ADCR_CH_SFT 0 +#define NAU8821_ADCR_CH_MASK 0x3 + +/* I2S_PCM_CTRL1 (0x1c) */ +#define NAU8821_I2S_BP_SFT 7 +#define NAU8821_I2S_BP_MASK (0x1 << NAU8821_I2S_BP_SFT) +#define NAU8821_I2S_BP_INV (0x1 << NAU8821_I2S_BP_SFT) +#define NAU8821_I2S_PCMB_SFT 6 +#define NAU8821_I2S_PCMB_MASK (0x1 << NAU8821_I2S_PCMB_SFT) +#define NAU8821_I2S_PCMB_EN (0x1 << NAU8821_I2S_PCMB_SFT) +#define NAU8821_I2S_DL_SFT 2 +#define NAU8821_I2S_DL_MASK (0x3 << NAU8821_I2S_DL_SFT) +#define NAU8821_I2S_DL_32 (0x3 << NAU8821_I2S_DL_SFT) +#define NAU8821_I2S_DL_24 (0x2 << NAU8821_I2S_DL_SFT) +#define NAU8821_I2S_DL_20 (0x1 << NAU8821_I2S_DL_SFT) +#define NAU8821_I2S_DL_16 (0x0 << NAU8821_I2S_DL_SFT) +#define NAU8821_I2S_DF_MASK 0x3 +#define NAU8821_I2S_DF_PCM_AB 0x3 +#define NAU8821_I2S_DF_I2S 0x2 +#define NAU8821_I2S_DF_LEFT 0x1 +#define NAU8821_I2S_DF_RIGTH 0x0 + +/* I2S_PCM_CTRL2 (0x1d) */ +#define NAU8821_I2S_TRISTATE_SFT 15 +#define NAU8821_I2S_TRISTATE (0x1 << NAU8821_I2S_TRISTATE_SFT) +#define NAU8821_I2S_LRC_DIV_SFT 12 +#define NAU8821_I2S_LRC_DIV_MASK (0x3 << NAU8821_I2S_LRC_DIV_SFT) +#define NAU8821_I2S_MS_SFT 3 +#define NAU8821_I2S_MS_MASK (0x1 << NAU8821_I2S_MS_SFT) +#define NAU8821_I2S_MS_MASTER (0x1 << NAU8821_I2S_MS_SFT) +#define NAU8821_I2S_MS_SLAVE (0x0 << NAU8821_I2S_MS_SFT) +#define NAU8821_I2S_BLK_DIV_MASK 0x7 + +/* LEFT_TIME_SLOT (0x1e) */ +#define NAU8821_TSLOT_L_OFFSET_MASK 0x3ff +#define NAU8821_DIS_FS_SHORT_DET (0x1 << 13) + +/* RIGHT_TIME_SLOT (0x1f) */ +#define NAU8821_TSLOT_R_OFFSET_MASK 0x3ff + +/* BIQ0_COF10 (0x2a) */ +#define NAU8821_BIQ0_ADC_EN_SFT 3 +#define NAU8821_BIQ0_ADC_EN_EN (0x1 << NAU8821_BIQ0_ADC_EN_SFT) + +/* ADC_RATE (0x2b) */ +#define NAU8821_ADC_SYNC_DOWN_SFT 0 +#define NAU8821_ADC_SYNC_DOWN_MASK 0x3 +#define NAU8821_ADC_SYNC_DOWN_256 0x3 +#define NAU8821_ADC_SYNC_DOWN_128 0x2 +#define NAU8821_ADC_SYNC_DOWN_64 0x1 +#define NAU8821_ADC_SYNC_DOWN_32 0x0 +#define NAU8821_ADC_L_SRC_SFT 15 +#define NAU8821_ADC_L_SRC_EN (0x1 << NAU8821_ADC_L_SRC_SFT) +#define NAU8821_ADC_R_SRC_SFT 14 +#define NAU8821_ADC_R_SRC_EN (0x1 << NAU8821_ADC_R_SRC_SFT) + +/* DAC_CTRL1 (0x2c) */ +#define NAU8821_DAC_OVERSAMPLE_SFT 0 +#define NAU8821_DAC_OVERSAMPLE_MASK 0x7 +#define NAU8821_DAC_OVERSAMPLE_32 0x4 +#define NAU8821_DAC_OVERSAMPLE_128 0x2 +#define NAU8821_DAC_OVERSAMPLE_256 0x1 +#define NAU8821_DAC_OVERSAMPLE_64 0x0 + +/* DAC_DGAIN_CTRL (0x2f) */ +#define NAU8821_DAC1_TO_DAC0_ST_SFT 8 +#define NAU8821_DAC1_TO_DAC0_ST_MASK (0xff << NAU8821_DAC1_TO_DAC0_ST_SFT) +#define NAU8821_DAC0_TO_DAC1_ST_SFT 0 +#define NAU8821_DAC0_TO_DAC1_ST_MASK 0xff + +/* MUTE_CTRL (0x31) */ +#define NAU8821_DAC_ZC_EN (0x1 << 12) +#define NAU8821_DAC_SOFT_MUTE (0x1 << 9) +#define NAU8821_ADC_ZC_EN (0x1 << 2) +#define NAU8821_ADC_SOFT_MUTE (0x1 << 1) + +/* HSVOL_CTRL (0x32) */ +#define NAU8821_HP_MUTE (0x1 << 15) +#define NAU8821_HP_MUTE_AUTO (0x1 << 14) +#define NAU8821_HPL_MUTE (0x1 << 13) +#define NAU8821_HPR_MUTE (0x1 << 12) +#define NAU8821_HPL_VOL_SFT 4 +#define NAU8821_HPL_VOL_MASK (0x3 << NAU8821_HPL_VOL_SFT) +#define NAU8821_HPR_VOL_SFT 0 +#define NAU8821_HPR_VOL_MASK (0x3 << NAU8821_HPR_VOL_SFT) + +/* DACR_CTRL (0x34) */ +#define NAU8821_DACR_CH_VOL_SFT 8 +#define NAU8821_DACR_CH_VOL_MASK (0xff << NAU8821_DACR_CH_VOL_SFT) +#define NAU8821_DACL_CH_VOL_SFT 0 +#define NAU8821_DACL_CH_VOL_MASK 0xff + +/* ADC_DGAIN_CTRL1 (0x35) */ +#define NAU8821_ADCR_CH_VOL_SFT 8 +#define NAU8821_ADCR_CH_VOL_MASK (0xff << NAU8821_ADCR_CH_VOL_SFT) +#define NAU8821_ADCL_CH_VOL_SFT 0 +#define NAU8821_ADCL_CH_VOL_MASK 0xff + +/* BIQ1_COF10 (0x4a) */ +#define NAU8821_BIQ1_DAC_EN_SFT 3 +#define NAU8821_BIQ1_DAC_EN_EN (0x1 << NAU8821_BIQ1_DAC_EN_SFT) + +/* CLASSG_CTRL (0x4b) */ +#define NAU8821_CLASSG_TIMER_SFT 8 +#define NAU8821_CLASSG_TIMER_MASK (0x3f << NAU8821_CLASSG_TIMER_SFT) +#define NAU8821_CLASSG_TIMER_64MS (0x20 << NAU8821_CLASSG_TIMER_SFT) +#define NAU8821_CLASSG_TIMER_32MS (0x10 << NAU8821_CLASSG_TIMER_SFT) +#define NAU8821_CLASSG_TIMER_16MS (0x8 << NAU8821_CLASSG_TIMER_SFT) +#define NAU8821_CLASSG_TIMER_8MS (0x4 << NAU8821_CLASSG_TIMER_SFT) +#define NAU8821_CLASSG_TIMER_2MS (0x2 << NAU8821_CLASSG_TIMER_SFT) +#define NAU8821_CLASSG_TIMER_1MS (0x1 << NAU8821_CLASSG_TIMER_SFT) +#define NAU8821_CLASSG_RDAC_EN_SFT 2 +#define NAU8821_CLASSG_RDAC_EN (0x1 << NAU8821_CLASSG_RDAC_EN_SFT) +#define NAU8821_CLASSG_LDAC_EN_SFT 1 +#define NAU8821_CLASSG_LDAC_EN (0x1 << NAU8821_CLASSG_LDAC_EN_SFT) +#define NAU8821_CLASSG_EN_SFT 0 +#define NAU8821_CLASSG_EN 0x1 + +/* IMM_MODE_CTRL (0x4c) */ +#define NAU8821_IMM_THD_SFT 8 +#define NAU8821_IMM_THD_MASK (0x3f << NAU8821_IMM_THD_SFT) +#define NAU8821_IMM_GEN_VOL_SFT 6 +#define NAU8821_IMM_GEN_VOL_MASK (0x3 << NAU8821_IMM_GEN_VOL_SFT) +#define NAU8821_IMM_CYC_SFT 4 +#define NAU8821_IMM_CYC_MASK (0x3 << NAU8821_IMM_CYC_SFT) +#define NAU8821_IMM_EN (0x1 << 3) +#define NAU8821_IMM_DAC_SRC_MASK 0x3 + +/* I2C_DEVICE_ID (0x58) */ +#define NAU8821_KEYDET (0x1 << 7) +#define NAU8821_MICDET (0x1 << 6) +#define NAU8821_SOFTWARE_ID_MASK 0x3 + +/* BIAS_ADJ (0x66) */ +#define NAU8821_BIAS_HP_IMP (0x1 << 15) +#define NAU8821_BIAS_TESTDAC_SFT 8 +#define NAU8821_BIAS_TESTDAC_EN (0x3 << NAU8821_BIAS_TESTDAC_SFT) +#define NAU8821_BIAS_TESTDACR_EN (0x2 << NAU8821_BIAS_TESTDAC_SFT) +#define NAU8821_BIAS_TESTDACL_EN (0x1 << NAU8821_BIAS_TESTDAC_SFT) +#define NAU8821_BIAS_VMID (0x1 << 6) +#define NAU8821_BIAS_VMID_SEL_SFT 4 +#define NAU8821_BIAS_VMID_SEL_MASK (0x3 << NAU8821_BIAS_VMID_SEL_SFT) + +/* ANALOG_CONTROL_1 (0x69) */ +#define NAU8821_JD_POL_SFT 2 +#define NAU8821_JD_POL_MASK (0x1 << NAU8821_JD_POL_SFT) +#define NAU8821_JD_POL_INV (0x1 << NAU8821_JD_POL_SFT) +#define NAU8821_JD_OUT_POL_SFT 1 +#define NAU8821_JD_OUT_POL_MASK (0x1 << NAU8821_JD_OUT_POL_SFT) +#define NAU8821_JD_OUT_POL_INV (0x1 << NAU8821_JD_OUT_POL_SFT) +#define NAU8821_JD_EN_SFT 0 +#define NAU8821_JD_EN 0x1 + +/* ANALOG_CONTROL_2 (0x6a) */ +#define NAU8821_HP_NON_CLASSG_CURRENT_2xADJ (0x1 << 12) +#define NAU8821_DAC_CAPACITOR_MSB (0x1 << 1) +#define NAU8821_DAC_CAPACITOR_LSB 0x1 + +/* ANALOG_ADC_1 (0x71) */ +#define NAU8821_MICDET_EN_SFT 0 +#define NAU8821_MICDET_MASK 0x1 +#define NAU8821_MICDET_DIS 0x1 +#define NAU8821_MICDET_EN 0x0 + +/* ANALOG_ADC_2 (0x72) */ +#define NAU8821_ADC_VREFSEL_SFT 8 +#define NAU8821_ADC_VREFSEL_MASK (0x3 << NAU8821_ADC_VREFSEL_SFT) +#define NAU8821_POWERUP_ADCL_SFT 6 +#define NAU8821_POWERUP_ADCL (0x1 << NAU8821_POWERUP_ADCL_SFT) +#define NAU8821_POWERUP_ADCR_SFT 4 +#define NAU8821_POWERUP_ADCR (0x1 << NAU8821_POWERUP_ADCR_SFT) + +/* RDAC (0x73) */ +#define NAU8821_DACR_EN_SFT 13 +#define NAU8821_DACR_EN (0x3 << NAU8821_DACR_EN_SFT) +#define NAU8821_DACL_EN_SFT 12 +#define NAU8821_DACL_EN (0x3 << NAU8821_DACL_EN_SFT) +#define NAU8821_DACR_CLK_EN_SFT 9 +#define NAU8821_DACR_CLK_EN (0x3 << NAU8821_DACR_CLK_EN_SFT) +#define NAU8821_DACL_CLK_EN_SFT 8 +#define NAU8821_DACL_CLK_EN (0x3 << NAU8821_DACL_CLK_EN_SFT) +#define NAU8821_DAC_CLK_DELAY_SFT 4 +#define NAU8821_DAC_CLK_DELAY_MASK (0x7 << NAU8821_DAC_CLK_DELAY_SFT) +#define NAU8821_DAC_VREF_SFT 2 +#define NAU8821_DAC_VREF_MASK (0x3 << NAU8821_DAC_VREF_SFT) + +/* MIC_BIAS (0x74) */ +#define NAU8821_MICBIAS_JKR2 (0x1 << 12) +#define NAU8821_MICBIAS_POWERUP_SFT 8 +#define NAU8821_MICBIAS_VOLTAGE_SFT 0 +#define NAU8821_MICBIAS_VOLTAGE_MASK 0x7 + +/* BOOST (0x76) */ +#define NAU8821_PRECHARGE_DIS (0x1 << 13) +#define NAU8821_GLOBAL_BIAS_EN (0x1 << 12) +#define NAU8821_HP_BOOST_DIS_SFT 9 +#define NAU8821_HP_BOOST_DIS (0x1 << NAU8821_HP_BOOST_DIS_SFT) +#define NAU8821_HP_BOOST_G_DIS (0x1 << 8) +#define NAU8821_SHORT_SHUTDOWN_EN (0x1 << 6) + +/* FEPGA (0x77) */ +#define NAU8821_FEPGA_MODEL_SFT 4 +#define NAU8821_FEPGA_MODEL_MASK (0xf << NAU8821_FEPGA_MODEL_SFT) +#define NAU8821_FEPGA_MODER_SFT 0 +#define NAU8821_FEPGA_MODER_MASK 0xf + +/* PGA_GAIN (0x7e) */ +#define NAU8821_PGA_GAIN_L_SFT 8 +#define NAU8821_PGA_GAIN_L_MASK (0x3f << NAU8821_PGA_GAIN_L_SFT) +#define NAU8821_PGA_GAIN_R_SFT 0 +#define NAU8821_PGA_GAIN_R_MASK 0x3f + +/* POWER_UP_CONTROL (0x7f) */ +#define NAU8821_PUP_PGA_L_SFT 15 +#define NAU8821_PUP_PGA_L (0x1 << NAU8821_PUP_PGA_L_SFT) +#define NAU8821_PUP_PGA_R_SFT 14 +#define NAU8821_PUP_PGA_R (0x1 << NAU8821_PUP_PGA_R_SFT) +#define NAU8821_PUP_INTEG_R_SFT 5 +#define NAU8821_PUP_INTEG_R (0x1 << NAU8821_PUP_INTEG_R_SFT) +#define NAU8821_PUP_INTEG_L_SFT 4 +#define NAU8821_PUP_INTEG_L (0x1 << NAU8821_PUP_INTEG_L_SFT) +#define NAU8821_PUP_DRV_INSTG_R_SFT 3 +#define NAU8821_PUP_DRV_INSTG_R (0x1 << NAU8821_PUP_DRV_INSTG_R_SFT) +#define NAU8821_PUP_DRV_INSTG_L_SFT 2 +#define NAU8821_PUP_DRV_INSTG_L (0x1 << NAU8821_PUP_DRV_INSTG_L_SFT) +#define NAU8821_PUP_MAIN_DRV_R_SFT 1 +#define NAU8821_PUP_MAIN_DRV_R (0x1 << NAU8821_PUP_MAIN_DRV_R_SFT) +#define NAU8821_PUP_MAIN_DRV_L_SFT 0 +#define NAU8821_PUP_MAIN_DRV_L 0x1 + +/* CHARGE_PUMP (0x80) */ +#define NAU8821_JAMNODCLOW (0x1 << 10) +#define NAU8821_POWER_DOWN_DACR_SFT 9 +#define NAU8821_POWER_DOWN_DACR (0x1 << NAU8821_POWER_DOWN_DACR_SFT) +#define NAU8821_POWER_DOWN_DACL_SFT 8 +#define NAU8821_POWER_DOWN_DACL (0x1 << NAU8821_POWER_DOWN_DACL_SFT) +#define NAU8821_CHANRGE_PUMP_EN_SFT 5 +#define NAU8821_CHANRGE_PUMP_EN (0x1 << NAU8821_CHANRGE_PUMP_EN_SFT) + +/* GENERAL_STATUS (0x82) */ +#define NAU8821_GPIO2_IN_SFT 1 +#define NAU8821_GPIO2_IN (0x1 << NAU8821_GPIO2_IN_SFT) + +#define NUVOTON_CODEC_DAI "nau8821-hifi" + +/* System Clock Source */ +enum { + NAU8821_CLK_DIS, + NAU8821_CLK_MCLK, + NAU8821_CLK_INTERNAL, + NAU8821_CLK_FLL_MCLK, + NAU8821_CLK_FLL_BLK, + NAU8821_CLK_FLL_FS, +}; + +struct nau8821 { + struct device *dev; + struct regmap *regmap; + struct snd_soc_dapm_context *dapm; + struct snd_soc_jack *jack; + struct work_struct jdet_work; + int irq; + int clk_id; + int micbias_voltage; + int vref_impedance; + bool jkdet_enable; + bool jkdet_pull_enable; + bool jkdet_pull_up; + int jkdet_polarity; + int jack_insert_debounce; + int jack_eject_debounce; + int fs; + int dmic_clk_threshold; +}; + +int nau8821_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack); + +#endif /* __NAU8821_H__ */ diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c index f946ef65a4c1..d0dd1542f78a 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -8,6 +8,7 @@ #include <linux/module.h> #include <linux/delay.h> +#include <linux/dmi.h> #include <linux/init.h> #include <linux/i2c.h> #include <linux/regmap.h> @@ -27,6 +28,13 @@ #include "nau8824.h" +#define NAU8824_JD_ACTIVE_HIGH BIT(0) +#define NAU8824_MONO_SPEAKER BIT(1) + +static int nau8824_quirk; +static int quirk_override = -1; +module_param_named(quirk, quirk_override, uint, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); static int nau8824_config_sysclk(struct nau8824 *nau8824, int clk_id, unsigned int freq); @@ -1845,6 +1853,63 @@ static int nau8824_read_device_properties(struct device *dev, return 0; } +/* Please keep this list alphabetically sorted */ +static const struct dmi_system_id nau8824_quirk_table[] = { + { + /* Cyberbook T116 rugged tablet */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "20170531"), + }, + .driver_data = (void *)(NAU8824_JD_ACTIVE_HIGH | + NAU8824_MONO_SPEAKER), + }, + { + /* CUBE iwork8 Air */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "cube"), + DMI_MATCH(DMI_PRODUCT_NAME, "i1-TF"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + }, + .driver_data = (void *)(NAU8824_MONO_SPEAKER), + }, + { + /* Pipo W2S */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "PIPO"), + DMI_MATCH(DMI_PRODUCT_NAME, "W2S"), + }, + .driver_data = (void *)(NAU8824_MONO_SPEAKER), + }, + {} +}; + +static void nau8824_check_quirks(void) +{ + const struct dmi_system_id *dmi_id; + + if (quirk_override != -1) { + nau8824_quirk = quirk_override; + return; + } + + dmi_id = dmi_first_match(nau8824_quirk_table); + if (dmi_id) + nau8824_quirk = (unsigned long)dmi_id->driver_data; +} + +const char *nau8824_components(void) +{ + nau8824_check_quirks(); + + if (nau8824_quirk & NAU8824_MONO_SPEAKER) + return "cfg-spk:1"; + else + return "cfg-spk:2"; +} +EXPORT_SYMBOL_GPL(nau8824_components); + static int nau8824_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -1869,6 +1934,11 @@ static int nau8824_i2c_probe(struct i2c_client *i2c, nau8824->irq = i2c->irq; sema_init(&nau8824->jd_sem, 1); + nau8824_check_quirks(); + + if (nau8824_quirk & NAU8824_JD_ACTIVE_HIGH) + nau8824->jkdet_polarity = 0; + nau8824_print_device_properties(nau8824); ret = regmap_read(nau8824->regmap, NAU8824_REG_I2C_DEVICE_ID, &value); diff --git a/sound/soc/codecs/nau8824.h b/sound/soc/codecs/nau8824.h index 1d7bdd8e0523..de4bae8281d0 100644 --- a/sound/soc/codecs/nau8824.h +++ b/sound/soc/codecs/nau8824.h @@ -197,7 +197,7 @@ /* JACK_DET_CTRL (0x0D) */ #define NAU8824_JACK_EJECT_DT_SFT 2 #define NAU8824_JACK_EJECT_DT_MASK (0x3 << NAU8824_JACK_EJECT_DT_SFT) -#define NAU8824_JACK_LOGIC 0x1 +#define NAU8824_JACK_LOGIC (0x1 << 1) /* INTERRUPT_SETTING_1 (0x0F) */ @@ -470,6 +470,7 @@ struct nau8824_osr_attr { int nau8824_enable_jack_detect(struct snd_soc_component *component, struct snd_soc_jack *jack); +const char *nau8824_components(void); #endif /* _NAU8824_H */ diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 67de0e49ccf4..7734bc35ab21 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -47,6 +47,7 @@ static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id, unsigned int freq); +static bool nau8825_is_jack_inserted(struct regmap *regmap); struct nau8825_fll { int mclk_src; @@ -981,6 +982,31 @@ static int nau8825_output_dac_event(struct snd_soc_dapm_widget *w, return 0; } +static int system_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8825->regmap; + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + dev_dbg(nau8825->dev, "system clock control : POWER OFF\n"); + /* Set clock source to disable or internal clock before the + * playback or capture end. Codec needs clock for Jack + * detection and button press if jack inserted; otherwise, + * the clock should be closed. + */ + if (nau8825_is_jack_inserted(regmap)) { + nau8825_configure_sysclk(nau8825, + NAU8825_CLK_INTERNAL, 0); + } else { + nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0); + } + } + + return 0; +} + static int nau8825_biq_coeff_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1094,6 +1120,9 @@ static const struct snd_kcontrol_new nau8825_dacr_mux = static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = { SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, NAU8825_REG_I2S_PCM_CTRL2, 15, 1), + SND_SOC_DAPM_AIF_IN("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("System Clock", SND_SOC_NOPM, 0, 0, + system_clock_control, SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_INPUT("MIC"), SND_SOC_DAPM_MICBIAS("MICBIAS", NAU8825_REG_MIC_BIAS, 8, 0), @@ -1182,9 +1211,11 @@ static const struct snd_soc_dapm_route nau8825_dapm_routes[] = { {"ADC", NULL, "ADC Clock"}, {"ADC", NULL, "ADC Power"}, {"AIFTX", NULL, "ADC"}, + {"AIFTX", NULL, "System Clock"}, - {"DDACL", NULL, "Playback"}, - {"DDACR", NULL, "Playback"}, + {"AIFRX", NULL, "System Clock"}, + {"DDACL", NULL, "AIFRX"}, + {"DDACR", NULL, "AIFRX"}, {"DDACL", NULL, "DDAC Clock"}, {"DDACR", NULL, "DDAC Clock"}, {"DACL Mux", "DACL", "DDACL"}, @@ -1434,6 +1465,12 @@ int nau8825_enable_jack_detect(struct snd_soc_component *component, nau8825->jack = jack; + if (!nau8825->jack) { + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, + NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | + NAU8825_SPKR_DWN1L, 0); + return 0; + } /* Ground HP Outputs[1:0], needed for headset auto detection * Enable Automatic Mic/Gnd switching reading on insert interrupt[6] */ @@ -2416,6 +2453,12 @@ static int __maybe_unused nau8825_resume(struct snd_soc_component *component) return 0; } +static int nau8825_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + return nau8825_enable_jack_detect(component, jack); +} + static const struct snd_soc_component_driver nau8825_component_driver = { .probe = nau8825_component_probe, .remove = nau8825_component_remove, @@ -2430,6 +2473,7 @@ static const struct snd_soc_component_driver nau8825_component_driver = { .num_dapm_widgets = ARRAY_SIZE(nau8825_dapm_widgets), .dapm_routes = nau8825_dapm_routes, .num_dapm_routes = ARRAY_SIZE(nau8825_dapm_routes), + .set_jack = nau8825_set_jack, .suspend_bias_off = 1, .idle_bias_on = 1, .use_pmdown_time = 1, diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c index b8cfc250612c..f39f98bbc97f 100644 --- a/sound/soc/codecs/pcm5102a.c +++ b/sound/soc/codecs/pcm5102a.c @@ -17,7 +17,7 @@ static struct snd_soc_dai_driver pcm5102a_dai = { .playback = { .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, + .rates = SNDRV_PCM_RATE_8000_384000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index faff2b558687..297af7ff824c 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -1311,6 +1311,14 @@ static int rt1011_r0_load_info(struct snd_kcontrol *kcontrol, .put = rt1011_r0_load_mode_put \ } +static const char * const rt1011_i2s_ref_texts[] = { + "Left Channel", "Right Channel" +}; + +static SOC_ENUM_SINGLE_DECL(rt1011_i2s_ref_enum, + RT1011_TDM1_SET_1, 7, + rt1011_i2s_ref_texts); + static const struct snd_kcontrol_new rt1011_snd_controls[] = { /* I2S Data In Selection */ SOC_ENUM("DIN Source", rt1011_din_source_enum), @@ -1349,6 +1357,8 @@ static const struct snd_kcontrol_new rt1011_snd_controls[] = { /* R0 temperature */ SOC_SINGLE("R0 Temperature", RT1011_STP_INITIAL_RESISTANCE_TEMP, 2, 255, 0), + /* I2S Reference */ + SOC_ENUM("I2S Reference", rt1011_i2s_ref_enum), }; static int rt1011_is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, diff --git a/sound/soc/codecs/rt1015.c b/sound/soc/codecs/rt1015.c index c0c5952cdff7..6a27dfacd81c 100644 --- a/sound/soc/codecs/rt1015.c +++ b/sound/soc/codecs/rt1015.c @@ -864,7 +864,7 @@ static int rt1015_set_component_pll(struct snd_soc_component *component, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt1016.c b/sound/soc/codecs/rt1016.c index 7561d202274c..9845cdddcb4c 100644 --- a/sound/soc/codecs/rt1016.c +++ b/sound/soc/codecs/rt1016.c @@ -490,7 +490,7 @@ static int rt1016_set_component_pll(struct snd_soc_component *component, ret = rl6231_pll_calc(freq_in, freq_out * 4, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt1019.c b/sound/soc/codecs/rt1019.c index 8c0b00242bb8..80b7ca0e4e1e 100644 --- a/sound/soc/codecs/rt1019.c +++ b/sound/soc/codecs/rt1019.c @@ -359,7 +359,7 @@ static int rt1019_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt1305.c b/sound/soc/codecs/rt1305.c index 7a0094578e46..a9c473537a91 100644 --- a/sound/soc/codecs/rt1305.c +++ b/sound/soc/codecs/rt1305.c @@ -841,7 +841,7 @@ static int rt1305_set_component_pll(struct snd_soc_component *component, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt1308.c b/sound/soc/codecs/rt1308.c index b4e5546e2e21..c555b77b3c5c 100644 --- a/sound/soc/codecs/rt1308.c +++ b/sound/soc/codecs/rt1308.c @@ -664,7 +664,7 @@ static int rt1308_set_component_pll(struct snd_soc_component *component, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 4b1ad5054e8d..577680df7052 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -936,7 +936,7 @@ static int rt5514_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index fd0d3a08e9dd..8e6414468a87 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1133,7 +1133,7 @@ static int rt5616_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index cd1db5caabad..d01fe73ab9c8 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -1909,7 +1909,7 @@ static int rt5640_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 9408ee63cb26..197c56047947 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2969,7 +2969,7 @@ static int rt5645_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index fc0c83b73f09..f302c25688d1 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -1487,7 +1487,7 @@ static int rt5651_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } @@ -2261,11 +2261,8 @@ static int rt5651_i2c_probe(struct i2c_client *i2c, ret = devm_request_irq(&i2c->dev, rt5651->irq, rt5651_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING - | IRQF_ONESHOT, "rt5651", rt5651); - if (ret == 0) { - /* Gets re-enabled by rt5651_set_jack() */ - disable_irq(rt5651->irq); - } else { + | IRQF_ONESHOT | IRQF_NO_AUTOEN, "rt5651", rt5651); + if (ret) { dev_warn(&i2c->dev, "Failed to reguest IRQ %d: %d\n", rt5651->irq, ret); rt5651->irq = -ENXIO; diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 4a50b169fe03..e1503c2eee81 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -3509,7 +3509,7 @@ static int rt5659_set_component_pll(struct snd_soc_component *component, int pll ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5660.c b/sound/soc/codecs/rt5660.c index 33ff9156358b..3b50fb29864e 100644 --- a/sound/soc/codecs/rt5660.c +++ b/sound/soc/codecs/rt5660.c @@ -1046,7 +1046,7 @@ static int rt5660_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5663.c b/sound/soc/codecs/rt5663.c index be9fc58ff681..0389b2bb360e 100644 --- a/sound/soc/codecs/rt5663.c +++ b/sound/soc/codecs/rt5663.c @@ -2941,7 +2941,7 @@ static int rt5663_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index e59323fd5bf2..33e889802ff8 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -4374,7 +4374,7 @@ static int rt5665_set_component_pll(struct snd_soc_component *component, int pll ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c index 6ab1a8bc3735..fb09715bf932 100644 --- a/sound/soc/codecs/rt5668.c +++ b/sound/soc/codecs/rt5668.c @@ -2171,7 +2171,7 @@ static int rt5668_set_component_pll(struct snd_soc_component *component, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index ecbaf129a6e3..ce7684752bb0 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -2577,7 +2577,7 @@ static int rt5670_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index f655228c8c4b..4a8c267d4fbc 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -4557,7 +4557,7 @@ static int rt5677_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, ret = rt5677_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } diff --git a/sound/soc/codecs/rt5682-i2c.c b/sound/soc/codecs/rt5682-i2c.c index b9d5d7a0975b..983347b65127 100644 --- a/sound/soc/codecs/rt5682-i2c.c +++ b/sound/soc/codecs/rt5682-i2c.c @@ -139,6 +139,8 @@ static int rt5682_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, rt5682); + rt5682->i2c_dev = &i2c->dev; + rt5682->pdata = i2s_default_platform_data; if (pdata) @@ -276,6 +278,21 @@ static int rt5682_i2c_probe(struct i2c_client *i2c, dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); } +#ifdef CONFIG_COMMON_CLK + /* Check if MCLK provided */ + rt5682->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5682->mclk)) + return PTR_ERR(rt5682->mclk); + + /* Register CCF DAI clock control */ + ret = rt5682_register_dai_clks(rt5682); + if (ret) + return ret; + + /* Initial setup for CCF */ + rt5682->lrck[RT5682_AIF1] = 48000; +#endif + return devm_snd_soc_register_component(&i2c->dev, &rt5682_soc_component_dev, rt5682_dai, ARRAY_SIZE(rt5682_dai)); diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c index 4a64cab99c55..78b4cb5fb6c8 100644 --- a/sound/soc/codecs/rt5682.c +++ b/sound/soc/codecs/rt5682.c @@ -46,6 +46,8 @@ static const struct reg_sequence patch_list[] = { {RT5682_SAR_IL_CMD_1, 0x22b7}, {RT5682_SAR_IL_CMD_3, 0x0365}, {RT5682_SAR_IL_CMD_6, 0x0110}, + {RT5682_CHARGE_PUMP_1, 0x0210}, + {RT5682_HP_LOGIC_CTRL_2, 0x0007}, }; void rt5682_apply_patch_list(struct rt5682_priv *rt5682, struct device *dev) @@ -1515,21 +1517,29 @@ static int rt5682_hp_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - snd_soc_component_write(component, - RT5682_HP_LOGIC_CTRL_2, 0x0012); - snd_soc_component_write(component, - RT5682_HP_CTRL_2, 0x6000); + snd_soc_component_update_bits(component, RT5682_HP_CTRL_2, + RT5682_HP_C2_DAC_AMP_MUTE, 0); + snd_soc_component_update_bits(component, RT5682_HP_LOGIC_CTRL_2, + RT5682_HP_LC2_SIG_SOUR2_MASK, RT5682_HP_LC2_SIG_SOUR2_REG); snd_soc_component_update_bits(component, RT5682_DEPOP_1, 0x60, 0x60); snd_soc_component_update_bits(component, RT5682_DAC_ADC_DIG_VOL1, 0x00c0, 0x0080); + snd_soc_component_update_bits(component, RT5682_HP_CTRL_2, + RT5682_HP_C2_DAC_L_EN | RT5682_HP_C2_DAC_R_EN, + RT5682_HP_C2_DAC_L_EN | RT5682_HP_C2_DAC_R_EN); + usleep_range(5000, 10000); + snd_soc_component_update_bits(component, RT5682_CHARGE_PUMP_1, + RT5682_CP_SW_SIZE_MASK, RT5682_CP_SW_SIZE_L); break; case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, RT5682_HP_CTRL_2, + RT5682_HP_C2_DAC_L_EN | RT5682_HP_C2_DAC_R_EN, 0); + snd_soc_component_update_bits(component, RT5682_CHARGE_PUMP_1, + RT5682_CP_SW_SIZE_MASK, RT5682_CP_SW_SIZE_M); snd_soc_component_update_bits(component, RT5682_DEPOP_1, 0x60, 0x0); - snd_soc_component_write(component, - RT5682_HP_CTRL_2, 0x0000); snd_soc_component_update_bits(component, RT5682_DAC_ADC_DIG_VOL1, 0x00c0, 0x0000); break; @@ -1637,6 +1647,23 @@ static SOC_VALUE_ENUM_SINGLE_DECL(rt5682_adcdat_pin_enum, static const struct snd_kcontrol_new rt5682_adcdat_pin_ctrl = SOC_DAPM_ENUM("ADCDAT", rt5682_adcdat_pin_enum); +static const unsigned int rt5682_hpo_sig_out_values[] = { + 2, + 7, +}; + +static const char * const rt5682_hpo_sig_out_mode[] = { + "Legacy", + "OneBit", +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5682_hpo_sig_out_enum, + RT5682_HP_LOGIC_CTRL_2, 0, RT5682_HP_LC2_SIG_SOUR1_MASK, + rt5682_hpo_sig_out_mode, rt5682_hpo_sig_out_values); + +static const struct snd_kcontrol_new rt5682_hpo_sig_demux = + SOC_DAPM_ENUM("HPO Signal Demux", rt5682_hpo_sig_out_enum); + static const struct snd_soc_dapm_widget rt5682_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("LDO2", RT5682_PWR_ANLG_3, RT5682_PWR_LDO2_BIT, 0, NULL, 0), @@ -1820,6 +1847,10 @@ static const struct snd_soc_dapm_widget rt5682_dapm_widgets[] = { SND_SOC_DAPM_SWITCH("HPOR Playback", SND_SOC_NOPM, 0, 0, &hpor_switch), + SND_SOC_DAPM_OUT_DRV("HPO Legacy", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HPO OneBit", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_DEMUX("HPO Signal Demux", SND_SOC_NOPM, 0, 0, &rt5682_hpo_sig_demux), + /* CLK DET */ SND_SOC_DAPM_SUPPLY("CLKDET SYS", RT5682_CLK_DET, RT5682_SYS_CLK_DET_SFT, 0, NULL, 0), @@ -1987,10 +2018,19 @@ static const struct snd_soc_dapm_route rt5682_dapm_routes[] = { {"HP Amp", NULL, "Charge Pump"}, {"HP Amp", NULL, "CLKDET SYS"}, {"HP Amp", NULL, "Vref1"}, - {"HPOL Playback", "Switch", "HP Amp"}, - {"HPOR Playback", "Switch", "HP Amp"}, + + {"HPO Signal Demux", NULL, "HP Amp"}, + + {"HPO Legacy", "Legacy", "HPO Signal Demux"}, + {"HPO OneBit", "OneBit", "HPO Signal Demux"}, + + {"HPOL Playback", "Switch", "HPO Legacy"}, + {"HPOR Playback", "Switch", "HPO Legacy"}, + {"HPOL", NULL, "HPOL Playback"}, {"HPOR", NULL, "HPOR Playback"}, + {"HPOL", NULL, "HPO OneBit"}, + {"HPOR", NULL, "HPO OneBit"}, }; static int rt5682_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, @@ -2327,7 +2367,7 @@ static int rt5682_set_component_pll(struct snd_soc_component *component, pll2_fout1 = 3840000; ret = rl6231_pll_calc(freq_in, pll2_fout1, &pll2f_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } @@ -2339,7 +2379,7 @@ static int rt5682_set_component_pll(struct snd_soc_component *component, ret = rl6231_pll_calc(pll2_fout1, freq_out, &pll2b_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", + dev_err(component->dev, "Unsupported input clock %d\n", pll2_fout1); return ret; } @@ -2390,7 +2430,7 @@ static int rt5682_set_component_pll(struct snd_soc_component *component, ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); if (ret < 0) { - dev_err(component->dev, "Unsupport input clock %d\n", + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); return ret; } @@ -2510,7 +2550,7 @@ static int rt5682_set_bias_level(struct snd_soc_component *component, static bool rt5682_clk_check(struct rt5682_priv *rt5682) { if (!rt5682->master[RT5682_AIF1]) { - dev_dbg(rt5682->component->dev, "sysclk/dai not set correctly\n"); + dev_dbg(rt5682->i2c_dev, "sysclk/dai not set correctly\n"); return false; } return true; @@ -2521,13 +2561,15 @@ static int rt5682_wclk_prepare(struct clk_hw *hw) struct rt5682_priv *rt5682 = container_of(hw, struct rt5682_priv, dai_clks_hw[RT5682_DAI_WCLK_IDX]); - struct snd_soc_component *component = rt5682->component; - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); + struct snd_soc_component *component; + struct snd_soc_dapm_context *dapm; if (!rt5682_clk_check(rt5682)) return -EINVAL; + component = rt5682->component; + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_mutex_lock(dapm); snd_soc_dapm_force_enable_pin_unlocked(dapm, "MICBIAS"); @@ -2557,13 +2599,15 @@ static void rt5682_wclk_unprepare(struct clk_hw *hw) struct rt5682_priv *rt5682 = container_of(hw, struct rt5682_priv, dai_clks_hw[RT5682_DAI_WCLK_IDX]); - struct snd_soc_component *component = rt5682->component; - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); + struct snd_soc_component *component; + struct snd_soc_dapm_context *dapm; if (!rt5682_clk_check(rt5682)) return; + component = rt5682->component; + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_mutex_lock(dapm); snd_soc_dapm_disable_pin_unlocked(dapm, "MICBIAS"); @@ -2587,7 +2631,6 @@ static unsigned long rt5682_wclk_recalc_rate(struct clk_hw *hw, struct rt5682_priv *rt5682 = container_of(hw, struct rt5682_priv, dai_clks_hw[RT5682_DAI_WCLK_IDX]); - struct snd_soc_component *component = rt5682->component; const char * const clk_name = clk_hw_get_name(hw); if (!rt5682_clk_check(rt5682)) @@ -2597,7 +2640,7 @@ static unsigned long rt5682_wclk_recalc_rate(struct clk_hw *hw, */ if (rt5682->lrck[RT5682_AIF1] != CLK_48 && rt5682->lrck[RT5682_AIF1] != CLK_44) { - dev_warn(component->dev, "%s: clk %s only support %d or %d Hz output\n", + dev_warn(rt5682->i2c_dev, "%s: clk %s only support %d or %d Hz output\n", __func__, clk_name, CLK_44, CLK_48); return 0; } @@ -2611,7 +2654,6 @@ static long rt5682_wclk_round_rate(struct clk_hw *hw, unsigned long rate, struct rt5682_priv *rt5682 = container_of(hw, struct rt5682_priv, dai_clks_hw[RT5682_DAI_WCLK_IDX]); - struct snd_soc_component *component = rt5682->component; const char * const clk_name = clk_hw_get_name(hw); if (!rt5682_clk_check(rt5682)) @@ -2621,7 +2663,7 @@ static long rt5682_wclk_round_rate(struct clk_hw *hw, unsigned long rate, * It will force to 48kHz if not both. */ if (rate != CLK_48 && rate != CLK_44) { - dev_warn(component->dev, "%s: clk %s only support %d or %d Hz output\n", + dev_warn(rt5682->i2c_dev, "%s: clk %s only support %d or %d Hz output\n", __func__, clk_name, CLK_44, CLK_48); rate = CLK_48; } @@ -2635,7 +2677,7 @@ static int rt5682_wclk_set_rate(struct clk_hw *hw, unsigned long rate, struct rt5682_priv *rt5682 = container_of(hw, struct rt5682_priv, dai_clks_hw[RT5682_DAI_WCLK_IDX]); - struct snd_soc_component *component = rt5682->component; + struct snd_soc_component *component; struct clk_hw *parent_hw; const char * const clk_name = clk_hw_get_name(hw); int pre_div; @@ -2644,6 +2686,8 @@ static int rt5682_wclk_set_rate(struct clk_hw *hw, unsigned long rate, if (!rt5682_clk_check(rt5682)) return -EINVAL; + component = rt5682->component; + /* * Whether the wclk's parent clk (mclk) exists or not, please ensure * it is fixed or set to 48MHz before setting wclk rate. It's a @@ -2653,12 +2697,12 @@ static int rt5682_wclk_set_rate(struct clk_hw *hw, unsigned long rate, */ parent_hw = clk_hw_get_parent(hw); if (!parent_hw) - dev_warn(component->dev, + dev_warn(rt5682->i2c_dev, "Parent mclk of wclk not acquired in driver. Please ensure mclk was provided as %d Hz.\n", CLK_PLL2_FIN); if (parent_rate != CLK_PLL2_FIN) - dev_warn(component->dev, "clk %s only support %d Hz input\n", + dev_warn(rt5682->i2c_dev, "clk %s only support %d Hz input\n", clk_name, CLK_PLL2_FIN); /* @@ -2690,10 +2734,9 @@ static unsigned long rt5682_bclk_recalc_rate(struct clk_hw *hw, struct rt5682_priv *rt5682 = container_of(hw, struct rt5682_priv, dai_clks_hw[RT5682_DAI_BCLK_IDX]); - struct snd_soc_component *component = rt5682->component; unsigned int bclks_per_wclk; - bclks_per_wclk = snd_soc_component_read(component, RT5682_TDM_TCON_CTRL); + regmap_read(rt5682->regmap, RT5682_TDM_TCON_CTRL, &bclks_per_wclk); switch (bclks_per_wclk & RT5682_TDM_BCLK_MS1_MASK) { case RT5682_TDM_BCLK_MS1_256: @@ -2754,20 +2797,22 @@ static int rt5682_bclk_set_rate(struct clk_hw *hw, unsigned long rate, struct rt5682_priv *rt5682 = container_of(hw, struct rt5682_priv, dai_clks_hw[RT5682_DAI_BCLK_IDX]); - struct snd_soc_component *component = rt5682->component; + struct snd_soc_component *component; struct snd_soc_dai *dai; unsigned long factor; if (!rt5682_clk_check(rt5682)) return -EINVAL; + component = rt5682->component; + factor = rt5682_bclk_get_factor(rate, parent_rate); for_each_component_dais(component, dai) if (dai->id == RT5682_AIF1) break; if (!dai) { - dev_err(component->dev, "dai %d not found in component\n", + dev_err(rt5682->i2c_dev, "dai %d not found in component\n", RT5682_AIF1); return -ENODEV; } @@ -2790,10 +2835,9 @@ static const struct clk_ops rt5682_dai_clk_ops[RT5682_DAI_NUM_CLKS] = { }, }; -static int rt5682_register_dai_clks(struct snd_soc_component *component) +int rt5682_register_dai_clks(struct rt5682_priv *rt5682) { - struct device *dev = component->dev; - struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + struct device *dev = rt5682->i2c_dev; struct rt5682_platform_data *pdata = &rt5682->pdata; struct clk_hw *dai_clk_hw; int i, ret; @@ -2851,6 +2895,7 @@ static int rt5682_register_dai_clks(struct snd_soc_component *component) return 0; } +EXPORT_SYMBOL_GPL(rt5682_register_dai_clks); #endif /* CONFIG_COMMON_CLK */ static int rt5682_probe(struct snd_soc_component *component) @@ -2860,9 +2905,6 @@ static int rt5682_probe(struct snd_soc_component *component) unsigned long time; struct snd_soc_dapm_context *dapm = &component->dapm; -#ifdef CONFIG_COMMON_CLK - int ret; -#endif rt5682->component = component; if (rt5682->is_sdw) { @@ -2874,26 +2916,6 @@ static int rt5682_probe(struct snd_soc_component *component) dev_err(&slave->dev, "Initialization not complete, timed out\n"); return -ETIMEDOUT; } - } else { -#ifdef CONFIG_COMMON_CLK - /* Check if MCLK provided */ - rt5682->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(rt5682->mclk)) { - if (PTR_ERR(rt5682->mclk) != -ENOENT) { - ret = PTR_ERR(rt5682->mclk); - return ret; - } - rt5682->mclk = NULL; - } - - /* Register CCF DAI clock control */ - ret = rt5682_register_dai_clks(component); - if (ret) - return ret; - - /* Initial setup for CCF */ - rt5682->lrck[RT5682_AIF1] = CLK_48; -#endif } snd_soc_dapm_disable_pin(dapm, "MICBIAS"); diff --git a/sound/soc/codecs/rt5682.h b/sound/soc/codecs/rt5682.h index b59221048ebf..d93829c35585 100644 --- a/sound/soc/codecs/rt5682.h +++ b/sound/soc/codecs/rt5682.h @@ -375,6 +375,14 @@ #define RT5682_R_VOL_MASK (0x3f) #define RT5682_R_VOL_SFT 0 +/* Headphone Amp Control 2 (0x0003) */ +#define RT5682_HP_C2_DAC_AMP_MUTE_SFT 15 +#define RT5682_HP_C2_DAC_AMP_MUTE (0x1 << 15) +#define RT5682_HP_C2_DAC_L_EN_SFT 14 +#define RT5682_HP_C2_DAC_L_EN (0x1 << 14) +#define RT5682_HP_C2_DAC_R_EN_SFT 13 +#define RT5682_HP_C2_DAC_R_EN (0x1 << 13) + /*Headphone Amp L/R Analog Gain and Digital NG2 Gain Control (0x0005 0x0006)*/ #define RT5682_G_HP (0xf << 8) #define RT5682_G_HP_SFT 8 @@ -1265,6 +1273,10 @@ #define RT5682_HPA_CP_BIAS_6UA (0x3 << 2) /* Charge Pump Internal Register1 (0x0125) */ +#define RT5682_CP_SW_SIZE_MASK (0x7 << 8) +#define RT5682_CP_SW_SIZE_L (0x4 << 8) +#define RT5682_CP_SW_SIZE_M (0x2 << 8) +#define RT5682_CP_SW_SIZE_S (0x1 << 8) #define RT5682_CP_CLK_HP_MASK (0x3 << 4) #define RT5682_CP_CLK_HP_100KHZ (0x0 << 4) #define RT5682_CP_CLK_HP_200KHZ (0x1 << 4) @@ -1315,6 +1327,14 @@ #define RT5682_DEB_STO_DAC_MASK (0x7 << 4) #define RT5682_DEB_80_MS (0x0 << 4) +/* HP Behavior Logic Control 2 (0x01db) */ +#define RT5682_HP_LC2_SIG_SOUR2_MASK (0x1 << 4) +#define RT5682_HP_LC2_SIG_SOUR2_REG (0x1 << 4) +#define RT5682_HP_LC2_SIG_SOUR2_DC_CAL (0x0 << 4) +#define RT5682_HP_LC2_SIG_SOUR1_MASK (0x7) +#define RT5682_HP_LC2_SIG_SOUR1_1BIT (0x7) +#define RT5682_HP_LC2_SIG_SOUR1_LEGA (0x2) + /* SAR ADC Inline Command Control 1 (0x0210) */ #define RT5682_SAR_BUTT_DET_MASK (0x1 << 15) #define RT5682_SAR_BUTT_DET_EN (0x1 << 15) @@ -1408,6 +1428,7 @@ enum { struct rt5682_priv { struct snd_soc_component *component; + struct device *i2c_dev; struct rt5682_platform_data pdata; struct regmap *regmap; struct regmap *sdw_regmap; @@ -1462,6 +1483,8 @@ void rt5682_calibrate(struct rt5682_priv *rt5682); void rt5682_reset(struct rt5682_priv *rt5682); int rt5682_parse_dt(struct rt5682_priv *rt5682, struct device *dev); +int rt5682_register_dai_clks(struct rt5682_priv *rt5682); + #define RT5682_REG_NUM 318 extern const struct reg_default rt5682_reg[RT5682_REG_NUM]; diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c new file mode 100644 index 000000000000..470957fcad6b --- /dev/null +++ b/sound/soc/codecs/rt5682s.c @@ -0,0 +1,3197 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// rt5682s.c -- RT5682I-VS ALSA SoC audio component driver +// +// Copyright 2021 Realtek Semiconductor Corp. +// Author: Derek Fang <derek.fang@realtek.com> +// + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/acpi.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/mutex.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/rt5682s.h> + +#include "rt5682s.h" + +#define DEVICE_ID 0x6749 + +static const struct rt5682s_platform_data i2s_default_platform_data = { + .dmic1_data_pin = RT5682S_DMIC1_DATA_GPIO2, + .dmic1_clk_pin = RT5682S_DMIC1_CLK_GPIO3, + .jd_src = RT5682S_JD1, + .dai_clk_names[RT5682S_DAI_WCLK_IDX] = "rt5682-dai-wclk", + .dai_clk_names[RT5682S_DAI_BCLK_IDX] = "rt5682-dai-bclk", +}; + +static const char *rt5682s_supply_names[RT5682S_NUM_SUPPLIES] = { + "AVDD", + "MICVDD", +}; + +static const struct reg_sequence patch_list[] = { + {RT5682S_I2C_CTRL, 0x0007}, + {RT5682S_DIG_IN_CTRL_1, 0x0000}, + {RT5682S_CHOP_DAC_2, 0x2020}, + {RT5682S_VREF_REC_OP_FB_CAP_CTRL_2, 0x0101}, + {RT5682S_VREF_REC_OP_FB_CAP_CTRL_1, 0x80c0}, + {RT5682S_HP_CALIB_CTRL_9, 0x0002}, + {RT5682S_DEPOP_1, 0x0000}, + {RT5682S_HP_CHARGE_PUMP_2, 0x3c15}, + {RT5682S_DAC1_DIG_VOL, 0xfefe}, + {RT5682S_SAR_IL_CMD_2, 0xac00}, + {RT5682S_SAR_IL_CMD_3, 0x024c}, + {RT5682S_CBJ_CTRL_6, 0x0804}, +}; + +static void rt5682s_apply_patch_list(struct rt5682s_priv *rt5682s, + struct device *dev) +{ + int ret; + + ret = regmap_multi_reg_write(rt5682s->regmap, patch_list, ARRAY_SIZE(patch_list)); + if (ret) + dev_warn(dev, "Failed to apply regmap patch: %d\n", ret); +} + +static const struct reg_default rt5682s_reg[] = { + {0x0002, 0x8080}, + {0x0003, 0x0001}, + {0x0005, 0x0000}, + {0x0006, 0x0000}, + {0x0008, 0x8007}, + {0x000b, 0x0000}, + {0x000f, 0x4000}, + {0x0010, 0x4040}, + {0x0011, 0x0000}, + {0x0012, 0x0000}, + {0x0013, 0x1200}, + {0x0014, 0x200a}, + {0x0015, 0x0404}, + {0x0016, 0x0404}, + {0x0017, 0x05a4}, + {0x0019, 0xffff}, + {0x001c, 0x2f2f}, + {0x001f, 0x0000}, + {0x0022, 0x5757}, + {0x0023, 0x0039}, + {0x0024, 0x000b}, + {0x0026, 0xc0c4}, + {0x0029, 0x8080}, + {0x002a, 0xa0a0}, + {0x002b, 0x0300}, + {0x0030, 0x0000}, + {0x003c, 0x08c0}, + {0x0044, 0x1818}, + {0x004b, 0x00c0}, + {0x004c, 0x0000}, + {0x004d, 0x0000}, + {0x0061, 0x00c0}, + {0x0062, 0x008a}, + {0x0063, 0x0800}, + {0x0064, 0x0000}, + {0x0065, 0x0000}, + {0x0066, 0x0030}, + {0x0067, 0x000c}, + {0x0068, 0x0000}, + {0x0069, 0x0000}, + {0x006a, 0x0000}, + {0x006b, 0x0000}, + {0x006c, 0x0000}, + {0x006d, 0x2200}, + {0x006e, 0x0810}, + {0x006f, 0xe4de}, + {0x0070, 0x3320}, + {0x0071, 0x0000}, + {0x0073, 0x0000}, + {0x0074, 0x0000}, + {0x0075, 0x0002}, + {0x0076, 0x0001}, + {0x0079, 0x0000}, + {0x007a, 0x0000}, + {0x007b, 0x0000}, + {0x007c, 0x0100}, + {0x007e, 0x0000}, + {0x007f, 0x0000}, + {0x0080, 0x0000}, + {0x0083, 0x0000}, + {0x0084, 0x0000}, + {0x0085, 0x0000}, + {0x0086, 0x0005}, + {0x0087, 0x0000}, + {0x0088, 0x0000}, + {0x008c, 0x0003}, + {0x008e, 0x0060}, + {0x008f, 0x4da1}, + {0x0091, 0x1c15}, + {0x0092, 0x0425}, + {0x0093, 0x0000}, + {0x0094, 0x0080}, + {0x0095, 0x008f}, + {0x0096, 0x0000}, + {0x0097, 0x0000}, + {0x0098, 0x0000}, + {0x0099, 0x0000}, + {0x009a, 0x0000}, + {0x009b, 0x0000}, + {0x009c, 0x0000}, + {0x009d, 0x0000}, + {0x009e, 0x0000}, + {0x009f, 0x0009}, + {0x00a0, 0x0000}, + {0x00a3, 0x0002}, + {0x00a4, 0x0001}, + {0x00b6, 0x0000}, + {0x00b7, 0x0000}, + {0x00b8, 0x0000}, + {0x00b9, 0x0002}, + {0x00be, 0x0000}, + {0x00c0, 0x0160}, + {0x00c1, 0x82a0}, + {0x00c2, 0x0000}, + {0x00d0, 0x0000}, + {0x00d2, 0x3300}, + {0x00d3, 0x2200}, + {0x00d4, 0x0000}, + {0x00d9, 0x0000}, + {0x00da, 0x0000}, + {0x00db, 0x0000}, + {0x00dc, 0x00c0}, + {0x00dd, 0x2220}, + {0x00de, 0x3131}, + {0x00df, 0x3131}, + {0x00e0, 0x3131}, + {0x00e2, 0x0000}, + {0x00e3, 0x4000}, + {0x00e4, 0x0aa0}, + {0x00e5, 0x3131}, + {0x00e6, 0x3131}, + {0x00e7, 0x3131}, + {0x00e8, 0x3131}, + {0x00ea, 0xb320}, + {0x00eb, 0x0000}, + {0x00f0, 0x0000}, + {0x00f6, 0x0000}, + {0x00fa, 0x0000}, + {0x00fb, 0x0000}, + {0x00fc, 0x0000}, + {0x00fd, 0x0000}, + {0x00fe, 0x10ec}, + {0x00ff, 0x6749}, + {0x0100, 0xa000}, + {0x010b, 0x0066}, + {0x010c, 0x6666}, + {0x010d, 0x2202}, + {0x010e, 0x6666}, + {0x010f, 0xa800}, + {0x0110, 0x0006}, + {0x0111, 0x0460}, + {0x0112, 0x2000}, + {0x0113, 0x0200}, + {0x0117, 0x8000}, + {0x0118, 0x0303}, + {0x0125, 0x0020}, + {0x0132, 0x5026}, + {0x0136, 0x8000}, + {0x0139, 0x0005}, + {0x013a, 0x3030}, + {0x013b, 0xa000}, + {0x013c, 0x4110}, + {0x013f, 0x0000}, + {0x0145, 0x0022}, + {0x0146, 0x0000}, + {0x0147, 0x0000}, + {0x0148, 0x0000}, + {0x0156, 0x0022}, + {0x0157, 0x0303}, + {0x0158, 0x2222}, + {0x0159, 0x0000}, + {0x0160, 0x4ec0}, + {0x0161, 0x0080}, + {0x0162, 0x0200}, + {0x0163, 0x0800}, + {0x0164, 0x0000}, + {0x0165, 0x0000}, + {0x0166, 0x0000}, + {0x0167, 0x000f}, + {0x0168, 0x000f}, + {0x0169, 0x0001}, + {0x0190, 0x4131}, + {0x0194, 0x0000}, + {0x0195, 0x0000}, + {0x0197, 0x0022}, + {0x0198, 0x0000}, + {0x0199, 0x0000}, + {0x01ac, 0x0000}, + {0x01ad, 0x0000}, + {0x01ae, 0x0000}, + {0x01af, 0x2000}, + {0x01b0, 0x0000}, + {0x01b1, 0x0000}, + {0x01b2, 0x0000}, + {0x01b3, 0x0017}, + {0x01b4, 0x004b}, + {0x01b5, 0x0000}, + {0x01b6, 0x03e8}, + {0x01b7, 0x0000}, + {0x01b8, 0x0000}, + {0x01b9, 0x0400}, + {0x01ba, 0xb5b6}, + {0x01bb, 0x9124}, + {0x01bc, 0x4924}, + {0x01bd, 0x0009}, + {0x01be, 0x0018}, + {0x01bf, 0x002a}, + {0x01c0, 0x004c}, + {0x01c1, 0x0097}, + {0x01c2, 0x01c3}, + {0x01c3, 0x03e9}, + {0x01c4, 0x1389}, + {0x01c5, 0xc351}, + {0x01c6, 0x02a0}, + {0x01c7, 0x0b0f}, + {0x01c8, 0x402f}, + {0x01c9, 0x0702}, + {0x01ca, 0x0000}, + {0x01cb, 0x0000}, + {0x01cc, 0x5757}, + {0x01cd, 0x5757}, + {0x01ce, 0x5757}, + {0x01cf, 0x5757}, + {0x01d0, 0x5757}, + {0x01d1, 0x5757}, + {0x01d2, 0x5757}, + {0x01d3, 0x5757}, + {0x01d4, 0x5757}, + {0x01d5, 0x5757}, + {0x01d6, 0x0000}, + {0x01d7, 0x0000}, + {0x01d8, 0x0162}, + {0x01d9, 0x0007}, + {0x01da, 0x0000}, + {0x01db, 0x0004}, + {0x01dc, 0x0000}, + {0x01de, 0x7c00}, + {0x01df, 0x0020}, + {0x01e0, 0x04c1}, + {0x01e1, 0x0000}, + {0x01e2, 0x0000}, + {0x01e3, 0x0000}, + {0x01e4, 0x0000}, + {0x01e5, 0x0000}, + {0x01e6, 0x0001}, + {0x01e7, 0x0000}, + {0x01e8, 0x0000}, + {0x01eb, 0x0000}, + {0x01ec, 0x0000}, + {0x01ed, 0x0000}, + {0x01ee, 0x0000}, + {0x01ef, 0x0000}, + {0x01f0, 0x0000}, + {0x01f1, 0x0000}, + {0x01f2, 0x0000}, + {0x01f3, 0x0000}, + {0x01f4, 0x0000}, + {0x0210, 0x6297}, + {0x0211, 0xa004}, + {0x0212, 0x0365}, + {0x0213, 0xf7ff}, + {0x0214, 0xf24c}, + {0x0215, 0x0102}, + {0x0216, 0x00a3}, + {0x0217, 0x0048}, + {0x0218, 0xa2c0}, + {0x0219, 0x0400}, + {0x021a, 0x00c8}, + {0x021b, 0x00c0}, + {0x021c, 0x0000}, + {0x021d, 0x024c}, + {0x02fa, 0x0000}, + {0x02fb, 0x0000}, + {0x02fc, 0x0000}, + {0x03fe, 0x0000}, + {0x03ff, 0x0000}, + {0x0500, 0x0000}, + {0x0600, 0x0000}, + {0x0610, 0x6666}, + {0x0611, 0xa9aa}, + {0x0620, 0x6666}, + {0x0621, 0xa9aa}, + {0x0630, 0x6666}, + {0x0631, 0xa9aa}, + {0x0640, 0x6666}, + {0x0641, 0xa9aa}, + {0x07fa, 0x0000}, + {0x08fa, 0x0000}, + {0x08fb, 0x0000}, + {0x0d00, 0x0000}, + {0x1100, 0x0000}, + {0x1101, 0x0000}, + {0x1102, 0x0000}, + {0x1103, 0x0000}, + {0x1104, 0x0000}, + {0x1105, 0x0000}, + {0x1106, 0x0000}, + {0x1107, 0x0000}, + {0x1108, 0x0000}, + {0x1109, 0x0000}, + {0x110a, 0x0000}, + {0x110b, 0x0000}, + {0x110c, 0x0000}, + {0x1111, 0x0000}, + {0x1112, 0x0000}, + {0x1113, 0x0000}, + {0x1114, 0x0000}, + {0x1115, 0x0000}, + {0x1116, 0x0000}, + {0x1117, 0x0000}, + {0x1118, 0x0000}, + {0x1119, 0x0000}, + {0x111a, 0x0000}, + {0x111b, 0x0000}, + {0x111c, 0x0000}, + {0x1401, 0x0404}, + {0x1402, 0x0007}, + {0x1403, 0x0365}, + {0x1404, 0x0210}, + {0x1405, 0x0365}, + {0x1406, 0x0210}, + {0x1407, 0x0000}, + {0x1408, 0x0000}, + {0x1409, 0x0000}, + {0x140a, 0x0000}, + {0x140b, 0x0000}, + {0x140c, 0x0000}, + {0x140d, 0x0000}, + {0x140e, 0x0000}, + {0x140f, 0x0000}, + {0x1410, 0x0000}, + {0x1411, 0x0000}, + {0x1801, 0x0004}, + {0x1802, 0x0000}, + {0x1803, 0x0000}, + {0x1804, 0x0000}, + {0x1805, 0x00ff}, + {0x2c00, 0x0000}, + {0x3400, 0x0200}, + {0x3404, 0x0000}, + {0x3405, 0x0000}, + {0x3406, 0x0000}, + {0x3407, 0x0000}, + {0x3408, 0x0000}, + {0x3409, 0x0000}, + {0x340a, 0x0000}, + {0x340b, 0x0000}, + {0x340c, 0x0000}, + {0x340d, 0x0000}, + {0x340e, 0x0000}, + {0x340f, 0x0000}, + {0x3410, 0x0000}, + {0x3411, 0x0000}, + {0x3412, 0x0000}, + {0x3413, 0x0000}, + {0x3414, 0x0000}, + {0x3415, 0x0000}, + {0x3424, 0x0000}, + {0x3425, 0x0000}, + {0x3426, 0x0000}, + {0x3427, 0x0000}, + {0x3428, 0x0000}, + {0x3429, 0x0000}, + {0x342a, 0x0000}, + {0x342b, 0x0000}, + {0x342c, 0x0000}, + {0x342d, 0x0000}, + {0x342e, 0x0000}, + {0x342f, 0x0000}, + {0x3430, 0x0000}, + {0x3431, 0x0000}, + {0x3432, 0x0000}, + {0x3433, 0x0000}, + {0x3434, 0x0000}, + {0x3435, 0x0000}, + {0x3440, 0x6319}, + {0x3441, 0x3771}, + {0x3500, 0x0002}, + {0x3501, 0x5728}, + {0x3b00, 0x3010}, + {0x3b01, 0x3300}, + {0x3b02, 0x2200}, + {0x3b03, 0x0100}, +}; + +static bool rt5682s_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5682S_RESET: + case RT5682S_CBJ_CTRL_2: + case RT5682S_I2S1_F_DIV_CTRL_2: + case RT5682S_I2S2_F_DIV_CTRL_2: + case RT5682S_INT_ST_1: + case RT5682S_GPIO_ST: + case RT5682S_IL_CMD_1: + case RT5682S_4BTN_IL_CMD_1: + case RT5682S_AJD1_CTRL: + case RT5682S_VERSION_ID...RT5682S_DEVICE_ID: + case RT5682S_STO_NG2_CTRL_1: + case RT5682S_STO_NG2_CTRL_5...RT5682S_STO_NG2_CTRL_7: + case RT5682S_STO1_DAC_SIL_DET: + case RT5682S_HP_IMP_SENS_CTRL_1...RT5682S_HP_IMP_SENS_CTRL_4: + case RT5682S_HP_IMP_SENS_CTRL_13: + case RT5682S_HP_IMP_SENS_CTRL_14: + case RT5682S_HP_IMP_SENS_CTRL_43...RT5682S_HP_IMP_SENS_CTRL_46: + case RT5682S_HP_CALIB_CTRL_1: + case RT5682S_HP_CALIB_CTRL_10: + case RT5682S_HP_CALIB_ST_1...RT5682S_HP_CALIB_ST_11: + case RT5682S_SAR_IL_CMD_2...RT5682S_SAR_IL_CMD_5: + case RT5682S_SAR_IL_CMD_10: + case RT5682S_SAR_IL_CMD_11: + case RT5682S_VERSION_ID_HIDE: + case RT5682S_VERSION_ID_CUS: + case RT5682S_I2C_TRANS_CTRL: + case RT5682S_DMIC_FLOAT_DET: + case RT5682S_HA_CMP_OP_1: + case RT5682S_NEW_CBJ_DET_CTL_10...RT5682S_NEW_CBJ_DET_CTL_16: + case RT5682S_CLK_SW_TEST_1: + case RT5682S_CLK_SW_TEST_2: + case RT5682S_EFUSE_READ_1...RT5682S_EFUSE_READ_18: + case RT5682S_PILOT_DIG_CTL_1: + return true; + default: + return false; + } +} + +static bool rt5682s_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5682S_RESET: + case RT5682S_VERSION_ID: + case RT5682S_VENDOR_ID: + case RT5682S_DEVICE_ID: + case RT5682S_HP_CTRL_1: + case RT5682S_HP_CTRL_2: + case RT5682S_HPL_GAIN: + case RT5682S_HPR_GAIN: + case RT5682S_I2C_CTRL: + case RT5682S_CBJ_BST_CTRL: + case RT5682S_CBJ_DET_CTRL: + case RT5682S_CBJ_CTRL_1...RT5682S_CBJ_CTRL_8: + case RT5682S_DAC1_DIG_VOL: + case RT5682S_STO1_ADC_DIG_VOL: + case RT5682S_STO1_ADC_BOOST: + case RT5682S_HP_IMP_GAIN_1: + case RT5682S_HP_IMP_GAIN_2: + case RT5682S_SIDETONE_CTRL: + case RT5682S_STO1_ADC_MIXER: + case RT5682S_AD_DA_MIXER: + case RT5682S_STO1_DAC_MIXER: + case RT5682S_A_DAC1_MUX: + case RT5682S_DIG_INF2_DATA: + case RT5682S_REC_MIXER: + case RT5682S_CAL_REC: + case RT5682S_HP_ANA_OST_CTRL_1...RT5682S_HP_ANA_OST_CTRL_3: + case RT5682S_PWR_DIG_1...RT5682S_PWR_MIXER: + case RT5682S_MB_CTRL: + case RT5682S_CLK_GATE_TCON_1...RT5682S_CLK_GATE_TCON_3: + case RT5682S_CLK_DET...RT5682S_LPF_AD_DMIC: + case RT5682S_I2S1_SDP: + case RT5682S_I2S2_SDP: + case RT5682S_ADDA_CLK_1: + case RT5682S_ADDA_CLK_2: + case RT5682S_I2S1_F_DIV_CTRL_1: + case RT5682S_I2S1_F_DIV_CTRL_2: + case RT5682S_TDM_CTRL: + case RT5682S_TDM_ADDA_CTRL_1: + case RT5682S_TDM_ADDA_CTRL_2: + case RT5682S_DATA_SEL_CTRL_1: + case RT5682S_TDM_TCON_CTRL_1: + case RT5682S_TDM_TCON_CTRL_2: + case RT5682S_GLB_CLK: + case RT5682S_PLL_TRACK_1...RT5682S_PLL_TRACK_6: + case RT5682S_PLL_TRACK_11: + case RT5682S_DEPOP_1: + case RT5682S_HP_CHARGE_PUMP_1: + case RT5682S_HP_CHARGE_PUMP_2: + case RT5682S_HP_CHARGE_PUMP_3: + case RT5682S_MICBIAS_1...RT5682S_MICBIAS_3: + case RT5682S_PLL_TRACK_12...RT5682S_PLL_CTRL_7: + case RT5682S_RC_CLK_CTRL: + case RT5682S_I2S2_M_CLK_CTRL_1: + case RT5682S_I2S2_F_DIV_CTRL_1: + case RT5682S_I2S2_F_DIV_CTRL_2: + case RT5682S_IRQ_CTRL_1...RT5682S_IRQ_CTRL_4: + case RT5682S_INT_ST_1: + case RT5682S_GPIO_CTRL_1: + case RT5682S_GPIO_CTRL_2: + case RT5682S_GPIO_ST: + case RT5682S_HP_AMP_DET_CTRL_1: + case RT5682S_MID_HP_AMP_DET: + case RT5682S_LOW_HP_AMP_DET: + case RT5682S_DELAY_BUF_CTRL: + case RT5682S_SV_ZCD_1: + case RT5682S_SV_ZCD_2: + case RT5682S_IL_CMD_1...RT5682S_IL_CMD_6: + case RT5682S_4BTN_IL_CMD_1...RT5682S_4BTN_IL_CMD_7: + case RT5682S_ADC_STO1_HP_CTRL_1: + case RT5682S_ADC_STO1_HP_CTRL_2: + case RT5682S_AJD1_CTRL: + case RT5682S_JD_CTRL_1: + case RT5682S_DUMMY_1...RT5682S_DUMMY_3: + case RT5682S_DAC_ADC_DIG_VOL1: + case RT5682S_BIAS_CUR_CTRL_2...RT5682S_BIAS_CUR_CTRL_10: + case RT5682S_VREF_REC_OP_FB_CAP_CTRL_1: + case RT5682S_VREF_REC_OP_FB_CAP_CTRL_2: + case RT5682S_CHARGE_PUMP_1: + case RT5682S_DIG_IN_CTRL_1: + case RT5682S_PAD_DRIVING_CTRL: + case RT5682S_CHOP_DAC_1: + case RT5682S_CHOP_DAC_2: + case RT5682S_CHOP_ADC: + case RT5682S_CALIB_ADC_CTRL: + case RT5682S_VOL_TEST: + case RT5682S_SPKVDD_DET_ST: + case RT5682S_TEST_MODE_CTRL_1...RT5682S_TEST_MODE_CTRL_4: + case RT5682S_PLL_INTERNAL_1...RT5682S_PLL_INTERNAL_4: + case RT5682S_STO_NG2_CTRL_1...RT5682S_STO_NG2_CTRL_10: + case RT5682S_STO1_DAC_SIL_DET: + case RT5682S_SIL_PSV_CTRL1: + case RT5682S_SIL_PSV_CTRL2: + case RT5682S_SIL_PSV_CTRL3: + case RT5682S_SIL_PSV_CTRL4: + case RT5682S_SIL_PSV_CTRL5: + case RT5682S_HP_IMP_SENS_CTRL_1...RT5682S_HP_IMP_SENS_CTRL_46: + case RT5682S_HP_LOGIC_CTRL_1...RT5682S_HP_LOGIC_CTRL_3: + case RT5682S_HP_CALIB_CTRL_1...RT5682S_HP_CALIB_CTRL_11: + case RT5682S_HP_CALIB_ST_1...RT5682S_HP_CALIB_ST_11: + case RT5682S_SAR_IL_CMD_1...RT5682S_SAR_IL_CMD_14: + case RT5682S_DUMMY_4...RT5682S_DUMMY_6: + case RT5682S_VERSION_ID_HIDE: + case RT5682S_VERSION_ID_CUS: + case RT5682S_SCAN_CTL: + case RT5682S_HP_AMP_DET: + case RT5682S_BIAS_CUR_CTRL_11: + case RT5682S_BIAS_CUR_CTRL_12: + case RT5682S_BIAS_CUR_CTRL_13: + case RT5682S_BIAS_CUR_CTRL_14: + case RT5682S_BIAS_CUR_CTRL_15: + case RT5682S_BIAS_CUR_CTRL_16: + case RT5682S_BIAS_CUR_CTRL_17: + case RT5682S_BIAS_CUR_CTRL_18: + case RT5682S_I2C_TRANS_CTRL: + case RT5682S_DUMMY_7: + case RT5682S_DUMMY_8: + case RT5682S_DMIC_FLOAT_DET: + case RT5682S_HA_CMP_OP_1...RT5682S_HA_CMP_OP_13: + case RT5682S_HA_CMP_OP_14...RT5682S_HA_CMP_OP_25: + case RT5682S_NEW_CBJ_DET_CTL_1...RT5682S_NEW_CBJ_DET_CTL_16: + case RT5682S_DA_FILTER_1...RT5682S_DA_FILTER_5: + case RT5682S_CLK_SW_TEST_1: + case RT5682S_CLK_SW_TEST_2: + case RT5682S_CLK_SW_TEST_3...RT5682S_CLK_SW_TEST_14: + case RT5682S_EFUSE_MANU_WRITE_1...RT5682S_EFUSE_MANU_WRITE_6: + case RT5682S_EFUSE_READ_1...RT5682S_EFUSE_READ_18: + case RT5682S_EFUSE_TIMING_CTL_1: + case RT5682S_EFUSE_TIMING_CTL_2: + case RT5682S_PILOT_DIG_CTL_1: + case RT5682S_PILOT_DIG_CTL_2: + case RT5682S_HP_AMP_DET_CTL_1...RT5682S_HP_AMP_DET_CTL_4: + return true; + default: + return false; + } +} + +static void rt5682s_reset(struct rt5682s_priv *rt5682s) +{ + regmap_write(rt5682s->regmap, RT5682S_RESET, 0); +} + +static int rt5682s_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read(component, RT5682S_4BTN_IL_CMD_1); + btn_type = val & 0xfff0; + snd_soc_component_write(component, RT5682S_4BTN_IL_CMD_1, val); + dev_dbg(component->dev, "%s btn_type=%x\n", __func__, btn_type); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_2, + RT5682S_SAR_ADC_PSV_MASK, RT5682S_SAR_ADC_PSV_ENTRY); + + return btn_type; +} + +enum { + SAR_PWR_OFF, + SAR_PWR_NORMAL, + SAR_PWR_SAVING, +}; + +static void rt5682s_sar_power_mode(struct snd_soc_component *component, + int mode, int jd_step) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + mutex_lock(&rt5682s->sar_mutex); + + switch (mode) { + case SAR_PWR_SAVING: + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_3, + RT5682S_CBJ_IN_BUF_MASK, RT5682S_CBJ_IN_BUF_DIS); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_1, + RT5682S_MB1_PATH_MASK | RT5682S_MB2_PATH_MASK, + RT5682S_CTRL_MB1_REG | RT5682S_CTRL_MB2_REG); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_1, + RT5682S_SAR_BUTDET_MASK | RT5682S_SAR_BUTDET_POW_MASK | + RT5682S_SAR_SEL_MB1_2_CTL_MASK, RT5682S_SAR_BUTDET_DIS | + RT5682S_SAR_BUTDET_POW_SAV | RT5682S_SAR_SEL_MB1_2_MANU); + usleep_range(5000, 5500); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_1, + RT5682S_SAR_BUTDET_MASK, RT5682S_SAR_BUTDET_EN); + usleep_range(5000, 5500); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_2, + RT5682S_SAR_ADC_PSV_MASK, RT5682S_SAR_ADC_PSV_ENTRY); + break; + case SAR_PWR_NORMAL: + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_3, + RT5682S_CBJ_IN_BUF_MASK, RT5682S_CBJ_IN_BUF_EN); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_1, + RT5682S_MB1_PATH_MASK | RT5682S_MB2_PATH_MASK, + RT5682S_CTRL_MB1_FSM | RT5682S_CTRL_MB2_FSM); + if (!jd_step) { + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_1, + RT5682S_SAR_SEL_MB1_2_CTL_MASK, RT5682S_SAR_SEL_MB1_2_AUTO); + usleep_range(5000, 5500); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_1, + RT5682S_SAR_BUTDET_MASK | RT5682S_SAR_BUTDET_POW_MASK, + RT5682S_SAR_BUTDET_EN | RT5682S_SAR_BUTDET_POW_NORM); + } + break; + case SAR_PWR_OFF: + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_1, + RT5682S_SAR_BUTDET_MASK | RT5682S_SAR_BUTDET_POW_MASK | + RT5682S_SAR_SEL_MB1_2_CTL_MASK, RT5682S_SAR_BUTDET_DIS | + RT5682S_SAR_BUTDET_POW_SAV | RT5682S_SAR_SEL_MB1_2_MANU); + break; + default: + dev_err(component->dev, "Invalid SAR Power mode: %d\n", mode); + break; + } + + mutex_unlock(&rt5682s->sar_mutex); +} + +static void rt5682s_enable_push_button_irq(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_13, + RT5682S_SAR_SOUR_MASK, RT5682S_SAR_SOUR_BTN); + snd_soc_component_write(component, RT5682S_IL_CMD_1, 0x0040); + snd_soc_component_update_bits(component, RT5682S_4BTN_IL_CMD_2, + RT5682S_4BTN_IL_MASK | RT5682S_4BTN_IL_RST_MASK, + RT5682S_4BTN_IL_EN | RT5682S_4BTN_IL_NOR); + snd_soc_component_update_bits(component, RT5682S_IRQ_CTRL_3, + RT5682S_IL_IRQ_MASK, RT5682S_IL_IRQ_EN); +} + +static void rt5682s_disable_push_button_irq(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, RT5682S_IRQ_CTRL_3, + RT5682S_IL_IRQ_MASK, RT5682S_IL_IRQ_DIS); + snd_soc_component_update_bits(component, RT5682S_4BTN_IL_CMD_2, + RT5682S_4BTN_IL_MASK, RT5682S_4BTN_IL_DIS); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_13, + RT5682S_SAR_SOUR_MASK, RT5682S_SAR_SOUR_TYPE); +} + +/** + * rt5682s_headset_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ +static int rt5682s_headset_detect(struct snd_soc_component *component, int jack_insert) +{ + unsigned int val, count; + int jack_type = 0; + + if (jack_insert) { + rt5682s_disable_push_button_irq(component); + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF1 | RT5682S_PWR_VREF2 | RT5682S_PWR_MB, + RT5682S_PWR_VREF1 | RT5682S_PWR_VREF2 | RT5682S_PWR_MB); + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_FV1 | RT5682S_PWR_FV2, 0); + usleep_range(15000, 20000); + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_FV1 | RT5682S_PWR_FV2, + RT5682S_PWR_FV1 | RT5682S_PWR_FV2); + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_3, + RT5682S_PWR_CBJ, RT5682S_PWR_CBJ); + snd_soc_component_write(component, RT5682S_SAR_IL_CMD_3, 0x0365); + snd_soc_component_update_bits(component, RT5682S_HP_CHARGE_PUMP_2, + RT5682S_OSW_L_MASK | RT5682S_OSW_R_MASK, + RT5682S_OSW_L_DIS | RT5682S_OSW_R_DIS); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_13, + RT5682S_SAR_SOUR_MASK, RT5682S_SAR_SOUR_TYPE); + rt5682s_sar_power_mode(component, SAR_PWR_NORMAL, 1); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_1, + RT5682S_TRIG_JD_MASK, RT5682S_TRIG_JD_LOW); + usleep_range(45000, 50000); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_1, + RT5682S_TRIG_JD_MASK, RT5682S_TRIG_JD_HIGH); + + count = 0; + do { + usleep_range(10000, 15000); + val = snd_soc_component_read(component, RT5682S_CBJ_CTRL_2) + & RT5682S_JACK_TYPE_MASK; + count++; + } while (val == 0 && count < 50); + + dev_dbg(component->dev, "%s, val=%d, count=%d\n", __func__, val, count); + + switch (val) { + case 0x1: + case 0x2: + jack_type = SND_JACK_HEADSET; + snd_soc_component_write(component, RT5682S_SAR_IL_CMD_3, 0x024c); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_1, + RT5682S_FAST_OFF_MASK, RT5682S_FAST_OFF_EN); + snd_soc_component_update_bits(component, RT5682S_SAR_IL_CMD_1, + RT5682S_SAR_SEL_MB1_2_MASK, val << RT5682S_SAR_SEL_MB1_2_SFT); + if (!snd_soc_dapm_get_pin_status(&component->dapm, "SAR")) + rt5682s_sar_power_mode(component, SAR_PWR_SAVING, 1); + rt5682s_enable_push_button_irq(component); + break; + default: + jack_type = SND_JACK_HEADPHONE; + break; + } + snd_soc_component_update_bits(component, RT5682S_HP_CHARGE_PUMP_2, + RT5682S_OSW_L_MASK | RT5682S_OSW_R_MASK, + RT5682S_OSW_L_EN | RT5682S_OSW_R_EN); + usleep_range(35000, 40000); + } else { + rt5682s_sar_power_mode(component, SAR_PWR_OFF, 1); + rt5682s_disable_push_button_irq(component); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_1, + RT5682S_TRIG_JD_MASK, RT5682S_TRIG_JD_LOW); + + if (!snd_soc_dapm_get_pin_status(&component->dapm, "MICBIAS")) + snd_soc_component_update_bits(component, + RT5682S_PWR_ANLG_1, RT5682S_PWR_MB, 0); + if (!snd_soc_dapm_get_pin_status(&component->dapm, "Vref2")) + snd_soc_component_update_bits(component, + RT5682S_PWR_ANLG_1, RT5682S_PWR_VREF2, 0); + + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_3, + RT5682S_PWR_CBJ, 0); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_1, + RT5682S_FAST_OFF_MASK, RT5682S_FAST_OFF_DIS); + snd_soc_component_update_bits(component, RT5682S_CBJ_CTRL_3, + RT5682S_CBJ_IN_BUF_MASK, RT5682S_CBJ_IN_BUF_DIS); + jack_type = 0; + } + + dev_dbg(component->dev, "jack_type = %d\n", jack_type); + + return jack_type; +} + +static void rt5682s_jack_detect_handler(struct work_struct *work) +{ + struct rt5682s_priv *rt5682s = + container_of(work, struct rt5682s_priv, jack_detect_work.work); + int val, btn_type; + + while (!rt5682s->component) + usleep_range(10000, 15000); + + while (!rt5682s->component->card->instantiated) + usleep_range(10000, 15000); + + mutex_lock(&rt5682s->jdet_mutex); + mutex_lock(&rt5682s->calibrate_mutex); + + val = snd_soc_component_read(rt5682s->component, RT5682S_AJD1_CTRL) + & RT5682S_JDH_RS_MASK; + if (!val) { + /* jack in */ + if (rt5682s->jack_type == 0) { + /* jack was out, report jack type */ + rt5682s->jack_type = rt5682s_headset_detect(rt5682s->component, 1); + rt5682s->irq_work_delay_time = 0; + } else if ((rt5682s->jack_type & SND_JACK_HEADSET) == SND_JACK_HEADSET) { + /* jack is already in, report button event */ + rt5682s->jack_type = SND_JACK_HEADSET; + btn_type = rt5682s_button_detect(rt5682s->component); + /** + * rt5682s can report three kinds of button behavior, + * one click, double click and hold. However, + * currently we will report button pressed/released + * event. So all the three button behaviors are + * treated as button pressed. + */ + switch (btn_type) { + case 0x8000: + case 0x4000: + case 0x2000: + rt5682s->jack_type |= SND_JACK_BTN_0; + break; + case 0x1000: + case 0x0800: + case 0x0400: + rt5682s->jack_type |= SND_JACK_BTN_1; + break; + case 0x0200: + case 0x0100: + case 0x0080: + rt5682s->jack_type |= SND_JACK_BTN_2; + break; + case 0x0040: + case 0x0020: + case 0x0010: + rt5682s->jack_type |= SND_JACK_BTN_3; + break; + case 0x0000: /* unpressed */ + break; + default: + dev_err(rt5682s->component->dev, + "Unexpected button code 0x%04x\n", btn_type); + break; + } + } + } else { + /* jack out */ + rt5682s->jack_type = rt5682s_headset_detect(rt5682s->component, 0); + rt5682s->irq_work_delay_time = 50; + } + + snd_soc_jack_report(rt5682s->hs_jack, rt5682s->jack_type, + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (rt5682s->jack_type & (SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3)) + schedule_delayed_work(&rt5682s->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5682s->jd_check_work); + + mutex_unlock(&rt5682s->calibrate_mutex); + mutex_unlock(&rt5682s->jdet_mutex); +} + +static void rt5682s_jd_check_handler(struct work_struct *work) +{ + struct rt5682s_priv *rt5682s = + container_of(work, struct rt5682s_priv, jd_check_work.work); + + if (snd_soc_component_read(rt5682s->component, RT5682S_AJD1_CTRL) + & RT5682S_JDH_RS_MASK) { + /* jack out */ + rt5682s->jack_type = rt5682s_headset_detect(rt5682s->component, 0); + + snd_soc_jack_report(rt5682s->hs_jack, rt5682s->jack_type, + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + } else { + schedule_delayed_work(&rt5682s->jd_check_work, 500); + } +} + +static irqreturn_t rt5682s_irq(int irq, void *data) +{ + struct rt5682s_priv *rt5682s = data; + + mod_delayed_work(system_power_efficient_wq, &rt5682s->jack_detect_work, + msecs_to_jiffies(rt5682s->irq_work_delay_time)); + + return IRQ_HANDLED; +} + +static int rt5682s_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + int btndet_delay = 16; + + rt5682s->hs_jack = hs_jack; + + if (!hs_jack) { + regmap_update_bits(rt5682s->regmap, RT5682S_IRQ_CTRL_2, + RT5682S_JD1_EN_MASK, RT5682S_JD1_DIS); + regmap_update_bits(rt5682s->regmap, RT5682S_RC_CLK_CTRL, + RT5682S_POW_JDH, 0); + cancel_delayed_work_sync(&rt5682s->jack_detect_work); + + return 0; + } + + switch (rt5682s->pdata.jd_src) { + case RT5682S_JD1: + regmap_update_bits(rt5682s->regmap, RT5682S_CBJ_CTRL_5, + RT5682S_JD_FAST_OFF_SRC_MASK, RT5682S_JD_FAST_OFF_SRC_JDH); + regmap_update_bits(rt5682s->regmap, RT5682S_CBJ_CTRL_2, + RT5682S_EXT_JD_SRC, RT5682S_EXT_JD_SRC_MANUAL); + regmap_update_bits(rt5682s->regmap, RT5682S_CBJ_CTRL_1, + RT5682S_EMB_JD_MASK | RT5682S_DET_TYPE | + RT5682S_POL_FAST_OFF_MASK | RT5682S_MIC_CAP_MASK, + RT5682S_EMB_JD_EN | RT5682S_DET_TYPE | + RT5682S_POL_FAST_OFF_HIGH | RT5682S_MIC_CAP_HS); + regmap_update_bits(rt5682s->regmap, RT5682S_SAR_IL_CMD_1, + RT5682S_SAR_POW_MASK, RT5682S_SAR_POW_EN); + regmap_update_bits(rt5682s->regmap, RT5682S_GPIO_CTRL_1, + RT5682S_GP1_PIN_MASK, RT5682S_GP1_PIN_IRQ); + regmap_update_bits(rt5682s->regmap, RT5682S_PWR_ANLG_3, + RT5682S_PWR_BGLDO, RT5682S_PWR_BGLDO); + regmap_update_bits(rt5682s->regmap, RT5682S_PWR_ANLG_2, + RT5682S_PWR_JD_MASK, RT5682S_PWR_JD_ENABLE); + regmap_update_bits(rt5682s->regmap, RT5682S_RC_CLK_CTRL, + RT5682S_POW_IRQ | RT5682S_POW_JDH, RT5682S_POW_IRQ | RT5682S_POW_JDH); + regmap_update_bits(rt5682s->regmap, RT5682S_IRQ_CTRL_2, + RT5682S_JD1_EN_MASK | RT5682S_JD1_POL_MASK, + RT5682S_JD1_EN | RT5682S_JD1_POL_NOR); + regmap_update_bits(rt5682s->regmap, RT5682S_4BTN_IL_CMD_4, + RT5682S_4BTN_IL_HOLD_WIN_MASK | RT5682S_4BTN_IL_CLICK_WIN_MASK, + (btndet_delay << RT5682S_4BTN_IL_HOLD_WIN_SFT | btndet_delay)); + regmap_update_bits(rt5682s->regmap, RT5682S_4BTN_IL_CMD_5, + RT5682S_4BTN_IL_HOLD_WIN_MASK | RT5682S_4BTN_IL_CLICK_WIN_MASK, + (btndet_delay << RT5682S_4BTN_IL_HOLD_WIN_SFT | btndet_delay)); + regmap_update_bits(rt5682s->regmap, RT5682S_4BTN_IL_CMD_6, + RT5682S_4BTN_IL_HOLD_WIN_MASK | RT5682S_4BTN_IL_CLICK_WIN_MASK, + (btndet_delay << RT5682S_4BTN_IL_HOLD_WIN_SFT | btndet_delay)); + regmap_update_bits(rt5682s->regmap, RT5682S_4BTN_IL_CMD_7, + RT5682S_4BTN_IL_HOLD_WIN_MASK | RT5682S_4BTN_IL_CLICK_WIN_MASK, + (btndet_delay << RT5682S_4BTN_IL_HOLD_WIN_SFT | btndet_delay)); + + mod_delayed_work(system_power_efficient_wq, + &rt5682s->jack_detect_work, msecs_to_jiffies(250)); + break; + + case RT5682S_JD_NULL: + regmap_update_bits(rt5682s->regmap, RT5682S_IRQ_CTRL_2, + RT5682S_JD1_EN_MASK, RT5682S_JD1_DIS); + regmap_update_bits(rt5682s->regmap, RT5682S_RC_CLK_CTRL, + RT5682S_POW_JDH, 0); + break; + + default: + dev_warn(component->dev, "Wrong JD source\n"); + break; + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9562, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); +static const DECLARE_TLV_DB_SCALE(cbj_bst_tlv, -1200, 150, 0); + +static const struct snd_kcontrol_new rt5682s_snd_controls[] = { + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5682S_DAC1_DIG_VOL, + RT5682S_L_VOL_SFT + 1, RT5682S_R_VOL_SFT + 1, 127, 0, dac_vol_tlv), + + /* CBJ Boost Volume */ + SOC_SINGLE_TLV("CBJ Boost Volume", RT5682S_REC_MIXER, + RT5682S_BST_CBJ_SFT, 35, 0, cbj_bst_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("STO1 ADC Capture Switch", RT5682S_STO1_ADC_DIG_VOL, + RT5682S_L_MUTE_SFT, RT5682S_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("STO1 ADC Capture Volume", RT5682S_STO1_ADC_DIG_VOL, + RT5682S_L_VOL_SFT + 1, RT5682S_R_VOL_SFT + 1, 63, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Gain Volume", RT5682S_STO1_ADC_BOOST, + RT5682S_STO1_ADC_L_BST_SFT, RT5682S_STO1_ADC_R_BST_SFT, 3, 0, adc_bst_tlv), +}; + +/** + * rt5682s_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5682S can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the component driver will turn on + * ASRC for these filters if ASRC is selected as their clock source. + */ +int rt5682s_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + switch (clk_src) { + case RT5682S_CLK_SEL_SYS: + case RT5682S_CLK_SEL_I2S1_ASRC: + case RT5682S_CLK_SEL_I2S2_ASRC: + break; + + default: + return -EINVAL; + } + + if (filter_mask & RT5682S_DA_STEREO1_FILTER) { + snd_soc_component_update_bits(component, RT5682S_PLL_TRACK_2, + RT5682S_FILTER_CLK_SEL_MASK, clk_src << RT5682S_FILTER_CLK_SEL_SFT); + } + + if (filter_mask & RT5682S_AD_STEREO1_FILTER) { + snd_soc_component_update_bits(component, RT5682S_PLL_TRACK_3, + RT5682S_FILTER_CLK_SEL_MASK, clk_src << RT5682S_FILTER_CLK_SEL_SFT); + } + + snd_soc_component_update_bits(component, RT5682S_PLL_TRACK_11, + RT5682S_ASRCIN_AUTO_CLKOUT_MASK, RT5682S_ASRCIN_AUTO_CLKOUT_EN); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5682s_sel_asrc_clk_src); + +static int rt5682s_div_sel(struct rt5682s_priv *rt5682s, + int target, const int div[], int size) +{ + int i; + + if (rt5682s->sysclk < target) { + dev_err(rt5682s->component->dev, + "sysclk rate %d is too low\n", rt5682s->sysclk); + return 0; + } + + for (i = 0; i < size - 1; i++) { + dev_dbg(rt5682s->component->dev, "div[%d]=%d\n", i, div[i]); + if (target * div[i] == rt5682s->sysclk) + return i; + if (target * div[i + 1] > rt5682s->sysclk) { + dev_dbg(rt5682s->component->dev, + "can't find div for sysclk %d\n", rt5682s->sysclk); + return i; + } + } + + if (target * div[i] < rt5682s->sysclk) + dev_err(rt5682s->component->dev, + "sysclk rate %d is too high\n", rt5682s->sysclk); + + return size - 1; +} + +static int get_clk_info(int sclk, int rate) +{ + int i; + static const int pd[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48}; + + if (sclk <= 0 || rate <= 0) + return -EINVAL; + + rate = rate << 8; + for (i = 0; i < ARRAY_SIZE(pd); i++) + if (sclk == rate * pd[i]) + return i; + + return -EINVAL; +} + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + * Choose dmic clock between 1MHz and 3MHz. + * It is better for clock to approximate 3MHz. + */ +static int set_dmic_clk(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 rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + int idx, dmic_clk_rate = 3072000; + static const int div[] = {2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128}; + + if (rt5682s->pdata.dmic_clk_rate) + dmic_clk_rate = rt5682s->pdata.dmic_clk_rate; + + idx = rt5682s_div_sel(rt5682s, dmic_clk_rate, div, ARRAY_SIZE(div)); + + snd_soc_component_update_bits(component, RT5682S_DMIC_CTRL_1, + RT5682S_DMIC_CLK_MASK, idx << RT5682S_DMIC_CLK_SFT); + + return 0; +} + +static int set_filter_clk(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 rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + int ref, val, reg, idx; + static const int div_f[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48}; + static const int div_o[] = {1, 2, 4, 6, 8, 12, 16, 24, 32, 48}; + + val = snd_soc_component_read(component, RT5682S_GPIO_CTRL_1) + & RT5682S_GP4_PIN_MASK; + + if (w->shift == RT5682S_PWR_ADC_S1F_BIT && val == RT5682S_GP4_PIN_ADCDAT2) + ref = 256 * rt5682s->lrck[RT5682S_AIF2]; + else + ref = 256 * rt5682s->lrck[RT5682S_AIF1]; + + idx = rt5682s_div_sel(rt5682s, ref, div_f, ARRAY_SIZE(div_f)); + + if (w->shift == RT5682S_PWR_ADC_S1F_BIT) + reg = RT5682S_PLL_TRACK_3; + else + reg = RT5682S_PLL_TRACK_2; + + snd_soc_component_update_bits(component, reg, + RT5682S_FILTER_CLK_DIV_MASK, idx << RT5682S_FILTER_CLK_DIV_SFT); + + /* select over sample rate */ + for (idx = 0; idx < ARRAY_SIZE(div_o); idx++) { + if (rt5682s->sysclk <= 12288000 * div_o[idx]) + break; + } + + snd_soc_component_update_bits(component, RT5682S_ADDA_CLK_1, + RT5682S_ADC_OSR_MASK | RT5682S_DAC_OSR_MASK, + (idx << RT5682S_ADC_OSR_SFT) | (idx << RT5682S_DAC_OSR_SFT)); + + return 0; +} + +static int set_dmic_power(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 rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + unsigned int delay = 50, val; + + if (rt5682s->pdata.dmic_delay) + delay = rt5682s->pdata.dmic_delay; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = (snd_soc_component_read(component, RT5682S_GLB_CLK) + & RT5682S_SCLK_SRC_MASK) >> RT5682S_SCLK_SRC_SFT; + if (val == RT5682S_CLK_SRC_PLL1 || val == RT5682S_CLK_SRC_PLL2) + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_MB, + RT5682S_PWR_VREF2 | RT5682S_PWR_MB); + + /*Add delay to avoid pop noise*/ + msleep(delay); + break; + + case SND_SOC_DAPM_POST_PMD: + if (!rt5682s->jack_type) { + if (!snd_soc_dapm_get_pin_status(w->dapm, "MICBIAS")) + snd_soc_component_update_bits(component, + RT5682S_PWR_ANLG_1, RT5682S_PWR_MB, 0); + if (!snd_soc_dapm_get_pin_status(w->dapm, "Vref2")) + snd_soc_component_update_bits(component, + RT5682S_PWR_ANLG_1, RT5682S_PWR_VREF2, 0); + } + break; + } + + return 0; +} + +static int set_i2s_clk(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 rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + int pre_div, id; + unsigned int reg, mask, sft; + + if (event != SND_SOC_DAPM_PRE_PMU) + return 0; + + if (w->shift == RT5682S_PWR_I2S2_BIT) { + id = RT5682S_AIF2; + reg = RT5682S_I2S2_M_CLK_CTRL_1; + mask = RT5682S_I2S2_M_D_MASK; + sft = RT5682S_I2S2_M_D_SFT; + } else { + id = RT5682S_AIF1; + reg = RT5682S_ADDA_CLK_1; + mask = RT5682S_I2S_M_D_MASK; + sft = RT5682S_I2S_M_D_SFT; + } + + if (!rt5682s->master[id]) + return 0; + + pre_div = get_clk_info(rt5682s->sysclk, rt5682s->lrck[id]); + if (pre_div < 0) { + dev_err(component->dev, "get pre_div failed\n"); + return -EINVAL; + } + + dev_dbg(component->dev, "lrck is %dHz and pre_div is %d for iis %d master\n", + rt5682s->lrck[id], pre_div, id); + snd_soc_component_update_bits(component, reg, mask, pre_div << sft); + + return 0; +} + +static int is_sys_clk_from_plla(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + if ((rt5682s->sysclk_src == RT5682S_CLK_SRC_PLL1) || + (rt5682s->sysclk_src == RT5682S_CLK_SRC_PLL2 && rt5682s->pll_comb == USE_PLLAB)) + return 1; + + return 0; +} + +static int is_sys_clk_from_pllb(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + if (rt5682s->sysclk_src == RT5682S_CLK_SRC_PLL2) + return 1; + + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int reg, sft, val; + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (w->shift) { + case RT5682S_ADC_STO1_ASRC_SFT: + reg = RT5682S_PLL_TRACK_3; + sft = RT5682S_FILTER_CLK_SEL_SFT; + break; + case RT5682S_DAC_STO1_ASRC_SFT: + reg = RT5682S_PLL_TRACK_2; + sft = RT5682S_FILTER_CLK_SEL_SFT; + break; + default: + return 0; + } + + val = (snd_soc_component_read(component, reg) >> sft) & 0xf; + switch (val) { + case RT5682S_CLK_SEL_I2S1_ASRC: + case RT5682S_CLK_SEL_I2S2_ASRC: + return 1; + default: + return 0; + } +} + +static int rt5682s_hp_amp_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 rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5682S_DEPOP_1, + RT5682S_OUT_HP_L_EN | RT5682S_OUT_HP_R_EN, + RT5682S_OUT_HP_L_EN | RT5682S_OUT_HP_R_EN); + usleep_range(15000, 20000); + snd_soc_component_update_bits(component, RT5682S_DEPOP_1, + RT5682S_LDO_PUMP_EN | RT5682S_PUMP_EN | + RT5682S_CAPLESS_L_EN | RT5682S_CAPLESS_R_EN, + RT5682S_LDO_PUMP_EN | RT5682S_PUMP_EN | + RT5682S_CAPLESS_L_EN | RT5682S_CAPLESS_R_EN); + snd_soc_component_write(component, RT5682S_BIAS_CUR_CTRL_11, 0x6666); + snd_soc_component_write(component, RT5682S_BIAS_CUR_CTRL_12, 0xa82a); + + mutex_lock(&rt5682s->jdet_mutex); + + snd_soc_component_update_bits(component, RT5682S_HP_CTRL_2, + RT5682S_HPO_L_PATH_MASK | RT5682S_HPO_R_PATH_MASK | + RT5682S_HPO_SEL_IP_EN_SW, RT5682S_HPO_L_PATH_EN | + RT5682S_HPO_R_PATH_EN | RT5682S_HPO_IP_EN_GATING); + usleep_range(5000, 10000); + snd_soc_component_update_bits(component, RT5682S_HP_AMP_DET_CTL_1, + RT5682S_CP_SW_SIZE_MASK, RT5682S_CP_SW_SIZE_L | RT5682S_CP_SW_SIZE_S); + + mutex_unlock(&rt5682s->jdet_mutex); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, RT5682S_HP_CTRL_2, + RT5682S_HPO_L_PATH_MASK | RT5682S_HPO_R_PATH_MASK | + RT5682S_HPO_SEL_IP_EN_SW, 0); + snd_soc_component_update_bits(component, RT5682S_HP_AMP_DET_CTL_1, + RT5682S_CP_SW_SIZE_MASK, RT5682S_CP_SW_SIZE_M); + snd_soc_component_update_bits(component, RT5682S_DEPOP_1, + RT5682S_LDO_PUMP_EN | RT5682S_PUMP_EN | + RT5682S_CAPLESS_L_EN | RT5682S_CAPLESS_R_EN, 0); + snd_soc_component_update_bits(component, RT5682S_DEPOP_1, + RT5682S_OUT_HP_L_EN | RT5682S_OUT_HP_R_EN, 0); + break; + } + + return 0; +} + +static int sar_power_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 rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + if ((rt5682s->jack_type & SND_JACK_HEADSET) != SND_JACK_HEADSET) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + rt5682s_sar_power_mode(component, SAR_PWR_NORMAL, 0); + break; + case SND_SOC_DAPM_POST_PMD: + rt5682s_sar_power_mode(component, SAR_PWR_SAVING, 0); + break; + } + + return 0; +} + +/* Interface data select */ +static const char * const rt5682s_data_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5682s_if2_adc_enum, RT5682S_DIG_INF2_DATA, + RT5682S_IF2_ADC_SEL_SFT, rt5682s_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682s_if1_01_adc_enum, RT5682S_TDM_ADDA_CTRL_1, + RT5682S_IF1_ADC1_SEL_SFT, rt5682s_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682s_if1_23_adc_enum, RT5682S_TDM_ADDA_CTRL_1, + RT5682S_IF1_ADC2_SEL_SFT, rt5682s_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682s_if1_45_adc_enum, RT5682S_TDM_ADDA_CTRL_1, + RT5682S_IF1_ADC3_SEL_SFT, rt5682s_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682s_if1_67_adc_enum, RT5682S_TDM_ADDA_CTRL_1, + RT5682S_IF1_ADC4_SEL_SFT, rt5682s_data_select); + +static const struct snd_kcontrol_new rt5682s_if2_adc_swap_mux = + SOC_DAPM_ENUM("IF2 ADC Swap Mux", rt5682s_if2_adc_enum); + +static const struct snd_kcontrol_new rt5682s_if1_01_adc_swap_mux = + SOC_DAPM_ENUM("IF1 01 ADC Swap Mux", rt5682s_if1_01_adc_enum); + +static const struct snd_kcontrol_new rt5682s_if1_23_adc_swap_mux = + SOC_DAPM_ENUM("IF1 23 ADC Swap Mux", rt5682s_if1_23_adc_enum); + +static const struct snd_kcontrol_new rt5682s_if1_45_adc_swap_mux = + SOC_DAPM_ENUM("IF1 45 ADC Swap Mux", rt5682s_if1_45_adc_enum); + +static const struct snd_kcontrol_new rt5682s_if1_67_adc_swap_mux = + SOC_DAPM_ENUM("IF1 67 ADC Swap Mux", rt5682s_if1_67_adc_enum); + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5682s_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5682S_STO1_ADC_MIXER, + RT5682S_M_STO1_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5682S_STO1_ADC_MIXER, + RT5682S_M_STO1_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682s_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5682S_STO1_ADC_MIXER, + RT5682S_M_STO1_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5682S_STO1_ADC_MIXER, + RT5682S_M_STO1_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682s_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5682S_AD_DA_MIXER, + RT5682S_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5682S_AD_DA_MIXER, + RT5682S_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682s_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5682S_AD_DA_MIXER, + RT5682S_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5682S_AD_DA_MIXER, + RT5682S_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682s_sto1_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5682S_STO1_DAC_MIXER, + RT5682S_M_DAC_L1_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5682S_STO1_DAC_MIXER, + RT5682S_M_DAC_R1_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682s_sto1_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5682S_STO1_DAC_MIXER, + RT5682S_M_DAC_L1_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5682S_STO1_DAC_MIXER, + RT5682S_M_DAC_R1_STO_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5682s_rec1_l_mix[] = { + SOC_DAPM_SINGLE("CBJ Switch", RT5682S_REC_MIXER, + RT5682S_M_CBJ_RM1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682s_rec1_r_mix[] = { + SOC_DAPM_SINGLE("CBJ Switch", RT5682S_REC_MIXER, + RT5682S_M_CBJ_RM1_R_SFT, 1, 1), +}; + +/* STO1 ADC1 Source */ +/* MX-26 [13] [5] */ +static const char * const rt5682s_sto1_adc1_src[] = { + "DAC MIX", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5682s_sto1_adc1l_enum, RT5682S_STO1_ADC_MIXER, + RT5682S_STO1_ADC1L_SRC_SFT, rt5682s_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5682s_sto1_adc1l_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5682s_sto1_adc1l_enum); + +static SOC_ENUM_SINGLE_DECL(rt5682s_sto1_adc1r_enum, RT5682S_STO1_ADC_MIXER, + RT5682S_STO1_ADC1R_SRC_SFT, rt5682s_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5682s_sto1_adc1r_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5682s_sto1_adc1r_enum); + +/* STO1 ADC Source */ +/* MX-26 [11:10] [3:2] */ +static const char * const rt5682s_sto1_adc_src[] = { + "ADC1 L", "ADC1 R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5682s_sto1_adcl_enum, RT5682S_STO1_ADC_MIXER, + RT5682S_STO1_ADCL_SRC_SFT, rt5682s_sto1_adc_src); + +static const struct snd_kcontrol_new rt5682s_sto1_adcl_mux = + SOC_DAPM_ENUM("Stereo1 ADCL Source", rt5682s_sto1_adcl_enum); + +static SOC_ENUM_SINGLE_DECL(rt5682s_sto1_adcr_enum, RT5682S_STO1_ADC_MIXER, + RT5682S_STO1_ADCR_SRC_SFT, rt5682s_sto1_adc_src); + +static const struct snd_kcontrol_new rt5682s_sto1_adcr_mux = + SOC_DAPM_ENUM("Stereo1 ADCR Source", rt5682s_sto1_adcr_enum); + +/* STO1 ADC2 Source */ +/* MX-26 [12] [4] */ +static const char * const rt5682s_sto1_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5682s_sto1_adc2l_enum, RT5682S_STO1_ADC_MIXER, + RT5682S_STO1_ADC2L_SRC_SFT, rt5682s_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5682s_sto1_adc2l_mux = + SOC_DAPM_ENUM("Stereo1 ADC2L Source", rt5682s_sto1_adc2l_enum); + +static SOC_ENUM_SINGLE_DECL(rt5682s_sto1_adc2r_enum, RT5682S_STO1_ADC_MIXER, + RT5682S_STO1_ADC2R_SRC_SFT, rt5682s_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5682s_sto1_adc2r_mux = + SOC_DAPM_ENUM("Stereo1 ADC2R Source", rt5682s_sto1_adc2r_enum); + +/* MX-79 [6:4] I2S1 ADC data location */ +static const unsigned int rt5682s_if1_adc_slot_values[] = { + 0, 2, 4, 6, +}; + +static const char * const rt5682s_if1_adc_slot_src[] = { + "Slot 0", "Slot 2", "Slot 4", "Slot 6" +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5682s_if1_adc_slot_enum, + RT5682S_TDM_CTRL, RT5682S_TDM_ADC_LCA_SFT, RT5682S_TDM_ADC_LCA_MASK, + rt5682s_if1_adc_slot_src, rt5682s_if1_adc_slot_values); + +static const struct snd_kcontrol_new rt5682s_if1_adc_slot_mux = + SOC_DAPM_ENUM("IF1 ADC Slot location", rt5682s_if1_adc_slot_enum); + +/* Analog DAC L1 Source, Analog DAC R1 Source*/ +/* MX-2B [4], MX-2B [0]*/ +static const char * const rt5682s_alg_dac1_src[] = { + "Stereo1 DAC Mixer", "DAC1" +}; + +static SOC_ENUM_SINGLE_DECL(rt5682s_alg_dac_l1_enum, RT5682S_A_DAC1_MUX, + RT5682S_A_DACL1_SFT, rt5682s_alg_dac1_src); + +static const struct snd_kcontrol_new rt5682s_alg_dac_l1_mux = + SOC_DAPM_ENUM("Analog DAC L1 Source", rt5682s_alg_dac_l1_enum); + +static SOC_ENUM_SINGLE_DECL(rt5682s_alg_dac_r1_enum, RT5682S_A_DAC1_MUX, + RT5682S_A_DACR1_SFT, rt5682s_alg_dac1_src); + +static const struct snd_kcontrol_new rt5682s_alg_dac_r1_mux = + SOC_DAPM_ENUM("Analog DAC R1 Source", rt5682s_alg_dac_r1_enum); + +static const unsigned int rt5682s_adcdat_pin_values[] = { + 1, 3, +}; + +static const char * const rt5682s_adcdat_pin_select[] = { + "ADCDAT1", "ADCDAT2", +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5682s_adcdat_pin_enum, + RT5682S_GPIO_CTRL_1, RT5682S_GP4_PIN_SFT, RT5682S_GP4_PIN_MASK, + rt5682s_adcdat_pin_select, rt5682s_adcdat_pin_values); + +static const struct snd_kcontrol_new rt5682s_adcdat_pin_ctrl = + SOC_DAPM_ENUM("ADCDAT", rt5682s_adcdat_pin_enum); + +static const struct snd_soc_dapm_widget rt5682s_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO MB1", RT5682S_PWR_ANLG_3, + RT5682S_PWR_LDO_MB1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO MB2", RT5682S_PWR_ANLG_3, + RT5682S_PWR_LDO_MB2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO", RT5682S_PWR_ANLG_3, + RT5682S_PWR_LDO_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Vref2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* PLL Powers */ + SND_SOC_DAPM_SUPPLY_S("PLLA_LDO", 0, RT5682S_PWR_ANLG_3, + RT5682S_PWR_LDO_PLLA_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("PLLB_LDO", 0, RT5682S_PWR_ANLG_3, + RT5682S_PWR_LDO_PLLB_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("PLLA_BIAS", 0, RT5682S_PWR_ANLG_3, + RT5682S_PWR_BIAS_PLLA_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("PLLB_BIAS", 0, RT5682S_PWR_ANLG_3, + RT5682S_PWR_BIAS_PLLB_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("PLLA", 0, RT5682S_PWR_ANLG_3, + RT5682S_PWR_PLLA_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("PLLB", 0, RT5682S_PWR_ANLG_3, + RT5682S_PWR_PLLB_BIT, 0, set_filter_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY_S("PLLA_RST", 1, RT5682S_PWR_ANLG_3, + RT5682S_RSTB_PLLA_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("PLLB_RST", 1, RT5682S_PWR_ANLG_3, + RT5682S_RSTB_PLLB_BIT, 0, NULL, 0), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("DAC STO1 ASRC", 1, RT5682S_PLL_TRACK_1, + RT5682S_DAC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5682S_PLL_TRACK_1, + RT5682S_ADC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("AD ASRC", 1, RT5682S_PLL_TRACK_1, + RT5682S_AD_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DA ASRC", 1, RT5682S_PLL_TRACK_1, + RT5682S_DA_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC ASRC", 1, RT5682S_PLL_TRACK_1, + RT5682S_DMIC_ASRC_SFT, 0, NULL, 0), + + /* Input Side */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5682S_PWR_ANLG_2, + RT5682S_PWR_MB1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", RT5682S_PWR_ANLG_2, + RT5682S_PWR_MB2_BIT, 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + + SND_SOC_DAPM_INPUT("IN1P"), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5682S_DMIC_CTRL_1, RT5682S_DMIC_1_EN_SFT, 0, + set_dmic_power, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1 CBJ", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX1L", SND_SOC_NOPM, 0, 0, rt5682s_rec1_l_mix, + ARRAY_SIZE(rt5682s_rec1_l_mix)), + SND_SOC_DAPM_MIXER("RECMIX1R", SND_SOC_NOPM, 0, 0, rt5682s_rec1_r_mix, + ARRAY_SIZE(rt5682s_rec1_r_mix)), + SND_SOC_DAPM_SUPPLY("RECMIX1L Power", RT5682S_CAL_REC, + RT5682S_PWR_RM1_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RECMIX1R Power", RT5682S_CAL_REC, + RT5682S_PWR_RM1_R_BIT, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC1 L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC1 R", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("ADC1 L Power", RT5682S_PWR_DIG_1, + RT5682S_PWR_ADC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 R Power", RT5682S_PWR_DIG_1, + RT5682S_PWR_ADC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 clock", RT5682S_CHOP_ADC, + RT5682S_CKGEN_ADC1_SFT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_sto1_adc1l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_sto1_adc1r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_sto1_adc2l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_sto1_adc2r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_sto1_adcl_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_sto1_adcr_mux), + SND_SOC_DAPM_MUX("IF1_ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_if1_adc_slot_mux), + + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("ADC Stereo1 Filter", RT5682S_PWR_DIG_2, + RT5682S_PWR_ADC_S1F_BIT, 0, set_filter_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", RT5682S_STO1_ADC_DIG_VOL, + RT5682S_L_MUTE_SFT, 1, rt5682s_sto1_adc_l_mix, + ARRAY_SIZE(rt5682s_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXR", RT5682S_STO1_ADC_DIG_VOL, + RT5682S_R_MUTE_SFT, 1, rt5682s_sto1_adc_r_mix, + ARRAY_SIZE(rt5682s_sto1_adc_r_mix)), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5682S_PWR_DIG_1, RT5682S_PWR_I2S1_BIT, + 0, set_i2s_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("I2S2", RT5682S_PWR_DIG_1, RT5682S_PWR_I2S2_BIT, + 0, set_i2s_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("IF1 01 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_if1_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 23 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_if1_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 45 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_if1_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 67 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_if1_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682s_if2_adc_swap_mux), + + SND_SOC_DAPM_MUX("ADCDAT Mux", SND_SOC_NOPM, 0, 0, &rt5682s_adcdat_pin_ctrl), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, RT5682S_I2S1_SDP, + RT5682S_SEL_ADCDAT_SFT, 1), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, RT5682S_I2S2_SDP, + RT5682S_I2S2_PIN_CFG_SFT, 1), + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5682s_dac_l_mix, ARRAY_SIZE(rt5682s_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5682s_dac_r_mix, ARRAY_SIZE(rt5682s_dac_r_mix)), + + /* DAC channel Mux */ + SND_SOC_DAPM_MUX("DAC L1 Source", SND_SOC_NOPM, 0, 0, &rt5682s_alg_dac_l1_mux), + SND_SOC_DAPM_MUX("DAC R1 Source", SND_SOC_NOPM, 0, 0, &rt5682s_alg_dac_r1_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("DAC Stereo1 Filter", RT5682S_PWR_DIG_2, + RT5682S_PWR_DAC_S1F_BIT, 0, set_filter_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5682s_sto1_dac_l_mix, ARRAY_SIZE(rt5682s_sto1_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5682s_sto1_dac_r_mix, ARRAY_SIZE(rt5682s_sto1_dac_r_mix)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, RT5682S_PWR_DIG_1, RT5682S_PWR_DAC_L1_BIT, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, RT5682S_PWR_DIG_1, RT5682S_PWR_DAC_R1_BIT, 0), + + /* HPO */ + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, rt5682s_hp_amp_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), + + /* CLK DET */ + SND_SOC_DAPM_SUPPLY("CLKDET SYS", RT5682S_CLK_DET, + RT5682S_SYS_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET PLL1", RT5682S_CLK_DET, + RT5682S_PLL1_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MCLK0 DET PWR", RT5682S_PWR_ANLG_2, + RT5682S_PWR_MCLK0_WD_BIT, 0, NULL, 0), + + /* SAR */ + SND_SOC_DAPM_SUPPLY("SAR", SND_SOC_NOPM, 0, 0, sar_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route rt5682s_dapm_routes[] = { + /*PLL*/ + {"ADC Stereo1 Filter", NULL, "PLLA", is_sys_clk_from_plla}, + {"ADC Stereo1 Filter", NULL, "PLLB", is_sys_clk_from_pllb}, + {"DAC Stereo1 Filter", NULL, "PLLA", is_sys_clk_from_plla}, + {"DAC Stereo1 Filter", NULL, "PLLB", is_sys_clk_from_pllb}, + {"PLLA", NULL, "PLLA_LDO"}, + {"PLLA", NULL, "PLLA_BIAS"}, + {"PLLA", NULL, "PLLA_RST"}, + {"PLLB", NULL, "PLLB_LDO"}, + {"PLLB", NULL, "PLLB_BIAS"}, + {"PLLB", NULL, "PLLB_RST"}, + + /*ASRC*/ + {"ADC Stereo1 Filter", NULL, "ADC STO1 ASRC", is_using_asrc}, + {"DAC Stereo1 Filter", NULL, "DAC STO1 ASRC", is_using_asrc}, + {"ADC STO1 ASRC", NULL, "AD ASRC"}, + {"ADC STO1 ASRC", NULL, "DA ASRC"}, + {"DAC STO1 ASRC", NULL, "AD ASRC"}, + {"DAC STO1 ASRC", NULL, "DA ASRC"}, + + {"CLKDET SYS", NULL, "MCLK0 DET PWR"}, + + {"BST1 CBJ", NULL, "IN1P"}, + {"BST1 CBJ", NULL, "SAR"}, + + {"RECMIX1L", "CBJ Switch", "BST1 CBJ"}, + {"RECMIX1L", NULL, "RECMIX1L Power"}, + {"RECMIX1R", "CBJ Switch", "BST1 CBJ"}, + {"RECMIX1R", NULL, "RECMIX1R Power"}, + + {"ADC1 L", NULL, "RECMIX1L"}, + {"ADC1 L", NULL, "ADC1 L Power"}, + {"ADC1 L", NULL, "ADC1 clock"}, + {"ADC1 R", NULL, "RECMIX1R"}, + {"ADC1 R", NULL, "ADC1 R Power"}, + {"ADC1 R", NULL, "ADC1 clock"}, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC L1", NULL, "DMIC1 Power"}, + {"DMIC R1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC1 Power"}, + {"DMIC CLK", NULL, "DMIC ASRC"}, + + {"Stereo1 ADC L Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC L Mux", "ADC1 R", "ADC1 R"}, + {"Stereo1 ADC R Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC R Mux", "ADC1 R", "ADC1 R"}, + + {"Stereo1 ADC L1 Mux", "ADC", "Stereo1 ADC L Mux"}, + {"Stereo1 ADC L1 Mux", "DAC MIX", "Stereo1 DAC MIXL"}, + {"Stereo1 ADC L2 Mux", "DMIC", "DMIC L1"}, + {"Stereo1 ADC L2 Mux", "DAC MIX", "Stereo1 DAC MIXL"}, + + {"Stereo1 ADC R1 Mux", "ADC", "Stereo1 ADC R Mux"}, + {"Stereo1 ADC R1 Mux", "DAC MIX", "Stereo1 DAC MIXR"}, + {"Stereo1 ADC R2 Mux", "DMIC", "DMIC R1"}, + {"Stereo1 ADC R2 Mux", "DAC MIX", "Stereo1 DAC MIXR"}, + + {"Stereo1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux"}, + {"Stereo1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux"}, + {"Stereo1 ADC MIXL", NULL, "ADC Stereo1 Filter"}, + + {"Stereo1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux"}, + {"Stereo1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux"}, + {"Stereo1 ADC MIXR", NULL, "ADC Stereo1 Filter"}, + + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXL"}, + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXR"}, + + {"IF1 01 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + + {"IF1_ADC Mux", "Slot 0", "IF1 01 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 2", "IF1 23 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 4", "IF1 45 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 6", "IF1 67 ADC Swap Mux"}, + {"ADCDAT Mux", "ADCDAT1", "IF1_ADC Mux"}, + {"AIF1TX", NULL, "I2S1"}, + {"AIF1TX", NULL, "ADCDAT Mux"}, + {"IF2 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"ADCDAT Mux", "ADCDAT2", "IF2 ADC Swap Mux"}, + {"AIF2TX", NULL, "ADCDAT Mux"}, + + {"IF1 DAC1 L", NULL, "AIF1RX"}, + {"IF1 DAC1 L", NULL, "I2S1"}, + {"IF1 DAC1 L", NULL, "DAC Stereo1 Filter"}, + {"IF1 DAC1 R", NULL, "AIF1RX"}, + {"IF1 DAC1 R", NULL, "I2S1"}, + {"IF1 DAC1 R", NULL, "DAC Stereo1 Filter"}, + + {"DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL"}, + {"DAC1 MIXL", "DAC1 Switch", "IF1 DAC1 L"}, + {"DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR"}, + {"DAC1 MIXR", "DAC1 Switch", "IF1 DAC1 R"}, + + {"Stereo1 DAC MIXL", "DAC L1 Switch", "DAC1 MIXL"}, + {"Stereo1 DAC MIXL", "DAC R1 Switch", "DAC1 MIXR"}, + + {"Stereo1 DAC MIXR", "DAC R1 Switch", "DAC1 MIXR"}, + {"Stereo1 DAC MIXR", "DAC L1 Switch", "DAC1 MIXL"}, + + {"DAC L1 Source", "DAC1", "DAC1 MIXL"}, + {"DAC L1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXL"}, + {"DAC R1 Source", "DAC1", "DAC1 MIXR"}, + {"DAC R1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXR"}, + + {"DAC L1", NULL, "DAC L1 Source"}, + {"DAC R1", NULL, "DAC R1 Source"}, + + {"HP Amp", NULL, "DAC L1"}, + {"HP Amp", NULL, "DAC R1"}, + {"HP Amp", NULL, "CLKDET SYS"}, + {"HP Amp", NULL, "SAR"}, + + {"HPOL", NULL, "HP Amp"}, + {"HPOR", NULL, "HP Amp"}, +}; + +static int rt5682s_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int cl, val = 0; + + if (tx_mask || rx_mask) + snd_soc_component_update_bits(component, + RT5682S_TDM_ADDA_CTRL_2, RT5682S_TDM_EN, RT5682S_TDM_EN); + else + snd_soc_component_update_bits(component, + RT5682S_TDM_ADDA_CTRL_2, RT5682S_TDM_EN, 0); + + switch (slots) { + case 4: + val |= RT5682S_TDM_TX_CH_4; + val |= RT5682S_TDM_RX_CH_4; + break; + case 6: + val |= RT5682S_TDM_TX_CH_6; + val |= RT5682S_TDM_RX_CH_6; + break; + case 8: + val |= RT5682S_TDM_TX_CH_8; + val |= RT5682S_TDM_RX_CH_8; + break; + case 2: + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5682S_TDM_CTRL, + RT5682S_TDM_TX_CH_MASK | RT5682S_TDM_RX_CH_MASK, val); + + switch (slot_width) { + case 8: + if (tx_mask || rx_mask) + return -EINVAL; + cl = RT5682S_I2S1_TX_CHL_8 | RT5682S_I2S1_RX_CHL_8; + break; + case 16: + val = RT5682S_TDM_CL_16; + cl = RT5682S_I2S1_TX_CHL_16 | RT5682S_I2S1_RX_CHL_16; + break; + case 20: + val = RT5682S_TDM_CL_20; + cl = RT5682S_I2S1_TX_CHL_20 | RT5682S_I2S1_RX_CHL_20; + break; + case 24: + val = RT5682S_TDM_CL_24; + cl = RT5682S_I2S1_TX_CHL_24 | RT5682S_I2S1_RX_CHL_24; + break; + case 32: + val = RT5682S_TDM_CL_32; + cl = RT5682S_I2S1_TX_CHL_32 | RT5682S_I2S1_RX_CHL_32; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5682S_TDM_TCON_CTRL_1, + RT5682S_TDM_CL_MASK, val); + snd_soc_component_update_bits(component, RT5682S_I2S1_SDP, + RT5682S_I2S1_TX_CHL_MASK | RT5682S_I2S1_RX_CHL_MASK, cl); + + return 0; +} + +static int rt5682s_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; + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + unsigned int len_1 = 0, len_2 = 0; + int frame_size; + + rt5682s->lrck[dai->id] = params_rate(params); + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + + switch (params_width(params)) { + case 16: + break; + case 20: + len_1 |= RT5682S_I2S1_DL_20; + len_2 |= RT5682S_I2S2_DL_20; + break; + case 24: + len_1 |= RT5682S_I2S1_DL_24; + len_2 |= RT5682S_I2S2_DL_24; + break; + case 32: + len_1 |= RT5682S_I2S1_DL_32; + len_2 |= RT5682S_I2S2_DL_24; + break; + case 8: + len_1 |= RT5682S_I2S2_DL_8; + len_2 |= RT5682S_I2S2_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5682S_AIF1: + snd_soc_component_update_bits(component, RT5682S_I2S1_SDP, + RT5682S_I2S1_DL_MASK, len_1); + if (params_channels(params) == 1) /* mono mode */ + snd_soc_component_update_bits(component, RT5682S_I2S1_SDP, + RT5682S_I2S1_MONO_MASK, RT5682S_I2S1_MONO_EN); + else + snd_soc_component_update_bits(component, RT5682S_I2S1_SDP, + RT5682S_I2S1_MONO_MASK, RT5682S_I2S1_MONO_DIS); + break; + case RT5682S_AIF2: + snd_soc_component_update_bits(component, RT5682S_I2S2_SDP, + RT5682S_I2S2_DL_MASK, len_2); + if (params_channels(params) == 1) /* mono mode */ + snd_soc_component_update_bits(component, RT5682S_I2S2_SDP, + RT5682S_I2S2_MONO_MASK, RT5682S_I2S2_MONO_EN); + else + snd_soc_component_update_bits(component, RT5682S_I2S2_SDP, + RT5682S_I2S2_MONO_MASK, RT5682S_I2S2_MONO_DIS); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5682s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, tdm_ctrl = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5682s->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + rt5682s->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5682S_I2S_BP_INV; + tdm_ctrl |= RT5682S_TDM_S_BP_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + if (dai->id == RT5682S_AIF1) + tdm_ctrl |= RT5682S_TDM_S_LP_INV | RT5682S_TDM_M_BP_INV; + else + return -EINVAL; + break; + case SND_SOC_DAIFMT_IB_IF: + if (dai->id == RT5682S_AIF1) + tdm_ctrl |= RT5682S_TDM_S_BP_INV | RT5682S_TDM_S_LP_INV | + RT5682S_TDM_M_BP_INV | RT5682S_TDM_M_LP_INV; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5682S_I2S_DF_LEFT; + tdm_ctrl |= RT5682S_TDM_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5682S_I2S_DF_PCM_A; + tdm_ctrl |= RT5682S_TDM_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5682S_I2S_DF_PCM_B; + tdm_ctrl |= RT5682S_TDM_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5682S_AIF1: + snd_soc_component_update_bits(component, RT5682S_I2S1_SDP, + RT5682S_I2S_DF_MASK, reg_val); + snd_soc_component_update_bits(component, RT5682S_TDM_TCON_CTRL_1, + RT5682S_TDM_MS_MASK | RT5682S_TDM_S_BP_MASK | + RT5682S_TDM_DF_MASK | RT5682S_TDM_M_BP_MASK | + RT5682S_TDM_M_LP_MASK | RT5682S_TDM_S_LP_MASK, + tdm_ctrl | rt5682s->master[dai->id]); + break; + case RT5682S_AIF2: + if (rt5682s->master[dai->id] == 0) + reg_val |= RT5682S_I2S2_MS_S; + snd_soc_component_update_bits(component, RT5682S_I2S2_SDP, + RT5682S_I2S2_MS_MASK | RT5682S_I2S_BP_MASK | + RT5682S_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5682s_set_component_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + unsigned int src = 0; + + if (freq == rt5682s->sysclk && clk_id == rt5682s->sysclk_src) + return 0; + + switch (clk_id) { + case RT5682S_SCLK_S_MCLK: + src = RT5682S_CLK_SRC_MCLK; + break; + case RT5682S_SCLK_S_PLL1: + src = RT5682S_CLK_SRC_PLL1; + break; + case RT5682S_SCLK_S_PLL2: + src = RT5682S_CLK_SRC_PLL2; + break; + case RT5682S_SCLK_S_RCCLK: + src = RT5682S_CLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5682S_GLB_CLK, + RT5682S_SCLK_SRC_MASK, src << RT5682S_SCLK_SRC_SFT); + snd_soc_component_update_bits(component, RT5682S_ADDA_CLK_1, + RT5682S_I2S_M_CLK_SRC_MASK, src << RT5682S_I2S_M_CLK_SRC_SFT); + snd_soc_component_update_bits(component, RT5682S_I2S2_M_CLK_CTRL_1, + RT5682S_I2S2_M_CLK_SRC_MASK, src << RT5682S_I2S2_M_CLK_SRC_SFT); + + rt5682s->sysclk = freq; + rt5682s->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; +} + +static const struct pll_calc_map plla_table[] = { + {2048000, 24576000, 0, 46, 2, true, false, false, false}, + {256000, 24576000, 0, 382, 2, true, false, false, false}, + {512000, 24576000, 0, 190, 2, true, false, false, false}, + {4096000, 24576000, 0, 22, 2, true, false, false, false}, + {1024000, 24576000, 0, 94, 2, true, false, false, false}, + {11289600, 22579200, 1, 22, 2, false, false, false, false}, + {1411200, 22579200, 0, 62, 2, true, false, false, false}, + {2822400, 22579200, 0, 30, 2, true, false, false, false}, + {12288000, 24576000, 1, 22, 2, false, false, false, false}, + {1536000, 24576000, 0, 62, 2, true, false, false, false}, + {3072000, 24576000, 0, 30, 2, true, false, false, false}, + {24576000, 49152000, 4, 22, 0, false, false, false, false}, + {3072000, 49152000, 0, 30, 0, true, false, false, false}, + {6144000, 49152000, 0, 30, 0, false, false, false, false}, + {49152000, 98304000, 10, 22, 0, false, true, false, false}, + {6144000, 98304000, 0, 30, 0, false, true, false, false}, + {12288000, 98304000, 1, 22, 0, false, true, false, false}, + {48000000, 3840000, 10, 22, 23, false, false, false, false}, + {24000000, 3840000, 4, 22, 23, false, false, false, false}, + {19200000, 3840000, 3, 23, 23, false, false, false, false}, + {38400000, 3840000, 8, 23, 23, false, false, false, false}, +}; + +static const struct pll_calc_map pllb_table[] = { + {48000000, 24576000, 8, 6, 3, false, false, false, false}, + {48000000, 22579200, 23, 12, 3, false, false, false, true}, + {24000000, 24576000, 3, 6, 3, false, false, false, false}, + {24000000, 22579200, 23, 26, 3, false, false, false, true}, + {19200000, 24576000, 2, 6, 3, false, false, false, false}, + {19200000, 22579200, 3, 5, 3, false, false, false, true}, + {38400000, 24576000, 6, 6, 3, false, false, false, false}, + {38400000, 22579200, 8, 5, 3, false, false, false, true}, + {3840000, 49152000, 0, 6, 0, true, false, false, false}, +}; + +static int find_pll_inter_combination(unsigned int f_in, unsigned int f_out, + struct pll_calc_map *a, struct pll_calc_map *b) +{ + int i, j; + + /* Look at PLLA table */ + for (i = 0; i < ARRAY_SIZE(plla_table); i++) { + if (plla_table[i].freq_in == f_in && plla_table[i].freq_out == f_out) { + memcpy(a, plla_table + i, sizeof(*a)); + return USE_PLLA; + } + } + + /* Look at PLLB table */ + for (i = 0; i < ARRAY_SIZE(pllb_table); i++) { + if (pllb_table[i].freq_in == f_in && pllb_table[i].freq_out == f_out) { + memcpy(b, pllb_table + i, sizeof(*b)); + return USE_PLLB; + } + } + + /* Find a combination of PLLA & PLLB */ + for (i = ARRAY_SIZE(plla_table) - 1; i >= 0; i--) { + if (plla_table[i].freq_in == f_in && plla_table[i].freq_out == 3840000) { + for (j = ARRAY_SIZE(pllb_table) - 1; j >= 0; j--) { + if (pllb_table[j].freq_in == 3840000 && + pllb_table[j].freq_out == f_out) { + memcpy(a, plla_table + i, sizeof(*a)); + memcpy(b, pllb_table + j, sizeof(*b)); + return USE_PLLAB; + } + } + } + } + + return -EINVAL; +} + +static int rt5682s_set_component_pll(struct snd_soc_component *component, + int pll_id, int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + struct pll_calc_map a_map, b_map; + + if (source == rt5682s->pll_src[pll_id] && freq_in == rt5682s->pll_in[pll_id] && + freq_out == rt5682s->pll_out[pll_id]) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + rt5682s->pll_in[pll_id] = 0; + rt5682s->pll_out[pll_id] = 0; + snd_soc_component_update_bits(component, RT5682S_GLB_CLK, + RT5682S_SCLK_SRC_MASK, RT5682S_CLK_SRC_MCLK << RT5682S_SCLK_SRC_SFT); + return 0; + } + + switch (source) { + case RT5682S_PLL_S_MCLK: + snd_soc_component_update_bits(component, RT5682S_GLB_CLK, + RT5682S_PLL_SRC_MASK, RT5682S_PLL_SRC_MCLK); + break; + case RT5682S_PLL_S_BCLK1: + snd_soc_component_update_bits(component, RT5682S_GLB_CLK, + RT5682S_PLL_SRC_MASK, RT5682S_PLL_SRC_BCLK1); + break; + default: + dev_err(component->dev, "Unknown PLL Source %d\n", source); + return -EINVAL; + } + + rt5682s->pll_comb = find_pll_inter_combination(freq_in, freq_out, + &a_map, &b_map); + + if ((pll_id == RT5682S_PLL1 && rt5682s->pll_comb == USE_PLLA) || + (pll_id == RT5682S_PLL2 && (rt5682s->pll_comb == USE_PLLB || + rt5682s->pll_comb == USE_PLLAB))) { + dev_dbg(component->dev, + "Supported freq conversion for PLL%d:(%d->%d): %d\n", + pll_id + 1, freq_in, freq_out, rt5682s->pll_comb); + } else { + dev_err(component->dev, + "Unsupported freq conversion for PLL%d:(%d->%d): %d\n", + pll_id + 1, freq_in, freq_out, rt5682s->pll_comb); + return -EINVAL; + } + + if (rt5682s->pll_comb == USE_PLLA || rt5682s->pll_comb == USE_PLLAB) { + dev_dbg(component->dev, + "PLLA: fin=%d fout=%d m_bp=%d k_bp=%d m=%d n=%d k=%d\n", + a_map.freq_in, a_map.freq_out, a_map.m_bp, a_map.k_bp, + (a_map.m_bp ? 0 : a_map.m), a_map.n, (a_map.k_bp ? 0 : a_map.k)); + snd_soc_component_update_bits(component, RT5682S_PLL_CTRL_1, + RT5682S_PLLA_N_MASK, a_map.n); + snd_soc_component_update_bits(component, RT5682S_PLL_CTRL_2, + RT5682S_PLLA_M_MASK | RT5682S_PLLA_K_MASK, + a_map.m << RT5682S_PLLA_M_SFT | a_map.k); + snd_soc_component_update_bits(component, RT5682S_PLL_CTRL_6, + RT5682S_PLLA_M_BP_MASK | RT5682S_PLLA_K_BP_MASK, + a_map.m_bp << RT5682S_PLLA_M_BP_SFT | + a_map.k_bp << RT5682S_PLLA_K_BP_SFT); + } + + if (rt5682s->pll_comb == USE_PLLB || rt5682s->pll_comb == USE_PLLAB) { + dev_dbg(component->dev, + "PLLB: fin=%d fout=%d m_bp=%d k_bp=%d m=%d n=%d k=%d byp_ps=%d sel_ps=%d\n", + b_map.freq_in, b_map.freq_out, b_map.m_bp, b_map.k_bp, + (b_map.m_bp ? 0 : b_map.m), b_map.n, (b_map.k_bp ? 0 : b_map.k), + b_map.byp_ps, b_map.sel_ps); + snd_soc_component_update_bits(component, RT5682S_PLL_CTRL_3, + RT5682S_PLLB_N_MASK, b_map.n); + snd_soc_component_update_bits(component, RT5682S_PLL_CTRL_4, + RT5682S_PLLB_M_MASK | RT5682S_PLLB_K_MASK, + b_map.m << RT5682S_PLLB_M_SFT | b_map.k); + snd_soc_component_update_bits(component, RT5682S_PLL_CTRL_6, + RT5682S_PLLB_SEL_PS_MASK | RT5682S_PLLB_BYP_PS_MASK | + RT5682S_PLLB_M_BP_MASK | RT5682S_PLLB_K_BP_MASK, + b_map.sel_ps << RT5682S_PLLB_SEL_PS_SFT | + b_map.byp_ps << RT5682S_PLLB_BYP_PS_SFT | + b_map.m_bp << RT5682S_PLLB_M_BP_SFT | + b_map.k_bp << RT5682S_PLLB_K_BP_SFT); + } + + if (rt5682s->pll_comb == USE_PLLB) + snd_soc_component_update_bits(component, RT5682S_PLL_CTRL_7, + RT5682S_PLLB_SRC_MASK, RT5682S_PLLB_SRC_DFIN); + + rt5682s->pll_in[pll_id] = freq_in; + rt5682s->pll_out[pll_id] = freq_out; + rt5682s->pll_src[pll_id] = source; + + return 0; +} + +static int rt5682s_set_bclk1_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + rt5682s->bclk[dai->id] = ratio; + + switch (ratio) { + case 256: + snd_soc_component_update_bits(component, RT5682S_TDM_TCON_CTRL_1, + RT5682S_TDM_BCLK_MS1_MASK, RT5682S_TDM_BCLK_MS1_256); + break; + case 128: + snd_soc_component_update_bits(component, RT5682S_TDM_TCON_CTRL_1, + RT5682S_TDM_BCLK_MS1_MASK, RT5682S_TDM_BCLK_MS1_128); + break; + case 64: + snd_soc_component_update_bits(component, RT5682S_TDM_TCON_CTRL_1, + RT5682S_TDM_BCLK_MS1_MASK, RT5682S_TDM_BCLK_MS1_64); + break; + case 32: + snd_soc_component_update_bits(component, RT5682S_TDM_TCON_CTRL_1, + RT5682S_TDM_BCLK_MS1_MASK, RT5682S_TDM_BCLK_MS1_32); + break; + default: + dev_err(dai->dev, "Invalid bclk1 ratio %d\n", ratio); + return -EINVAL; + } + + return 0; +} + +static int rt5682s_set_bclk2_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + rt5682s->bclk[dai->id] = ratio; + + switch (ratio) { + case 64: + snd_soc_component_update_bits(component, RT5682S_ADDA_CLK_2, + RT5682S_I2S2_BCLK_MS2_MASK, RT5682S_I2S2_BCLK_MS2_64); + break; + case 32: + snd_soc_component_update_bits(component, RT5682S_ADDA_CLK_2, + RT5682S_I2S2_BCLK_MS2_MASK, RT5682S_I2S2_BCLK_MS2_32); + break; + default: + dev_err(dai->dev, "Invalid bclk2 ratio %d\n", ratio); + return -EINVAL; + } + + return 0; +} + +static int rt5682s_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(rt5682s->regmap, RT5682S_PWR_DIG_1, + RT5682S_PWR_LDO, RT5682S_PWR_LDO); + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(rt5682s->regmap, RT5682S_PWR_DIG_1, + RT5682S_DIG_GATE_CTRL, RT5682S_DIG_GATE_CTRL); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(rt5682s->regmap, RT5682S_PWR_DIG_1, + RT5682S_DIG_GATE_CTRL | RT5682S_PWR_LDO, 0); + break; + case SND_SOC_BIAS_ON: + break; + } + + return 0; +} + +#ifdef CONFIG_COMMON_CLK +#define CLK_PLL2_FIN 48000000 +#define CLK_48 48000 +#define CLK_44 44100 + +static bool rt5682s_clk_check(struct rt5682s_priv *rt5682s) +{ + if (!rt5682s->master[RT5682S_AIF1]) { + dev_dbg(rt5682s->component->dev, "dai clk fmt not set correctly\n"); + return false; + } + return true; +} + +static int rt5682s_wclk_prepare(struct clk_hw *hw) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_WCLK_IDX]); + struct snd_soc_component *component = rt5682s->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + + snd_soc_dapm_mutex_lock(dapm); + + snd_soc_dapm_force_enable_pin_unlocked(dapm, "MICBIAS"); + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_MB, RT5682S_PWR_MB); + + snd_soc_dapm_force_enable_pin_unlocked(dapm, "Vref2"); + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_FV2, RT5682S_PWR_VREF2); + usleep_range(15000, 20000); + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_FV2, RT5682S_PWR_FV2); + + snd_soc_dapm_force_enable_pin_unlocked(dapm, "I2S1"); + /* Only need to power PLLB due to the rate set restriction */ + snd_soc_dapm_force_enable_pin_unlocked(dapm, "PLLB"); + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); + + return 0; +} + +static void rt5682s_wclk_unprepare(struct clk_hw *hw) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_WCLK_IDX]); + struct snd_soc_component *component = rt5682s->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (!rt5682s_clk_check(rt5682s)) + return; + + snd_soc_dapm_mutex_lock(dapm); + + snd_soc_dapm_disable_pin_unlocked(dapm, "MICBIAS"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Vref2"); + if (!rt5682s->jack_type) + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_FV2 | RT5682S_PWR_MB, 0); + + snd_soc_dapm_disable_pin_unlocked(dapm, "I2S1"); + snd_soc_dapm_disable_pin_unlocked(dapm, "PLLB"); + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +static unsigned long rt5682s_wclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_WCLK_IDX]); + struct snd_soc_component *component = rt5682s->component; + const char * const clk_name = clk_hw_get_name(hw); + + if (!rt5682s_clk_check(rt5682s)) + return 0; + /* + * Only accept to set wclk rate to 44.1k or 48kHz. + */ + if (rt5682s->lrck[RT5682S_AIF1] != CLK_48 && + rt5682s->lrck[RT5682S_AIF1] != CLK_44) { + dev_warn(component->dev, "%s: clk %s only support %d or %d Hz output\n", + __func__, clk_name, CLK_44, CLK_48); + return 0; + } + + return rt5682s->lrck[RT5682S_AIF1]; +} + +static long rt5682s_wclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_WCLK_IDX]); + struct snd_soc_component *component = rt5682s->component; + const char * const clk_name = clk_hw_get_name(hw); + + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + /* + * Only accept to set wclk rate to 44.1k or 48kHz. + * It will force to 48kHz if not both. + */ + if (rate != CLK_48 && rate != CLK_44) { + dev_warn(component->dev, "%s: clk %s only support %d or %d Hz output\n", + __func__, clk_name, CLK_44, CLK_48); + rate = CLK_48; + } + + return rate; +} + +static int rt5682s_wclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_WCLK_IDX]); + struct snd_soc_component *component = rt5682s->component; + struct clk *parent_clk; + const char * const clk_name = clk_hw_get_name(hw); + unsigned int clk_pll2_fout; + + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + + /* + * Whether the wclk's parent clk (mclk) exists or not, please ensure + * it is fixed or set to 48MHz before setting wclk rate. It's a + * temporary limitation. Only accept 48MHz clk as the clk provider. + * + * It will set the codec anyway by assuming mclk is 48MHz. + */ + parent_clk = clk_get_parent(hw->clk); + if (!parent_clk) + dev_warn(component->dev, + "Parent mclk of wclk not acquired in driver. Please ensure mclk was provided as %d Hz.\n", + CLK_PLL2_FIN); + + if (parent_rate != CLK_PLL2_FIN) + dev_warn(component->dev, "clk %s only support %d Hz input\n", + clk_name, CLK_PLL2_FIN); + + /* + * To achieve the rate conversion from 48MHz to 44.1k or 48kHz, + * PLL2 is needed. + */ + clk_pll2_fout = rate * 512; + rt5682s_set_component_pll(component, RT5682S_PLL2, RT5682S_PLL_S_MCLK, + CLK_PLL2_FIN, clk_pll2_fout); + + rt5682s_set_component_sysclk(component, RT5682S_SCLK_S_PLL2, 0, + clk_pll2_fout, SND_SOC_CLOCK_IN); + + rt5682s->lrck[RT5682S_AIF1] = rate; + + return 0; +} + +static unsigned long rt5682s_bclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_BCLK_IDX]); + struct snd_soc_component *component = rt5682s->component; + unsigned int bclks_per_wclk; + + bclks_per_wclk = snd_soc_component_read(component, RT5682S_TDM_TCON_CTRL_1); + + switch (bclks_per_wclk & RT5682S_TDM_BCLK_MS1_MASK) { + case RT5682S_TDM_BCLK_MS1_256: + return parent_rate * 256; + case RT5682S_TDM_BCLK_MS1_128: + return parent_rate * 128; + case RT5682S_TDM_BCLK_MS1_64: + return parent_rate * 64; + case RT5682S_TDM_BCLK_MS1_32: + return parent_rate * 32; + default: + return 0; + } +} + +static unsigned long rt5682s_bclk_get_factor(unsigned long rate, + unsigned long parent_rate) +{ + unsigned long factor; + + factor = rate / parent_rate; + if (factor < 64) + return 32; + else if (factor < 128) + return 64; + else if (factor < 256) + return 128; + else + return 256; +} + +static long rt5682s_bclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_BCLK_IDX]); + unsigned long factor; + + if (!*parent_rate || !rt5682s_clk_check(rt5682s)) + return -EINVAL; + + /* + * BCLK rates are set as a multiplier of WCLK in HW. + * We don't allow changing the parent WCLK. We just do + * some rounding down based on the parent WCLK rate + * and find the appropriate multiplier of BCLK to + * get the rounded down BCLK value. + */ + factor = rt5682s_bclk_get_factor(rate, *parent_rate); + + return *parent_rate * factor; +} + +static int rt5682s_bclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct rt5682s_priv *rt5682s = + container_of(hw, struct rt5682s_priv, dai_clks_hw[RT5682S_DAI_BCLK_IDX]); + struct snd_soc_component *component = rt5682s->component; + struct snd_soc_dai *dai; + unsigned long factor; + + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + + factor = rt5682s_bclk_get_factor(rate, parent_rate); + + for_each_component_dais(component, dai) + if (dai->id == RT5682S_AIF1) + break; + if (!dai) { + dev_err(component->dev, "dai %d not found in component\n", + RT5682S_AIF1); + return -ENODEV; + } + + return rt5682s_set_bclk1_ratio(dai, factor); +} + +static const struct clk_ops rt5682s_dai_clk_ops[RT5682S_DAI_NUM_CLKS] = { + [RT5682S_DAI_WCLK_IDX] = { + .prepare = rt5682s_wclk_prepare, + .unprepare = rt5682s_wclk_unprepare, + .recalc_rate = rt5682s_wclk_recalc_rate, + .round_rate = rt5682s_wclk_round_rate, + .set_rate = rt5682s_wclk_set_rate, + }, + [RT5682S_DAI_BCLK_IDX] = { + .recalc_rate = rt5682s_bclk_recalc_rate, + .round_rate = rt5682s_bclk_round_rate, + .set_rate = rt5682s_bclk_set_rate, + }, +}; + +static int rt5682s_register_dai_clks(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + struct rt5682s_platform_data *pdata = &rt5682s->pdata; + struct clk_hw *dai_clk_hw; + int i, ret; + + for (i = 0; i < RT5682S_DAI_NUM_CLKS; ++i) { + struct clk_init_data init = { }; + + dai_clk_hw = &rt5682s->dai_clks_hw[i]; + + switch (i) { + case RT5682S_DAI_WCLK_IDX: + /* Make MCLK the parent of WCLK */ + if (rt5682s->mclk) { + init.parent_data = &(struct clk_parent_data){ + .fw_name = "mclk", + }; + init.num_parents = 1; + } + break; + case RT5682S_DAI_BCLK_IDX: + /* Make WCLK the parent of BCLK */ + init.parent_hws = &(const struct clk_hw *){ + &rt5682s->dai_clks_hw[RT5682S_DAI_WCLK_IDX] + }; + init.num_parents = 1; + break; + default: + dev_err(dev, "Invalid clock index\n"); + return -EINVAL; + } + + init.name = pdata->dai_clk_names[i]; + init.ops = &rt5682s_dai_clk_ops[i]; + init.flags = CLK_GET_RATE_NOCACHE | CLK_SET_RATE_GATE; + dai_clk_hw->init = &init; + + ret = devm_clk_hw_register(dev, dai_clk_hw); + if (ret) { + dev_warn(dev, "Failed to register %s: %d\n", init.name, ret); + return ret; + } + + if (dev->of_node) { + devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, dai_clk_hw); + } else { + ret = devm_clk_hw_register_clkdev(dev, dai_clk_hw, + init.name, dev_name(dev)); + if (ret) + return ret; + } + } + + return 0; +} + +static int rt5682s_dai_probe_clks(struct snd_soc_component *component) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + int ret; + + /* Check if MCLK provided */ + rt5682s->mclk = devm_clk_get(component->dev, "mclk"); + if (IS_ERR(rt5682s->mclk)) { + if (PTR_ERR(rt5682s->mclk) != -ENOENT) { + ret = PTR_ERR(rt5682s->mclk); + return ret; + } + rt5682s->mclk = NULL; + } + + /* Register CCF DAI clock control */ + ret = rt5682s_register_dai_clks(component); + if (ret) + return ret; + + /* Initial setup for CCF */ + rt5682s->lrck[RT5682S_AIF1] = CLK_48; + + return 0; +} +#else +static inline int rt5682s_dai_probe_clks(struct snd_soc_component *component) +{ + return 0; +} +#endif /* CONFIG_COMMON_CLK */ + +static int rt5682s_probe(struct snd_soc_component *component) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = &component->dapm; + int ret; + + rt5682s->component = component; + + ret = rt5682s_dai_probe_clks(component); + if (ret) + return ret; + + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + snd_soc_dapm_disable_pin(dapm, "Vref2"); + snd_soc_dapm_sync(dapm); + return 0; +} + +static void rt5682s_remove(struct snd_soc_component *component) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + rt5682s_reset(rt5682s); +} + +#ifdef CONFIG_PM +static int rt5682s_suspend(struct snd_soc_component *component) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&rt5682s->jack_detect_work); + cancel_delayed_work_sync(&rt5682s->jd_check_work); + + if (rt5682s->hs_jack && rt5682s->jack_type == SND_JACK_HEADSET) + snd_soc_component_update_bits(component, RT5682S_4BTN_IL_CMD_2, + RT5682S_4BTN_IL_MASK, RT5682S_4BTN_IL_DIS); + + regcache_cache_only(rt5682s->regmap, true); + regcache_mark_dirty(rt5682s->regmap); + + return 0; +} + +static int rt5682s_resume(struct snd_soc_component *component) +{ + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5682s->regmap, false); + regcache_sync(rt5682s->regmap); + + if (rt5682s->hs_jack) { + rt5682s->jack_type = 0; + rt5682s_sar_power_mode(component, SAR_PWR_NORMAL, 0); + mod_delayed_work(system_power_efficient_wq, + &rt5682s->jack_detect_work, msecs_to_jiffies(0)); + } + + return 0; +} +#else +#define rt5682s_suspend NULL +#define rt5682s_resume NULL +#endif + +static const struct snd_soc_dai_ops rt5682s_aif1_dai_ops = { + .hw_params = rt5682s_hw_params, + .set_fmt = rt5682s_set_dai_fmt, + .set_tdm_slot = rt5682s_set_tdm_slot, + .set_bclk_ratio = rt5682s_set_bclk1_ratio, +}; + +static const struct snd_soc_dai_ops rt5682s_aif2_dai_ops = { + .hw_params = rt5682s_hw_params, + .set_fmt = rt5682s_set_dai_fmt, + .set_bclk_ratio = rt5682s_set_bclk2_ratio, +}; + +static const struct snd_soc_component_driver rt5682s_soc_component_dev = { + .probe = rt5682s_probe, + .remove = rt5682s_remove, + .suspend = rt5682s_suspend, + .resume = rt5682s_resume, + .set_bias_level = rt5682s_set_bias_level, + .controls = rt5682s_snd_controls, + .num_controls = ARRAY_SIZE(rt5682s_snd_controls), + .dapm_widgets = rt5682s_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5682s_dapm_widgets), + .dapm_routes = rt5682s_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5682s_dapm_routes), + .set_sysclk = rt5682s_set_component_sysclk, + .set_pll = rt5682s_set_component_pll, + .set_jack = rt5682s_set_jack_detect, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int rt5682s_parse_dt(struct rt5682s_priv *rt5682s, struct device *dev) +{ + device_property_read_u32(dev, "realtek,dmic1-data-pin", + &rt5682s->pdata.dmic1_data_pin); + device_property_read_u32(dev, "realtek,dmic1-clk-pin", + &rt5682s->pdata.dmic1_clk_pin); + device_property_read_u32(dev, "realtek,jd-src", + &rt5682s->pdata.jd_src); + device_property_read_u32(dev, "realtek,dmic-clk-rate-hz", + &rt5682s->pdata.dmic_clk_rate); + device_property_read_u32(dev, "realtek,dmic-delay-ms", + &rt5682s->pdata.dmic_delay); + + rt5682s->pdata.ldo1_en = of_get_named_gpio(dev->of_node, + "realtek,ldo1-en-gpios", 0); + + if (device_property_read_string_array(dev, "clock-output-names", + rt5682s->pdata.dai_clk_names, + RT5682S_DAI_NUM_CLKS) < 0) + dev_warn(dev, "Using default DAI clk names: %s, %s\n", + rt5682s->pdata.dai_clk_names[RT5682S_DAI_WCLK_IDX], + rt5682s->pdata.dai_clk_names[RT5682S_DAI_BCLK_IDX]); + + rt5682s->pdata.dmic_clk_driving_high = device_property_read_bool(dev, + "realtek,dmic-clk-driving-high"); + + return 0; +} + +static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) +{ + unsigned int count, value; + + mutex_lock(&rt5682s->calibrate_mutex); + + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0xaa80); + usleep_range(15000, 20000); + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0xfa80); + regmap_write(rt5682s->regmap, RT5682S_PWR_DIG_1, 0x01c0); + regmap_write(rt5682s->regmap, RT5682S_MICBIAS_2, 0x0380); + regmap_write(rt5682s->regmap, RT5682S_GLB_CLK, 0x8000); + regmap_write(rt5682s->regmap, RT5682S_ADDA_CLK_1, 0x1001); + regmap_write(rt5682s->regmap, RT5682S_CHOP_DAC_2, 0x3030); + regmap_write(rt5682s->regmap, RT5682S_CHOP_ADC, 0xb000); + regmap_write(rt5682s->regmap, RT5682S_STO1_ADC_MIXER, 0x686c); + regmap_write(rt5682s->regmap, RT5682S_CAL_REC, 0x5151); + regmap_write(rt5682s->regmap, RT5682S_HP_CALIB_CTRL_2, 0x0321); + regmap_write(rt5682s->regmap, RT5682S_HP_LOGIC_CTRL_2, 0x0004); + regmap_write(rt5682s->regmap, RT5682S_HP_CALIB_CTRL_1, 0x7c00); + regmap_write(rt5682s->regmap, RT5682S_HP_CALIB_CTRL_1, 0xfc00); + + for (count = 0; count < 60; count++) { + regmap_read(rt5682s->regmap, RT5682S_HP_CALIB_ST_1, &value); + if (!(value & 0x8000)) + break; + + usleep_range(10000, 10005); + } + + if (count >= 60) + dev_err(rt5682s->component->dev, "HP Calibration Failure\n"); + + /* restore settings */ + regmap_write(rt5682s->regmap, RT5682S_MICBIAS_2, 0x0180); + regmap_write(rt5682s->regmap, RT5682S_CAL_REC, 0x5858); + regmap_write(rt5682s->regmap, RT5682S_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5682s->regmap, RT5682S_HP_CALIB_CTRL_2, 0x0320); + regmap_write(rt5682s->regmap, RT5682S_PWR_DIG_1, 0x00c0); + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0x0800); + regmap_write(rt5682s->regmap, RT5682S_GLB_CLK, 0x0000); + + mutex_unlock(&rt5682s->calibrate_mutex); +} + +static const struct regmap_config rt5682s_regmap = { + .reg_bits = 16, + .val_bits = 16, + .max_register = RT5682S_MAX_REG, + .volatile_reg = rt5682s_volatile_register, + .readable_reg = rt5682s_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5682s_reg, + .num_reg_defaults = ARRAY_SIZE(rt5682s_reg), + .use_single_read = true, + .use_single_write = true, +}; + +static struct snd_soc_dai_driver rt5682s_dai[] = { + { + .name = "rt5682s-aif1", + .id = RT5682S_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5682S_STEREO_RATES, + .formats = RT5682S_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5682S_STEREO_RATES, + .formats = RT5682S_FORMATS, + }, + .ops = &rt5682s_aif1_dai_ops, + }, + { + .name = "rt5682s-aif2", + .id = RT5682S_AIF2, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5682S_STEREO_RATES, + .formats = RT5682S_FORMATS, + }, + .ops = &rt5682s_aif2_dai_ops, + }, +}; + +static void rt5682s_i2c_disable_regulators(void *data) +{ + struct rt5682s_priv *rt5682s = data; + + regulator_bulk_disable(ARRAY_SIZE(rt5682s->supplies), rt5682s->supplies); +} + +static int rt5682s_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5682s_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5682s_priv *rt5682s; + int i, ret; + unsigned int val; + + rt5682s = devm_kzalloc(&i2c->dev, sizeof(struct rt5682s_priv), GFP_KERNEL); + if (!rt5682s) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5682s); + + rt5682s->pdata = i2s_default_platform_data; + + if (pdata) + rt5682s->pdata = *pdata; + else + rt5682s_parse_dt(rt5682s, &i2c->dev); + + rt5682s->regmap = devm_regmap_init_i2c(i2c, &rt5682s_regmap); + if (IS_ERR(rt5682s->regmap)) { + ret = PTR_ERR(rt5682s->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(rt5682s->supplies); i++) + rt5682s->supplies[i].supply = rt5682s_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, + ARRAY_SIZE(rt5682s->supplies), rt5682s->supplies); + if (ret) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&i2c->dev, rt5682s_i2c_disable_regulators, rt5682s); + if (ret) + return ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(rt5682s->supplies), rt5682s->supplies); + if (ret) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + if (gpio_is_valid(rt5682s->pdata.ldo1_en)) { + if (devm_gpio_request_one(&i2c->dev, rt5682s->pdata.ldo1_en, + GPIOF_OUT_INIT_HIGH, "rt5682s")) + dev_err(&i2c->dev, "Fail gpio_request gpio_ldo\n"); + } + + /* Sleep for 50 ms minimum */ + usleep_range(50000, 55000); + + regmap_read(rt5682s->regmap, RT5682S_DEVICE_ID, &val); + if (val != DEVICE_ID) { + dev_err(&i2c->dev, "Device with ID register %x is not rt5682s\n", val); + return -ENODEV; + } + + rt5682s_reset(rt5682s); + rt5682s_apply_patch_list(rt5682s, &i2c->dev); + + regmap_update_bits(rt5682s->regmap, RT5682S_PWR_DIG_2, + RT5682S_DLDO_I_LIMIT_MASK, RT5682S_DLDO_I_LIMIT_DIS); + usleep_range(20000, 25000); + + mutex_init(&rt5682s->calibrate_mutex); + mutex_init(&rt5682s->sar_mutex); + mutex_init(&rt5682s->jdet_mutex); + rt5682s_calibrate(rt5682s); + + regmap_update_bits(rt5682s->regmap, RT5682S_MICBIAS_2, + RT5682S_PWR_CLK25M_MASK | RT5682S_PWR_CLK1M_MASK, + RT5682S_PWR_CLK25M_PD | RT5682S_PWR_CLK1M_PU); + regmap_update_bits(rt5682s->regmap, RT5682S_PWR_ANLG_1, + RT5682S_PWR_BG, RT5682S_PWR_BG); + regmap_update_bits(rt5682s->regmap, RT5682S_HP_LOGIC_CTRL_2, + RT5682S_HP_SIG_SRC_MASK, RT5682S_HP_SIG_SRC_1BIT_CTL); + regmap_update_bits(rt5682s->regmap, RT5682S_HP_CHARGE_PUMP_2, + RT5682S_PM_HP_MASK, RT5682S_PM_HP_HV); + regmap_update_bits(rt5682s->regmap, RT5682S_HP_AMP_DET_CTL_1, + RT5682S_CP_SW_SIZE_MASK, RT5682S_CP_SW_SIZE_M); + + /* DMIC data pin */ + switch (rt5682s->pdata.dmic1_data_pin) { + case RT5682S_DMIC1_DATA_NULL: + break; + case RT5682S_DMIC1_DATA_GPIO2: /* share with LRCK2 */ + regmap_update_bits(rt5682s->regmap, RT5682S_DMIC_CTRL_1, + RT5682S_DMIC_1_DP_MASK, RT5682S_DMIC_1_DP_GPIO2); + regmap_update_bits(rt5682s->regmap, RT5682S_GPIO_CTRL_1, + RT5682S_GP2_PIN_MASK, RT5682S_GP2_PIN_DMIC_SDA); + break; + case RT5682S_DMIC1_DATA_GPIO5: /* share with DACDAT1 */ + regmap_update_bits(rt5682s->regmap, RT5682S_DMIC_CTRL_1, + RT5682S_DMIC_1_DP_MASK, RT5682S_DMIC_1_DP_GPIO5); + regmap_update_bits(rt5682s->regmap, RT5682S_GPIO_CTRL_1, + RT5682S_GP5_PIN_MASK, RT5682S_GP5_PIN_DMIC_SDA); + break; + default: + dev_warn(&i2c->dev, "invalid DMIC_DAT pin\n"); + break; + } + + /* DMIC clk pin */ + switch (rt5682s->pdata.dmic1_clk_pin) { + case RT5682S_DMIC1_CLK_NULL: + break; + case RT5682S_DMIC1_CLK_GPIO1: /* share with IRQ */ + regmap_update_bits(rt5682s->regmap, RT5682S_GPIO_CTRL_1, + RT5682S_GP1_PIN_MASK, RT5682S_GP1_PIN_DMIC_CLK); + break; + case RT5682S_DMIC1_CLK_GPIO3: /* share with BCLK2 */ + regmap_update_bits(rt5682s->regmap, RT5682S_GPIO_CTRL_1, + RT5682S_GP3_PIN_MASK, RT5682S_GP3_PIN_DMIC_CLK); + if (rt5682s->pdata.dmic_clk_driving_high) + regmap_update_bits(rt5682s->regmap, RT5682S_PAD_DRIVING_CTRL, + RT5682S_PAD_DRV_GP3_MASK, RT5682S_PAD_DRV_GP3_HIGH); + break; + default: + dev_warn(&i2c->dev, "invalid DMIC_CLK pin\n"); + break; + } + + INIT_DELAYED_WORK(&rt5682s->jack_detect_work, rt5682s_jack_detect_handler); + INIT_DELAYED_WORK(&rt5682s->jd_check_work, rt5682s_jd_check_handler); + + if (i2c->irq) { + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, rt5682s_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "rt5682s", rt5682s); + if (ret) + dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); + } + + return devm_snd_soc_register_component(&i2c->dev, &rt5682s_soc_component_dev, + rt5682s_dai, ARRAY_SIZE(rt5682s_dai)); +} + +static void rt5682s_i2c_shutdown(struct i2c_client *client) +{ + struct rt5682s_priv *rt5682s = i2c_get_clientdata(client); + + disable_irq(client->irq); + cancel_delayed_work_sync(&rt5682s->jack_detect_work); + cancel_delayed_work_sync(&rt5682s->jd_check_work); + + rt5682s_reset(rt5682s); +} + +static int rt5682s_i2c_remove(struct i2c_client *client) +{ + rt5682s_i2c_shutdown(client); + + return 0; +} + +static const struct of_device_id rt5682s_of_match[] = { + {.compatible = "realtek,rt5682s"}, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5682s_of_match); + +static const struct acpi_device_id rt5682s_acpi_match[] = { + {"RTL5682", 0,}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt5682s_acpi_match); + +static const struct i2c_device_id rt5682s_i2c_id[] = { + {"rt5682s", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt5682s_i2c_id); + +static struct i2c_driver rt5682s_i2c_driver = { + .driver = { + .name = "rt5682s", + .of_match_table = rt5682s_of_match, + .acpi_match_table = rt5682s_acpi_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = rt5682s_i2c_probe, + .remove = rt5682s_i2c_remove, + .shutdown = rt5682s_i2c_shutdown, + .id_table = rt5682s_i2c_id, +}; +module_i2c_driver(rt5682s_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5682I-VS driver"); +MODULE_AUTHOR("Derek Fang <derek.fang@realtek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5682s.h b/sound/soc/codecs/rt5682s.h new file mode 100644 index 000000000000..1bf2ef7ce578 --- /dev/null +++ b/sound/soc/codecs/rt5682s.h @@ -0,0 +1,1474 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt5682s.h -- RT5682I-VS ALSA SoC audio driver + * + * Copyright 2021 Realtek Microelectronics + * Author: Derek Fang <derek.fang@realtek.com> + */ + +#ifndef __RT5682S_H__ +#define __RT5682S_H__ + +#include <sound/rt5682s.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> + + +/* Info */ +#define RT5682S_RESET 0x0000 +#define RT5682S_VERSION_ID 0x00fd +#define RT5682S_VENDOR_ID 0x00fe +#define RT5682S_DEVICE_ID 0x00ff +/* I/O - Output */ +#define RT5682S_HP_CTRL_1 0x0002 +#define RT5682S_HP_CTRL_2 0x0003 +#define RT5682S_HPL_GAIN 0x0005 +#define RT5682S_HPR_GAIN 0x0006 + +#define RT5682S_I2C_CTRL 0x0008 + +/* I/O - Input */ +#define RT5682S_CBJ_BST_CTRL 0x000b +#define RT5682S_CBJ_DET_CTRL 0x000f +#define RT5682S_CBJ_CTRL_1 0x0010 +#define RT5682S_CBJ_CTRL_2 0x0011 +#define RT5682S_CBJ_CTRL_3 0x0012 +#define RT5682S_CBJ_CTRL_4 0x0013 +#define RT5682S_CBJ_CTRL_5 0x0014 +#define RT5682S_CBJ_CTRL_6 0x0015 +#define RT5682S_CBJ_CTRL_7 0x0016 +#define RT5682S_CBJ_CTRL_8 0x0017 +/* I/O - ADC/DAC/DMIC */ +#define RT5682S_DAC1_DIG_VOL 0x0019 +#define RT5682S_STO1_ADC_DIG_VOL 0x001c +#define RT5682S_STO1_ADC_BOOST 0x001f +#define RT5682S_HP_IMP_GAIN_1 0x0022 +#define RT5682S_HP_IMP_GAIN_2 0x0023 +/* Mixer - D-D */ +#define RT5682S_SIDETONE_CTRL 0x0024 +#define RT5682S_STO1_ADC_MIXER 0x0026 +#define RT5682S_AD_DA_MIXER 0x0029 +#define RT5682S_STO1_DAC_MIXER 0x002a +#define RT5682S_A_DAC1_MUX 0x002b +#define RT5682S_DIG_INF2_DATA 0x0030 +/* Mixer - ADC */ +#define RT5682S_REC_MIXER 0x003c +#define RT5682S_CAL_REC 0x0044 +/* HP Analog Offset Control */ +#define RT5682S_HP_ANA_OST_CTRL_1 0x004b +#define RT5682S_HP_ANA_OST_CTRL_2 0x004c +#define RT5682S_HP_ANA_OST_CTRL_3 0x004d +/* Power */ +#define RT5682S_PWR_DIG_1 0x0061 +#define RT5682S_PWR_DIG_2 0x0062 +#define RT5682S_PWR_ANLG_1 0x0063 +#define RT5682S_PWR_ANLG_2 0x0064 +#define RT5682S_PWR_ANLG_3 0x0065 +#define RT5682S_PWR_MIXER 0x0066 + +#define RT5682S_MB_CTRL 0x0067 +#define RT5682S_CLK_GATE_TCON_1 0x0068 +#define RT5682S_CLK_GATE_TCON_2 0x0069 +#define RT5682S_CLK_GATE_TCON_3 0x006a +/* Clock Detect */ +#define RT5682S_CLK_DET 0x006b +/* Filter Auto Reset */ +#define RT5682S_RESET_LPF_CTRL 0x006c +#define RT5682S_RESET_HPF_CTRL 0x006d +/* DMIC */ +#define RT5682S_DMIC_CTRL_1 0x006e +#define RT5682S_LPF_AD_DMIC 0x006f +/* Format - ADC/DAC */ +#define RT5682S_I2S1_SDP 0x0070 +#define RT5682S_I2S2_SDP 0x0071 +#define RT5682S_ADDA_CLK_1 0x0073 +#define RT5682S_ADDA_CLK_2 0x0074 +#define RT5682S_I2S1_F_DIV_CTRL_1 0x0075 +#define RT5682S_I2S1_F_DIV_CTRL_2 0x0076 +/* Format - TDM Control */ +#define RT5682S_TDM_CTRL 0x0079 +#define RT5682S_TDM_ADDA_CTRL_1 0x007a +#define RT5682S_TDM_ADDA_CTRL_2 0x007b +#define RT5682S_DATA_SEL_CTRL_1 0x007c +#define RT5682S_TDM_TCON_CTRL_1 0x007e +#define RT5682S_TDM_TCON_CTRL_2 0x007f +/* Function - Analog */ +#define RT5682S_GLB_CLK 0x0080 +#define RT5682S_PLL_TRACK_1 0x0083 +#define RT5682S_PLL_TRACK_2 0x0084 +#define RT5682S_PLL_TRACK_3 0x0085 +#define RT5682S_PLL_TRACK_4 0x0086 +#define RT5682S_PLL_TRACK_5 0x0087 +#define RT5682S_PLL_TRACK_6 0x0088 +#define RT5682S_PLL_TRACK_11 0x008c +#define RT5682S_DEPOP_1 0x008e +#define RT5682S_HP_CHARGE_PUMP_1 0x008f +#define RT5682S_HP_CHARGE_PUMP_2 0x0091 +#define RT5682S_HP_CHARGE_PUMP_3 0x0092 +#define RT5682S_MICBIAS_1 0x0093 +#define RT5682S_MICBIAS_2 0x0094 +#define RT5682S_MICBIAS_3 0x0095 + +#define RT5682S_PLL_TRACK_12 0x0096 +#define RT5682S_PLL_TRACK_14 0x0097 +#define RT5682S_PLL_CTRL_1 0x0098 +#define RT5682S_PLL_CTRL_2 0x0099 +#define RT5682S_PLL_CTRL_3 0x009a +#define RT5682S_PLL_CTRL_4 0x009b +#define RT5682S_PLL_CTRL_5 0x009c +#define RT5682S_PLL_CTRL_6 0x009d +#define RT5682S_PLL_CTRL_7 0x009e + +#define RT5682S_RC_CLK_CTRL 0x009f +#define RT5682S_I2S2_M_CLK_CTRL_1 0x00a0 +#define RT5682S_I2S2_F_DIV_CTRL_1 0x00a3 +#define RT5682S_I2S2_F_DIV_CTRL_2 0x00a4 + +#define RT5682S_IRQ_CTRL_1 0x00b6 +#define RT5682S_IRQ_CTRL_2 0x00b7 +#define RT5682S_IRQ_CTRL_3 0x00b8 +#define RT5682S_IRQ_CTRL_4 0x00b9 +#define RT5682S_INT_ST_1 0x00be +#define RT5682S_GPIO_CTRL_1 0x00c0 +#define RT5682S_GPIO_CTRL_2 0x00c1 +#define RT5682S_GPIO_ST 0x00c2 +#define RT5682S_HP_AMP_DET_CTRL_1 0x00d0 +#define RT5682S_MID_HP_AMP_DET 0x00d2 +#define RT5682S_LOW_HP_AMP_DET 0x00d3 +#define RT5682S_DELAY_BUF_CTRL 0x00d4 +#define RT5682S_SV_ZCD_1 0x00d9 +#define RT5682S_SV_ZCD_2 0x00da +#define RT5682S_IL_CMD_1 0x00db +#define RT5682S_IL_CMD_2 0x00dc +#define RT5682S_IL_CMD_3 0x00dd +#define RT5682S_IL_CMD_4 0x00de +#define RT5682S_IL_CMD_5 0x00df +#define RT5682S_IL_CMD_6 0x00e0 +#define RT5682S_4BTN_IL_CMD_1 0x00e2 +#define RT5682S_4BTN_IL_CMD_2 0x00e3 +#define RT5682S_4BTN_IL_CMD_3 0x00e4 +#define RT5682S_4BTN_IL_CMD_4 0x00e5 +#define RT5682S_4BTN_IL_CMD_5 0x00e6 +#define RT5682S_4BTN_IL_CMD_6 0x00e7 +#define RT5682S_4BTN_IL_CMD_7 0x00e8 + +#define RT5682S_ADC_STO1_HP_CTRL_1 0x00ea +#define RT5682S_ADC_STO1_HP_CTRL_2 0x00eb +#define RT5682S_AJD1_CTRL 0x00f0 +#define RT5682S_JD_CTRL_1 0x00f6 +/* General Control */ +#define RT5682S_DUMMY_1 0x00fa +#define RT5682S_DUMMY_2 0x00fb +#define RT5682S_DUMMY_3 0x00fc + +#define RT5682S_DAC_ADC_DIG_VOL1 0x0100 +#define RT5682S_BIAS_CUR_CTRL_2 0x010b +#define RT5682S_BIAS_CUR_CTRL_3 0x010c +#define RT5682S_BIAS_CUR_CTRL_4 0x010d +#define RT5682S_BIAS_CUR_CTRL_5 0x010e +#define RT5682S_BIAS_CUR_CTRL_6 0x010f +#define RT5682S_BIAS_CUR_CTRL_7 0x0110 +#define RT5682S_BIAS_CUR_CTRL_8 0x0111 +#define RT5682S_BIAS_CUR_CTRL_9 0x0112 +#define RT5682S_BIAS_CUR_CTRL_10 0x0113 +#define RT5682S_VREF_REC_OP_FB_CAP_CTRL_1 0x0117 +#define RT5682S_VREF_REC_OP_FB_CAP_CTRL_2 0x0118 +#define RT5682S_CHARGE_PUMP_1 0x0125 +#define RT5682S_DIG_IN_CTRL_1 0x0132 +#define RT5682S_PAD_DRIVING_CTRL 0x0136 +#define RT5682S_CHOP_DAC_1 0x0139 +#define RT5682S_CHOP_DAC_2 0x013a +#define RT5682S_CHOP_ADC 0x013b +#define RT5682S_CALIB_ADC_CTRL 0x013c +#define RT5682S_VOL_TEST 0x013f +#define RT5682S_SPKVDD_DET_ST 0x0142 +#define RT5682S_TEST_MODE_CTRL_1 0x0145 +#define RT5682S_TEST_MODE_CTRL_2 0x0146 +#define RT5682S_TEST_MODE_CTRL_3 0x0147 +#define RT5682S_TEST_MODE_CTRL_4 0x0148 +#define RT5682S_PLL_INTERNAL_1 0x0156 +#define RT5682S_PLL_INTERNAL_2 0x0157 +#define RT5682S_PLL_INTERNAL_3 0x0158 +#define RT5682S_PLL_INTERNAL_4 0x0159 +#define RT5682S_STO_NG2_CTRL_1 0x0160 +#define RT5682S_STO_NG2_CTRL_2 0x0161 +#define RT5682S_STO_NG2_CTRL_3 0x0162 +#define RT5682S_STO_NG2_CTRL_4 0x0163 +#define RT5682S_STO_NG2_CTRL_5 0x0164 +#define RT5682S_STO_NG2_CTRL_6 0x0165 +#define RT5682S_STO_NG2_CTRL_7 0x0166 +#define RT5682S_STO_NG2_CTRL_8 0x0167 +#define RT5682S_STO_NG2_CTRL_9 0x0168 +#define RT5682S_STO_NG2_CTRL_10 0x0169 +#define RT5682S_STO1_DAC_SIL_DET 0x0190 +#define RT5682S_SIL_PSV_CTRL1 0x0194 +#define RT5682S_SIL_PSV_CTRL2 0x0195 +#define RT5682S_SIL_PSV_CTRL3 0x0197 +#define RT5682S_SIL_PSV_CTRL4 0x0198 +#define RT5682S_SIL_PSV_CTRL5 0x0199 +#define RT5682S_HP_IMP_SENS_CTRL_1 0x01ac +#define RT5682S_HP_IMP_SENS_CTRL_2 0x01ad +#define RT5682S_HP_IMP_SENS_CTRL_3 0x01ae +#define RT5682S_HP_IMP_SENS_CTRL_4 0x01af +#define RT5682S_HP_IMP_SENS_CTRL_5 0x01b0 +#define RT5682S_HP_IMP_SENS_CTRL_6 0x01b1 +#define RT5682S_HP_IMP_SENS_CTRL_7 0x01b2 +#define RT5682S_HP_IMP_SENS_CTRL_8 0x01b3 +#define RT5682S_HP_IMP_SENS_CTRL_9 0x01b4 +#define RT5682S_HP_IMP_SENS_CTRL_10 0x01b5 +#define RT5682S_HP_IMP_SENS_CTRL_11 0x01b6 +#define RT5682S_HP_IMP_SENS_CTRL_12 0x01b7 +#define RT5682S_HP_IMP_SENS_CTRL_13 0x01b8 +#define RT5682S_HP_IMP_SENS_CTRL_14 0x01b9 +#define RT5682S_HP_IMP_SENS_CTRL_15 0x01ba +#define RT5682S_HP_IMP_SENS_CTRL_16 0x01bb +#define RT5682S_HP_IMP_SENS_CTRL_17 0x01bc +#define RT5682S_HP_IMP_SENS_CTRL_18 0x01bd +#define RT5682S_HP_IMP_SENS_CTRL_19 0x01be +#define RT5682S_HP_IMP_SENS_CTRL_20 0x01bf +#define RT5682S_HP_IMP_SENS_CTRL_21 0x01c0 +#define RT5682S_HP_IMP_SENS_CTRL_22 0x01c1 +#define RT5682S_HP_IMP_SENS_CTRL_23 0x01c2 +#define RT5682S_HP_IMP_SENS_CTRL_24 0x01c3 +#define RT5682S_HP_IMP_SENS_CTRL_25 0x01c4 +#define RT5682S_HP_IMP_SENS_CTRL_26 0x01c5 +#define RT5682S_HP_IMP_SENS_CTRL_27 0x01c6 +#define RT5682S_HP_IMP_SENS_CTRL_28 0x01c7 +#define RT5682S_HP_IMP_SENS_CTRL_29 0x01c8 +#define RT5682S_HP_IMP_SENS_CTRL_30 0x01c9 +#define RT5682S_HP_IMP_SENS_CTRL_31 0x01ca +#define RT5682S_HP_IMP_SENS_CTRL_32 0x01cb +#define RT5682S_HP_IMP_SENS_CTRL_33 0x01cc +#define RT5682S_HP_IMP_SENS_CTRL_34 0x01cd +#define RT5682S_HP_IMP_SENS_CTRL_35 0x01ce +#define RT5682S_HP_IMP_SENS_CTRL_36 0x01cf +#define RT5682S_HP_IMP_SENS_CTRL_37 0x01d0 +#define RT5682S_HP_IMP_SENS_CTRL_38 0x01d1 +#define RT5682S_HP_IMP_SENS_CTRL_39 0x01d2 +#define RT5682S_HP_IMP_SENS_CTRL_40 0x01d3 +#define RT5682S_HP_IMP_SENS_CTRL_41 0x01d4 +#define RT5682S_HP_IMP_SENS_CTRL_42 0x01d5 +#define RT5682S_HP_IMP_SENS_CTRL_43 0x01d6 +#define RT5682S_HP_IMP_SENS_CTRL_44 0x01d7 +#define RT5682S_HP_IMP_SENS_CTRL_45 0x01d8 +#define RT5682S_HP_IMP_SENS_CTRL_46 0x01d9 +#define RT5682S_HP_LOGIC_CTRL_1 0x01da +#define RT5682S_HP_LOGIC_CTRL_2 0x01db +#define RT5682S_HP_LOGIC_CTRL_3 0x01dc +#define RT5682S_HP_CALIB_CTRL_1 0x01de +#define RT5682S_HP_CALIB_CTRL_2 0x01df +#define RT5682S_HP_CALIB_CTRL_3 0x01e0 +#define RT5682S_HP_CALIB_CTRL_4 0x01e1 +#define RT5682S_HP_CALIB_CTRL_5 0x01e2 +#define RT5682S_HP_CALIB_CTRL_6 0x01e3 +#define RT5682S_HP_CALIB_CTRL_7 0x01e4 +#define RT5682S_HP_CALIB_CTRL_8 0x01e5 +#define RT5682S_HP_CALIB_CTRL_9 0x01e6 +#define RT5682S_HP_CALIB_CTRL_10 0x01e7 +#define RT5682S_HP_CALIB_CTRL_11 0x01e8 +#define RT5682S_HP_CALIB_ST_1 0x01ea +#define RT5682S_HP_CALIB_ST_2 0x01eb +#define RT5682S_HP_CALIB_ST_3 0x01ec +#define RT5682S_HP_CALIB_ST_4 0x01ed +#define RT5682S_HP_CALIB_ST_5 0x01ee +#define RT5682S_HP_CALIB_ST_6 0x01ef +#define RT5682S_HP_CALIB_ST_7 0x01f0 +#define RT5682S_HP_CALIB_ST_8 0x01f1 +#define RT5682S_HP_CALIB_ST_9 0x01f2 +#define RT5682S_HP_CALIB_ST_10 0x01f3 +#define RT5682S_HP_CALIB_ST_11 0x01f4 +#define RT5682S_SAR_IL_CMD_1 0x0210 +#define RT5682S_SAR_IL_CMD_2 0x0211 +#define RT5682S_SAR_IL_CMD_3 0x0212 +#define RT5682S_SAR_IL_CMD_4 0x0213 +#define RT5682S_SAR_IL_CMD_5 0x0214 +#define RT5682S_SAR_IL_CMD_6 0x0215 +#define RT5682S_SAR_IL_CMD_7 0x0216 +#define RT5682S_SAR_IL_CMD_8 0x0217 +#define RT5682S_SAR_IL_CMD_9 0x0218 +#define RT5682S_SAR_IL_CMD_10 0x0219 +#define RT5682S_SAR_IL_CMD_11 0x021a +#define RT5682S_SAR_IL_CMD_12 0x021b +#define RT5682S_SAR_IL_CMD_13 0x021c +#define RT5682S_SAR_IL_CMD_14 0x021d +#define RT5682S_DUMMY_4 0x02fa +#define RT5682S_DUMMY_5 0x02fb +#define RT5682S_DUMMY_6 0x02fc +#define RT5682S_VERSION_ID_HIDE 0x03fe +#define RT5682S_VERSION_ID_CUS 0x03ff +#define RT5682S_SCAN_CTL 0x0500 +#define RT5682S_HP_AMP_DET 0x0600 +#define RT5682S_BIAS_CUR_CTRL_11 0x0610 +#define RT5682S_BIAS_CUR_CTRL_12 0x0611 +#define RT5682S_BIAS_CUR_CTRL_13 0x0620 +#define RT5682S_BIAS_CUR_CTRL_14 0x0621 +#define RT5682S_BIAS_CUR_CTRL_15 0x0630 +#define RT5682S_BIAS_CUR_CTRL_16 0x0631 +#define RT5682S_BIAS_CUR_CTRL_17 0x0640 +#define RT5682S_BIAS_CUR_CTRL_18 0x0641 +#define RT5682S_I2C_TRANS_CTRL 0x07fa +#define RT5682S_DUMMY_7 0x08fa +#define RT5682S_DUMMY_8 0x08fb +#define RT5682S_DMIC_FLOAT_DET 0x0d00 +#define RT5682S_HA_CMP_OP_1 0x1100 +#define RT5682S_HA_CMP_OP_2 0x1101 +#define RT5682S_HA_CMP_OP_3 0x1102 +#define RT5682S_HA_CMP_OP_4 0x1103 +#define RT5682S_HA_CMP_OP_5 0x1104 +#define RT5682S_HA_CMP_OP_6 0x1105 +#define RT5682S_HA_CMP_OP_7 0x1106 +#define RT5682S_HA_CMP_OP_8 0x1107 +#define RT5682S_HA_CMP_OP_9 0x1108 +#define RT5682S_HA_CMP_OP_10 0x1109 +#define RT5682S_HA_CMP_OP_11 0x110a +#define RT5682S_HA_CMP_OP_12 0x110b +#define RT5682S_HA_CMP_OP_13 0x110c +#define RT5682S_HA_CMP_OP_14 0x1111 +#define RT5682S_HA_CMP_OP_15 0x1112 +#define RT5682S_HA_CMP_OP_16 0x1113 +#define RT5682S_HA_CMP_OP_17 0x1114 +#define RT5682S_HA_CMP_OP_18 0x1115 +#define RT5682S_HA_CMP_OP_19 0x1116 +#define RT5682S_HA_CMP_OP_20 0x1117 +#define RT5682S_HA_CMP_OP_21 0x1118 +#define RT5682S_HA_CMP_OP_22 0x1119 +#define RT5682S_HA_CMP_OP_23 0x111a +#define RT5682S_HA_CMP_OP_24 0x111b +#define RT5682S_HA_CMP_OP_25 0x111c +#define RT5682S_NEW_CBJ_DET_CTL_1 0x1401 +#define RT5682S_NEW_CBJ_DET_CTL_2 0x1402 +#define RT5682S_NEW_CBJ_DET_CTL_3 0x1403 +#define RT5682S_NEW_CBJ_DET_CTL_4 0x1404 +#define RT5682S_NEW_CBJ_DET_CTL_5 0x1406 +#define RT5682S_NEW_CBJ_DET_CTL_6 0x1407 +#define RT5682S_NEW_CBJ_DET_CTL_7 0x1408 +#define RT5682S_NEW_CBJ_DET_CTL_8 0x1409 +#define RT5682S_NEW_CBJ_DET_CTL_9 0x140a +#define RT5682S_NEW_CBJ_DET_CTL_10 0x140b +#define RT5682S_NEW_CBJ_DET_CTL_11 0x140c +#define RT5682S_NEW_CBJ_DET_CTL_12 0x140d +#define RT5682S_NEW_CBJ_DET_CTL_13 0x140e +#define RT5682S_NEW_CBJ_DET_CTL_14 0x140f +#define RT5682S_NEW_CBJ_DET_CTL_15 0x1410 +#define RT5682S_NEW_CBJ_DET_CTL_16 0x1411 +#define RT5682S_DA_FILTER_1 0x1801 +#define RT5682S_DA_FILTER_2 0x1802 +#define RT5682S_DA_FILTER_3 0x1803 +#define RT5682S_DA_FILTER_4 0x1804 +#define RT5682S_DA_FILTER_5 0x1805 +#define RT5682S_CLK_SW_TEST_1 0x2c00 +#define RT5682S_CLK_SW_TEST_2 0x3400 +#define RT5682S_CLK_SW_TEST_3 0x3404 +#define RT5682S_CLK_SW_TEST_4 0x3405 +#define RT5682S_CLK_SW_TEST_5 0x3406 +#define RT5682S_CLK_SW_TEST_6 0x3407 +#define RT5682S_CLK_SW_TEST_7 0x3408 +#define RT5682S_CLK_SW_TEST_8 0x3409 +#define RT5682S_CLK_SW_TEST_9 0x340a +#define RT5682S_CLK_SW_TEST_10 0x340b +#define RT5682S_CLK_SW_TEST_11 0x340c +#define RT5682S_CLK_SW_TEST_12 0x340d +#define RT5682S_CLK_SW_TEST_13 0x340e +#define RT5682S_CLK_SW_TEST_14 0x340f +#define RT5682S_EFUSE_MANU_WRITE_1 0x3410 +#define RT5682S_EFUSE_MANU_WRITE_2 0x3411 +#define RT5682S_EFUSE_MANU_WRITE_3 0x3412 +#define RT5682S_EFUSE_MANU_WRITE_4 0x3413 +#define RT5682S_EFUSE_MANU_WRITE_5 0x3414 +#define RT5682S_EFUSE_MANU_WRITE_6 0x3415 +#define RT5682S_EFUSE_READ_1 0x3424 +#define RT5682S_EFUSE_READ_2 0x3425 +#define RT5682S_EFUSE_READ_3 0x3426 +#define RT5682S_EFUSE_READ_4 0x3427 +#define RT5682S_EFUSE_READ_5 0x3428 +#define RT5682S_EFUSE_READ_6 0x3429 +#define RT5682S_EFUSE_READ_7 0x342a +#define RT5682S_EFUSE_READ_8 0x342b +#define RT5682S_EFUSE_READ_9 0x342c +#define RT5682S_EFUSE_READ_10 0x342d +#define RT5682S_EFUSE_READ_11 0x342e +#define RT5682S_EFUSE_READ_12 0x342f +#define RT5682S_EFUSE_READ_13 0x3430 +#define RT5682S_EFUSE_READ_14 0x3431 +#define RT5682S_EFUSE_READ_15 0x3432 +#define RT5682S_EFUSE_READ_16 0x3433 +#define RT5682S_EFUSE_READ_17 0x3434 +#define RT5682S_EFUSE_READ_18 0x3435 +#define RT5682S_EFUSE_TIMING_CTL_1 0x3440 +#define RT5682S_EFUSE_TIMING_CTL_2 0x3441 +#define RT5682S_PILOT_DIG_CTL_1 0x3500 +#define RT5682S_PILOT_DIG_CTL_2 0x3501 +#define RT5682S_HP_AMP_DET_CTL_1 0x3b00 +#define RT5682S_HP_AMP_DET_CTL_2 0x3b01 +#define RT5682S_HP_AMP_DET_CTL_3 0x3b02 +#define RT5682S_HP_AMP_DET_CTL_4 0x3b03 + +#define RT5682S_MAX_REG (RT5682S_HP_AMP_DET_CTL_4) + +/* global definition */ +#define RT5682S_L_MUTE (0x1 << 15) +#define RT5682S_L_MUTE_SFT 15 +#define RT5682S_R_MUTE (0x1 << 7) +#define RT5682S_R_MUTE_SFT 7 +#define RT5682S_L_VOL_SFT 8 +#define RT5682S_R_VOL_SFT 0 +#define RT5682S_CLK_SRC_MCLK (0x0) +#define RT5682S_CLK_SRC_PLL1 (0x1) +#define RT5682S_CLK_SRC_PLL2 (0x2) +#define RT5682S_CLK_SRC_RCCLK (0x4) /* 25M */ + + +/* Headphone Amp Control 2 (0x0003) */ +#define RT5682S_HPO_L_PATH_MASK (0x1 << 14) +#define RT5682S_HPO_L_PATH_EN (0x1 << 14) +#define RT5682S_HPO_L_PATH_DIS (0x0 << 14) +#define RT5682S_HPO_R_PATH_MASK (0x1 << 13) +#define RT5682S_HPO_R_PATH_EN (0x1 << 13) +#define RT5682S_HPO_R_PATH_DIS (0x0 << 13) +#define RT5682S_HPO_SEL_IP_EN_SW (0x1) +#define RT5682S_HPO_IP_EN_GATING (0x1) +#define RT5682S_HPO_IP_NO_GATING (0x0) + +/*Headphone Amp L/R Analog Gain and Digital NG2 Gain Control (0x0005 0x0006)*/ +#define RT5682S_G_HP (0xf << 8) +#define RT5682S_G_HP_SFT 8 +#define RT5682S_G_STO_DA_DMIX (0xf) +#define RT5682S_G_STO_DA_SFT 0 + +/* Embeeded Jack and Type Detection Control 2 (0x0010) */ +#define RT5682S_EMB_JD_MASK (0x1 << 15) +#define RT5682S_EMB_JD_EN (0x1 << 15) +#define RT5682S_EMB_JD_EN_SFT 15 +#define RT5682S_EMB_JD_RST (0x1 << 14) +#define RT5682S_JD_MODE (0x1 << 13) +#define RT5682S_JD_MODE_SFT 13 +#define RT5682S_DET_TYPE (0x1 << 12) +#define RT5682S_DET_TYPE_SFT 12 +#define RT5682S_POLA_EXT_JD_MASK (0x1 << 11) +#define RT5682S_POLA_EXT_JD_LOW (0x1 << 11) +#define RT5682S_POLA_EXT_JD_HIGH (0x0 << 11) +#define RT5682S_SEL_FAST_OFF_MASK (0x3 << 9) +#define RT5682S_SEL_FAST_OFF_SFT 9 +#define RT5682S_POL_FAST_OFF_MASK (0x1 << 8) +#define RT5682S_POL_FAST_OFF_HIGH (0x1 << 8) +#define RT5682S_POL_FAST_OFF_LOW (0x0 << 8) +#define RT5682S_FAST_OFF_MASK (0x1 << 7) +#define RT5682S_FAST_OFF_EN (0x1 << 7) +#define RT5682S_FAST_OFF_DIS (0x0 << 7) +#define RT5682S_VREF_POW_MASK (0x1 << 6) +#define RT5682S_VREF_POW_FSM (0x0 << 6) +#define RT5682S_VREF_POW_REG (0x1 << 6) +#define RT5682S_MB1_PATH_BIT 5 +#define RT5682S_MB1_PATH_MASK (0x1 << 5) +#define RT5682S_CTRL_MB1_REG (0x1 << 5) +#define RT5682S_CTRL_MB1_FSM (0x0 << 5) +#define RT5682S_MB2_PATH_BIT 4 +#define RT5682S_MB2_PATH_MASK (0x1 << 4) +#define RT5682S_CTRL_MB2_REG (0x1 << 4) +#define RT5682S_CTRL_MB2_FSM (0x0 << 4) +#define RT5682S_TRIG_JD_MASK (0x1 << 3) +#define RT5682S_TRIG_JD_HIGH (0x1 << 3) +#define RT5682S_TRIG_JD_LOW (0x0 << 3) +#define RT5682S_MIC_CAP_MASK (0x1 << 1) +#define RT5682S_MIC_CAP_HS (0x1 << 1) +#define RT5682S_MIC_CAP_HP (0x0 << 1) +#define RT5682S_MIC_CAP_SRC_MASK (0x1) +#define RT5682S_MIC_CAP_SRC_REG (0x1) +#define RT5682S_MIC_CAP_SRC_ANA (0x0) + +/* Embeeded Jack and Type Detection Control 3 (0x0011) */ +#define RT5682S_SEL_CBJ_TYPE_SLOW (0x1 << 15) +#define RT5682S_SEL_CBJ_TYPE_NORM (0x0 << 15) +#define RT5682S_SEL_CBJ_TYPE_MASK (0x1 << 15) +#define RT5682S_POW_BG_MB1_MASK (0x1 << 13) +#define RT5682S_POW_BG_MB1_REG (0x1 << 13) +#define RT5682S_POW_BG_MB1_FSM (0x0 << 13) +#define RT5682S_POW_BG_MB2_MASK (0x1 << 12) +#define RT5682S_POW_BG_MB2_REG (0x1 << 12) +#define RT5682S_POW_BG_MB2_FSM (0x0 << 12) +#define RT5682S_EXT_JD_SRC (0x7 << 4) +#define RT5682S_EXT_JD_SRC_SFT 4 +#define RT5682S_EXT_JD_SRC_GPIO_JD1 (0x0 << 4) +#define RT5682S_EXT_JD_SRC_GPIO_JD2 (0x1 << 4) +#define RT5682S_EXT_JD_SRC_JDH (0x2 << 4) +#define RT5682S_EXT_JD_SRC_JDL (0x3 << 4) +#define RT5682S_EXT_JD_SRC_MANUAL (0x4 << 4) +#define RT5682S_JACK_TYPE_MASK (0x3) + +/* Combo Jack and Type Detection Control 4 (0x0012) */ +#define RT5682S_CBJ_IN_BUF_MASK (0x1 << 7) +#define RT5682S_CBJ_IN_BUF_EN (0x1 << 7) +#define RT5682S_CBJ_IN_BUF_DIS (0x0 << 7) +#define RT5682S_CBJ_IN_BUF_BIT 7 + +/* Combo Jack and Type Detection Control 5 (0x0013) */ +#define RT5682S_SEL_SHT_MID_TON_MASK (0x3 << 12) +#define RT5682S_SEL_SHT_MID_TON_2 (0x0 << 12) +#define RT5682S_SEL_SHT_MID_TON_3 (0x1 << 12) +#define RT5682S_CBJ_JD_TEST_MASK (0x1 << 6) +#define RT5682S_CBJ_JD_TEST_NORM (0x0 << 6) +#define RT5682S_CBJ_JD_TEST_MODE (0x1 << 6) + +/* Combo Jack and Type Detection Control 6 (0x0014) */ +#define RT5682S_JD_FAST_OFF_SRC_MASK (0x7 << 8) +#define RT5682S_JD_FAST_OFF_SRC_JDH (0x6 << 8) +#define RT5682S_JD_FAST_OFF_SRC_GPIO6 (0x5 << 8) +#define RT5682S_JD_FAST_OFF_SRC_GPIO5 (0x4 << 8) +#define RT5682S_JD_FAST_OFF_SRC_GPIO4 (0x3 << 8) +#define RT5682S_JD_FAST_OFF_SRC_GPIO3 (0x2 << 8) +#define RT5682S_JD_FAST_OFF_SRC_GPIO2 (0x1 << 8) +#define RT5682S_JD_FAST_OFF_SRC_GPIO1 (0x0 << 8) + +/* DAC1 Digital Volume (0x0019) */ +#define RT5682S_DAC_L1_VOL_MASK (0xff << 8) +#define RT5682S_DAC_L1_VOL_SFT 8 +#define RT5682S_DAC_R1_VOL_MASK (0xff) +#define RT5682S_DAC_R1_VOL_SFT 0 + +/* ADC Digital Volume Control (0x001c) */ +#define RT5682S_ADC_L_VOL_MASK (0x7f << 8) +#define RT5682S_ADC_L_VOL_SFT 8 +#define RT5682S_ADC_R_VOL_MASK (0x7f) +#define RT5682S_ADC_R_VOL_SFT 0 + +/* Stereo1 ADC Boost Gain Control (0x001f) */ +#define RT5682S_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5682S_STO1_ADC_L_BST_SFT 14 +#define RT5682S_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5682S_STO1_ADC_R_BST_SFT 12 + +/* Sidetone Control (0x0024) */ +#define RT5682S_ST_SRC_SEL (0x1 << 8) +#define RT5682S_ST_SRC_SFT 8 +#define RT5682S_ST_EN_MASK (0x1 << 6) +#define RT5682S_ST_DIS (0x0 << 6) +#define RT5682S_ST_EN (0x1 << 6) +#define RT5682S_ST_EN_SFT 6 + +/* Stereo1 ADC Mixer Control (0x0026) */ +#define RT5682S_M_STO1_ADC_L1 (0x1 << 15) +#define RT5682S_M_STO1_ADC_L1_SFT 15 +#define RT5682S_M_STO1_ADC_L2 (0x1 << 14) +#define RT5682S_M_STO1_ADC_L2_SFT 14 +#define RT5682S_STO1_ADC1L_SRC_MASK (0x1 << 13) +#define RT5682S_STO1_ADC1L_SRC_SFT 13 +#define RT5682S_STO1_ADC1_SRC_ADC (0x1 << 13) +#define RT5682S_STO1_ADC1_SRC_DACMIX (0x0 << 13) +#define RT5682S_STO1_ADC2L_SRC_MASK (0x1 << 12) +#define RT5682S_STO1_ADC2L_SRC_SFT 12 +#define RT5682S_STO1_ADCL_SRC_MASK (0x3 << 10) +#define RT5682S_STO1_ADCL_SRC_SFT 10 +#define RT5682S_M_STO1_ADC_R1 (0x1 << 7) +#define RT5682S_M_STO1_ADC_R1_SFT 7 +#define RT5682S_M_STO1_ADC_R2 (0x1 << 6) +#define RT5682S_M_STO1_ADC_R2_SFT 6 +#define RT5682S_STO1_ADC1R_SRC_MASK (0x1 << 5) +#define RT5682S_STO1_ADC1R_SRC_SFT 5 +#define RT5682S_STO1_ADC2R_SRC_MASK (0x1 << 4) +#define RT5682S_STO1_ADC2R_SRC_SFT 4 +#define RT5682S_STO1_ADCR_SRC_MASK (0x3 << 2) +#define RT5682S_STO1_ADCR_SRC_SFT 2 + +/* ADC Mixer to DAC Mixer Control (0x0029) */ +#define RT5682S_M_ADCMIX_L (0x1 << 15) +#define RT5682S_M_ADCMIX_L_SFT 15 +#define RT5682S_M_DAC1_L (0x1 << 14) +#define RT5682S_M_DAC1_L_SFT 14 +#define RT5682S_M_ADCMIX_R (0x1 << 7) +#define RT5682S_M_ADCMIX_R_SFT 7 +#define RT5682S_M_DAC1_R (0x1 << 6) +#define RT5682S_M_DAC1_R_SFT 6 + +/* Stereo1 DAC Mixer Control (0x002a) */ +#define RT5682S_M_DAC_L1_STO_L (0x1 << 15) +#define RT5682S_M_DAC_L1_STO_L_SFT 15 +#define RT5682S_G_DAC_L1_STO_L_MASK (0x1 << 14) +#define RT5682S_G_DAC_L1_STO_L_SFT 14 +#define RT5682S_M_DAC_R1_STO_L (0x1 << 13) +#define RT5682S_M_DAC_R1_STO_L_SFT 13 +#define RT5682S_G_DAC_R1_STO_L_MASK (0x1 << 12) +#define RT5682S_G_DAC_R1_STO_L_SFT 12 +#define RT5682S_M_DAC_L1_STO_R (0x1 << 7) +#define RT5682S_M_DAC_L1_STO_R_SFT 7 +#define RT5682S_G_DAC_L1_STO_R_MASK (0x1 << 6) +#define RT5682S_G_DAC_L1_STO_R_SFT 6 +#define RT5682S_M_DAC_R1_STO_R (0x1 << 5) +#define RT5682S_M_DAC_R1_STO_R_SFT 5 +#define RT5682S_G_DAC_R1_STO_R_MASK (0x1 << 4) +#define RT5682S_G_DAC_R1_STO_R_SFT 4 + +/* Analog DAC1 Input Source Control (0x002b) */ +#define RT5682S_M_ST_STO_L (0x1 << 9) +#define RT5682S_M_ST_STO_L_SFT 9 +#define RT5682S_M_ST_STO_R (0x1 << 8) +#define RT5682S_M_ST_STO_R_SFT 8 +#define RT5682S_DAC_L1_SRC_MASK (0x1 << 4) +#define RT5682S_A_DACL1_SFT 4 +#define RT5682S_DAC_R1_SRC_MASK (0x1) +#define RT5682S_A_DACR1_SFT 0 + +/* Digital Interface Data Control (0x0030) */ +#define RT5682S_IF2_DAC_SEL_MASK (0x3 << 2) +#define RT5682S_IF2_DAC_SEL_SFT 2 +#define RT5682S_IF2_ADC_SEL_MASK (0x3 << 0) +#define RT5682S_IF2_ADC_SEL_SFT 0 + +/* REC Left/Right Mixer Control 2 (0x003c) */ +#define RT5682S_BST_CBJ_MASK (0x3f << 8) +#define RT5682S_BST_CBJ_SFT 8 +#define RT5682S_M_CBJ_RM1_L (0x1 << 7) +#define RT5682S_M_CBJ_RM1_L_SFT 7 +#define RT5682S_M_CBJ_RM1_R (0x1 << 6) +#define RT5682S_M_CBJ_RM1_R_SFT 6 + +/* REC Left/Right Mixer Calibration Control(0x0044) */ +#define RT5682S_PWR_RM1_R_BIT 8 +#define RT5682S_PWR_RM1_L_BIT 0 + +/* Power Management for Digital 1 (0x0061) */ +#define RT5682S_PWR_I2S1 (0x1 << 15) +#define RT5682S_PWR_I2S1_BIT 15 +#define RT5682S_PWR_I2S2 (0x1 << 14) +#define RT5682S_PWR_I2S2_BIT 14 +#define RT5682S_PRE_CHR_DAC_L1 (0x1 << 13) +#define RT5682S_PRE_CHR_DAC_L1_BIT 13 +#define RT5682S_PRE_CHR_DAC_R1 (0x1 << 12) +#define RT5682S_PRE_CHR_DAC_R1_BIT 12 +#define RT5682S_PWR_DAC_L1 (0x1 << 11) +#define RT5682S_PWR_DAC_L1_BIT 11 +#define RT5682S_PWR_DAC_R1 (0x1 << 10) +#define RT5682S_PWR_DAC_R1_BIT 10 +#define RT5682S_PWR_LDO (0x1 << 8) +#define RT5682S_PWR_LDO_BIT 8 +#define RT5682S_PWR_D2S_L (0x1 << 7) +#define RT5682S_PWR_D2S_L_BIT 7 +#define RT5682S_PWR_D2S_R (0x1 << 6) +#define RT5682S_PWR_D2S_R_BIT 6 +#define RT5682S_PWR_ADC_L1 (0x1 << 4) +#define RT5682S_PWR_ADC_L1_BIT 4 +#define RT5682S_PWR_ADC_R1 (0x1 << 3) +#define RT5682S_PWR_ADC_R1_BIT 3 +#define RT5682S_EFUSE_SW_EN (0x1 << 2) +#define RT5682S_EFUSE_SW_DIS (0x0 << 2) +#define RT5682S_PWR_EFUSE (0x1 << 1) +#define RT5682S_PWR_EFUSE_BIT 1 +#define RT5682S_DIG_GATE_CTRL (0x1 << 0) +#define RT5682S_DIG_GATE_CTRL_SFT 0 + +/* Power Management for Digital 2 (0x0062) */ +#define RT5682S_PWR_ADC_S1F (0x1 << 15) +#define RT5682S_PWR_ADC_S1F_BIT 15 +#define RT5682S_PWR_DAC_S1F (0x1 << 10) +#define RT5682S_PWR_DAC_S1F_BIT 10 +#define RT5682S_DLDO_I_LIMIT_MASK (0x1 << 7) +#define RT5682S_DLDO_I_LIMIT_EN (0x1 << 7) +#define RT5682S_DLDO_I_LIMIT_DIS (0x0 << 7) +#define RT5682S_DLDO_I_BIAS_SEL_4 (0x1 << 6) +#define RT5682S_DLDO_I_BIAS_SEL_0 (0x0 << 6) +#define RT5682S_DLDO_REG_TEST_1 (0x1 << 5) +#define RT5682S_DLDO_REG_TEST_0 (0x0 << 5) +#define RT5682S_DLDO_SRC_REG (0x1 << 4) +#define RT5682S_DLDO_SRC_EFUSE (0x0 << 4) + +/* Power Management for Analog 1 (0x0063) */ +#define RT5682S_PWR_VREF1 (0x1 << 15) +#define RT5682S_PWR_VREF1_BIT 15 +#define RT5682S_PWR_FV1 (0x1 << 14) +#define RT5682S_PWR_FV1_BIT 14 +#define RT5682S_PWR_VREF2 (0x1 << 13) +#define RT5682S_PWR_VREF2_BIT 13 +#define RT5682S_PWR_FV2 (0x1 << 12) +#define RT5682S_PWR_FV2_BIT 12 +#define RT5682S_LDO1_DBG_MASK (0x3 << 10) +#define RT5682S_PWR_MB (0x1 << 9) +#define RT5682S_PWR_MB_BIT 9 +#define RT5682S_PWR_BG (0x1 << 7) +#define RT5682S_PWR_BG_BIT 7 +#define RT5682S_LDO1_BYPASS_MASK (0x1 << 6) +#define RT5682S_LDO1_BYPASS (0x1 << 6) +#define RT5682S_LDO1_NOT_BYPASS (0x0 << 6) + +/* Power Management for Analog 2 (0x0064) */ +#define RT5682S_PWR_MCLK0_WD (0x1 << 15) +#define RT5682S_PWR_MCLK0_WD_BIT 15 +#define RT5682S_PWR_MCLK1_WD (0x1 << 14) +#define RT5682S_PWR_MCLK1_WD_BIT 14 +#define RT5682S_RST_MCLK0 (0x1 << 13) +#define RT5682S_RST_MCLK0_BIT 13 +#define RT5682S_RST_MCLK1 (0x1 << 12) +#define RT5682S_RST_MCLK1_BIT 12 +#define RT5682S_PWR_MB1 (0x1 << 11) +#define RT5682S_PWR_MB1_PWR_DOWN (0x0 << 11) +#define RT5682S_PWR_MB1_BIT 11 +#define RT5682S_PWR_MB2 (0x1 << 10) +#define RT5682S_PWR_MB2_PWR_DOWN (0x0 << 10) +#define RT5682S_PWR_MB2_BIT 10 +#define RT5682S_PWR_JD_MASK (0x1 << 0) +#define RT5682S_PWR_JD_ENABLE (0x1 << 0) +#define RT5682S_PWR_JD_DISABLE (0x0 << 0) + +/* Power Management for Analog 3 (0x0065) */ +#define RT5682S_PWR_LDO_PLLA (0x1 << 15) +#define RT5682S_PWR_LDO_PLLA_BIT 15 +#define RT5682S_PWR_LDO_PLLB (0x1 << 14) +#define RT5682S_PWR_LDO_PLLB_BIT 14 +#define RT5682S_PWR_BIAS_PLLA (0x1 << 13) +#define RT5682S_PWR_BIAS_PLLA_BIT 13 +#define RT5682S_PWR_BIAS_PLLB (0x1 << 12) +#define RT5682S_PWR_BIAS_PLLB_BIT 12 +#define RT5682S_PWR_CBJ (0x1 << 9) +#define RT5682S_PWR_CBJ_BIT 9 +#define RT5682S_RSTB_PLLB (0x1 << 7) +#define RT5682S_RSTB_PLLB_BIT 7 +#define RT5682S_RSTB_PLLA (0x1 << 6) +#define RT5682S_RSTB_PLLA_BIT 6 +#define RT5682S_PWR_PLLB (0x1 << 5) +#define RT5682S_PWR_PLLB_BIT 5 +#define RT5682S_PWR_PLLA (0x1 << 4) +#define RT5682S_PWR_PLLA_BIT 4 +#define RT5682S_PWR_LDO_MB2 (0x1 << 2) +#define RT5682S_PWR_LDO_MB2_BIT 2 +#define RT5682S_PWR_LDO_MB1 (0x1 << 1) +#define RT5682S_PWR_LDO_MB1_BIT 1 +#define RT5682S_PWR_BGLDO (0x1 << 0) +#define RT5682S_PWR_BGLDO_BIT 0 + +/* Power Management for Mixer (0x0066) */ +#define RT5682S_PWR_CLK_COMP_8FS (0x1 << 15) +#define RT5682S_PWR_CLK_COMP_8FS_BIT 15 +#define RT5682S_DBG_BGLDO_MASK (0x3 << 12) +#define RT5682S_DBG_BGLDO_SFT 12 +#define RT5682S_DBG_BGLDO_MB1_MASK (0x3 << 10) +#define RT5682S_DBG_BGLDO_MB1_SFT 10 +#define RT5682S_DBG_BGLDO_MB2_MASK (0x3 << 8) +#define RT5682S_DBG_BGLDO_MB2_SFT 8 +#define RT5682S_DLDO_BGLDO_MASK (0x3 << 6) +#define RT5682S_DLDO_BGLDO_MB2_SFT 6 +#define RT5682S_PWR_STO1_DAC_L (0x1 << 5) +#define RT5682S_PWR_STO1_DAC_L_BIT 5 +#define RT5682S_PWR_STO1_DAC_R (0x1 << 4) +#define RT5682S_PWR_STO1_DAC_R_BIT 4 +#define RT5682S_DVO_BGLDO_MB1_MASK (0x3 << 2) +#define RT5682S_DVO_BGLDO_MB1_SFT 2 +#define RT5682S_DVO_BGLDO_MB2_MASK (0x3 << 0) + +/* MCLK and System Clock Detection Control (0x006b) */ +#define RT5682S_SYS_CLK_DET (0x1 << 15) +#define RT5682S_SYS_CLK_DET_SFT 15 +#define RT5682S_PLL1_CLK_DET (0x1 << 14) +#define RT5682S_PLL1_CLK_DET_SFT 14 + +/* Digital Microphone Control 1 (0x006e) */ +#define RT5682S_DMIC_1_EN_MASK (0x1 << 15) +#define RT5682S_DMIC_1_EN_SFT 15 +#define RT5682S_DMIC_1_DIS (0x0 << 15) +#define RT5682S_DMIC_1_EN (0x1 << 15) +#define RT5682S_FIFO_CLK_DIV_MASK (0x7 << 12) +#define RT5682S_FIFO_CLK_DIV_2 (0x1 << 12) +#define RT5682S_DMIC_1_DP_MASK (0x3 << 4) +#define RT5682S_DMIC_1_DP_SFT 4 +#define RT5682S_DMIC_1_DP_GPIO2 (0x0 << 4) +#define RT5682S_DMIC_1_DP_GPIO5 (0x1 << 4) +#define RT5682S_DMIC_CLK_MASK (0xf << 0) +#define RT5682S_DMIC_CLK_SFT 0 + +/* I2S1 Audio Serial Data Port Control (0x0070) */ +#define RT5682S_SEL_ADCDAT_MASK (0x1 << 15) +#define RT5682S_SEL_ADCDAT_OUT (0x0 << 15) +#define RT5682S_SEL_ADCDAT_IN (0x1 << 15) +#define RT5682S_SEL_ADCDAT_SFT 15 +#define RT5682S_I2S1_TX_CHL_MASK (0x7 << 12) +#define RT5682S_I2S1_TX_CHL_SFT 12 +#define RT5682S_I2S1_TX_CHL_16 (0x0 << 12) +#define RT5682S_I2S1_TX_CHL_20 (0x1 << 12) +#define RT5682S_I2S1_TX_CHL_24 (0x2 << 12) +#define RT5682S_I2S1_TX_CHL_32 (0x3 << 12) +#define RT5682S_I2S1_TX_CHL_8 (0x4 << 12) +#define RT5682S_I2S1_RX_CHL_MASK (0x7 << 8) +#define RT5682S_I2S1_RX_CHL_SFT 8 +#define RT5682S_I2S1_RX_CHL_16 (0x0 << 8) +#define RT5682S_I2S1_RX_CHL_20 (0x1 << 8) +#define RT5682S_I2S1_RX_CHL_24 (0x2 << 8) +#define RT5682S_I2S1_RX_CHL_32 (0x3 << 8) +#define RT5682S_I2S1_RX_CHL_8 (0x4 << 8) +#define RT5682S_I2S1_MONO_MASK (0x1 << 7) +#define RT5682S_I2S1_MONO_EN (0x1 << 7) +#define RT5682S_I2S1_MONO_DIS (0x0 << 7) +#define RT5682S_I2S1_DL_MASK (0x7 << 4) +#define RT5682S_I2S1_DL_SFT 4 +#define RT5682S_I2S1_DL_16 (0x0 << 4) +#define RT5682S_I2S1_DL_20 (0x1 << 4) +#define RT5682S_I2S1_DL_24 (0x2 << 4) +#define RT5682S_I2S1_DL_32 (0x3 << 4) +#define RT5682S_I2S1_DL_8 (0x4 << 4) + +/* I2S1/2 Audio Serial Data Port Control (0x0071) */ +#define RT5682S_I2S2_MS_MASK (0x1 << 15) +#define RT5682S_I2S2_MS_SFT 15 +#define RT5682S_I2S2_MS_M (0x0 << 15) +#define RT5682S_I2S2_MS_S (0x1 << 15) +#define RT5682S_I2S2_PIN_CFG_MASK (0x1 << 14) +#define RT5682S_I2S2_PIN_CFG_SFT 14 +#define RT5682S_I2S2_OUT_MASK (0x1 << 9) +#define RT5682S_I2S2_OUT_SFT 9 +#define RT5682S_I2S2_OUT_UM (0x0 << 9) +#define RT5682S_I2S2_OUT_M (0x1 << 9) +#define RT5682S_I2S_BP_MASK (0x1 << 8) +#define RT5682S_I2S_BP_SFT 8 +#define RT5682S_I2S_BP_NOR (0x0 << 8) +#define RT5682S_I2S_BP_INV (0x1 << 8) +#define RT5682S_I2S2_MONO_MASK (0x1 << 7) +#define RT5682S_I2S2_MONO_EN (0x1 << 7) +#define RT5682S_I2S2_MONO_DIS (0x0 << 7) +#define RT5682S_I2S2_DL_MASK (0x7 << 4) +#define RT5682S_I2S2_DL_SFT 4 +#define RT5682S_I2S2_DL_8 (0x0 << 4) +#define RT5682S_I2S2_DL_16 (0x1 << 4) +#define RT5682S_I2S2_DL_20 (0x2 << 4) +#define RT5682S_I2S2_DL_24 (0x3 << 4) +#define RT5682S_I2S2_DL_32 (0x4 << 4) +#define RT5682S_I2S_DF_MASK (0x7) +#define RT5682S_I2S_DF_SFT 0 +#define RT5682S_I2S_DF_I2S (0x0) +#define RT5682S_I2S_DF_LEFT (0x1) +#define RT5682S_I2S_DF_PCM_A (0x2) +#define RT5682S_I2S_DF_PCM_B (0x3) +#define RT5682S_I2S_DF_PCM_A_N (0x6) +#define RT5682S_I2S_DF_PCM_B_N (0x7) + +/* ADC/DAC Clock Control 1 (0x0073) */ +#define RT5682S_ADC_OSR_MASK (0xf << 12) +#define RT5682S_ADC_OSR_SFT 12 +#define RT5682S_ADC_OSR_D_1 (0x0 << 12) +#define RT5682S_ADC_OSR_D_2 (0x1 << 12) +#define RT5682S_ADC_OSR_D_4 (0x2 << 12) +#define RT5682S_ADC_OSR_D_6 (0x3 << 12) +#define RT5682S_ADC_OSR_D_8 (0x4 << 12) +#define RT5682S_ADC_OSR_D_12 (0x5 << 12) +#define RT5682S_ADC_OSR_D_16 (0x6 << 12) +#define RT5682S_ADC_OSR_D_24 (0x7 << 12) +#define RT5682S_ADC_OSR_D_32 (0x8 << 12) +#define RT5682S_ADC_OSR_D_48 (0x9 << 12) +#define RT5682S_I2S_M_D_MASK (0xf << 8) +#define RT5682S_I2S_M_D_SFT 8 +#define RT5682S_I2S_M_D_1 (0x0 << 8) +#define RT5682S_I2S_M_D_2 (0x1 << 8) +#define RT5682S_I2S_M_D_3 (0x2 << 8) +#define RT5682S_I2S_M_D_4 (0x3 << 8) +#define RT5682S_I2S_M_D_6 (0x4 << 8) +#define RT5682S_I2S_M_D_8 (0x5 << 8) +#define RT5682S_I2S_M_D_12 (0x6 << 8) +#define RT5682S_I2S_M_D_16 (0x7 << 8) +#define RT5682S_I2S_M_D_24 (0x8 << 8) +#define RT5682S_I2S_M_D_32 (0x9 << 8) +#define RT5682S_I2S_M_D_48 (0x10 << 8) +#define RT5682S_I2S_M_CLK_SRC_MASK (0x7 << 4) +#define RT5682S_I2S_M_CLK_SRC_SFT 4 +#define RT5682S_DAC_OSR_MASK (0xf << 0) +#define RT5682S_DAC_OSR_SFT 0 +#define RT5682S_DAC_OSR_D_1 (0x0 << 0) +#define RT5682S_DAC_OSR_D_2 (0x1 << 0) +#define RT5682S_DAC_OSR_D_4 (0x2 << 0) +#define RT5682S_DAC_OSR_D_6 (0x3 << 0) +#define RT5682S_DAC_OSR_D_8 (0x4 << 0) +#define RT5682S_DAC_OSR_D_12 (0x5 << 0) +#define RT5682S_DAC_OSR_D_16 (0x6 << 0) +#define RT5682S_DAC_OSR_D_24 (0x7 << 0) +#define RT5682S_DAC_OSR_D_32 (0x8 << 0) +#define RT5682S_DAC_OSR_D_48 (0x9 << 0) + +/* ADC/DAC Clock Control 2 (0x0074) */ +#define RT5682S_I2S2_BCLK_MS2_MASK (0x1 << 11) +#define RT5682S_I2S2_BCLK_MS2_SFT 11 +#define RT5682S_I2S2_BCLK_MS2_32 (0x0 << 11) +#define RT5682S_I2S2_BCLK_MS2_64 (0x1 << 11) + + +/* TDM control 1 (0x0079) */ +#define RT5682S_TDM_TX_CH_MASK (0x3 << 12) +#define RT5682S_TDM_TX_CH_2 (0x0 << 12) +#define RT5682S_TDM_TX_CH_4 (0x1 << 12) +#define RT5682S_TDM_TX_CH_6 (0x2 << 12) +#define RT5682S_TDM_TX_CH_8 (0x3 << 12) +#define RT5682S_TDM_RX_CH_MASK (0x3 << 8) +#define RT5682S_TDM_RX_CH_2 (0x0 << 8) +#define RT5682S_TDM_RX_CH_4 (0x1 << 8) +#define RT5682S_TDM_RX_CH_6 (0x2 << 8) +#define RT5682S_TDM_RX_CH_8 (0x3 << 8) +#define RT5682S_TDM_ADC_LCA_MASK (0x7 << 4) +#define RT5682S_TDM_ADC_LCA_SFT 4 +#define RT5682S_TDM_ADC_DL_SFT 0 + +/* TDM control 2 (0x007a) */ +#define RT5682S_IF1_ADC1_SEL_SFT 14 +#define RT5682S_IF1_ADC2_SEL_SFT 12 +#define RT5682S_IF1_ADC3_SEL_SFT 10 +#define RT5682S_IF1_ADC4_SEL_SFT 8 +#define RT5682S_TDM_ADC_SEL_SFT 3 + +/* TDM control 3 (0x007b) */ +#define RT5682S_TDM_EN (0x1 << 7) + +/* TDM/I2S control (0x007e) */ +#define RT5682S_TDM_S_BP_MASK (0x1 << 15) +#define RT5682S_TDM_S_BP_SFT 15 +#define RT5682S_TDM_S_BP_NOR (0x0 << 15) +#define RT5682S_TDM_S_BP_INV (0x1 << 15) +#define RT5682S_TDM_S_LP_MASK (0x1 << 14) +#define RT5682S_TDM_S_LP_SFT 14 +#define RT5682S_TDM_S_LP_NOR (0x0 << 14) +#define RT5682S_TDM_S_LP_INV (0x1 << 14) +#define RT5682S_TDM_DF_MASK (0x7 << 11) +#define RT5682S_TDM_DF_SFT 11 +#define RT5682S_TDM_DF_I2S (0x0 << 11) +#define RT5682S_TDM_DF_LEFT (0x1 << 11) +#define RT5682S_TDM_DF_PCM_A (0x2 << 11) +#define RT5682S_TDM_DF_PCM_B (0x3 << 11) +#define RT5682S_TDM_DF_PCM_A_N (0x6 << 11) +#define RT5682S_TDM_DF_PCM_B_N (0x7 << 11) +#define RT5682S_TDM_BCLK_MS1_MASK (0x3 << 8) +#define RT5682S_TDM_BCLK_MS1_SFT 8 +#define RT5682S_TDM_BCLK_MS1_32 (0x0 << 8) +#define RT5682S_TDM_BCLK_MS1_64 (0x1 << 8) +#define RT5682S_TDM_BCLK_MS1_128 (0x2 << 8) +#define RT5682S_TDM_BCLK_MS1_256 (0x3 << 8) +#define RT5682S_TDM_BCLK_MS1_16 (0x4 << 8) +#define RT5682S_TDM_CL_MASK (0x3 << 4) +#define RT5682S_TDM_CL_16 (0x0 << 4) +#define RT5682S_TDM_CL_20 (0x1 << 4) +#define RT5682S_TDM_CL_24 (0x2 << 4) +#define RT5682S_TDM_CL_32 (0x3 << 4) +#define RT5682S_TDM_M_BP_MASK (0x1 << 2) +#define RT5682S_TDM_M_BP_SFT 2 +#define RT5682S_TDM_M_BP_NOR (0x0 << 2) +#define RT5682S_TDM_M_BP_INV (0x1 << 2) +#define RT5682S_TDM_M_LP_MASK (0x1 << 1) +#define RT5682S_TDM_M_LP_SFT 1 +#define RT5682S_TDM_M_LP_NOR (0x0 << 1) +#define RT5682S_TDM_M_LP_INV (0x1 << 1) +#define RT5682S_TDM_MS_MASK (0x1 << 0) +#define RT5682S_TDM_MS_SFT 0 +#define RT5682S_TDM_MS_S (0x0 << 0) +#define RT5682S_TDM_MS_M (0x1 << 0) + +/* Global Clock Control (0x0080) */ +#define RT5682S_SCLK_SRC_MASK (0x7 << 13) +#define RT5682S_SCLK_SRC_SFT 13 +#define RT5682S_PLL_SRC_MASK (0x3 << 8) +#define RT5682S_PLL_SRC_SFT 8 +#define RT5682S_PLL_SRC_MCLK (0x0 << 8) +#define RT5682S_PLL_SRC_BCLK1 (0x1 << 8) +#define RT5682S_PLL_SRC_RC (0x3 << 8) + +/* PLL tracking mode 1 (0x0083) */ +#define RT5682S_DA_ASRC_MASK (0x1 << 13) +#define RT5682S_DA_ASRC_SFT 13 +#define RT5682S_DAC_STO1_ASRC_MASK (0x1 << 12) +#define RT5682S_DAC_STO1_ASRC_SFT 12 +#define RT5682S_AD_ASRC_MASK (0x1 << 8) +#define RT5682S_AD_ASRC_SFT 8 +#define RT5682S_AD_ASRC_SEL_MASK (0x1 << 4) +#define RT5682S_AD_ASRC_SEL_SFT 4 +#define RT5682S_DMIC_ASRC_MASK (0x1 << 3) +#define RT5682S_DMIC_ASRC_SFT 3 +#define RT5682S_ADC_STO1_ASRC_MASK (0x1 << 2) +#define RT5682S_ADC_STO1_ASRC_SFT 2 +#define RT5682S_DA_ASRC_SEL_MASK (0x1 << 0) +#define RT5682S_DA_ASRC_SEL_SFT 0 + +/* PLL tracking mode 2 3 (0x0084)(0x0085)*/ +#define RT5682S_FILTER_CLK_SEL_MASK (0x7 << 12) +#define RT5682S_FILTER_CLK_SEL_SFT 12 +#define RT5682S_FILTER_CLK_DIV_MASK (0xf << 8) +#define RT5682S_FILTER_CLK_DIV_SFT 8 + +/* ASRC Control 4 (0x0086) */ +#define RT5682S_ASRCIN_FTK_N1_MASK (0x3 << 14) +#define RT5682S_ASRCIN_FTK_N1_SFT 14 +#define RT5682S_ASRCIN_FTK_N2_MASK (0x3 << 12) +#define RT5682S_ASRCIN_FTK_N2_SFT 12 +#define RT5682S_ASRCIN_FTK_M1_MASK (0x7 << 8) +#define RT5682S_ASRCIN_FTK_M1_SFT 8 +#define RT5682S_ASRCIN_FTK_M2_MASK (0x7 << 4) +#define RT5682S_ASRCIN_FTK_M2_SFT 4 + +/* ASRC Control 11 (0x008c) */ +#define RT5682S_ASRCIN_AUTO_CLKOUT_MASK (0x1 << 5) +#define RT5682S_ASRCIN_AUTO_CLKOUT_EN (0x1 << 5) +#define RT5682S_ASRCIN_AUTO_CLKOUT_DIS (0x0 << 5) +#define RT5682S_ASRCIN_AUTO_RST_MASK (0x1 << 4) +#define RT5682S_ASRCIN_AUTO_RST_EN (0x1 << 4) +#define RT5682S_ASRCIN_AUTO_RST_DIS (0x0 << 4) +#define RT5682S_SEL_LRCK_DET_MASK (0x3) +#define RT5682S_SEL_LRCK_DET_DIV8 (0x3) +#define RT5682S_SEL_LRCK_DET_DIV4 (0x2) +#define RT5682S_SEL_LRCK_DET_DIV2 (0x1) +#define RT5682S_SEL_LRCK_DET_DIV1 (0x0) + +/* Depop Mode Control 1 (0x008e) */ +#define RT5682S_OUT_HP_L_EN (0x1 << 6) +#define RT5682S_OUT_HP_R_EN (0x1 << 5) +#define RT5682S_LDO_PUMP_EN (0x1 << 4) +#define RT5682S_LDO_PUMP_EN_SFT 4 +#define RT5682S_PUMP_EN (0x1 << 3) +#define RT5682S_PUMP_EN_SFT 3 +#define RT5682S_CAPLESS_L_EN (0x1 << 1) +#define RT5682S_CAPLESS_L_EN_SFT 1 +#define RT5682S_CAPLESS_R_EN (0x1 << 0) +#define RT5682S_CAPLESS_R_EN_SFT 0 + +/* Depop Mode Control 2 (0x8f) */ +#define RT5682S_RAMP_MASK (0x1 << 12) +#define RT5682S_RAMP_SFT 12 +#define RT5682S_RAMP_DIS (0x0 << 12) +#define RT5682S_RAMP_EN (0x1 << 12) +#define RT5682S_BPS_MASK (0x1 << 11) +#define RT5682S_BPS_SFT 11 +#define RT5682S_BPS_DIS (0x0 << 11) +#define RT5682S_BPS_EN (0x1 << 11) +#define RT5682S_FAST_UPDN_MASK (0x1 << 10) +#define RT5682S_FAST_UPDN_SFT 10 +#define RT5682S_FAST_UPDN_DIS (0x0 << 10) +#define RT5682S_FAST_UPDN_EN (0x1 << 10) +#define RT5682S_VLO_MASK (0x1 << 7) +#define RT5682S_VLO_SFT 7 +#define RT5682S_VLO_3V (0x0 << 7) +#define RT5682S_VLO_33V (0x1 << 7) + +/* HPOUT charge pump 1 (0x0091) */ +#define RT5682S_OSW_L_MASK (0x1 << 11) +#define RT5682S_OSW_L_SFT 11 +#define RT5682S_OSW_L_DIS (0x0 << 11) +#define RT5682S_OSW_L_EN (0x1 << 11) +#define RT5682S_OSW_R_MASK (0x1 << 10) +#define RT5682S_OSW_R_SFT 10 +#define RT5682S_OSW_R_DIS (0x0 << 10) +#define RT5682S_OSW_R_EN (0x1 << 10) +#define RT5682S_PM_HP_MASK (0x3 << 8) +#define RT5682S_PM_HP_SFT 8 +#define RT5682S_PM_HP_LV (0x0 << 8) +#define RT5682S_PM_HP_MV (0x1 << 8) +#define RT5682S_PM_HP_HV (0x2 << 8) + +/* Micbias Control1 (0x93) */ +#define RT5682S_MIC1_OV_MASK (0x3 << 14) +#define RT5682S_MIC1_OV_SFT 14 +#define RT5682S_MIC1_OV_2V7 (0x0 << 14) +#define RT5682S_MIC1_OV_2V4 (0x1 << 14) +#define RT5682S_MIC1_OV_2V25 (0x3 << 14) +#define RT5682S_MIC1_OV_1V8 (0x4 << 14) +#define RT5682S_MIC2_OV_MASK (0x3 << 8) +#define RT5682S_MIC2_OV_SFT 8 +#define RT5682S_MIC2_OV_2V7 (0x0 << 8) +#define RT5682S_MIC2_OV_2V4 (0x1 << 8) +#define RT5682S_MIC2_OV_2V25 (0x3 << 8) +#define RT5682S_MIC2_OV_1V8 (0x4 << 8) + +/* Micbias Control2 (0x0094) */ +#define RT5682S_PWR_CLK25M_MASK (0x1 << 9) +#define RT5682S_PWR_CLK25M_SFT 9 +#define RT5682S_PWR_CLK25M_PD (0x0 << 9) +#define RT5682S_PWR_CLK25M_PU (0x1 << 9) +#define RT5682S_PWR_CLK1M_MASK (0x1 << 8) +#define RT5682S_PWR_CLK1M_SFT 8 +#define RT5682S_PWR_CLK1M_PD (0x0 << 8) +#define RT5682S_PWR_CLK1M_PU (0x1 << 8) + +/* PLL M/N/K Code Control 1 (0x0098) */ +#define RT5682S_PLLA_N_MASK (0x1ff << 0) + +/* PLL M/N/K Code Control 2 (0x0099) */ +#define RT5682S_PLLA_M_MASK (0x1f << 8) +#define RT5682S_PLLA_M_SFT 8 +#define RT5682S_PLLA_K_MASK (0x1f << 0) + +/* PLL M/N/K Code Control 3 (0x009a) */ +#define RT5682S_PLLB_N_MASK (0x3ff << 0) + +/* PLL M/N/K Code Control 4 (0x009b) */ +#define RT5682S_PLLB_M_MASK (0x1f << 8) +#define RT5682S_PLLB_M_SFT 8 +#define RT5682S_PLLB_K_MASK (0x1f << 0) + +/* PLL M/N/K Code Control 6 (0x009d) */ +#define RT5682S_PLLB_SEL_PS_MASK (0x1 << 13) +#define RT5682S_PLLB_SEL_PS_SFT 13 +#define RT5682S_PLLB_BYP_PS_MASK (0x1 << 12) +#define RT5682S_PLLB_BYP_PS_SFT 12 +#define RT5682S_PLLB_M_BP_MASK (0x1 << 11) +#define RT5682S_PLLB_M_BP_SFT 11 +#define RT5682S_PLLB_K_BP_MASK (0x1 << 10) +#define RT5682S_PLLB_K_BP_SFT 10 +#define RT5682S_PLLA_M_BP_MASK (0x1 << 7) +#define RT5682S_PLLA_M_BP_SFT 7 +#define RT5682S_PLLA_K_BP_MASK (0x1 << 6) +#define RT5682S_PLLA_K_BP_SFT 6 + +/* PLL M/N/K Code Control 7 (0x009e) */ +#define RT5682S_PLLB_SRC_MASK (0x1) +#define RT5682S_PLLB_SRC_DFIN (0x1) +#define RT5682S_PLLB_SRC_PLLA (0x0) + +/* RC Clock Control (0x009f) */ +#define RT5682S_POW_IRQ (0x1 << 15) +#define RT5682S_POW_JDH (0x1 << 14) + +/* I2S2 Master Mode Clock Control 1 (0x00a0) */ +#define RT5682S_I2S2_M_CLK_SRC_MASK (0x7 << 4) +#define RT5682S_I2S2_M_CLK_SRC_SFT 4 +#define RT5682S_I2S2_M_D_MASK (0xf << 0) +#define RT5682S_I2S2_M_D_1 (0x0) +#define RT5682S_I2S2_M_D_2 (0x1) +#define RT5682S_I2S2_M_D_3 (0x2) +#define RT5682S_I2S2_M_D_4 (0x3) +#define RT5682S_I2S2_M_D_6 (0x4) +#define RT5682S_I2S2_M_D_8 (0x5) +#define RT5682S_I2S2_M_D_12 (0x6) +#define RT5682S_I2S2_M_D_16 (0x7) +#define RT5682S_I2S2_M_D_24 (0x8) +#define RT5682S_I2S2_M_D_32 (0x9) +#define RT5682S_I2S2_M_D_48 (0xa) +#define RT5682S_I2S2_M_D_SFT 0 + +/* IRQ Control 1 (0x00b6) */ +#define RT5682S_JD1_PULSE_EN_MASK (0x1 << 10) +#define RT5682S_JD1_PULSE_EN_SFT 10 +#define RT5682S_JD1_PULSE_DIS (0x0 << 10) +#define RT5682S_JD1_PULSE_EN (0x1 << 10) + +/* IRQ Control 2 (0x00b7) */ +#define RT5682S_JD1_EN_MASK (0x1 << 15) +#define RT5682S_JD1_EN_SFT 15 +#define RT5682S_JD1_DIS (0x0 << 15) +#define RT5682S_JD1_EN (0x1 << 15) +#define RT5682S_JD1_POL_MASK (0x1 << 13) +#define RT5682S_JD1_POL_NOR (0x0 << 13) +#define RT5682S_JD1_POL_INV (0x1 << 13) +#define RT5682S_JD1_IRQ_MASK (0x1 << 10) +#define RT5682S_JD1_IRQ_LEV (0x0 << 10) +#define RT5682S_JD1_IRQ_PUL (0x1 << 10) + +/* IRQ Control 3 (0x00b8) */ +#define RT5682S_IL_IRQ_MASK (0x1 << 7) +#define RT5682S_IL_IRQ_DIS (0x0 << 7) +#define RT5682S_IL_IRQ_EN (0x1 << 7) +#define RT5682S_IL_IRQ_TYPE_MASK (0x1 << 4) +#define RT5682S_IL_IRQ_LEV (0x0 << 4) +#define RT5682S_IL_IRQ_PUL (0x1 << 4) + +/* GPIO Control 1 (0x00c0) */ +#define RT5682S_GP1_PIN_MASK (0x3 << 14) +#define RT5682S_GP1_PIN_SFT 14 +#define RT5682S_GP1_PIN_GPIO1 (0x0 << 14) +#define RT5682S_GP1_PIN_IRQ (0x1 << 14) +#define RT5682S_GP1_PIN_DMIC_CLK (0x2 << 14) +#define RT5682S_GP2_PIN_MASK (0x3 << 12) +#define RT5682S_GP2_PIN_SFT 12 +#define RT5682S_GP2_PIN_GPIO2 (0x0 << 12) +#define RT5682S_GP2_PIN_LRCK2 (0x1 << 12) +#define RT5682S_GP2_PIN_DMIC_SDA (0x2 << 12) +#define RT5682S_GP3_PIN_MASK (0x3 << 10) +#define RT5682S_GP3_PIN_SFT 10 +#define RT5682S_GP3_PIN_GPIO3 (0x0 << 10) +#define RT5682S_GP3_PIN_BCLK2 (0x1 << 10) +#define RT5682S_GP3_PIN_DMIC_CLK (0x2 << 10) +#define RT5682S_GP4_PIN_MASK (0x3 << 8) +#define RT5682S_GP4_PIN_SFT 8 +#define RT5682S_GP4_PIN_GPIO4 (0x0 << 8) +#define RT5682S_GP4_PIN_ADCDAT1 (0x1 << 8) +#define RT5682S_GP4_PIN_DMIC_CLK (0x2 << 8) +#define RT5682S_GP4_PIN_ADCDAT2 (0x3 << 8) +#define RT5682S_GP5_PIN_MASK (0x3 << 6) +#define RT5682S_GP5_PIN_SFT 6 +#define RT5682S_GP5_PIN_GPIO5 (0x0 << 6) +#define RT5682S_GP5_PIN_DACDAT1 (0x1 << 6) +#define RT5682S_GP5_PIN_DMIC_SDA (0x2 << 6) +#define RT5682S_GP6_PIN_MASK (0x1 << 5) +#define RT5682S_GP6_PIN_SFT 5 +#define RT5682S_GP6_PIN_GPIO6 (0x0 << 5) +#define RT5682S_GP6_PIN_LRCK1 (0x1 << 5) + +/* GPIO Control 2 (0x00c1)*/ +#define RT5682S_GP1_PF_MASK (0x1 << 15) +#define RT5682S_GP1_PF_IN (0x0 << 15) +#define RT5682S_GP1_PF_OUT (0x1 << 15) +#define RT5682S_GP1_OUT_MASK (0x1 << 14) +#define RT5682S_GP1_OUT_L (0x0 << 14) +#define RT5682S_GP1_OUT_H (0x1 << 14) +#define RT5682S_GP2_PF_MASK (0x1 << 13) +#define RT5682S_GP2_PF_IN (0x0 << 13) +#define RT5682S_GP2_PF_OUT (0x1 << 13) +#define RT5682S_GP2_OUT_MASK (0x1 << 12) +#define RT5682S_GP2_OUT_L (0x0 << 12) +#define RT5682S_GP2_OUT_H (0x1 << 12) +#define RT5682S_GP3_PF_MASK (0x1 << 11) +#define RT5682S_GP3_PF_IN (0x0 << 11) +#define RT5682S_GP3_PF_OUT (0x1 << 11) +#define RT5682S_GP3_OUT_MASK (0x1 << 10) +#define RT5682S_GP3_OUT_L (0x0 << 10) +#define RT5682S_GP3_OUT_H (0x1 << 10) +#define RT5682S_GP4_PF_MASK (0x1 << 9) +#define RT5682S_GP4_PF_IN (0x0 << 9) +#define RT5682S_GP4_PF_OUT (0x1 << 9) +#define RT5682S_GP4_OUT_MASK (0x1 << 8) +#define RT5682S_GP4_OUT_L (0x0 << 8) +#define RT5682S_GP4_OUT_H (0x1 << 8) +#define RT5682S_GP5_PF_MASK (0x1 << 7) +#define RT5682S_GP5_PF_IN (0x0 << 7) +#define RT5682S_GP5_PF_OUT (0x1 << 7) +#define RT5682S_GP5_OUT_MASK (0x1 << 6) +#define RT5682S_GP5_OUT_L (0x0 << 6) +#define RT5682S_GP5_OUT_H (0x1 << 6) +#define RT5682S_GP6_PF_MASK (0x1 << 5) +#define RT5682S_GP6_PF_IN (0x0 << 5) +#define RT5682S_GP6_PF_OUT (0x1 << 5) +#define RT5682S_GP6_OUT_MASK (0x1 << 4) +#define RT5682S_GP6_OUT_L (0x0 << 4) +#define RT5682S_GP6_OUT_H (0x1 << 4) + +/* GPIO Status (0x00c2) */ +#define RT5682S_GP6_ST (0x1 << 6) +#define RT5682S_GP5_ST (0x1 << 5) +#define RT5682S_GP4_ST (0x1 << 4) +#define RT5682S_GP3_ST (0x1 << 3) +#define RT5682S_GP2_ST (0x1 << 2) +#define RT5682S_GP1_ST (0x1 << 1) + +/* Soft volume and zero cross control 1 (0x00d9) */ +#define RT5682S_ZCD_MASK (0x1 << 10) +#define RT5682S_ZCD_SFT 10 +#define RT5682S_ZCD_PD (0x0 << 10) +#define RT5682S_ZCD_PU (0x1 << 10) + +/* 4 Button Inline Command Control 2 (0x00e3) */ +#define RT5682S_4BTN_IL_MASK (0x1 << 15) +#define RT5682S_4BTN_IL_EN (0x1 << 15) +#define RT5682S_4BTN_IL_DIS (0x0 << 15) +#define RT5682S_4BTN_IL_RST_MASK (0x1 << 14) +#define RT5682S_4BTN_IL_NOR (0x1 << 14) +#define RT5682S_4BTN_IL_RST (0x0 << 14) + +/* 4 Button Inline Command Control 3~6 (0x00e5~0x00e8) */ +#define RT5682S_4BTN_IL_HOLD_WIN_MASK (0x7f << 8) +#define RT5682S_4BTN_IL_HOLD_WIN_SFT 8 +#define RT5682S_4BTN_IL_CLICK_WIN_MASK (0x7f) +#define RT5682S_4BTN_IL_CLICK_WIN_SFT 0 + +/* Analog JD Control (0x00f0) */ +#define RT5682S_JDH_RS_MASK (0x1 << 4) +#define RT5682S_JDH_NO_PLUG (0x1 << 4) +#define RT5682S_JDH_PLUG (0x0 << 4) + +/* Charge Pump Internal Register1 (0x0125) */ +#define RT5682S_CP_CLK_HP_MASK (0x3 << 4) +#define RT5682S_CP_CLK_HP_100KHZ (0x0 << 4) +#define RT5682S_CP_CLK_HP_200KHZ (0x1 << 4) +#define RT5682S_CP_CLK_HP_300KHZ (0x2 << 4) +#define RT5682S_CP_CLK_HP_600KHZ (0x3 << 4) + +/* Pad Driving Control (0x0136) */ +#define RT5682S_PAD_DRV_GP1_MASK (0x1 << 14) +#define RT5682S_PAD_DRV_GP1_HIGH (0x1 << 14) +#define RT5682S_PAD_DRV_GP1_LOW (0x0 << 14) +#define RT5682S_PAD_DRV_GP2_MASK (0x1 << 12) +#define RT5682S_PAD_DRV_GP2_HIGH (0x1 << 12) +#define RT5682S_PAD_DRV_GP2_LOW (0x0 << 12) +#define RT5682S_PAD_DRV_GP3_MASK (0x1 << 10) +#define RT5682S_PAD_DRV_GP3_HIGH (0x1 << 10) +#define RT5682S_PAD_DRV_GP3_LOW (0x0 << 10) +#define RT5682S_PAD_DRV_GP4_MASK (0x1 << 8) +#define RT5682S_PAD_DRV_GP4_HIGH (0x1 << 8) +#define RT5682S_PAD_DRV_GP4_LOW (0x0 << 8) +#define RT5682S_PAD_DRV_GP5_MASK (0x1 << 6) +#define RT5682S_PAD_DRV_GP5_HIGH (0x1 << 6) +#define RT5682S_PAD_DRV_GP5_LOW (0x0 << 6) +#define RT5682S_PAD_DRV_GP6_MASK (0x1 << 4) +#define RT5682S_PAD_DRV_GP6_HIGH (0x1 << 4) +#define RT5682S_PAD_DRV_GP6_LOW (0x0 << 4) + +/* Chopper and Clock control for DAC (0x013a)*/ +#define RT5682S_CKXEN_DAC1_MASK (0x1 << 13) +#define RT5682S_CKXEN_DAC1_SFT 13 +#define RT5682S_CKGEN_DAC1_MASK (0x1 << 12) +#define RT5682S_CKGEN_DAC1_SFT 12 + +/* Chopper and Clock control for ADC (0x013b)*/ +#define RT5682S_CKXEN_ADC1_MASK (0x1 << 13) +#define RT5682S_CKXEN_ADC1_SFT 13 +#define RT5682S_CKGEN_ADC1_MASK (0x1 << 12) +#define RT5682S_CKGEN_ADC1_SFT 12 + +/* Volume test (0x013f)*/ +#define RT5682S_SEL_CLK_VOL_MASK (0x1 << 15) +#define RT5682S_SEL_CLK_VOL_EN (0x1 << 15) +#define RT5682S_SEL_CLK_VOL_DIS (0x0 << 15) + +/* Test Mode Control 1 (0x0145) */ +#define RT5682S_AD2DA_LB_MASK (0x1 << 10) +#define RT5682S_AD2DA_LB_SFT 10 + +/* Stereo Noise Gate Control 1 (0x0160) */ +#define RT5682S_NG2_EN_MASK (0x1 << 15) +#define RT5682S_NG2_EN (0x1 << 15) +#define RT5682S_NG2_DIS (0x0 << 15) + +/* Stereo1 DAC Silence Detection Control (0x0190) */ +#define RT5682S_DEB_STO_DAC_MASK (0x7 << 4) +#define RT5682S_DEB_80_MS (0x0 << 4) + +/* HP Behavior Logic Control 2 (0x01db) */ +#define RT5682S_HP_SIG_SRC_MASK (0x3) +#define RT5682S_HP_SIG_SRC_1BIT_CTL (0x3) +#define RT5682S_HP_SIG_SRC_REG (0x2) +#define RT5682S_HP_SIG_SRC_IMPE_REG (0x1) +#define RT5682S_HP_SIG_SRC_DC_CALI (0x0) + +/* SAR ADC Inline Command Control 1 (0x0210) */ +#define RT5682S_SAR_BUTDET_MASK (0x1 << 15) +#define RT5682S_SAR_BUTDET_EN (0x1 << 15) +#define RT5682S_SAR_BUTDET_DIS (0x0 << 15) +#define RT5682S_SAR_BUTDET_POW_MASK (0x1 << 14) +#define RT5682S_SAR_BUTDET_POW_SAV (0x1 << 14) +#define RT5682S_SAR_BUTDET_POW_NORM (0x0 << 14) +#define RT5682S_SAR_BUTDET_RST_MASK (0x1 << 13) +#define RT5682S_SAR_BUTDET_RST_NORM (0x1 << 13) +#define RT5682S_SAR_BUTDET_RST (0x0 << 13) +#define RT5682S_SAR_POW_MASK (0x1 << 12) +#define RT5682S_SAR_POW_EN (0x1 << 12) +#define RT5682S_SAR_POW_DIS (0x0 << 12) +#define RT5682S_SAR_RST_MASK (0x1 << 11) +#define RT5682S_SAR_RST_NORMAL (0x1 << 11) +#define RT5682S_SAR_RST (0x0 << 11) +#define RT5682S_SAR_BYPASS_MASK (0x1 << 10) +#define RT5682S_SAR_BYPASS_EN (0x1 << 10) +#define RT5682S_SAR_BYPASS_DIS (0x0 << 10) +#define RT5682S_SAR_SEL_MB1_2_MASK (0x3 << 8) +#define RT5682S_SAR_SEL_MB1_2_SFT 8 +#define RT5682S_SAR_SEL_MODE_MASK (0x1 << 7) +#define RT5682S_SAR_SEL_MODE_CMP (0x1 << 7) +#define RT5682S_SAR_SEL_MODE_ADC (0x0 << 7) +#define RT5682S_SAR_SEL_MB1_2_CTL_MASK (0x1 << 5) +#define RT5682S_SAR_SEL_MB1_2_AUTO (0x1 << 5) +#define RT5682S_SAR_SEL_MB1_2_MANU (0x0 << 5) +#define RT5682S_SAR_SEL_SIGNAL_MASK (0x1 << 4) +#define RT5682S_SAR_SEL_SIGNAL_AUTO (0x1 << 4) +#define RT5682S_SAR_SEL_SIGNAL_MANU (0x0 << 4) + +/* SAR ADC Inline Command Control 2 (0x0211) */ +#define RT5682S_SAR_ADC_PSV_MASK (0x1 << 4) +#define RT5682S_SAR_ADC_PSV_ENTRY (0x1 << 4) + + +/* SAR ADC Inline Command Control 13 (0x021c) */ +#define RT5682S_SAR_SOUR_MASK (0x3f) +#define RT5682S_SAR_SOUR_BTN (0x3f) +#define RT5682S_SAR_SOUR_TYPE (0x0) + +/* Headphone Amp Detection Control 1 (0x3b00) */ +#define RT5682S_CP_SW_SIZE_MASK (0x7 << 4) +#define RT5682S_CP_SW_SIZE_L (0x4 << 4) +#define RT5682S_CP_SW_SIZE_M (0x2 << 4) +#define RT5682S_CP_SW_SIZE_S (0x1 << 4) + +#define RT5682S_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5682S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +/* System Clock Source */ +enum { + RT5682S_SCLK_S_MCLK, + RT5682S_SCLK_S_PLL1, + RT5682S_SCLK_S_PLL2, + RT5682S_SCLK_S_RCCLK, +}; + +/* PLL Source */ +enum { + RT5682S_PLL_S_MCLK, + RT5682S_PLL_S_BCLK1, + RT5682S_PLL_S_BCLK2, + RT5682S_PLL_S_RCCLK, +}; + +enum { + RT5682S_PLL1, + RT5682S_PLL2, + RT5682S_PLLS, +}; + +enum { + RT5682S_AIF1, + RT5682S_AIF2, + RT5682S_AIFS +}; + +/* filter mask */ +enum { + RT5682S_DA_STEREO1_FILTER = 0x1, + RT5682S_AD_STEREO1_FILTER = (0x1 << 1), +}; + +enum { + RT5682S_CLK_SEL_SYS, + RT5682S_CLK_SEL_I2S1_ASRC, + RT5682S_CLK_SEL_I2S2_ASRC, +}; + +enum { + USE_PLLA, + USE_PLLB, + USE_PLLAB, +}; + +struct pll_calc_map { + unsigned int freq_in; + unsigned int freq_out; + int m; + int n; + int k; + bool m_bp; + bool k_bp; + bool byp_ps; + bool sel_ps; +}; + +#define RT5682S_NUM_SUPPLIES 2 + +struct rt5682s_priv { + struct snd_soc_component *component; + struct rt5682s_platform_data pdata; + struct regmap *regmap; + struct snd_soc_jack *hs_jack; + struct regulator_bulk_data supplies[RT5682S_NUM_SUPPLIES]; + struct delayed_work jack_detect_work; + struct delayed_work jd_check_work; + struct mutex calibrate_mutex; + struct mutex sar_mutex; + struct mutex jdet_mutex; + +#ifdef CONFIG_COMMON_CLK + struct clk_hw dai_clks_hw[RT5682S_DAI_NUM_CLKS]; + struct clk *mclk; +#endif + + int sysclk; + int sysclk_src; + int lrck[RT5682S_AIFS]; + int bclk[RT5682S_AIFS]; + int master[RT5682S_AIFS]; + + int pll_src[RT5682S_PLLS]; + int pll_in[RT5682S_PLLS]; + int pll_out[RT5682S_PLLS]; + int pll_comb; + + int jack_type; + int irq_work_delay_time; +}; + +int rt5682s_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +#endif /* __RT5682S_H__ */ diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c new file mode 100644 index 000000000000..f9574980a407 --- /dev/null +++ b/sound/soc/codecs/rt9120.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#define RT9120_REG_DEVID 0x00 +#define RT9120_REG_I2SFMT 0x02 +#define RT9120_REG_I2SWL 0x03 +#define RT9120_REG_SDIOSEL 0x04 +#define RT9120_REG_SYSCTL 0x05 +#define RT9120_REG_SPKGAIN 0x07 +#define RT9120_REG_VOLRAMP 0x0A +#define RT9120_REG_ERRRPT 0x10 +#define RT9120_REG_MSVOL 0x20 +#define RT9120_REG_SWRESET 0x40 +#define RT9120_REG_INTERNAL0 0x65 +#define RT9120_REG_INTERNAL1 0x69 +#define RT9120_REG_UVPOPT 0x6C + +#define RT9120_VID_MASK GENMASK(15, 8) +#define RT9120_SWRST_MASK BIT(7) +#define RT9120_MUTE_MASK GENMASK(5, 4) +#define RT9120_I2SFMT_MASK GENMASK(4, 2) +#define RT9120_I2SFMT_SHIFT 2 +#define RT9120_CFG_FMT_I2S 0 +#define RT9120_CFG_FMT_LEFTJ 1 +#define RT9120_CFG_FMT_RIGHTJ 2 +#define RT9120_CFG_FMT_DSPA 3 +#define RT9120_CFG_FMT_DSPB 7 +#define RT9120_AUDBIT_MASK GENMASK(1, 0) +#define RT9120_CFG_AUDBIT_16 0 +#define RT9120_CFG_AUDBIT_20 1 +#define RT9120_CFG_AUDBIT_24 2 +#define RT9120_AUDWL_MASK GENMASK(5, 0) +#define RT9120_CFG_WORDLEN_16 16 +#define RT9120_CFG_WORDLEN_24 24 +#define RT9120_CFG_WORDLEN_32 32 +#define RT9120_DVDD_UVSEL_MASK GENMASK(5, 4) + +#define RT9120_VENDOR_ID 0x4200 +#define RT9120_RESET_WAITMS 20 +#define RT9120_CHIPON_WAITMS 20 +#define RT9120_AMPON_WAITMS 50 +#define RT9120_AMPOFF_WAITMS 100 +#define RT9120_LVAPP_THRESUV 2000000 + +/* 8000 to 192000 supported , only 176400 not support */ +#define RT9120_RATES_MASK (SNDRV_PCM_RATE_8000_192000 &\ + ~SNDRV_PCM_RATE_176400) +#define RT9120_FMTS_MASK (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +struct rt9120_data { + struct device *dev; + struct regmap *regmap; +}; + +/* 11bit [min,max,step] = [-103.9375dB, 24dB, 0.0625dB] */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -1039375, 625, 1); + +/* {6, 8, 10, 12, 13, 14, 15, 16}dB */ +static const DECLARE_TLV_DB_RANGE(classd_tlv, + 0, 3, TLV_DB_SCALE_ITEM(600, 200, 0), + 4, 7, TLV_DB_SCALE_ITEM(1300, 100, 0) +); + +static const char * const sdo_select_text[] = { + "None", "INTF", "Final", "RMS Detect" +}; + +static const struct soc_enum sdo_select_enum = + SOC_ENUM_SINGLE(RT9120_REG_SDIOSEL, 4, ARRAY_SIZE(sdo_select_text), + sdo_select_text); + +static const struct snd_kcontrol_new rt9120_snd_controls[] = { + SOC_SINGLE_TLV("MS Volume", RT9120_REG_MSVOL, 0, 2047, 1, digital_tlv), + SOC_SINGLE_TLV("SPK Gain Volume", RT9120_REG_SPKGAIN, 0, 7, 0, classd_tlv), + SOC_SINGLE("PBTL Switch", RT9120_REG_SYSCTL, 3, 1, 0), + SOC_ENUM("SDO Select", sdo_select_enum), +}; + +static int internal_power_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); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_write(comp, RT9120_REG_ERRRPT, 0); + break; + case SND_SOC_DAPM_POST_PMU: + msleep(RT9120_AMPON_WAITMS); + break; + case SND_SOC_DAPM_POST_PMD: + msleep(RT9120_AMPOFF_WAITMS); + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt9120_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("DMIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_DAC("LDAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("RDAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("PWND", RT9120_REG_SYSCTL, 6, 1, + internal_power_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA("SPKL PA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPKR PA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), +}; + +static const struct snd_soc_dapm_route rt9120_dapm_routes[] = { + { "DMIX", NULL, "AIF Playback" }, + /* SPKL */ + { "LDAC", NULL, "PWND" }, + { "LDAC", NULL, "DMIX" }, + { "SPKL PA", NULL, "LDAC" }, + { "SPKL", NULL, "SPKL PA" }, + /* SPKR */ + { "RDAC", NULL, "PWND" }, + { "RDAC", NULL, "DMIX" }, + { "SPKR PA", NULL, "RDAC" }, + { "SPKR", NULL, "SPKR PA" }, + /* Cap */ + { "AIF Capture", NULL, "LDAC" }, + { "AIF Capture", NULL, "RDAC" }, +}; + +static int rt9120_codec_probe(struct snd_soc_component *comp) +{ + struct rt9120_data *data = snd_soc_component_get_drvdata(comp); + + snd_soc_component_init_regmap(comp, data->regmap); + + /* Internal setting */ + snd_soc_component_write(comp, RT9120_REG_INTERNAL1, 0x03); + snd_soc_component_write(comp, RT9120_REG_INTERNAL0, 0x69); + return 0; +} + +static const struct snd_soc_component_driver rt9120_component_driver = { + .probe = rt9120_codec_probe, + .controls = rt9120_snd_controls, + .num_controls = ARRAY_SIZE(rt9120_snd_controls), + .dapm_widgets = rt9120_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt9120_dapm_widgets), + .dapm_routes = rt9120_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt9120_dapm_routes), +}; + +static int rt9120_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *comp = dai->component; + unsigned int format; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = RT9120_CFG_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = RT9120_CFG_FMT_LEFTJ; + break; + case SND_SOC_DAIFMT_RIGHT_J: + format = RT9120_CFG_FMT_RIGHTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + format = RT9120_CFG_FMT_DSPA; + break; + case SND_SOC_DAIFMT_DSP_B: + format = RT9120_CFG_FMT_DSPB; + break; + default: + dev_err(dai->dev, "Unknown dai format\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(comp, RT9120_REG_I2SFMT, + RT9120_I2SFMT_MASK, + format << RT9120_I2SFMT_SHIFT); + return 0; +} + +static int rt9120_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *param, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *comp = dai->component; + unsigned int param_width, param_slot_width; + int width; + + switch (width = params_width(param)) { + case 16: + param_width = RT9120_CFG_AUDBIT_16; + break; + case 20: + param_width = RT9120_CFG_AUDBIT_20; + break; + case 24: + case 32: + param_width = RT9120_CFG_AUDBIT_24; + break; + default: + dev_err(dai->dev, "Unsupported data width [%d]\n", width); + return -EINVAL; + } + + snd_soc_component_update_bits(comp, RT9120_REG_I2SFMT, + RT9120_AUDBIT_MASK, param_width); + + switch (width = params_physical_width(param)) { + case 16: + param_slot_width = RT9120_CFG_WORDLEN_16; + break; + case 24: + param_slot_width = RT9120_CFG_WORDLEN_24; + break; + case 32: + param_slot_width = RT9120_CFG_WORDLEN_32; + break; + default: + dev_err(dai->dev, "Unsupported slot width [%d]\n", width); + return -EINVAL; + } + + snd_soc_component_update_bits(comp, RT9120_REG_I2SWL, + RT9120_AUDWL_MASK, param_slot_width); + return 0; +} + +static const struct snd_soc_dai_ops rt9120_dai_ops = { + .set_fmt = rt9120_set_fmt, + .hw_params = rt9120_hw_params, +}; + +static struct snd_soc_dai_driver rt9120_dai = { + .name = "rt9120_aif", + .playback = { + .stream_name = "AIF Playback", + .rates = RT9120_RATES_MASK, + .formats = RT9120_FMTS_MASK, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .stream_name = "AIF Capture", + .rates = RT9120_RATES_MASK, + .formats = RT9120_FMTS_MASK, + .rate_max = 192000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &rt9120_dai_ops, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, +}; + +static const struct regmap_range rt9120_rd_yes_ranges[] = { + regmap_reg_range(0x00, 0x0C), + regmap_reg_range(0x10, 0x15), + regmap_reg_range(0x20, 0x27), + regmap_reg_range(0x30, 0x38), + regmap_reg_range(0x3A, 0x40), + regmap_reg_range(0x65, 0x65), + regmap_reg_range(0x69, 0x69), + regmap_reg_range(0x6C, 0x6C) +}; + +static const struct regmap_access_table rt9120_rd_table = { + .yes_ranges = rt9120_rd_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(rt9120_rd_yes_ranges), +}; + +static const struct regmap_range rt9120_wr_yes_ranges[] = { + regmap_reg_range(0x00, 0x00), + regmap_reg_range(0x02, 0x0A), + regmap_reg_range(0x10, 0x15), + regmap_reg_range(0x20, 0x27), + regmap_reg_range(0x30, 0x38), + regmap_reg_range(0x3A, 0x3D), + regmap_reg_range(0x40, 0x40), + regmap_reg_range(0x65, 0x65), + regmap_reg_range(0x69, 0x69), + regmap_reg_range(0x6C, 0x6C) +}; + +static const struct regmap_access_table rt9120_wr_table = { + .yes_ranges = rt9120_wr_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(rt9120_wr_yes_ranges), +}; + +static int rt9120_get_reg_size(unsigned int reg) +{ + switch (reg) { + case 0x00: + case 0x09: + case 0x20 ... 0x27: + return 2; + case 0x30 ... 0x3D: + return 3; + case 0x3E ... 0x3F: + return 4; + default: + return 1; + } +} + +static int rt9120_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rt9120_data *data = context; + struct i2c_client *i2c = to_i2c_client(data->dev); + int size = rt9120_get_reg_size(reg); + u8 raw[4] = {0}; + int ret; + + ret = i2c_smbus_read_i2c_block_data(i2c, reg, size, raw); + if (ret < 0) + return ret; + else if (ret != size) + return -EIO; + + switch (size) { + case 4: + *val = be32_to_cpup((__be32 *)raw); + break; + case 3: + *val = raw[0] << 16 | raw[1] << 8 | raw[0]; + break; + case 2: + *val = be16_to_cpup((__be16 *)raw); + break; + default: + *val = raw[0]; + } + + return 0; +} + +static int rt9120_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct rt9120_data *data = context; + struct i2c_client *i2c = to_i2c_client(data->dev); + int size = rt9120_get_reg_size(reg); + __be32 be32_val; + u8 *rawp = (u8 *)&be32_val; + int offs = 4 - size; + + be32_val = cpu_to_be32(val); + return i2c_smbus_write_i2c_block_data(i2c, reg, size, rawp + offs); +} + +static const struct regmap_config rt9120_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = RT9120_REG_UVPOPT, + + .reg_read = rt9120_reg_read, + .reg_write = rt9120_reg_write, + + .wr_table = &rt9120_wr_table, + .rd_table = &rt9120_rd_table, +}; + +static int rt9120_check_vendor_info(struct rt9120_data *data) +{ + unsigned int devid; + int ret; + + ret = regmap_read(data->regmap, RT9120_REG_DEVID, &devid); + if (ret) + return ret; + + if ((devid & RT9120_VID_MASK) != RT9120_VENDOR_ID) { + dev_err(data->dev, "DEVID not correct [0x%04x]\n", devid); + return -ENODEV; + } + + return 0; +} + +static int rt9120_do_register_reset(struct rt9120_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, RT9120_REG_SWRESET, + RT9120_SWRST_MASK); + if (ret) + return ret; + + msleep(RT9120_RESET_WAITMS); + return 0; +} + +static int rt9120_probe(struct i2c_client *i2c) +{ + struct rt9120_data *data; + struct gpio_desc *pwdnn_gpio; + struct regulator *dvdd_supply; + int dvdd_supply_volt, ret; + + data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &i2c->dev; + i2c_set_clientdata(i2c, data); + + pwdnn_gpio = devm_gpiod_get_optional(&i2c->dev, "pwdnn", + GPIOD_OUT_HIGH); + if (IS_ERR(pwdnn_gpio)) { + dev_err(&i2c->dev, "Failed to initialize 'pwdnn' gpio\n"); + return PTR_ERR(pwdnn_gpio); + } else if (pwdnn_gpio) { + dev_dbg(&i2c->dev, "'pwdnn' from low to high, wait chip on\n"); + msleep(RT9120_CHIPON_WAITMS); + } + + data->regmap = devm_regmap_init(&i2c->dev, NULL, data, + &rt9120_regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(&i2c->dev, "Failed to init regmap [%d]\n", ret); + return ret; + } + + ret = rt9120_check_vendor_info(data); + if (ret) { + dev_err(&i2c->dev, "Failed to check vendor info\n"); + return ret; + } + + ret = rt9120_do_register_reset(data); + if (ret) { + dev_err(&i2c->dev, "Failed to do register reset\n"); + return ret; + } + + dvdd_supply = devm_regulator_get(&i2c->dev, "dvdd"); + if (IS_ERR(dvdd_supply)) { + dev_err(&i2c->dev, "No dvdd regulator found\n"); + return PTR_ERR(dvdd_supply); + } + + dvdd_supply_volt = regulator_get_voltage(dvdd_supply); + if (dvdd_supply_volt <= RT9120_LVAPP_THRESUV) { + dev_dbg(&i2c->dev, "dvdd low voltage design\n"); + ret = regmap_update_bits(data->regmap, RT9120_REG_UVPOPT, + RT9120_DVDD_UVSEL_MASK, 0); + if (ret) { + dev_err(&i2c->dev, "Failed to config dvdd uvsel\n"); + return ret; + } + } + + return devm_snd_soc_register_component(&i2c->dev, + &rt9120_component_driver, + &rt9120_dai, 1); +} + +static const struct of_device_id __maybe_unused rt9120_device_table[] = { + { .compatible = "richtek,rt9120", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt9120_device_table); + +static struct i2c_driver rt9120_driver = { + .driver = { + .name = "rt9120", + .of_match_table = rt9120_device_table, + }, + .probe_new = rt9120_probe, +}; +module_i2c_driver(rt9120_driver); + +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_DESCRIPTION("RT9120 Audio Amplifier Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tfa989x.c b/sound/soc/codecs/tfa989x.c index 643b45188b6f..eb2a7870148d 100644 --- a/sound/soc/codecs/tfa989x.c +++ b/sound/soc/codecs/tfa989x.c @@ -19,6 +19,7 @@ #define TFA989X_REVISIONNUMBER 0x03 #define TFA989X_REVISIONNUMBER_REV_MSK GENMASK(7, 0) /* device revision */ #define TFA989X_I2SREG 0x04 +#define TFA989X_I2SREG_RCV 2 /* receiver mode */ #define TFA989X_I2SREG_CHSA 6 /* amplifier input select */ #define TFA989X_I2SREG_CHSA_MSK GENMASK(7, 6) #define TFA989X_I2SREG_I2SSR 12 /* sample rate */ @@ -53,6 +54,7 @@ struct tfa989x_rev { }; struct tfa989x { + const struct tfa989x_rev *rev; struct regulator *vddd_supply; }; @@ -97,7 +99,25 @@ static const struct snd_soc_dapm_route tfa989x_dapm_routes[] = { {"Amp Input", "Right", "AIFINR"}, }; +static const char * const mode_text[] = { "Speaker", "Receiver" }; +static SOC_ENUM_SINGLE_DECL(mode_enum, TFA989X_I2SREG, TFA989X_I2SREG_RCV, mode_text); +static const struct snd_kcontrol_new tfa989x_mode_controls[] = { + SOC_ENUM("Mode", mode_enum), +}; + +static int tfa989x_probe(struct snd_soc_component *component) +{ + struct tfa989x *tfa989x = snd_soc_component_get_drvdata(component); + + if (tfa989x->rev->rev == TFA9897_REVISION) + return snd_soc_add_component_controls(component, tfa989x_mode_controls, + ARRAY_SIZE(tfa989x_mode_controls)); + + return 0; +} + static const struct snd_soc_component_driver tfa989x_component = { + .probe = tfa989x_probe, .dapm_widgets = tfa989x_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(tfa989x_dapm_widgets), .dapm_routes = tfa989x_dapm_routes, @@ -273,6 +293,7 @@ static int tfa989x_i2c_probe(struct i2c_client *i2c) if (!tfa989x) return -ENOMEM; + tfa989x->rev = rev; i2c_set_clientdata(i2c, tfa989x); tfa989x->vddd_supply = devm_regulator_get(dev, "vddd"); diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c index 04ad38311360..ed70e3d9baf2 100644 --- a/sound/soc/codecs/tlv320aic32x4-i2c.c +++ b/sound/soc/codecs/tlv320aic32x4-i2c.c @@ -44,7 +44,9 @@ static int aic32x4_i2c_probe(struct i2c_client *i2c, static int aic32x4_i2c_remove(struct i2c_client *i2c) { - return aic32x4_remove(&i2c->dev); + aic32x4_remove(&i2c->dev); + + return 0; } static const struct i2c_device_id aic32x4_i2c_id[] = { diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c index e81c72958a82..a8958cd1c692 100644 --- a/sound/soc/codecs/tlv320aic32x4-spi.c +++ b/sound/soc/codecs/tlv320aic32x4-spi.c @@ -48,7 +48,9 @@ static int aic32x4_spi_probe(struct spi_device *spi) static int aic32x4_spi_remove(struct spi_device *spi) { - return aic32x4_remove(&spi->dev); + aic32x4_remove(&spi->dev); + + return 0; } static const struct spi_device_id aic32x4_spi_id[] = { diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index d39c7d52ecfd..8f42fd7bc053 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -1418,13 +1418,11 @@ err_disable_regulators: } EXPORT_SYMBOL(aic32x4_probe); -int aic32x4_remove(struct device *dev) +void aic32x4_remove(struct device *dev) { struct aic32x4_priv *aic32x4 = dev_get_drvdata(dev); aic32x4_disable_regulators(aic32x4); - - return 0; } EXPORT_SYMBOL(aic32x4_remove); diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h index e9fd2e55d6c3..4de5bd9e8cc5 100644 --- a/sound/soc/codecs/tlv320aic32x4.h +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -18,7 +18,7 @@ enum aic32x4_type { extern const struct regmap_config aic32x4_regmap_config; int aic32x4_probe(struct device *dev, struct regmap *regmap); -int aic32x4_remove(struct device *dev); +void aic32x4_remove(struct device *dev); int aic32x4_register_clocks(struct device *dev, const char *mclk_name); /* tlv320aic32x4 register space (in decimal to match datasheet) */ diff --git a/sound/soc/codecs/tlv320aic3x-i2c.c b/sound/soc/codecs/tlv320aic3x-i2c.c index cd0558ed4dd4..2f272bc3f5da 100644 --- a/sound/soc/codecs/tlv320aic3x-i2c.c +++ b/sound/soc/codecs/tlv320aic3x-i2c.c @@ -32,7 +32,9 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *i static int aic3x_i2c_remove(struct i2c_client *i2c) { - return aic3x_remove(&i2c->dev); + aic3x_remove(&i2c->dev); + + return 0; } static const struct i2c_device_id aic3x_i2c_id[] = { diff --git a/sound/soc/codecs/tlv320aic3x-spi.c b/sound/soc/codecs/tlv320aic3x-spi.c index 8c7b6bb9223f..494e84402232 100644 --- a/sound/soc/codecs/tlv320aic3x-spi.c +++ b/sound/soc/codecs/tlv320aic3x-spi.c @@ -37,7 +37,9 @@ static int aic3x_spi_probe(struct spi_device *spi) static int aic3x_spi_remove(struct spi_device *spi) { - return aic3x_remove(&spi->dev); + aic3x_remove(&spi->dev); + + return 0; } static const struct spi_device_id aic3x_spi_id[] = { diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 7731593a5509..d53037b1509d 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1870,7 +1870,7 @@ err: } EXPORT_SYMBOL(aic3x_probe); -int aic3x_remove(struct device *dev) +void aic3x_remove(struct device *dev) { struct aic3x_priv *aic3x = dev_get_drvdata(dev); @@ -1881,7 +1881,6 @@ int aic3x_remove(struct device *dev) gpio_set_value(aic3x->gpio_reset, 0); gpio_free(aic3x->gpio_reset); } - return 0; } EXPORT_SYMBOL(aic3x_remove); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index 7e0063913017..14298f9e6d9b 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -14,7 +14,7 @@ struct regmap_config; extern const struct regmap_config aic3x_regmap; int aic3x_probe(struct device *dev, struct regmap *regmap, kernel_ulong_t driver_data); -int aic3x_remove(struct device *dev); +void aic3x_remove(struct device *dev); #define AIC3X_MODEL_3X 0 #define AIC3X_MODEL_33 1 diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index d885ced34f60..bc5d68c53e5a 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -4859,7 +4859,7 @@ static int wcd9335_codec_probe(struct snd_soc_component *component) snd_soc_component_init_regmap(component, wcd->regmap); /* Class-H Init*/ - wcd->clsh_ctrl = wcd_clsh_ctrl_alloc(component, wcd->version); + wcd->clsh_ctrl = wcd_clsh_ctrl_alloc(component, WCD9335); if (IS_ERR(wcd->clsh_ctrl)) return PTR_ERR(wcd->clsh_ctrl); diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index c35673e7f420..8863b533f9c4 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c @@ -145,13 +145,13 @@ static const struct regmap_range_cfg wm2200_ranges[] = { .window_start = WM2200_DSP2_ZM_0, .window_len = 1024, }, }; -static const struct wm_adsp_region wm2200_dsp1_regions[] = { +static const struct cs_dsp_region wm2200_dsp1_regions[] = { { .type = WMFW_ADSP1_PM, .base = WM2200_DSP1_PM_BASE }, { .type = WMFW_ADSP1_DM, .base = WM2200_DSP1_DM_BASE }, { .type = WMFW_ADSP1_ZM, .base = WM2200_DSP1_ZM_BASE }, }; -static const struct wm_adsp_region wm2200_dsp2_regions[] = { +static const struct cs_dsp_region wm2200_dsp2_regions[] = { { .type = WMFW_ADSP1_PM, .base = WM2200_DSP2_PM_BASE }, { .type = WMFW_ADSP1_DM, .base = WM2200_DSP2_DM_BASE }, { .type = WMFW_ADSP1_ZM, .base = WM2200_DSP2_ZM_BASE }, @@ -2202,23 +2202,23 @@ static int wm2200_i2c_probe(struct i2c_client *i2c, } for (i = 0; i < 2; i++) { - wm2200->dsp[i].type = WMFW_ADSP1; + wm2200->dsp[i].cs_dsp.type = WMFW_ADSP1; wm2200->dsp[i].part = "wm2200"; - wm2200->dsp[i].num = i + 1; - wm2200->dsp[i].dev = &i2c->dev; - wm2200->dsp[i].regmap = wm2200->regmap; - wm2200->dsp[i].sysclk_reg = WM2200_CLOCKING_3; - wm2200->dsp[i].sysclk_mask = WM2200_SYSCLK_FREQ_MASK; - wm2200->dsp[i].sysclk_shift = WM2200_SYSCLK_FREQ_SHIFT; + wm2200->dsp[i].cs_dsp.num = i + 1; + wm2200->dsp[i].cs_dsp.dev = &i2c->dev; + wm2200->dsp[i].cs_dsp.regmap = wm2200->regmap; + wm2200->dsp[i].cs_dsp.sysclk_reg = WM2200_CLOCKING_3; + wm2200->dsp[i].cs_dsp.sysclk_mask = WM2200_SYSCLK_FREQ_MASK; + wm2200->dsp[i].cs_dsp.sysclk_shift = WM2200_SYSCLK_FREQ_SHIFT; } - wm2200->dsp[0].base = WM2200_DSP1_CONTROL_1; - wm2200->dsp[0].mem = wm2200_dsp1_regions; - wm2200->dsp[0].num_mems = ARRAY_SIZE(wm2200_dsp1_regions); + wm2200->dsp[0].cs_dsp.base = WM2200_DSP1_CONTROL_1; + wm2200->dsp[0].cs_dsp.mem = wm2200_dsp1_regions; + wm2200->dsp[0].cs_dsp.num_mems = ARRAY_SIZE(wm2200_dsp1_regions); - wm2200->dsp[1].base = WM2200_DSP2_CONTROL_1; - wm2200->dsp[1].mem = wm2200_dsp2_regions; - wm2200->dsp[1].num_mems = ARRAY_SIZE(wm2200_dsp2_regions); + wm2200->dsp[1].cs_dsp.base = WM2200_DSP2_CONTROL_1; + wm2200->dsp[1].cs_dsp.mem = wm2200_dsp2_regions; + wm2200->dsp[1].cs_dsp.num_mems = ARRAY_SIZE(wm2200_dsp2_regions); for (i = 0; i < ARRAY_SIZE(wm2200->dsp); i++) wm_adsp1_init(&wm2200->dsp[i]); diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index 621598608bf0..da2f8998df87 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -44,7 +44,7 @@ static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); static DECLARE_TLV_DB_SCALE(noise_tlv, -13200, 600, 0); static DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); -static const struct wm_adsp_region wm5102_dsp1_regions[] = { +static const struct cs_dsp_region wm5102_dsp1_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x100000 }, { .type = WMFW_ADSP2_ZM, .base = 0x180000 }, { .type = WMFW_ADSP2_XM, .base = 0x190000 }, @@ -2046,13 +2046,13 @@ static int wm5102_probe(struct platform_device *pdev) arizona_init_dvfs(&wm5102->core); wm5102->core.adsp[0].part = "wm5102"; - wm5102->core.adsp[0].num = 1; - wm5102->core.adsp[0].type = WMFW_ADSP2; - wm5102->core.adsp[0].base = ARIZONA_DSP1_CONTROL_1; - wm5102->core.adsp[0].dev = arizona->dev; - wm5102->core.adsp[0].regmap = arizona->regmap; - wm5102->core.adsp[0].mem = wm5102_dsp1_regions; - wm5102->core.adsp[0].num_mems = ARRAY_SIZE(wm5102_dsp1_regions); + wm5102->core.adsp[0].cs_dsp.num = 1; + wm5102->core.adsp[0].cs_dsp.type = WMFW_ADSP2; + wm5102->core.adsp[0].cs_dsp.base = ARIZONA_DSP1_CONTROL_1; + wm5102->core.adsp[0].cs_dsp.dev = arizona->dev; + wm5102->core.adsp[0].cs_dsp.regmap = arizona->regmap; + wm5102->core.adsp[0].cs_dsp.mem = wm5102_dsp1_regions; + wm5102->core.adsp[0].cs_dsp.num_mems = ARRAY_SIZE(wm5102_dsp1_regions); ret = wm_adsp2_init(&wm5102->core.adsp[0]); if (ret != 0) diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index 5c2d45d05c97..4973ba1ed779 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -45,35 +45,35 @@ struct wm5110_priv { unsigned int in_pga_cache[6]; }; -static const struct wm_adsp_region wm5110_dsp1_regions[] = { +static const struct cs_dsp_region wm5110_dsp1_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x100000 }, { .type = WMFW_ADSP2_ZM, .base = 0x180000 }, { .type = WMFW_ADSP2_XM, .base = 0x190000 }, { .type = WMFW_ADSP2_YM, .base = 0x1a8000 }, }; -static const struct wm_adsp_region wm5110_dsp2_regions[] = { +static const struct cs_dsp_region wm5110_dsp2_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x200000 }, { .type = WMFW_ADSP2_ZM, .base = 0x280000 }, { .type = WMFW_ADSP2_XM, .base = 0x290000 }, { .type = WMFW_ADSP2_YM, .base = 0x2a8000 }, }; -static const struct wm_adsp_region wm5110_dsp3_regions[] = { +static const struct cs_dsp_region wm5110_dsp3_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x300000 }, { .type = WMFW_ADSP2_ZM, .base = 0x380000 }, { .type = WMFW_ADSP2_XM, .base = 0x390000 }, { .type = WMFW_ADSP2_YM, .base = 0x3a8000 }, }; -static const struct wm_adsp_region wm5110_dsp4_regions[] = { +static const struct cs_dsp_region wm5110_dsp4_regions[] = { { .type = WMFW_ADSP2_PM, .base = 0x400000 }, { .type = WMFW_ADSP2_ZM, .base = 0x480000 }, { .type = WMFW_ADSP2_XM, .base = 0x490000 }, { .type = WMFW_ADSP2_YM, .base = 0x4a8000 }, }; -static const struct wm_adsp_region *wm5110_dsp_regions[] = { +static const struct cs_dsp_region *wm5110_dsp_regions[] = { wm5110_dsp1_regions, wm5110_dsp2_regions, wm5110_dsp3_regions, @@ -2409,15 +2409,15 @@ static int wm5110_probe(struct platform_device *pdev) for (i = 0; i < WM5110_NUM_ADSP; i++) { wm5110->core.adsp[i].part = "wm5110"; - wm5110->core.adsp[i].num = i + 1; - wm5110->core.adsp[i].type = WMFW_ADSP2; - wm5110->core.adsp[i].dev = arizona->dev; - wm5110->core.adsp[i].regmap = arizona->regmap; + wm5110->core.adsp[i].cs_dsp.num = i + 1; + wm5110->core.adsp[i].cs_dsp.type = WMFW_ADSP2; + wm5110->core.adsp[i].cs_dsp.dev = arizona->dev; + wm5110->core.adsp[i].cs_dsp.regmap = arizona->regmap; - wm5110->core.adsp[i].base = ARIZONA_DSP1_CONTROL_1 + wm5110->core.adsp[i].cs_dsp.base = ARIZONA_DSP1_CONTROL_1 + (0x100 * i); - wm5110->core.adsp[i].mem = wm5110_dsp_regions[i]; - wm5110->core.adsp[i].num_mems + wm5110->core.adsp[i].cs_dsp.mem = wm5110_dsp_regions[i]; + wm5110->core.adsp[i].cs_dsp.num_mems = ARRAY_SIZE(wm5110_dsp1_regions); ret = wm_adsp2_init(&wm5110->core.adsp[i]); diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index dcee7b2bd3d7..86b1f6eaa599 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -713,18 +713,12 @@ static int wm8731_spi_probe(struct spi_device *spi) return 0; } -static int wm8731_spi_remove(struct spi_device *spi) -{ - return 0; -} - static struct spi_driver wm8731_spi_driver = { .driver = { .name = "wm8731", .of_match_table = wm8731_of_match, }, .probe = wm8731_spi_probe, - .remove = wm8731_spi_remove, }; #endif /* CONFIG_SPI_MASTER */ diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index a9a6d766a176..bf3a4415a85f 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -1252,17 +1252,11 @@ static int wm8900_spi_probe(struct spi_device *spi) return ret; } -static int wm8900_spi_remove(struct spi_device *spi) -{ - return 0; -} - static struct spi_driver wm8900_spi_driver = { .driver = { .name = "wm8900", }, .probe = wm8900_spi_probe, - .remove = wm8900_spi_remove, }; #endif /* CONFIG_SPI_MASTER */ diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index ba16bdf9e478..a5584ba962dc 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3538,9 +3538,8 @@ static int wm8962_set_pdata_from_of(struct i2c_client *i2c, pdata->gpio_init[i] = 0x0; } - pdata->mclk = devm_clk_get(&i2c->dev, NULL); - - return 0; + pdata->mclk = devm_clk_get_optional(&i2c->dev, NULL); + return PTR_ERR_OR_ZERO(pdata->mclk); } static int wm8962_i2c_probe(struct i2c_client *i2c, @@ -3572,14 +3571,6 @@ static int wm8962_i2c_probe(struct i2c_client *i2c, return ret; } - /* Mark the mclk pointer to NULL if no mclk assigned */ - if (IS_ERR(wm8962->pdata.mclk)) { - /* But do not ignore the request for probe defer */ - if (PTR_ERR(wm8962->pdata.mclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - wm8962->pdata.mclk = NULL; - } - for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) wm8962->supplies[i].supply = wm8962_supply_names[i]; diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index f7c800927cb2..d4f0d72cbcc8 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -19,7 +19,6 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> -#include <linux/vmalloc.h> #include <linux/workqueue.h> #include <linux/debugfs.h> #include <sound/core.h> @@ -33,15 +32,15 @@ #include "wm_adsp.h" #define adsp_crit(_dsp, fmt, ...) \ - dev_crit(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) + dev_crit(_dsp->cs_dsp.dev, "%s: " fmt, _dsp->cs_dsp.name, ##__VA_ARGS__) #define adsp_err(_dsp, fmt, ...) \ - dev_err(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) + dev_err(_dsp->cs_dsp.dev, "%s: " fmt, _dsp->cs_dsp.name, ##__VA_ARGS__) #define adsp_warn(_dsp, fmt, ...) \ - dev_warn(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) + dev_warn(_dsp->cs_dsp.dev, "%s: " fmt, _dsp->cs_dsp.name, ##__VA_ARGS__) #define adsp_info(_dsp, fmt, ...) \ - dev_info(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) + dev_info(_dsp->cs_dsp.dev, "%s: " fmt, _dsp->cs_dsp.name, ##__VA_ARGS__) #define adsp_dbg(_dsp, fmt, ...) \ - dev_dbg(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) + dev_dbg(_dsp->cs_dsp.dev, "%s: " fmt, _dsp->cs_dsp.name, ##__VA_ARGS__) #define compr_err(_obj, fmt, ...) \ adsp_err(_obj->dsp, "%s: " fmt, _obj->name ? _obj->name : "legacy", \ @@ -50,301 +49,10 @@ adsp_dbg(_obj->dsp, "%s: " fmt, _obj->name ? _obj->name : "legacy", \ ##__VA_ARGS__) -#define ADSP1_CONTROL_1 0x00 -#define ADSP1_CONTROL_2 0x02 -#define ADSP1_CONTROL_3 0x03 -#define ADSP1_CONTROL_4 0x04 -#define ADSP1_CONTROL_5 0x06 -#define ADSP1_CONTROL_6 0x07 -#define ADSP1_CONTROL_7 0x08 -#define ADSP1_CONTROL_8 0x09 -#define ADSP1_CONTROL_9 0x0A -#define ADSP1_CONTROL_10 0x0B -#define ADSP1_CONTROL_11 0x0C -#define ADSP1_CONTROL_12 0x0D -#define ADSP1_CONTROL_13 0x0F -#define ADSP1_CONTROL_14 0x10 -#define ADSP1_CONTROL_15 0x11 -#define ADSP1_CONTROL_16 0x12 -#define ADSP1_CONTROL_17 0x13 -#define ADSP1_CONTROL_18 0x14 -#define ADSP1_CONTROL_19 0x16 -#define ADSP1_CONTROL_20 0x17 -#define ADSP1_CONTROL_21 0x18 -#define ADSP1_CONTROL_22 0x1A -#define ADSP1_CONTROL_23 0x1B -#define ADSP1_CONTROL_24 0x1C -#define ADSP1_CONTROL_25 0x1E -#define ADSP1_CONTROL_26 0x20 -#define ADSP1_CONTROL_27 0x21 -#define ADSP1_CONTROL_28 0x22 -#define ADSP1_CONTROL_29 0x23 -#define ADSP1_CONTROL_30 0x24 -#define ADSP1_CONTROL_31 0x26 - -/* - * ADSP1 Control 19 - */ -#define ADSP1_WDMA_BUFFER_LENGTH_MASK 0x00FF /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ -#define ADSP1_WDMA_BUFFER_LENGTH_SHIFT 0 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ -#define ADSP1_WDMA_BUFFER_LENGTH_WIDTH 8 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ - - -/* - * ADSP1 Control 30 - */ -#define ADSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */ -#define ADSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */ -#define ADSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */ -#define ADSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */ -#define ADSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ -#define ADSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ -#define ADSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ -#define ADSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ -#define ADSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ -#define ADSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ -#define ADSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ -#define ADSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ -#define ADSP1_START 0x0001 /* DSP1_START */ -#define ADSP1_START_MASK 0x0001 /* DSP1_START */ -#define ADSP1_START_SHIFT 0 /* DSP1_START */ -#define ADSP1_START_WIDTH 1 /* DSP1_START */ - -/* - * ADSP1 Control 31 - */ -#define ADSP1_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */ -#define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ -#define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ - -#define ADSP2_CONTROL 0x0 -#define ADSP2_CLOCKING 0x1 -#define ADSP2V2_CLOCKING 0x2 -#define ADSP2_STATUS1 0x4 -#define ADSP2_WDMA_CONFIG_1 0x30 -#define ADSP2_WDMA_CONFIG_2 0x31 -#define ADSP2V2_WDMA_CONFIG_2 0x32 -#define ADSP2_RDMA_CONFIG_1 0x34 - -#define ADSP2_SCRATCH0 0x40 -#define ADSP2_SCRATCH1 0x41 -#define ADSP2_SCRATCH2 0x42 -#define ADSP2_SCRATCH3 0x43 - -#define ADSP2V2_SCRATCH0_1 0x40 -#define ADSP2V2_SCRATCH2_3 0x42 - -/* - * ADSP2 Control - */ - -#define ADSP2_MEM_ENA 0x0010 /* DSP1_MEM_ENA */ -#define ADSP2_MEM_ENA_MASK 0x0010 /* DSP1_MEM_ENA */ -#define ADSP2_MEM_ENA_SHIFT 4 /* DSP1_MEM_ENA */ -#define ADSP2_MEM_ENA_WIDTH 1 /* DSP1_MEM_ENA */ -#define ADSP2_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ -#define ADSP2_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ -#define ADSP2_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ -#define ADSP2_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ -#define ADSP2_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ -#define ADSP2_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ -#define ADSP2_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ -#define ADSP2_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ -#define ADSP2_START 0x0001 /* DSP1_START */ -#define ADSP2_START_MASK 0x0001 /* DSP1_START */ -#define ADSP2_START_SHIFT 0 /* DSP1_START */ -#define ADSP2_START_WIDTH 1 /* DSP1_START */ - -/* - * ADSP2 clocking - */ -#define ADSP2_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */ -#define ADSP2_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ -#define ADSP2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ - -/* - * ADSP2V2 clocking - */ -#define ADSP2V2_CLK_SEL_MASK 0x70000 /* CLK_SEL_ENA */ -#define ADSP2V2_CLK_SEL_SHIFT 16 /* CLK_SEL_ENA */ -#define ADSP2V2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ - -#define ADSP2V2_RATE_MASK 0x7800 /* DSP_RATE */ -#define ADSP2V2_RATE_SHIFT 11 /* DSP_RATE */ -#define ADSP2V2_RATE_WIDTH 4 /* DSP_RATE */ - -/* - * ADSP2 Status 1 - */ -#define ADSP2_RAM_RDY 0x0001 -#define ADSP2_RAM_RDY_MASK 0x0001 -#define ADSP2_RAM_RDY_SHIFT 0 -#define ADSP2_RAM_RDY_WIDTH 1 - -/* - * ADSP2 Lock support - */ -#define ADSP2_LOCK_CODE_0 0x5555 -#define ADSP2_LOCK_CODE_1 0xAAAA - -#define ADSP2_WATCHDOG 0x0A -#define ADSP2_BUS_ERR_ADDR 0x52 -#define ADSP2_REGION_LOCK_STATUS 0x64 -#define ADSP2_LOCK_REGION_1_LOCK_REGION_0 0x66 -#define ADSP2_LOCK_REGION_3_LOCK_REGION_2 0x68 -#define ADSP2_LOCK_REGION_5_LOCK_REGION_4 0x6A -#define ADSP2_LOCK_REGION_7_LOCK_REGION_6 0x6C -#define ADSP2_LOCK_REGION_9_LOCK_REGION_8 0x6E -#define ADSP2_LOCK_REGION_CTRL 0x7A -#define ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR 0x7C - -#define ADSP2_REGION_LOCK_ERR_MASK 0x8000 -#define ADSP2_ADDR_ERR_MASK 0x4000 -#define ADSP2_WDT_TIMEOUT_STS_MASK 0x2000 -#define ADSP2_CTRL_ERR_PAUSE_ENA 0x0002 -#define ADSP2_CTRL_ERR_EINT 0x0001 - -#define ADSP2_BUS_ERR_ADDR_MASK 0x00FFFFFF -#define ADSP2_XMEM_ERR_ADDR_MASK 0x0000FFFF -#define ADSP2_PMEM_ERR_ADDR_MASK 0x7FFF0000 -#define ADSP2_PMEM_ERR_ADDR_SHIFT 16 -#define ADSP2_WDT_ENA_MASK 0xFFFFFFFD - -#define ADSP2_LOCK_REGION_SHIFT 16 - #define ADSP_MAX_STD_CTRL_SIZE 512 -#define WM_ADSP_ACKED_CTL_TIMEOUT_MS 100 -#define WM_ADSP_ACKED_CTL_N_QUICKPOLLS 10 -#define WM_ADSP_ACKED_CTL_MIN_VALUE 0 -#define WM_ADSP_ACKED_CTL_MAX_VALUE 0xFFFFFF - -/* - * Event control messages - */ -#define WM_ADSP_FW_EVENT_SHUTDOWN 0x000001 - -/* - * HALO system info - */ -#define HALO_AHBM_WINDOW_DEBUG_0 0x02040 -#define HALO_AHBM_WINDOW_DEBUG_1 0x02044 - -/* - * HALO core - */ -#define HALO_SCRATCH1 0x005c0 -#define HALO_SCRATCH2 0x005c8 -#define HALO_SCRATCH3 0x005d0 -#define HALO_SCRATCH4 0x005d8 -#define HALO_CCM_CORE_CONTROL 0x41000 -#define HALO_CORE_SOFT_RESET 0x00010 -#define HALO_WDT_CONTROL 0x47000 - -/* - * HALO MPU banks - */ -#define HALO_MPU_XMEM_ACCESS_0 0x43000 -#define HALO_MPU_YMEM_ACCESS_0 0x43004 -#define HALO_MPU_WINDOW_ACCESS_0 0x43008 -#define HALO_MPU_XREG_ACCESS_0 0x4300C -#define HALO_MPU_YREG_ACCESS_0 0x43014 -#define HALO_MPU_XMEM_ACCESS_1 0x43018 -#define HALO_MPU_YMEM_ACCESS_1 0x4301C -#define HALO_MPU_WINDOW_ACCESS_1 0x43020 -#define HALO_MPU_XREG_ACCESS_1 0x43024 -#define HALO_MPU_YREG_ACCESS_1 0x4302C -#define HALO_MPU_XMEM_ACCESS_2 0x43030 -#define HALO_MPU_YMEM_ACCESS_2 0x43034 -#define HALO_MPU_WINDOW_ACCESS_2 0x43038 -#define HALO_MPU_XREG_ACCESS_2 0x4303C -#define HALO_MPU_YREG_ACCESS_2 0x43044 -#define HALO_MPU_XMEM_ACCESS_3 0x43048 -#define HALO_MPU_YMEM_ACCESS_3 0x4304C -#define HALO_MPU_WINDOW_ACCESS_3 0x43050 -#define HALO_MPU_XREG_ACCESS_3 0x43054 -#define HALO_MPU_YREG_ACCESS_3 0x4305C -#define HALO_MPU_XM_VIO_ADDR 0x43100 -#define HALO_MPU_XM_VIO_STATUS 0x43104 -#define HALO_MPU_YM_VIO_ADDR 0x43108 -#define HALO_MPU_YM_VIO_STATUS 0x4310C -#define HALO_MPU_PM_VIO_ADDR 0x43110 -#define HALO_MPU_PM_VIO_STATUS 0x43114 -#define HALO_MPU_LOCK_CONFIG 0x43140 - -/* - * HALO_AHBM_WINDOW_DEBUG_1 - */ -#define HALO_AHBM_CORE_ERR_ADDR_MASK 0x0fffff00 -#define HALO_AHBM_CORE_ERR_ADDR_SHIFT 8 -#define HALO_AHBM_FLAGS_ERR_MASK 0x000000ff - -/* - * HALO_CCM_CORE_CONTROL - */ -#define HALO_CORE_RESET 0x00000200 -#define HALO_CORE_EN 0x00000001 - -/* - * HALO_CORE_SOFT_RESET - */ -#define HALO_CORE_SOFT_RESET_MASK 0x00000001 - -/* - * HALO_WDT_CONTROL - */ -#define HALO_WDT_EN_MASK 0x00000001 - -/* - * HALO_MPU_?M_VIO_STATUS - */ -#define HALO_MPU_VIO_STS_MASK 0x007e0000 -#define HALO_MPU_VIO_STS_SHIFT 17 -#define HALO_MPU_VIO_ERR_WR_MASK 0x00008000 -#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff -#define HALO_MPU_VIO_ERR_SRC_SHIFT 0 - -static const struct wm_adsp_ops wm_adsp1_ops; -static const struct wm_adsp_ops wm_adsp2_ops[]; -static const struct wm_adsp_ops wm_halo_ops; - -struct wm_adsp_buf { - struct list_head list; - void *buf; -}; - -static struct wm_adsp_buf *wm_adsp_buf_alloc(const void *src, size_t len, - struct list_head *list) -{ - struct wm_adsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL); - - if (buf == NULL) - return NULL; - - buf->buf = vmalloc(len); - if (!buf->buf) { - kfree(buf); - return NULL; - } - memcpy(buf->buf, src, len); - - if (list) - list_add_tail(&buf->list, list); - - return buf; -} - -static void wm_adsp_buf_free(struct list_head *list) -{ - while (!list_empty(list)) { - struct wm_adsp_buf *buf = list_first_entry(list, - struct wm_adsp_buf, - list); - list_del(&buf->list); - vfree(buf->buf); - kfree(buf); - } -} +static const struct cs_dsp_client_ops wm_adsp1_client_ops; +static const struct cs_dsp_client_ops wm_adsp2_client_ops; #define WM_ADSP_FW_MBC_VSS 0 #define WM_ADSP_FW_HIFI 1 @@ -470,12 +178,10 @@ struct wm_adsp_compr { const char *name; }; -#define WM_ADSP_DATA_WORD_SIZE 3 - #define WM_ADSP_MIN_FRAGMENTS 1 #define WM_ADSP_MAX_FRAGMENTS 256 -#define WM_ADSP_MIN_FRAGMENT_SIZE (64 * WM_ADSP_DATA_WORD_SIZE) -#define WM_ADSP_MAX_FRAGMENT_SIZE (4096 * WM_ADSP_DATA_WORD_SIZE) +#define WM_ADSP_MIN_FRAGMENT_SIZE (64 * CS_DSP_DATA_WORD_SIZE) +#define WM_ADSP_MAX_FRAGMENT_SIZE (4096 * CS_DSP_DATA_WORD_SIZE) #define WM_ADSP_ALG_XM_STRUCT_MAGIC 0x49aec7 @@ -598,183 +304,11 @@ static const struct { struct wm_coeff_ctl { const char *name; - const char *fw_name; - /* Subname is needed to match with firmware */ - const char *subname; - unsigned int subname_len; - struct wm_adsp_alg_region alg_region; - struct wm_adsp *dsp; - unsigned int enabled:1; - struct list_head list; - void *cache; - unsigned int offset; - size_t len; - unsigned int set:1; + struct cs_dsp_coeff_ctl *cs_ctl; struct soc_bytes_ext bytes_ext; - unsigned int flags; - snd_ctl_elem_type_t type; -}; - -static const char *wm_adsp_mem_region_name(unsigned int type) -{ - switch (type) { - case WMFW_ADSP1_PM: - return "PM"; - case WMFW_HALO_PM_PACKED: - return "PM_PACKED"; - case WMFW_ADSP1_DM: - return "DM"; - case WMFW_ADSP2_XM: - return "XM"; - case WMFW_HALO_XM_PACKED: - return "XM_PACKED"; - case WMFW_ADSP2_YM: - return "YM"; - case WMFW_HALO_YM_PACKED: - return "YM_PACKED"; - case WMFW_ADSP1_ZM: - return "ZM"; - default: - return NULL; - } -} - -#ifdef CONFIG_DEBUG_FS -static void wm_adsp_debugfs_save_wmfwname(struct wm_adsp *dsp, const char *s) -{ - char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); - - kfree(dsp->wmfw_file_name); - dsp->wmfw_file_name = tmp; -} - -static void wm_adsp_debugfs_save_binname(struct wm_adsp *dsp, const char *s) -{ - char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); - - kfree(dsp->bin_file_name); - dsp->bin_file_name = tmp; -} - -static void wm_adsp_debugfs_clear(struct wm_adsp *dsp) -{ - kfree(dsp->wmfw_file_name); - kfree(dsp->bin_file_name); - dsp->wmfw_file_name = NULL; - dsp->bin_file_name = NULL; -} - -static ssize_t wm_adsp_debugfs_wmfw_read(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct wm_adsp *dsp = file->private_data; - ssize_t ret; - - mutex_lock(&dsp->pwr_lock); - - if (!dsp->wmfw_file_name || !dsp->booted) - ret = 0; - else - ret = simple_read_from_buffer(user_buf, count, ppos, - dsp->wmfw_file_name, - strlen(dsp->wmfw_file_name)); - - mutex_unlock(&dsp->pwr_lock); - return ret; -} - -static ssize_t wm_adsp_debugfs_bin_read(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct wm_adsp *dsp = file->private_data; - ssize_t ret; - - mutex_lock(&dsp->pwr_lock); - - if (!dsp->bin_file_name || !dsp->booted) - ret = 0; - else - ret = simple_read_from_buffer(user_buf, count, ppos, - dsp->bin_file_name, - strlen(dsp->bin_file_name)); - - mutex_unlock(&dsp->pwr_lock); - return ret; -} - -static const struct { - const char *name; - const struct file_operations fops; -} wm_adsp_debugfs_fops[] = { - { - .name = "wmfw_file_name", - .fops = { - .open = simple_open, - .read = wm_adsp_debugfs_wmfw_read, - }, - }, - { - .name = "bin_file_name", - .fops = { - .open = simple_open, - .read = wm_adsp_debugfs_bin_read, - }, - }, + struct work_struct work; }; -static void wm_adsp2_init_debugfs(struct wm_adsp *dsp, - struct snd_soc_component *component) -{ - struct dentry *root = NULL; - int i; - - root = debugfs_create_dir(dsp->name, component->debugfs_root); - - debugfs_create_bool("booted", 0444, root, &dsp->booted); - debugfs_create_bool("running", 0444, root, &dsp->running); - debugfs_create_x32("fw_id", 0444, root, &dsp->fw_id); - debugfs_create_x32("fw_version", 0444, root, &dsp->fw_id_version); - - for (i = 0; i < ARRAY_SIZE(wm_adsp_debugfs_fops); ++i) - debugfs_create_file(wm_adsp_debugfs_fops[i].name, 0444, root, - dsp, &wm_adsp_debugfs_fops[i].fops); - - dsp->debugfs_root = root; -} - -static void wm_adsp2_cleanup_debugfs(struct wm_adsp *dsp) -{ - wm_adsp_debugfs_clear(dsp); - debugfs_remove_recursive(dsp->debugfs_root); - dsp->debugfs_root = NULL; -} -#else -static inline void wm_adsp2_init_debugfs(struct wm_adsp *dsp, - struct snd_soc_component *component) -{ -} - -static inline void wm_adsp2_cleanup_debugfs(struct wm_adsp *dsp) -{ -} - -static inline void wm_adsp_debugfs_save_wmfwname(struct wm_adsp *dsp, - const char *s) -{ -} - -static inline void wm_adsp_debugfs_save_binname(struct wm_adsp *dsp, - const char *s) -{ -} - -static inline void wm_adsp_debugfs_clear(struct wm_adsp *dsp) -{ -} -#endif - int wm_adsp_fw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -802,14 +336,14 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, if (ucontrol->value.enumerated.item[0] >= WM_ADSP_NUM_FW) return -EINVAL; - mutex_lock(&dsp[e->shift_l].pwr_lock); + mutex_lock(&dsp[e->shift_l].cs_dsp.pwr_lock); - if (dsp[e->shift_l].booted || !list_empty(&dsp[e->shift_l].compr_list)) + if (dsp[e->shift_l].cs_dsp.booted || !list_empty(&dsp[e->shift_l].compr_list)) ret = -EBUSY; else dsp[e->shift_l].fw = ucontrol->value.enumerated.item[0]; - mutex_unlock(&dsp[e->shift_l].pwr_lock); + mutex_unlock(&dsp[e->shift_l].cs_dsp.pwr_lock); return ret; } @@ -826,270 +360,49 @@ const struct soc_enum wm_adsp_fw_enum[] = { }; EXPORT_SYMBOL_GPL(wm_adsp_fw_enum); -static const struct wm_adsp_region *wm_adsp_find_region(struct wm_adsp *dsp, - int type) -{ - int i; - - for (i = 0; i < dsp->num_mems; i++) - if (dsp->mem[i].type == type) - return &dsp->mem[i]; - - return NULL; -} - -static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *mem, - unsigned int offset) -{ - switch (mem->type) { - case WMFW_ADSP1_PM: - return mem->base + (offset * 3); - case WMFW_ADSP1_DM: - case WMFW_ADSP2_XM: - case WMFW_ADSP2_YM: - case WMFW_ADSP1_ZM: - return mem->base + (offset * 2); - default: - WARN(1, "Unknown memory region type"); - return offset; - } -} - -static unsigned int wm_halo_region_to_reg(struct wm_adsp_region const *mem, - unsigned int offset) -{ - switch (mem->type) { - case WMFW_ADSP2_XM: - case WMFW_ADSP2_YM: - return mem->base + (offset * 4); - case WMFW_HALO_XM_PACKED: - case WMFW_HALO_YM_PACKED: - return (mem->base + (offset * 3)) & ~0x3; - case WMFW_HALO_PM_PACKED: - return mem->base + (offset * 5); - default: - WARN(1, "Unknown memory region type"); - return offset; - } -} - -static void wm_adsp_read_fw_status(struct wm_adsp *dsp, - int noffs, unsigned int *offs) -{ - unsigned int i; - int ret; - - for (i = 0; i < noffs; ++i) { - ret = regmap_read(dsp->regmap, dsp->base + offs[i], &offs[i]); - if (ret) { - adsp_err(dsp, "Failed to read SCRATCH%u: %d\n", i, ret); - return; - } - } -} - -static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) -{ - unsigned int offs[] = { - ADSP2_SCRATCH0, ADSP2_SCRATCH1, ADSP2_SCRATCH2, ADSP2_SCRATCH3, - }; - - wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); - - adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", - offs[0], offs[1], offs[2], offs[3]); -} - -static void wm_adsp2v2_show_fw_status(struct wm_adsp *dsp) -{ - unsigned int offs[] = { ADSP2V2_SCRATCH0_1, ADSP2V2_SCRATCH2_3 }; - - wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); - - adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", - offs[0] & 0xFFFF, offs[0] >> 16, - offs[1] & 0xFFFF, offs[1] >> 16); -} - -static void wm_halo_show_fw_status(struct wm_adsp *dsp) -{ - unsigned int offs[] = { - HALO_SCRATCH1, HALO_SCRATCH2, HALO_SCRATCH3, HALO_SCRATCH4, - }; - - wm_adsp_read_fw_status(dsp, ARRAY_SIZE(offs), offs); - - adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", - offs[0], offs[1], offs[2], offs[3]); -} - static inline struct wm_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) { return container_of(ext, struct wm_coeff_ctl, bytes_ext); } -static int wm_coeff_base_reg(struct wm_coeff_ctl *ctl, unsigned int *reg) -{ - const struct wm_adsp_alg_region *alg_region = &ctl->alg_region; - struct wm_adsp *dsp = ctl->dsp; - const struct wm_adsp_region *mem; - - mem = wm_adsp_find_region(dsp, alg_region->type); - if (!mem) { - adsp_err(dsp, "No base for region %x\n", - alg_region->type); - return -EINVAL; - } - - *reg = dsp->ops->region_to_reg(mem, ctl->alg_region.base + ctl->offset); - - return 0; -} - static int wm_coeff_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) { struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; - switch (ctl->type) { + switch (cs_ctl->type) { case WMFW_CTL_TYPE_ACKED: uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->value.integer.min = WM_ADSP_ACKED_CTL_MIN_VALUE; - uinfo->value.integer.max = WM_ADSP_ACKED_CTL_MAX_VALUE; + uinfo->value.integer.min = CS_DSP_ACKED_CTL_MIN_VALUE; + uinfo->value.integer.max = CS_DSP_ACKED_CTL_MAX_VALUE; uinfo->value.integer.step = 1; uinfo->count = 1; break; default: uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; - uinfo->count = ctl->len; + uinfo->count = cs_ctl->len; break; } return 0; } -static int wm_coeff_write_acked_control(struct wm_coeff_ctl *ctl, - unsigned int event_id) -{ - struct wm_adsp *dsp = ctl->dsp; - __be32 val = cpu_to_be32(event_id); - unsigned int reg; - int i, ret; - - ret = wm_coeff_base_reg(ctl, ®); - if (ret) - return ret; - - adsp_dbg(dsp, "Sending 0x%x to acked control alg 0x%x %s:0x%x\n", - event_id, ctl->alg_region.alg, - wm_adsp_mem_region_name(ctl->alg_region.type), ctl->offset); - - ret = regmap_raw_write(dsp->regmap, reg, &val, sizeof(val)); - if (ret) { - adsp_err(dsp, "Failed to write %x: %d\n", reg, ret); - return ret; - } - - /* - * Poll for ack, we initially poll at ~1ms intervals for firmwares - * that respond quickly, then go to ~10ms polls. A firmware is unlikely - * to ack instantly so we do the first 1ms delay before reading the - * control to avoid a pointless bus transaction - */ - for (i = 0; i < WM_ADSP_ACKED_CTL_TIMEOUT_MS;) { - switch (i) { - case 0 ... WM_ADSP_ACKED_CTL_N_QUICKPOLLS - 1: - usleep_range(1000, 2000); - i++; - break; - default: - usleep_range(10000, 20000); - i += 10; - break; - } - - ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); - if (ret) { - adsp_err(dsp, "Failed to read %x: %d\n", reg, ret); - return ret; - } - - if (val == 0) { - adsp_dbg(dsp, "Acked control ACKED at poll %u\n", i); - return 0; - } - } - - adsp_warn(dsp, "Acked control @0x%x alg:0x%x %s:0x%x timed out\n", - reg, ctl->alg_region.alg, - wm_adsp_mem_region_name(ctl->alg_region.type), - ctl->offset); - - return -ETIMEDOUT; -} - -static int wm_coeff_write_ctrl_raw(struct wm_coeff_ctl *ctl, - const void *buf, size_t len) -{ - struct wm_adsp *dsp = ctl->dsp; - void *scratch; - int ret; - unsigned int reg; - - ret = wm_coeff_base_reg(ctl, ®); - if (ret) - return ret; - - scratch = kmemdup(buf, len, GFP_KERNEL | GFP_DMA); - if (!scratch) - return -ENOMEM; - - ret = regmap_raw_write(dsp->regmap, reg, scratch, - len); - if (ret) { - adsp_err(dsp, "Failed to write %zu bytes to %x: %d\n", - len, reg, ret); - kfree(scratch); - return ret; - } - adsp_dbg(dsp, "Wrote %zu bytes to %x\n", len, reg); - - kfree(scratch); - - return 0; -} - -static int wm_coeff_write_ctrl(struct wm_coeff_ctl *ctl, - const void *buf, size_t len) -{ - int ret = 0; - - if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) - ret = -EPERM; - else if (buf != ctl->cache) - memcpy(ctl->cache, buf, len); - - ctl->set = 1; - if (ctl->enabled && ctl->dsp->running) - ret = wm_coeff_write_ctrl_raw(ctl, buf, len); - - return ret; -} - static int wm_coeff_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) { struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; char *p = ucontrol->value.bytes.data; int ret = 0; - mutex_lock(&ctl->dsp->pwr_lock); - ret = wm_coeff_write_ctrl(ctl, p, ctl->len); - mutex_unlock(&ctl->dsp->pwr_lock); + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); return ret; } @@ -1100,16 +413,17 @@ static int wm_coeff_tlv_put(struct snd_kcontrol *kctl, struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; int ret = 0; - mutex_lock(&ctl->dsp->pwr_lock); + mutex_lock(&cs_ctl->dsp->pwr_lock); - if (copy_from_user(ctl->cache, bytes, size)) + if (copy_from_user(cs_ctl->cache, bytes, size)) ret = -EFAULT; else - ret = wm_coeff_write_ctrl(ctl, ctl->cache, size); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, cs_ctl->cache, size); - mutex_unlock(&ctl->dsp->pwr_lock); + mutex_unlock(&cs_ctl->dsp->pwr_lock); return ret; } @@ -1120,71 +434,21 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; unsigned int val = ucontrol->value.integer.value[0]; int ret; if (val == 0) return 0; /* 0 means no event */ - mutex_lock(&ctl->dsp->pwr_lock); + mutex_lock(&cs_ctl->dsp->pwr_lock); - if (ctl->enabled && ctl->dsp->running) - ret = wm_coeff_write_acked_control(ctl, val); + if (cs_ctl->enabled) + ret = cs_dsp_coeff_write_acked_control(cs_ctl, val); else ret = -EPERM; - mutex_unlock(&ctl->dsp->pwr_lock); - - return ret; -} - -static int wm_coeff_read_ctrl_raw(struct wm_coeff_ctl *ctl, - void *buf, size_t len) -{ - struct wm_adsp *dsp = ctl->dsp; - void *scratch; - int ret; - unsigned int reg; - - ret = wm_coeff_base_reg(ctl, ®); - if (ret) - return ret; - - scratch = kmalloc(len, GFP_KERNEL | GFP_DMA); - if (!scratch) - return -ENOMEM; - - ret = regmap_raw_read(dsp->regmap, reg, scratch, len); - if (ret) { - adsp_err(dsp, "Failed to read %zu bytes from %x: %d\n", - len, reg, ret); - kfree(scratch); - return ret; - } - adsp_dbg(dsp, "Read %zu bytes from %x\n", len, reg); - - memcpy(buf, scratch, len); - kfree(scratch); - - return 0; -} - -static int wm_coeff_read_ctrl(struct wm_coeff_ctl *ctl, void *buf, size_t len) -{ - int ret = 0; - - if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) { - if (ctl->enabled && ctl->dsp->running) - return wm_coeff_read_ctrl_raw(ctl, buf, len); - else - return -EPERM; - } else { - if (!ctl->flags && ctl->enabled && ctl->dsp->running) - ret = wm_coeff_read_ctrl_raw(ctl, ctl->cache, ctl->len); - - if (buf != ctl->cache) - memcpy(buf, ctl->cache, len); - } + mutex_unlock(&cs_ctl->dsp->pwr_lock); return ret; } @@ -1195,12 +459,13 @@ static int wm_coeff_get(struct snd_kcontrol *kctl, struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; char *p = ucontrol->value.bytes.data; int ret; - mutex_lock(&ctl->dsp->pwr_lock); - ret = wm_coeff_read_ctrl(ctl, p, ctl->len); - mutex_unlock(&ctl->dsp->pwr_lock); + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); return ret; } @@ -1211,16 +476,17 @@ static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; int ret = 0; - mutex_lock(&ctl->dsp->pwr_lock); + mutex_lock(&cs_ctl->dsp->pwr_lock); - ret = wm_coeff_read_ctrl(ctl, ctl->cache, size); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, cs_ctl->cache, size); - if (!ret && copy_to_user(bytes, ctl->cache, size)) + if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) ret = -EFAULT; - mutex_unlock(&ctl->dsp->pwr_lock); + mutex_unlock(&cs_ctl->dsp->pwr_lock); return ret; } @@ -1240,12 +506,6 @@ static int wm_coeff_get_acked(struct snd_kcontrol *kcontrol, return 0; } -struct wmfw_ctl_work { - struct wm_adsp *dsp; - struct wm_coeff_ctl *ctl; - struct work_struct work; -}; - static unsigned int wmfw_convert_flags(unsigned int in, unsigned int len) { unsigned int out, rd, wr, vol; @@ -1279,12 +539,10 @@ static unsigned int wmfw_convert_flags(unsigned int in, unsigned int len) static int wmfw_add_ctl(struct wm_adsp *dsp, struct wm_coeff_ctl *ctl) { + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; struct snd_kcontrol_new *kcontrol; int ret; - if (!ctl || !ctl->name) - return -EINVAL; - kcontrol = kzalloc(sizeof(*kcontrol), GFP_KERNEL); if (!kcontrol) return -ENOMEM; @@ -1294,16 +552,16 @@ static int wmfw_add_ctl(struct wm_adsp *dsp, struct wm_coeff_ctl *ctl) kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER; kcontrol->tlv.c = snd_soc_bytes_tlv_callback; kcontrol->private_value = (unsigned long)&ctl->bytes_ext; - kcontrol->access = wmfw_convert_flags(ctl->flags, ctl->len); + kcontrol->access = wmfw_convert_flags(cs_ctl->flags, cs_ctl->len); - switch (ctl->type) { + switch (cs_ctl->type) { case WMFW_CTL_TYPE_ACKED: kcontrol->get = wm_coeff_get_acked; kcontrol->put = wm_coeff_put_acked; break; default: if (kcontrol->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { - ctl->bytes_ext.max = ctl->len; + ctl->bytes_ext.max = cs_ctl->len; ctl->bytes_ext.get = wm_coeff_tlv_get; ctl->bytes_ext.put = wm_coeff_tlv_put; } else { @@ -1326,128 +584,55 @@ err_kcontrol: return ret; } -static int wm_coeff_init_control_caches(struct wm_adsp *dsp) -{ - struct wm_coeff_ctl *ctl; - int ret; - - list_for_each_entry(ctl, &dsp->ctl_list, list) { - if (!ctl->enabled || ctl->set) - continue; - if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) - continue; - - /* - * For readable controls populate the cache from the DSP memory. - * For non-readable controls the cache was zero-filled when - * created so we don't need to do anything. - */ - if (!ctl->flags || (ctl->flags & WMFW_CTL_FLAG_READABLE)) { - ret = wm_coeff_read_ctrl_raw(ctl, ctl->cache, ctl->len); - if (ret < 0) - return ret; - } - } - - return 0; -} - -static int wm_coeff_sync_controls(struct wm_adsp *dsp) -{ - struct wm_coeff_ctl *ctl; - int ret; - - list_for_each_entry(ctl, &dsp->ctl_list, list) { - if (!ctl->enabled) - continue; - if (ctl->set && !(ctl->flags & WMFW_CTL_FLAG_VOLATILE)) { - ret = wm_coeff_write_ctrl_raw(ctl, ctl->cache, - ctl->len); - if (ret < 0) - return ret; - } - } - - return 0; -} - -static void wm_adsp_signal_event_controls(struct wm_adsp *dsp, - unsigned int event) -{ - struct wm_coeff_ctl *ctl; - int ret; - - list_for_each_entry(ctl, &dsp->ctl_list, list) { - if (ctl->type != WMFW_CTL_TYPE_HOSTEVENT) - continue; - - if (!ctl->enabled) - continue; - - ret = wm_coeff_write_acked_control(ctl, event); - if (ret) - adsp_warn(dsp, - "Failed to send 0x%x event to alg 0x%x (%d)\n", - event, ctl->alg_region.alg, ret); - } -} - static void wm_adsp_ctl_work(struct work_struct *work) { - struct wmfw_ctl_work *ctl_work = container_of(work, - struct wmfw_ctl_work, - work); - - wmfw_add_ctl(ctl_work->dsp, ctl_work->ctl); - kfree(ctl_work); -} + struct wm_coeff_ctl *ctl = container_of(work, + struct wm_coeff_ctl, + work); + struct wm_adsp *dsp = container_of(ctl->cs_ctl->dsp, + struct wm_adsp, + cs_dsp); -static void wm_adsp_free_ctl_blk(struct wm_coeff_ctl *ctl) -{ - kfree(ctl->cache); - kfree(ctl->name); - kfree(ctl->subname); - kfree(ctl); + wmfw_add_ctl(dsp, ctl); } -static int wm_adsp_create_control(struct wm_adsp *dsp, - const struct wm_adsp_alg_region *alg_region, - unsigned int offset, unsigned int len, - const char *subname, unsigned int subname_len, - unsigned int flags, snd_ctl_elem_type_t type) +static int wm_adsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl) { + struct wm_adsp *dsp = container_of(cs_ctl->dsp, struct wm_adsp, cs_dsp); + struct cs_dsp *cs_dsp = &dsp->cs_dsp; struct wm_coeff_ctl *ctl; - struct wmfw_ctl_work *ctl_work; char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; const char *region_name; int ret; - region_name = wm_adsp_mem_region_name(alg_region->type); + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) + return 0; + + region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type); if (!region_name) { - adsp_err(dsp, "Unknown region type: %d\n", alg_region->type); + adsp_err(dsp, "Unknown region type: %d\n", cs_ctl->alg_region.type); return -EINVAL; } - switch (dsp->fw_ver) { + switch (cs_dsp->fw_ver) { case 0: case 1: snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %x", - dsp->name, region_name, alg_region->alg); - subname = NULL; /* don't append subname */ + cs_dsp->name, region_name, cs_ctl->alg_region.alg); break; case 2: ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, - "%s%c %.12s %x", dsp->name, *region_name, - wm_adsp_fw_text[dsp->fw], alg_region->alg); + "%s%c %.12s %x", cs_dsp->name, *region_name, + wm_adsp_fw_text[dsp->fw], cs_ctl->alg_region.alg); break; default: ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, - "%s %.12s %x", dsp->name, - wm_adsp_fw_text[dsp->fw], alg_region->alg); + "%s %.12s %x", cs_dsp->name, + wm_adsp_fw_text[dsp->fw], cs_ctl->alg_region.alg); break; } - if (subname) { + if (cs_ctl->subname) { int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; int skip = 0; @@ -1455,613 +640,70 @@ static int wm_adsp_create_control(struct wm_adsp *dsp, avail -= strlen(dsp->component->name_prefix) + 1; /* Truncate the subname from the start if it is too long */ - if (subname_len > avail) - skip = subname_len - avail; + if (cs_ctl->subname_len > avail) + skip = cs_ctl->subname_len - avail; snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, - " %.*s", subname_len - skip, subname + skip); - } - - list_for_each_entry(ctl, &dsp->ctl_list, list) { - if (!strcmp(ctl->name, name)) { - if (!ctl->enabled) - ctl->enabled = 1; - return 0; - } + " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); } ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); if (!ctl) return -ENOMEM; - ctl->fw_name = wm_adsp_fw_text[dsp->fw]; - ctl->alg_region = *alg_region; + ctl->cs_ctl = cs_ctl; + ctl->name = kmemdup(name, strlen(name) + 1, GFP_KERNEL); if (!ctl->name) { ret = -ENOMEM; goto err_ctl; } - if (subname) { - ctl->subname_len = subname_len; - ctl->subname = kmemdup(subname, - strlen(subname) + 1, GFP_KERNEL); - if (!ctl->subname) { - ret = -ENOMEM; - goto err_ctl_name; - } - } - ctl->enabled = 1; - ctl->set = 0; - ctl->dsp = dsp; - - ctl->flags = flags; - ctl->type = type; - ctl->offset = offset; - ctl->len = len; - ctl->cache = kzalloc(ctl->len, GFP_KERNEL); - if (!ctl->cache) { - ret = -ENOMEM; - goto err_ctl_subname; - } - - list_add(&ctl->list, &dsp->ctl_list); - if (flags & WMFW_CTL_FLAG_SYS) - return 0; - - ctl_work = kzalloc(sizeof(*ctl_work), GFP_KERNEL); - if (!ctl_work) { - ret = -ENOMEM; - goto err_list_del; - } + cs_ctl->priv = ctl; - ctl_work->dsp = dsp; - ctl_work->ctl = ctl; - INIT_WORK(&ctl_work->work, wm_adsp_ctl_work); - schedule_work(&ctl_work->work); + INIT_WORK(&ctl->work, wm_adsp_ctl_work); + schedule_work(&ctl->work); return 0; -err_list_del: - list_del(&ctl->list); - kfree(ctl->cache); -err_ctl_subname: - kfree(ctl->subname); -err_ctl_name: - kfree(ctl->name); err_ctl: kfree(ctl); return ret; } -struct wm_coeff_parsed_alg { - int id; - const u8 *name; - int name_len; - int ncoeff; -}; - -struct wm_coeff_parsed_coeff { - int offset; - int mem_type; - const u8 *name; - int name_len; - snd_ctl_elem_type_t ctl_type; - int flags; - int len; -}; - -static int wm_coeff_parse_string(int bytes, const u8 **pos, const u8 **str) -{ - int length; - - switch (bytes) { - case 1: - length = **pos; - break; - case 2: - length = le16_to_cpu(*((__le16 *)*pos)); - break; - default: - return 0; - } - - if (str) - *str = *pos + bytes; - - *pos += ((length + bytes) + 3) & ~0x03; - - return length; -} - -static int wm_coeff_parse_int(int bytes, const u8 **pos) -{ - int val = 0; - - switch (bytes) { - case 2: - val = le16_to_cpu(*((__le16 *)*pos)); - break; - case 4: - val = le32_to_cpu(*((__le32 *)*pos)); - break; - default: - break; - } - - *pos += bytes; - - return val; -} - -static inline void wm_coeff_parse_alg(struct wm_adsp *dsp, const u8 **data, - struct wm_coeff_parsed_alg *blk) -{ - const struct wmfw_adsp_alg_data *raw; - - switch (dsp->fw_ver) { - case 0: - case 1: - raw = (const struct wmfw_adsp_alg_data *)*data; - *data = raw->data; - - blk->id = le32_to_cpu(raw->id); - blk->name = raw->name; - blk->name_len = strlen(raw->name); - blk->ncoeff = le32_to_cpu(raw->ncoeff); - break; - default: - blk->id = wm_coeff_parse_int(sizeof(raw->id), data); - blk->name_len = wm_coeff_parse_string(sizeof(u8), data, - &blk->name); - wm_coeff_parse_string(sizeof(u16), data, NULL); - blk->ncoeff = wm_coeff_parse_int(sizeof(raw->ncoeff), data); - break; - } - - adsp_dbg(dsp, "Algorithm ID: %#x\n", blk->id); - adsp_dbg(dsp, "Algorithm name: %.*s\n", blk->name_len, blk->name); - adsp_dbg(dsp, "# of coefficient descriptors: %#x\n", blk->ncoeff); -} - -static inline void wm_coeff_parse_coeff(struct wm_adsp *dsp, const u8 **data, - struct wm_coeff_parsed_coeff *blk) -{ - const struct wmfw_adsp_coeff_data *raw; - const u8 *tmp; - int length; - - switch (dsp->fw_ver) { - case 0: - case 1: - raw = (const struct wmfw_adsp_coeff_data *)*data; - *data = *data + sizeof(raw->hdr) + le32_to_cpu(raw->hdr.size); - - blk->offset = le16_to_cpu(raw->hdr.offset); - blk->mem_type = le16_to_cpu(raw->hdr.type); - blk->name = raw->name; - blk->name_len = strlen(raw->name); - blk->ctl_type = (__force snd_ctl_elem_type_t)le16_to_cpu(raw->ctl_type); - blk->flags = le16_to_cpu(raw->flags); - blk->len = le32_to_cpu(raw->len); - break; - default: - tmp = *data; - blk->offset = wm_coeff_parse_int(sizeof(raw->hdr.offset), &tmp); - blk->mem_type = wm_coeff_parse_int(sizeof(raw->hdr.type), &tmp); - length = wm_coeff_parse_int(sizeof(raw->hdr.size), &tmp); - blk->name_len = wm_coeff_parse_string(sizeof(u8), &tmp, - &blk->name); - wm_coeff_parse_string(sizeof(u8), &tmp, NULL); - wm_coeff_parse_string(sizeof(u16), &tmp, NULL); - blk->ctl_type = - (__force snd_ctl_elem_type_t)wm_coeff_parse_int(sizeof(raw->ctl_type), - &tmp); - blk->flags = wm_coeff_parse_int(sizeof(raw->flags), &tmp); - blk->len = wm_coeff_parse_int(sizeof(raw->len), &tmp); - - *data = *data + sizeof(raw->hdr) + length; - break; - } - - adsp_dbg(dsp, "\tCoefficient type: %#x\n", blk->mem_type); - adsp_dbg(dsp, "\tCoefficient offset: %#x\n", blk->offset); - adsp_dbg(dsp, "\tCoefficient name: %.*s\n", blk->name_len, blk->name); - adsp_dbg(dsp, "\tCoefficient flags: %#x\n", blk->flags); - adsp_dbg(dsp, "\tALSA control type: %#x\n", blk->ctl_type); - adsp_dbg(dsp, "\tALSA control len: %#x\n", blk->len); -} - -static int wm_adsp_check_coeff_flags(struct wm_adsp *dsp, - const struct wm_coeff_parsed_coeff *coeff_blk, - unsigned int f_required, - unsigned int f_illegal) -{ - if ((coeff_blk->flags & f_illegal) || - ((coeff_blk->flags & f_required) != f_required)) { - adsp_err(dsp, "Illegal flags 0x%x for control type 0x%x\n", - coeff_blk->flags, coeff_blk->ctl_type); - return -EINVAL; - } - - return 0; -} - -static int wm_adsp_parse_coeff(struct wm_adsp *dsp, - const struct wmfw_region *region) -{ - struct wm_adsp_alg_region alg_region = {}; - struct wm_coeff_parsed_alg alg_blk; - struct wm_coeff_parsed_coeff coeff_blk; - const u8 *data = region->data; - int i, ret; - - wm_coeff_parse_alg(dsp, &data, &alg_blk); - for (i = 0; i < alg_blk.ncoeff; i++) { - wm_coeff_parse_coeff(dsp, &data, &coeff_blk); - - switch (coeff_blk.ctl_type) { - case SNDRV_CTL_ELEM_TYPE_BYTES: - break; - case WMFW_CTL_TYPE_ACKED: - if (coeff_blk.flags & WMFW_CTL_FLAG_SYS) - continue; /* ignore */ - - ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk, - WMFW_CTL_FLAG_VOLATILE | - WMFW_CTL_FLAG_WRITEABLE | - WMFW_CTL_FLAG_READABLE, - 0); - if (ret) - return -EINVAL; - break; - case WMFW_CTL_TYPE_HOSTEVENT: - ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk, - WMFW_CTL_FLAG_SYS | - WMFW_CTL_FLAG_VOLATILE | - WMFW_CTL_FLAG_WRITEABLE | - WMFW_CTL_FLAG_READABLE, - 0); - if (ret) - return -EINVAL; - break; - case WMFW_CTL_TYPE_HOST_BUFFER: - ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk, - WMFW_CTL_FLAG_SYS | - WMFW_CTL_FLAG_VOLATILE | - WMFW_CTL_FLAG_READABLE, - 0); - if (ret) - return -EINVAL; - break; - default: - adsp_err(dsp, "Unknown control type: %d\n", - coeff_blk.ctl_type); - return -EINVAL; - } - - alg_region.type = coeff_blk.mem_type; - alg_region.alg = alg_blk.id; - - ret = wm_adsp_create_control(dsp, &alg_region, - coeff_blk.offset, - coeff_blk.len, - coeff_blk.name, - coeff_blk.name_len, - coeff_blk.flags, - coeff_blk.ctl_type); - if (ret < 0) - adsp_err(dsp, "Failed to create control: %.*s, %d\n", - coeff_blk.name_len, coeff_blk.name, ret); - } - - return 0; -} - -static unsigned int wm_adsp1_parse_sizes(struct wm_adsp *dsp, - const char * const file, - unsigned int pos, - const struct firmware *firmware) -{ - const struct wmfw_adsp1_sizes *adsp1_sizes; - - adsp1_sizes = (void *)&firmware->data[pos]; - - adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", file, - le32_to_cpu(adsp1_sizes->dm), le32_to_cpu(adsp1_sizes->pm), - le32_to_cpu(adsp1_sizes->zm)); - - return pos + sizeof(*adsp1_sizes); -} - -static unsigned int wm_adsp2_parse_sizes(struct wm_adsp *dsp, - const char * const file, - unsigned int pos, - const struct firmware *firmware) -{ - const struct wmfw_adsp2_sizes *adsp2_sizes; - - adsp2_sizes = (void *)&firmware->data[pos]; - - adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", file, - le32_to_cpu(adsp2_sizes->xm), le32_to_cpu(adsp2_sizes->ym), - le32_to_cpu(adsp2_sizes->pm), le32_to_cpu(adsp2_sizes->zm)); - - return pos + sizeof(*adsp2_sizes); -} - -static bool wm_adsp_validate_version(struct wm_adsp *dsp, unsigned int version) -{ - switch (version) { - case 0: - adsp_warn(dsp, "Deprecated file format %d\n", version); - return true; - case 1: - case 2: - return true; - default: - return false; - } -} - -static bool wm_halo_validate_version(struct wm_adsp *dsp, unsigned int version) -{ - switch (version) { - case 3: - return true; - default: - return false; - } -} - -static int wm_adsp_load(struct wm_adsp *dsp) -{ - LIST_HEAD(buf_list); - const struct firmware *firmware; - struct regmap *regmap = dsp->regmap; - unsigned int pos = 0; - const struct wmfw_header *header; - const struct wmfw_adsp1_sizes *adsp1_sizes; - const struct wmfw_footer *footer; - const struct wmfw_region *region; - const struct wm_adsp_region *mem; - const char *region_name; - char *file, *text = NULL; - struct wm_adsp_buf *buf; - unsigned int reg; - int regions = 0; - int ret, offset, type; - - file = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (file == NULL) - return -ENOMEM; - - snprintf(file, PAGE_SIZE, "%s-%s-%s.wmfw", dsp->part, dsp->fwf_name, - wm_adsp_fw[dsp->fw].file); - file[PAGE_SIZE - 1] = '\0'; - - ret = request_firmware(&firmware, file, dsp->dev); - if (ret != 0) { - adsp_err(dsp, "Failed to request '%s'\n", file); - goto out; - } - ret = -EINVAL; - - pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); - if (pos >= firmware->size) { - adsp_err(dsp, "%s: file too short, %zu bytes\n", - file, firmware->size); - goto out_fw; - } - - header = (void *)&firmware->data[0]; - - if (memcmp(&header->magic[0], "WMFW", 4) != 0) { - adsp_err(dsp, "%s: invalid magic\n", file); - goto out_fw; - } - - if (!dsp->ops->validate_version(dsp, header->ver)) { - adsp_err(dsp, "%s: unknown file format %d\n", - file, header->ver); - goto out_fw; - } - - adsp_info(dsp, "Firmware version: %d\n", header->ver); - dsp->fw_ver = header->ver; - - if (header->core != dsp->type) { - adsp_err(dsp, "%s: invalid core %d != %d\n", - file, header->core, dsp->type); - goto out_fw; - } - - pos = sizeof(*header); - pos = dsp->ops->parse_sizes(dsp, file, pos, firmware); - - footer = (void *)&firmware->data[pos]; - pos += sizeof(*footer); - - if (le32_to_cpu(header->len) != pos) { - adsp_err(dsp, "%s: unexpected header length %d\n", - file, le32_to_cpu(header->len)); - goto out_fw; - } - - adsp_dbg(dsp, "%s: timestamp %llu\n", file, - le64_to_cpu(footer->timestamp)); - - while (pos < firmware->size && - sizeof(*region) < firmware->size - pos) { - region = (void *)&(firmware->data[pos]); - region_name = "Unknown"; - reg = 0; - text = NULL; - offset = le32_to_cpu(region->offset) & 0xffffff; - type = be32_to_cpu(region->type) & 0xff; - - switch (type) { - case WMFW_NAME_TEXT: - region_name = "Firmware name"; - text = kzalloc(le32_to_cpu(region->len) + 1, - GFP_KERNEL); - break; - case WMFW_ALGORITHM_DATA: - region_name = "Algorithm"; - ret = wm_adsp_parse_coeff(dsp, region); - if (ret != 0) - goto out_fw; - break; - case WMFW_INFO_TEXT: - region_name = "Information"; - text = kzalloc(le32_to_cpu(region->len) + 1, - GFP_KERNEL); - break; - case WMFW_ABSOLUTE: - region_name = "Absolute"; - reg = offset; - break; - case WMFW_ADSP1_PM: - case WMFW_ADSP1_DM: - case WMFW_ADSP2_XM: - case WMFW_ADSP2_YM: - case WMFW_ADSP1_ZM: - case WMFW_HALO_PM_PACKED: - case WMFW_HALO_XM_PACKED: - case WMFW_HALO_YM_PACKED: - mem = wm_adsp_find_region(dsp, type); - if (!mem) { - adsp_err(dsp, "No region of type: %x\n", type); - ret = -EINVAL; - goto out_fw; - } - - region_name = wm_adsp_mem_region_name(type); - reg = dsp->ops->region_to_reg(mem, offset); - break; - default: - adsp_warn(dsp, - "%s.%d: Unknown region type %x at %d(%x)\n", - file, regions, type, pos, pos); - break; - } - - adsp_dbg(dsp, "%s.%d: %d bytes at %d in %s\n", file, - regions, le32_to_cpu(region->len), offset, - region_name); - - if (le32_to_cpu(region->len) > - firmware->size - pos - sizeof(*region)) { - adsp_err(dsp, - "%s.%d: %s region len %d bytes exceeds file length %zu\n", - file, regions, region_name, - le32_to_cpu(region->len), firmware->size); - ret = -EINVAL; - goto out_fw; - } - - if (text) { - memcpy(text, region->data, le32_to_cpu(region->len)); - adsp_info(dsp, "%s: %s\n", file, text); - kfree(text); - text = NULL; - } - - if (reg) { - buf = wm_adsp_buf_alloc(region->data, - le32_to_cpu(region->len), - &buf_list); - if (!buf) { - adsp_err(dsp, "Out of memory\n"); - ret = -ENOMEM; - goto out_fw; - } - - ret = regmap_raw_write_async(regmap, reg, buf->buf, - le32_to_cpu(region->len)); - if (ret != 0) { - adsp_err(dsp, - "%s.%d: Failed to write %d bytes at %d in %s: %d\n", - file, regions, - le32_to_cpu(region->len), offset, - region_name, ret); - goto out_fw; - } - } - - pos += le32_to_cpu(region->len) + sizeof(*region); - regions++; - } - - ret = regmap_async_complete(regmap); - if (ret != 0) { - adsp_err(dsp, "Failed to complete async write: %d\n", ret); - goto out_fw; - } - - if (pos > firmware->size) - adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", - file, regions, pos - firmware->size); - - wm_adsp_debugfs_save_wmfwname(dsp, file); - -out_fw: - regmap_async_complete(regmap); - wm_adsp_buf_free(&buf_list); - release_firmware(firmware); - kfree(text); -out: - kfree(file); - - return ret; -} - -/* - * Find wm_coeff_ctl with input name as its subname - * If not found, return NULL - */ -static struct wm_coeff_ctl *wm_adsp_get_ctl(struct wm_adsp *dsp, - const char *name, int type, - unsigned int alg) +static void wm_adsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) { - struct wm_coeff_ctl *pos, *rslt = NULL; - const char *fw_txt = wm_adsp_fw_text[dsp->fw]; + struct wm_coeff_ctl *ctl = cs_ctl->priv; - list_for_each_entry(pos, &dsp->ctl_list, list) { - if (!pos->subname) - continue; - if (strncmp(pos->subname, name, pos->subname_len) == 0 && - pos->fw_name == fw_txt && - pos->alg_region.alg == alg && - pos->alg_region.type == type) { - rslt = pos; - break; - } - } + cancel_work_sync(&ctl->work); - return rslt; + kfree(ctl->name); + kfree(ctl); } int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, unsigned int alg, void *buf, size_t len) { + struct cs_dsp_coeff_ctl *cs_ctl; struct wm_coeff_ctl *ctl; struct snd_kcontrol *kcontrol; char ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int ret; - ctl = wm_adsp_get_ctl(dsp, name, type, alg); - if (!ctl) + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + if (!cs_ctl) return -EINVAL; - if (len > ctl->len) + ctl = cs_ctl->priv; + + if (len > cs_ctl->len) return -EINVAL; - ret = wm_coeff_write_ctrl(ctl, buf, len); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, buf, len); if (ret) return ret; - if (ctl->flags & WMFW_CTL_FLAG_SYS) + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) return 0; if (dsp->component->name_prefix) @@ -2087,683 +729,83 @@ EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, unsigned int alg, void *buf, size_t len) { - struct wm_coeff_ctl *ctl; + struct cs_dsp_coeff_ctl *cs_ctl; - ctl = wm_adsp_get_ctl(dsp, name, type, alg); - if (!ctl) + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + if (!cs_ctl) return -EINVAL; - if (len > ctl->len) + if (len > cs_ctl->len) return -EINVAL; - return wm_coeff_read_ctrl(ctl, buf, len); + return cs_dsp_coeff_read_ctrl(cs_ctl, buf, len); } EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); -static void wm_adsp_ctl_fixup_base(struct wm_adsp *dsp, - const struct wm_adsp_alg_region *alg_region) -{ - struct wm_coeff_ctl *ctl; - - list_for_each_entry(ctl, &dsp->ctl_list, list) { - if (ctl->fw_name == wm_adsp_fw_text[dsp->fw] && - alg_region->alg == ctl->alg_region.alg && - alg_region->type == ctl->alg_region.type) { - ctl->alg_region.base = alg_region->base; - } - } -} - -static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs, - const struct wm_adsp_region *mem, - unsigned int pos, unsigned int len) -{ - void *alg; - unsigned int reg; - int ret; - __be32 val; - - if (n_algs == 0) { - adsp_err(dsp, "No algorithms\n"); - return ERR_PTR(-EINVAL); - } - - if (n_algs > 1024) { - adsp_err(dsp, "Algorithm count %zx excessive\n", n_algs); - return ERR_PTR(-EINVAL); - } - - /* Read the terminator first to validate the length */ - reg = dsp->ops->region_to_reg(mem, pos + len); - - ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); - if (ret != 0) { - adsp_err(dsp, "Failed to read algorithm list end: %d\n", - ret); - return ERR_PTR(ret); - } - - if (be32_to_cpu(val) != 0xbedead) - adsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbedead\n", - reg, be32_to_cpu(val)); - - /* Convert length from DSP words to bytes */ - len *= sizeof(u32); - - alg = kzalloc(len, GFP_KERNEL | GFP_DMA); - if (!alg) - return ERR_PTR(-ENOMEM); - - reg = dsp->ops->region_to_reg(mem, pos); - - ret = regmap_raw_read(dsp->regmap, reg, alg, len); - if (ret != 0) { - adsp_err(dsp, "Failed to read algorithm list: %d\n", ret); - kfree(alg); - return ERR_PTR(ret); - } - - return alg; -} - -static struct wm_adsp_alg_region * - wm_adsp_find_alg_region(struct wm_adsp *dsp, int type, unsigned int id) -{ - struct wm_adsp_alg_region *alg_region; - - list_for_each_entry(alg_region, &dsp->alg_regions, list) { - if (id == alg_region->alg && type == alg_region->type) - return alg_region; - } - - return NULL; -} - -static struct wm_adsp_alg_region *wm_adsp_create_region(struct wm_adsp *dsp, - int type, __be32 id, - __be32 base) -{ - struct wm_adsp_alg_region *alg_region; - - alg_region = kzalloc(sizeof(*alg_region), GFP_KERNEL); - if (!alg_region) - return ERR_PTR(-ENOMEM); - - alg_region->type = type; - alg_region->alg = be32_to_cpu(id); - alg_region->base = be32_to_cpu(base); - - list_add_tail(&alg_region->list, &dsp->alg_regions); - - if (dsp->fw_ver > 0) - wm_adsp_ctl_fixup_base(dsp, alg_region); - - return alg_region; -} - -static void wm_adsp_free_alg_regions(struct wm_adsp *dsp) -{ - struct wm_adsp_alg_region *alg_region; - - while (!list_empty(&dsp->alg_regions)) { - alg_region = list_first_entry(&dsp->alg_regions, - struct wm_adsp_alg_region, - list); - list_del(&alg_region->list); - kfree(alg_region); - } -} - -static void wmfw_parse_id_header(struct wm_adsp *dsp, - struct wmfw_id_hdr *fw, int nalgs) -{ - dsp->fw_id = be32_to_cpu(fw->id); - dsp->fw_id_version = be32_to_cpu(fw->ver); - - adsp_info(dsp, "Firmware: %x v%d.%d.%d, %d algorithms\n", - dsp->fw_id, (dsp->fw_id_version & 0xff0000) >> 16, - (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, - nalgs); -} - -static void wmfw_v3_parse_id_header(struct wm_adsp *dsp, - struct wmfw_v3_id_hdr *fw, int nalgs) -{ - dsp->fw_id = be32_to_cpu(fw->id); - dsp->fw_id_version = be32_to_cpu(fw->ver); - dsp->fw_vendor_id = be32_to_cpu(fw->vendor_id); - - adsp_info(dsp, "Firmware: %x vendor: 0x%x v%d.%d.%d, %d algorithms\n", - dsp->fw_id, dsp->fw_vendor_id, - (dsp->fw_id_version & 0xff0000) >> 16, - (dsp->fw_id_version & 0xff00) >> 8, dsp->fw_id_version & 0xff, - nalgs); -} - -static int wm_adsp_create_regions(struct wm_adsp *dsp, __be32 id, int nregions, - const int *type, __be32 *base) +static void wm_adsp_release_firmware_files(struct wm_adsp *dsp, + const struct firmware *wmfw_firmware, + char *wmfw_filename, + const struct firmware *coeff_firmware, + char *coeff_filename) { - struct wm_adsp_alg_region *alg_region; - int i; - - for (i = 0; i < nregions; i++) { - alg_region = wm_adsp_create_region(dsp, type[i], id, base[i]); - if (IS_ERR(alg_region)) - return PTR_ERR(alg_region); - } + if (wmfw_firmware) + release_firmware(wmfw_firmware); + kfree(wmfw_filename); - return 0; + if (coeff_firmware) + release_firmware(coeff_firmware); + kfree(coeff_filename); } -static int wm_adsp1_setup_algs(struct wm_adsp *dsp) +static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, + const struct firmware **firmware, + char **filename, + char *suffix) { - struct wmfw_adsp1_id_hdr adsp1_id; - struct wmfw_adsp1_alg_hdr *adsp1_alg; - struct wm_adsp_alg_region *alg_region; - const struct wm_adsp_region *mem; - unsigned int pos, len; - size_t n_algs; - int i, ret; - - mem = wm_adsp_find_region(dsp, WMFW_ADSP1_DM); - if (WARN_ON(!mem)) - return -EINVAL; - - ret = regmap_raw_read(dsp->regmap, mem->base, &adsp1_id, - sizeof(adsp1_id)); - if (ret != 0) { - adsp_err(dsp, "Failed to read algorithm info: %d\n", - ret); - return ret; - } - - n_algs = be32_to_cpu(adsp1_id.n_algs); - - wmfw_parse_id_header(dsp, &adsp1_id.fw, n_algs); - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_ZM, - adsp1_id.fw.id, adsp1_id.zm); - if (IS_ERR(alg_region)) - return PTR_ERR(alg_region); - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_DM, - adsp1_id.fw.id, adsp1_id.dm); - if (IS_ERR(alg_region)) - return PTR_ERR(alg_region); - - /* Calculate offset and length in DSP words */ - pos = sizeof(adsp1_id) / sizeof(u32); - len = (sizeof(*adsp1_alg) * n_algs) / sizeof(u32); - - adsp1_alg = wm_adsp_read_algs(dsp, n_algs, mem, pos, len); - if (IS_ERR(adsp1_alg)) - return PTR_ERR(adsp1_alg); - - for (i = 0; i < n_algs; i++) { - adsp_info(dsp, "%d: ID %x v%d.%d.%d DM@%x ZM@%x\n", - i, be32_to_cpu(adsp1_alg[i].alg.id), - (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff0000) >> 16, - (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff00) >> 8, - be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff, - be32_to_cpu(adsp1_alg[i].dm), - be32_to_cpu(adsp1_alg[i].zm)); - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_DM, - adsp1_alg[i].alg.id, - adsp1_alg[i].dm); - if (IS_ERR(alg_region)) { - ret = PTR_ERR(alg_region); - goto out; - } - if (dsp->fw_ver == 0) { - if (i + 1 < n_algs) { - len = be32_to_cpu(adsp1_alg[i + 1].dm); - len -= be32_to_cpu(adsp1_alg[i].dm); - len *= 4; - wm_adsp_create_control(dsp, alg_region, 0, - len, NULL, 0, 0, - SNDRV_CTL_ELEM_TYPE_BYTES); - } else { - adsp_warn(dsp, "Missing length info for region DM with ID %x\n", - be32_to_cpu(adsp1_alg[i].alg.id)); - } - } - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_ZM, - adsp1_alg[i].alg.id, - adsp1_alg[i].zm); - if (IS_ERR(alg_region)) { - ret = PTR_ERR(alg_region); - goto out; - } - if (dsp->fw_ver == 0) { - if (i + 1 < n_algs) { - len = be32_to_cpu(adsp1_alg[i + 1].zm); - len -= be32_to_cpu(adsp1_alg[i].zm); - len *= 4; - wm_adsp_create_control(dsp, alg_region, 0, - len, NULL, 0, 0, - SNDRV_CTL_ELEM_TYPE_BYTES); - } else { - adsp_warn(dsp, "Missing length info for region ZM with ID %x\n", - be32_to_cpu(adsp1_alg[i].alg.id)); - } - } - } - -out: - kfree(adsp1_alg); - return ret; -} - -static int wm_adsp2_setup_algs(struct wm_adsp *dsp) -{ - struct wmfw_adsp2_id_hdr adsp2_id; - struct wmfw_adsp2_alg_hdr *adsp2_alg; - struct wm_adsp_alg_region *alg_region; - const struct wm_adsp_region *mem; - unsigned int pos, len; - size_t n_algs; - int i, ret; + struct cs_dsp *cs_dsp = &dsp->cs_dsp; + int ret = 0; - mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM); - if (WARN_ON(!mem)) - return -EINVAL; + *filename = kasprintf(GFP_KERNEL, "%s-%s-%s.%s", dsp->part, dsp->fwf_name, + wm_adsp_fw[dsp->fw].file, suffix); + if (*filename == NULL) + return -ENOMEM; - ret = regmap_raw_read(dsp->regmap, mem->base, &adsp2_id, - sizeof(adsp2_id)); + ret = request_firmware(firmware, *filename, cs_dsp->dev); if (ret != 0) { - adsp_err(dsp, "Failed to read algorithm info: %d\n", - ret); - return ret; + adsp_err(dsp, "Failed to request '%s'\n", *filename); + kfree(*filename); + *filename = NULL; } - n_algs = be32_to_cpu(adsp2_id.n_algs); - - wmfw_parse_id_header(dsp, &adsp2_id.fw, n_algs); - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_XM, - adsp2_id.fw.id, adsp2_id.xm); - if (IS_ERR(alg_region)) - return PTR_ERR(alg_region); - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_YM, - adsp2_id.fw.id, adsp2_id.ym); - if (IS_ERR(alg_region)) - return PTR_ERR(alg_region); - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_ZM, - adsp2_id.fw.id, adsp2_id.zm); - if (IS_ERR(alg_region)) - return PTR_ERR(alg_region); - - /* Calculate offset and length in DSP words */ - pos = sizeof(adsp2_id) / sizeof(u32); - len = (sizeof(*adsp2_alg) * n_algs) / sizeof(u32); - - adsp2_alg = wm_adsp_read_algs(dsp, n_algs, mem, pos, len); - if (IS_ERR(adsp2_alg)) - return PTR_ERR(adsp2_alg); - - for (i = 0; i < n_algs; i++) { - adsp_info(dsp, - "%d: ID %x v%d.%d.%d XM@%x YM@%x ZM@%x\n", - i, be32_to_cpu(adsp2_alg[i].alg.id), - (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff0000) >> 16, - (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff00) >> 8, - be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff, - be32_to_cpu(adsp2_alg[i].xm), - be32_to_cpu(adsp2_alg[i].ym), - be32_to_cpu(adsp2_alg[i].zm)); - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_XM, - adsp2_alg[i].alg.id, - adsp2_alg[i].xm); - if (IS_ERR(alg_region)) { - ret = PTR_ERR(alg_region); - goto out; - } - if (dsp->fw_ver == 0) { - if (i + 1 < n_algs) { - len = be32_to_cpu(adsp2_alg[i + 1].xm); - len -= be32_to_cpu(adsp2_alg[i].xm); - len *= 4; - wm_adsp_create_control(dsp, alg_region, 0, - len, NULL, 0, 0, - SNDRV_CTL_ELEM_TYPE_BYTES); - } else { - adsp_warn(dsp, "Missing length info for region XM with ID %x\n", - be32_to_cpu(adsp2_alg[i].alg.id)); - } - } - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_YM, - adsp2_alg[i].alg.id, - adsp2_alg[i].ym); - if (IS_ERR(alg_region)) { - ret = PTR_ERR(alg_region); - goto out; - } - if (dsp->fw_ver == 0) { - if (i + 1 < n_algs) { - len = be32_to_cpu(adsp2_alg[i + 1].ym); - len -= be32_to_cpu(adsp2_alg[i].ym); - len *= 4; - wm_adsp_create_control(dsp, alg_region, 0, - len, NULL, 0, 0, - SNDRV_CTL_ELEM_TYPE_BYTES); - } else { - adsp_warn(dsp, "Missing length info for region YM with ID %x\n", - be32_to_cpu(adsp2_alg[i].alg.id)); - } - } - - alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_ZM, - adsp2_alg[i].alg.id, - adsp2_alg[i].zm); - if (IS_ERR(alg_region)) { - ret = PTR_ERR(alg_region); - goto out; - } - if (dsp->fw_ver == 0) { - if (i + 1 < n_algs) { - len = be32_to_cpu(adsp2_alg[i + 1].zm); - len -= be32_to_cpu(adsp2_alg[i].zm); - len *= 4; - wm_adsp_create_control(dsp, alg_region, 0, - len, NULL, 0, 0, - SNDRV_CTL_ELEM_TYPE_BYTES); - } else { - adsp_warn(dsp, "Missing length info for region ZM with ID %x\n", - be32_to_cpu(adsp2_alg[i].alg.id)); - } - } - } - -out: - kfree(adsp2_alg); return ret; } -static int wm_halo_create_regions(struct wm_adsp *dsp, __be32 id, - __be32 xm_base, __be32 ym_base) -{ - static const int types[] = { - WMFW_ADSP2_XM, WMFW_HALO_XM_PACKED, - WMFW_ADSP2_YM, WMFW_HALO_YM_PACKED - }; - __be32 bases[] = { xm_base, xm_base, ym_base, ym_base }; - - return wm_adsp_create_regions(dsp, id, ARRAY_SIZE(types), types, bases); -} - -static int wm_halo_setup_algs(struct wm_adsp *dsp) +static int wm_adsp_request_firmware_files(struct wm_adsp *dsp, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) { - struct wmfw_halo_id_hdr halo_id; - struct wmfw_halo_alg_hdr *halo_alg; - const struct wm_adsp_region *mem; - unsigned int pos, len; - size_t n_algs; - int i, ret; - - mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM); - if (WARN_ON(!mem)) - return -EINVAL; - - ret = regmap_raw_read(dsp->regmap, mem->base, &halo_id, - sizeof(halo_id)); - if (ret != 0) { - adsp_err(dsp, "Failed to read algorithm info: %d\n", - ret); - return ret; - } - - n_algs = be32_to_cpu(halo_id.n_algs); - - wmfw_v3_parse_id_header(dsp, &halo_id.fw, n_algs); - - ret = wm_halo_create_regions(dsp, halo_id.fw.id, - halo_id.xm_base, halo_id.ym_base); - if (ret) - return ret; - - /* Calculate offset and length in DSP words */ - pos = sizeof(halo_id) / sizeof(u32); - len = (sizeof(*halo_alg) * n_algs) / sizeof(u32); - - halo_alg = wm_adsp_read_algs(dsp, n_algs, mem, pos, len); - if (IS_ERR(halo_alg)) - return PTR_ERR(halo_alg); - - for (i = 0; i < n_algs; i++) { - adsp_info(dsp, - "%d: ID %x v%d.%d.%d XM@%x YM@%x\n", - i, be32_to_cpu(halo_alg[i].alg.id), - (be32_to_cpu(halo_alg[i].alg.ver) & 0xff0000) >> 16, - (be32_to_cpu(halo_alg[i].alg.ver) & 0xff00) >> 8, - be32_to_cpu(halo_alg[i].alg.ver) & 0xff, - be32_to_cpu(halo_alg[i].xm_base), - be32_to_cpu(halo_alg[i].ym_base)); - - ret = wm_halo_create_regions(dsp, halo_alg[i].alg.id, - halo_alg[i].xm_base, - halo_alg[i].ym_base); - if (ret) - goto out; - } - -out: - kfree(halo_alg); - return ret; -} - -static int wm_adsp_load_coeff(struct wm_adsp *dsp) -{ - LIST_HEAD(buf_list); - struct regmap *regmap = dsp->regmap; - struct wmfw_coeff_hdr *hdr; - struct wmfw_coeff_item *blk; - const struct firmware *firmware; - const struct wm_adsp_region *mem; - struct wm_adsp_alg_region *alg_region; - const char *region_name; - int ret, pos, blocks, type, offset, reg; - char *file; - struct wm_adsp_buf *buf; - - file = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (file == NULL) - return -ENOMEM; - - snprintf(file, PAGE_SIZE, "%s-%s-%s.bin", dsp->part, dsp->fwf_name, - wm_adsp_fw[dsp->fw].file); - file[PAGE_SIZE - 1] = '\0'; - - ret = request_firmware(&firmware, file, dsp->dev); - if (ret != 0) { - adsp_warn(dsp, "Failed to request '%s'\n", file); - ret = 0; - goto out; - } - ret = -EINVAL; - - if (sizeof(*hdr) >= firmware->size) { - adsp_err(dsp, "%s: file too short, %zu bytes\n", - file, firmware->size); - goto out_fw; - } - - hdr = (void *)&firmware->data[0]; - if (memcmp(hdr->magic, "WMDR", 4) != 0) { - adsp_err(dsp, "%s: invalid magic\n", file); - goto out_fw; - } - - switch (be32_to_cpu(hdr->rev) & 0xff) { - case 1: - break; - default: - adsp_err(dsp, "%s: Unsupported coefficient file format %d\n", - file, be32_to_cpu(hdr->rev) & 0xff); - ret = -EINVAL; - goto out_fw; - } - - adsp_dbg(dsp, "%s: v%d.%d.%d\n", file, - (le32_to_cpu(hdr->ver) >> 16) & 0xff, - (le32_to_cpu(hdr->ver) >> 8) & 0xff, - le32_to_cpu(hdr->ver) & 0xff); - - pos = le32_to_cpu(hdr->len); - - blocks = 0; - while (pos < firmware->size && - sizeof(*blk) < firmware->size - pos) { - blk = (void *)(&firmware->data[pos]); - - type = le16_to_cpu(blk->type); - offset = le16_to_cpu(blk->offset); - - adsp_dbg(dsp, "%s.%d: %x v%d.%d.%d\n", - file, blocks, le32_to_cpu(blk->id), - (le32_to_cpu(blk->ver) >> 16) & 0xff, - (le32_to_cpu(blk->ver) >> 8) & 0xff, - le32_to_cpu(blk->ver) & 0xff); - adsp_dbg(dsp, "%s.%d: %d bytes at 0x%x in %x\n", - file, blocks, le32_to_cpu(blk->len), offset, type); - - reg = 0; - region_name = "Unknown"; - switch (type) { - case (WMFW_NAME_TEXT << 8): - case (WMFW_INFO_TEXT << 8): - case (WMFW_METADATA << 8): - break; - case (WMFW_ABSOLUTE << 8): - /* - * Old files may use this for global - * coefficients. - */ - if (le32_to_cpu(blk->id) == dsp->fw_id && - offset == 0) { - region_name = "global coefficients"; - mem = wm_adsp_find_region(dsp, type); - if (!mem) { - adsp_err(dsp, "No ZM\n"); - break; - } - reg = dsp->ops->region_to_reg(mem, 0); - - } else { - region_name = "register"; - reg = offset; - } - break; - - case WMFW_ADSP1_DM: - case WMFW_ADSP1_ZM: - case WMFW_ADSP2_XM: - case WMFW_ADSP2_YM: - case WMFW_HALO_XM_PACKED: - case WMFW_HALO_YM_PACKED: - case WMFW_HALO_PM_PACKED: - adsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n", - file, blocks, le32_to_cpu(blk->len), - type, le32_to_cpu(blk->id)); - - mem = wm_adsp_find_region(dsp, type); - if (!mem) { - adsp_err(dsp, "No base for region %x\n", type); - break; - } - - alg_region = wm_adsp_find_alg_region(dsp, type, - le32_to_cpu(blk->id)); - if (alg_region) { - reg = alg_region->base; - reg = dsp->ops->region_to_reg(mem, reg); - reg += offset; - } else { - adsp_err(dsp, "No %x for algorithm %x\n", - type, le32_to_cpu(blk->id)); - } - break; - - default: - adsp_err(dsp, "%s.%d: Unknown region type %x at %d\n", - file, blocks, type, pos); - break; - } - - if (reg) { - if (le32_to_cpu(blk->len) > - firmware->size - pos - sizeof(*blk)) { - adsp_err(dsp, - "%s.%d: %s region len %d bytes exceeds file length %zu\n", - file, blocks, region_name, - le32_to_cpu(blk->len), - firmware->size); - ret = -EINVAL; - goto out_fw; - } - - buf = wm_adsp_buf_alloc(blk->data, - le32_to_cpu(blk->len), - &buf_list); - if (!buf) { - adsp_err(dsp, "Out of memory\n"); - ret = -ENOMEM; - goto out_fw; - } - - adsp_dbg(dsp, "%s.%d: Writing %d bytes at %x\n", - file, blocks, le32_to_cpu(blk->len), - reg); - ret = regmap_raw_write_async(regmap, reg, buf->buf, - le32_to_cpu(blk->len)); - if (ret != 0) { - adsp_err(dsp, - "%s.%d: Failed to write to %x in %s: %d\n", - file, blocks, reg, region_name, ret); - } - } - - pos += (le32_to_cpu(blk->len) + sizeof(*blk) + 3) & ~0x03; - blocks++; - } + int ret = 0; - ret = regmap_async_complete(regmap); + ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename, "wmfw"); if (ret != 0) - adsp_err(dsp, "Failed to complete async write: %d\n", ret); - - if (pos > firmware->size) - adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", - file, blocks, pos - firmware->size); + return ret; - wm_adsp_debugfs_save_binname(dsp, file); + wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename, "bin"); -out_fw: - regmap_async_complete(regmap); - release_firmware(firmware); - wm_adsp_buf_free(&buf_list); -out: - kfree(file); - return ret; + return 0; } -static int wm_adsp_create_name(struct wm_adsp *dsp) +static int wm_adsp_common_init(struct wm_adsp *dsp) { char *p; - if (!dsp->name) { - dsp->name = devm_kasprintf(dsp->dev, GFP_KERNEL, "DSP%d", - dsp->num); - if (!dsp->name) - return -ENOMEM; - } + INIT_LIST_HEAD(&dsp->compr_list); + INIT_LIST_HEAD(&dsp->buffer_list); if (!dsp->fwf_name) { - p = devm_kstrdup(dsp->dev, dsp->name, GFP_KERNEL); + p = devm_kstrdup(dsp->cs_dsp.dev, dsp->cs_dsp.name, GFP_KERNEL); if (!p) return -ENOMEM; @@ -2775,28 +817,16 @@ static int wm_adsp_create_name(struct wm_adsp *dsp) return 0; } -static int wm_adsp_common_init(struct wm_adsp *dsp) +int wm_adsp1_init(struct wm_adsp *dsp) { int ret; - ret = wm_adsp_create_name(dsp); + dsp->cs_dsp.client_ops = &wm_adsp1_client_ops; + + ret = cs_dsp_adsp1_init(&dsp->cs_dsp); if (ret) return ret; - INIT_LIST_HEAD(&dsp->alg_regions); - INIT_LIST_HEAD(&dsp->ctl_list); - INIT_LIST_HEAD(&dsp->compr_list); - INIT_LIST_HEAD(&dsp->buffer_list); - - mutex_init(&dsp->pwr_lock); - - return 0; -} - -int wm_adsp1_init(struct wm_adsp *dsp) -{ - dsp->ops = &wm_adsp1_ops; - return wm_adsp_common_init(dsp); } EXPORT_SYMBOL_GPL(wm_adsp1_init); @@ -2808,314 +838,49 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); struct wm_adsp *dsp = &dsps[w->shift]; - struct wm_coeff_ctl *ctl; - int ret; - unsigned int val; + int ret = 0; + char *wmfw_filename = NULL; + const struct firmware *wmfw_firmware = NULL; + char *coeff_filename = NULL; + const struct firmware *coeff_firmware = NULL; dsp->component = component; - mutex_lock(&dsp->pwr_lock); - switch (event) { case SND_SOC_DAPM_POST_PMU: - regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, - ADSP1_SYS_ENA, ADSP1_SYS_ENA); - - /* - * For simplicity set the DSP clock rate to be the - * SYSCLK rate rather than making it configurable. - */ - if (dsp->sysclk_reg) { - ret = regmap_read(dsp->regmap, dsp->sysclk_reg, &val); - if (ret != 0) { - adsp_err(dsp, "Failed to read SYSCLK state: %d\n", - ret); - goto err_mutex; - } - - val = (val & dsp->sysclk_mask) >> dsp->sysclk_shift; - - ret = regmap_update_bits(dsp->regmap, - dsp->base + ADSP1_CONTROL_31, - ADSP1_CLK_SEL_MASK, val); - if (ret != 0) { - adsp_err(dsp, "Failed to set clock rate: %d\n", - ret); - goto err_mutex; - } - } - - ret = wm_adsp_load(dsp); - if (ret != 0) - goto err_ena; - - ret = wm_adsp1_setup_algs(dsp); - if (ret != 0) - goto err_ena; - - ret = wm_adsp_load_coeff(dsp); - if (ret != 0) - goto err_ena; - - /* Initialize caches for enabled and unset controls */ - ret = wm_coeff_init_control_caches(dsp); - if (ret != 0) - goto err_ena; - - /* Sync set controls */ - ret = wm_coeff_sync_controls(dsp); - if (ret != 0) - goto err_ena; - - dsp->booted = true; + ret = wm_adsp_request_firmware_files(dsp, + &wmfw_firmware, &wmfw_filename, + &coeff_firmware, &coeff_filename); + if (ret) + break; - /* Start the core running */ - regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, - ADSP1_CORE_ENA | ADSP1_START, - ADSP1_CORE_ENA | ADSP1_START); + ret = cs_dsp_adsp1_power_up(&dsp->cs_dsp, + wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, + wm_adsp_fw_text[dsp->fw]); - dsp->running = true; + wm_adsp_release_firmware_files(dsp, + wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename); break; - case SND_SOC_DAPM_PRE_PMD: - dsp->running = false; - dsp->booted = false; - - /* Halt the core */ - regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, - ADSP1_CORE_ENA | ADSP1_START, 0); - - regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_19, - ADSP1_WDMA_BUFFER_LENGTH_MASK, 0); - - regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, - ADSP1_SYS_ENA, 0); - - list_for_each_entry(ctl, &dsp->ctl_list, list) - ctl->enabled = 0; - - - wm_adsp_free_alg_regions(dsp); + cs_dsp_adsp1_power_down(&dsp->cs_dsp); break; - default: break; } - mutex_unlock(&dsp->pwr_lock); - - return 0; - -err_ena: - regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, - ADSP1_SYS_ENA, 0); -err_mutex: - mutex_unlock(&dsp->pwr_lock); - return ret; } EXPORT_SYMBOL_GPL(wm_adsp1_event); -static int wm_adsp2v2_enable_core(struct wm_adsp *dsp) -{ - unsigned int val; - int ret, count; - - /* Wait for the RAM to start, should be near instantaneous */ - for (count = 0; count < 10; ++count) { - ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, &val); - if (ret != 0) - return ret; - - if (val & ADSP2_RAM_RDY) - break; - - usleep_range(250, 500); - } - - if (!(val & ADSP2_RAM_RDY)) { - adsp_err(dsp, "Failed to start DSP RAM\n"); - return -EBUSY; - } - - adsp_dbg(dsp, "RAM ready after %d polls\n", count); - - return 0; -} - -static int wm_adsp2_enable_core(struct wm_adsp *dsp) -{ - int ret; - - ret = regmap_update_bits_async(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, ADSP2_SYS_ENA); - if (ret != 0) - return ret; - - return wm_adsp2v2_enable_core(dsp); -} - -static int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) -{ - struct regmap *regmap = dsp->regmap; - unsigned int code0, code1, lock_reg; - - if (!(lock_regions & WM_ADSP2_REGION_ALL)) - return 0; - - lock_regions &= WM_ADSP2_REGION_ALL; - lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; - - while (lock_regions) { - code0 = code1 = 0; - if (lock_regions & BIT(0)) { - code0 = ADSP2_LOCK_CODE_0; - code1 = ADSP2_LOCK_CODE_1; - } - if (lock_regions & BIT(1)) { - code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; - code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; - } - regmap_write(regmap, lock_reg, code0); - regmap_write(regmap, lock_reg, code1); - lock_regions >>= 2; - lock_reg += 2; - } - - return 0; -} - -static int wm_adsp2_enable_memory(struct wm_adsp *dsp) -{ - return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, ADSP2_MEM_ENA); -} - -static void wm_adsp2_disable_memory(struct wm_adsp *dsp) -{ - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); -} - -static void wm_adsp2_disable_core(struct wm_adsp *dsp) -{ - regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); - - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); -} - -static void wm_adsp2v2_disable_core(struct wm_adsp *dsp) -{ - regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); -} - -static void wm_adsp_boot_work(struct work_struct *work) -{ - struct wm_adsp *dsp = container_of(work, - struct wm_adsp, - boot_work); - int ret; - - mutex_lock(&dsp->pwr_lock); - - if (dsp->ops->enable_memory) { - ret = dsp->ops->enable_memory(dsp); - if (ret != 0) - goto err_mutex; - } - - if (dsp->ops->enable_core) { - ret = dsp->ops->enable_core(dsp); - if (ret != 0) - goto err_mem; - } - - ret = wm_adsp_load(dsp); - if (ret != 0) - goto err_ena; - - ret = dsp->ops->setup_algs(dsp); - if (ret != 0) - goto err_ena; - - ret = wm_adsp_load_coeff(dsp); - if (ret != 0) - goto err_ena; - - /* Initialize caches for enabled and unset controls */ - ret = wm_coeff_init_control_caches(dsp); - if (ret != 0) - goto err_ena; - - if (dsp->ops->disable_core) - dsp->ops->disable_core(dsp); - - dsp->booted = true; - - mutex_unlock(&dsp->pwr_lock); - - return; - -err_ena: - if (dsp->ops->disable_core) - dsp->ops->disable_core(dsp); -err_mem: - if (dsp->ops->disable_memory) - dsp->ops->disable_memory(dsp); -err_mutex: - mutex_unlock(&dsp->pwr_lock); -} - -static int wm_halo_configure_mpu(struct wm_adsp *dsp, unsigned int lock_regions) -{ - struct reg_sequence config[] = { - { dsp->base + HALO_MPU_LOCK_CONFIG, 0x5555 }, - { dsp->base + HALO_MPU_LOCK_CONFIG, 0xAAAA }, - { dsp->base + HALO_MPU_XMEM_ACCESS_0, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_YMEM_ACCESS_0, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_WINDOW_ACCESS_0, lock_regions }, - { dsp->base + HALO_MPU_XREG_ACCESS_0, lock_regions }, - { dsp->base + HALO_MPU_YREG_ACCESS_0, lock_regions }, - { dsp->base + HALO_MPU_XMEM_ACCESS_1, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_YMEM_ACCESS_1, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_WINDOW_ACCESS_1, lock_regions }, - { dsp->base + HALO_MPU_XREG_ACCESS_1, lock_regions }, - { dsp->base + HALO_MPU_YREG_ACCESS_1, lock_regions }, - { dsp->base + HALO_MPU_XMEM_ACCESS_2, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_YMEM_ACCESS_2, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_WINDOW_ACCESS_2, lock_regions }, - { dsp->base + HALO_MPU_XREG_ACCESS_2, lock_regions }, - { dsp->base + HALO_MPU_YREG_ACCESS_2, lock_regions }, - { dsp->base + HALO_MPU_XMEM_ACCESS_3, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_YMEM_ACCESS_3, 0xFFFFFFFF }, - { dsp->base + HALO_MPU_WINDOW_ACCESS_3, lock_regions }, - { dsp->base + HALO_MPU_XREG_ACCESS_3, lock_regions }, - { dsp->base + HALO_MPU_YREG_ACCESS_3, lock_regions }, - { dsp->base + HALO_MPU_LOCK_CONFIG, 0 }, - }; - - return regmap_multi_reg_write(dsp->regmap, config, ARRAY_SIZE(config)); -} - int wm_adsp2_set_dspclk(struct snd_soc_dapm_widget *w, unsigned int freq) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); struct wm_adsp *dsp = &dsps[w->shift]; - int ret; - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CLOCKING, - ADSP2_CLK_SEL_MASK, - freq << ADSP2_CLK_SEL_SHIFT); - if (ret) - adsp_err(dsp, "Failed to set clock rate: %d\n", ret); - - return ret; + return cs_dsp_set_dspclk(&dsp->cs_dsp, freq); } EXPORT_SYMBOL_GPL(wm_adsp2_set_dspclk); @@ -3145,7 +910,7 @@ int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol, struct wm_adsp *dsp = &dsps[mc->shift - 1]; char preload[32]; - snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->name); + snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->cs_dsp.name); dsp->preloaded = ucontrol->value.integer.value[0]; @@ -3162,16 +927,31 @@ int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put); -static void wm_adsp_stop_watchdog(struct wm_adsp *dsp) +static void wm_adsp_boot_work(struct work_struct *work) { - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, - ADSP2_WDT_ENA_MASK, 0); -} + struct wm_adsp *dsp = container_of(work, + struct wm_adsp, + boot_work); + int ret = 0; + char *wmfw_filename = NULL; + const struct firmware *wmfw_firmware = NULL; + char *coeff_filename = NULL; + const struct firmware *coeff_firmware = NULL; + + ret = wm_adsp_request_firmware_files(dsp, + &wmfw_firmware, &wmfw_filename, + &coeff_firmware, &coeff_filename); + if (ret) + return; -static void wm_halo_stop_watchdog(struct wm_adsp *dsp) -{ - regmap_update_bits(dsp->regmap, dsp->base + HALO_WDT_CONTROL, - HALO_WDT_EN_MASK, 0); + cs_dsp_power_up(&dsp->cs_dsp, + wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, + wm_adsp_fw_text[dsp->fw]); + + wm_adsp_release_firmware_files(dsp, + wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename); } int wm_adsp_early_event(struct snd_soc_dapm_widget *w, @@ -3180,33 +960,13 @@ int wm_adsp_early_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); struct wm_adsp *dsp = &dsps[w->shift]; - struct wm_coeff_ctl *ctl; switch (event) { case SND_SOC_DAPM_PRE_PMU: queue_work(system_unbound_wq, &dsp->boot_work); break; case SND_SOC_DAPM_PRE_PMD: - mutex_lock(&dsp->pwr_lock); - - wm_adsp_debugfs_clear(dsp); - - dsp->fw_id = 0; - dsp->fw_id_version = 0; - - dsp->booted = false; - - if (dsp->ops->disable_memory) - dsp->ops->disable_memory(dsp); - - list_for_each_entry(ctl, &dsp->ctl_list, list) - ctl->enabled = 0; - - wm_adsp_free_alg_regions(dsp); - - mutex_unlock(&dsp->pwr_lock); - - adsp_dbg(dsp, "Shutdown complete\n"); + cs_dsp_power_down(&dsp->cs_dsp); break; default: break; @@ -3216,17 +976,24 @@ int wm_adsp_early_event(struct snd_soc_dapm_widget *w, } EXPORT_SYMBOL_GPL(wm_adsp_early_event); -static int wm_adsp2_start_core(struct wm_adsp *dsp) +static int wm_adsp_event_post_run(struct cs_dsp *cs_dsp) { - return regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_CORE_ENA | ADSP2_START, - ADSP2_CORE_ENA | ADSP2_START); + struct wm_adsp *dsp = container_of(cs_dsp, struct wm_adsp, cs_dsp); + + if (wm_adsp_fw[dsp->fw].num_caps != 0) + return wm_adsp_buffer_init(dsp); + + return 0; } -static void wm_adsp2_stop_core(struct wm_adsp *dsp) +static void wm_adsp_event_post_stop(struct cs_dsp *cs_dsp) { - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_CORE_ENA | ADSP2_START, 0); + struct wm_adsp *dsp = container_of(cs_dsp, struct wm_adsp, cs_dsp); + + if (wm_adsp_fw[dsp->fw].num_caps != 0) + wm_adsp_buffer_free(dsp); + + dsp->fatal_error = false; } int wm_adsp_event(struct snd_soc_dapm_widget *w, @@ -3235,127 +1002,32 @@ int wm_adsp_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); struct wm_adsp *dsp = &dsps[w->shift]; - int ret; + int ret = 0; switch (event) { case SND_SOC_DAPM_POST_PMU: flush_work(&dsp->boot_work); - - mutex_lock(&dsp->pwr_lock); - - if (!dsp->booted) { - ret = -EIO; - goto err; - } - - if (dsp->ops->enable_core) { - ret = dsp->ops->enable_core(dsp); - if (ret != 0) - goto err; - } - - /* Sync set controls */ - ret = wm_coeff_sync_controls(dsp); - if (ret != 0) - goto err; - - if (dsp->ops->lock_memory) { - ret = dsp->ops->lock_memory(dsp, dsp->lock_regions); - if (ret != 0) { - adsp_err(dsp, "Error configuring MPU: %d\n", - ret); - goto err; - } - } - - if (dsp->ops->start_core) { - ret = dsp->ops->start_core(dsp); - if (ret != 0) - goto err; - } - - if (wm_adsp_fw[dsp->fw].num_caps != 0) { - ret = wm_adsp_buffer_init(dsp); - if (ret < 0) - goto err; - } - - dsp->running = true; - - mutex_unlock(&dsp->pwr_lock); + ret = cs_dsp_run(&dsp->cs_dsp); break; - case SND_SOC_DAPM_PRE_PMD: - /* Tell the firmware to cleanup */ - wm_adsp_signal_event_controls(dsp, WM_ADSP_FW_EVENT_SHUTDOWN); - - if (dsp->ops->stop_watchdog) - dsp->ops->stop_watchdog(dsp); - - /* Log firmware state, it can be useful for analysis */ - if (dsp->ops->show_fw_status) - dsp->ops->show_fw_status(dsp); - - mutex_lock(&dsp->pwr_lock); - - dsp->running = false; - - if (dsp->ops->stop_core) - dsp->ops->stop_core(dsp); - if (dsp->ops->disable_core) - dsp->ops->disable_core(dsp); - - if (wm_adsp_fw[dsp->fw].num_caps != 0) - wm_adsp_buffer_free(dsp); - - dsp->fatal_error = false; - - mutex_unlock(&dsp->pwr_lock); - - adsp_dbg(dsp, "Execution stopped\n"); + cs_dsp_stop(&dsp->cs_dsp); break; - default: break; } - return 0; -err: - if (dsp->ops->stop_core) - dsp->ops->stop_core(dsp); - if (dsp->ops->disable_core) - dsp->ops->disable_core(dsp); - mutex_unlock(&dsp->pwr_lock); return ret; } EXPORT_SYMBOL_GPL(wm_adsp_event); -static int wm_halo_start_core(struct wm_adsp *dsp) -{ - return regmap_update_bits(dsp->regmap, - dsp->base + HALO_CCM_CORE_CONTROL, - HALO_CORE_RESET | HALO_CORE_EN, - HALO_CORE_RESET | HALO_CORE_EN); -} - -static void wm_halo_stop_core(struct wm_adsp *dsp) -{ - regmap_update_bits(dsp->regmap, dsp->base + HALO_CCM_CORE_CONTROL, - HALO_CORE_EN, 0); - - /* reset halo core with CORE_SOFT_RESET */ - regmap_update_bits(dsp->regmap, dsp->base + HALO_CORE_SOFT_RESET, - HALO_CORE_SOFT_RESET_MASK, 1); -} - int wm_adsp2_component_probe(struct wm_adsp *dsp, struct snd_soc_component *component) { char preload[32]; - snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->name); + snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->cs_dsp.name); snd_soc_component_disable_pin(component, preload); - wm_adsp2_init_debugfs(dsp, component); + cs_dsp_init_debugfs(&dsp->cs_dsp, component->debugfs_root); dsp->component = component; @@ -3365,7 +1037,7 @@ EXPORT_SYMBOL_GPL(wm_adsp2_component_probe); int wm_adsp2_component_remove(struct wm_adsp *dsp, struct snd_soc_component *component) { - wm_adsp2_cleanup_debugfs(dsp); + cs_dsp_cleanup_debugfs(&dsp->cs_dsp); return 0; } @@ -3375,37 +1047,16 @@ int wm_adsp2_init(struct wm_adsp *dsp) { int ret; - ret = wm_adsp_common_init(dsp); - if (ret) - return ret; - - switch (dsp->rev) { - case 0: - /* - * Disable the DSP memory by default when in reset for a small - * power saving. - */ - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); - if (ret) { - adsp_err(dsp, - "Failed to clear memory retention: %d\n", ret); - return ret; - } + INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); - dsp->ops = &wm_adsp2_ops[0]; - break; - case 1: - dsp->ops = &wm_adsp2_ops[1]; - break; - default: - dsp->ops = &wm_adsp2_ops[2]; - break; - } + dsp->sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr); + dsp->cs_dsp.client_ops = &wm_adsp2_client_ops; - INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); + ret = cs_dsp_adsp2_init(&dsp->cs_dsp); + if (ret) + return ret; - return 0; + return wm_adsp_common_init(dsp); } EXPORT_SYMBOL_GPL(wm_adsp2_init); @@ -3413,28 +1064,22 @@ int wm_halo_init(struct wm_adsp *dsp) { int ret; - ret = wm_adsp_common_init(dsp); - if (ret) - return ret; + INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); - dsp->ops = &wm_halo_ops; + dsp->sys_config_size = sizeof(struct wm_halo_system_config_xm_hdr); + dsp->cs_dsp.client_ops = &wm_adsp2_client_ops; - INIT_WORK(&dsp->boot_work, wm_adsp_boot_work); + ret = cs_dsp_halo_init(&dsp->cs_dsp); + if (ret) + return ret; - return 0; + return wm_adsp_common_init(dsp); } EXPORT_SYMBOL_GPL(wm_halo_init); void wm_adsp2_remove(struct wm_adsp *dsp) { - struct wm_coeff_ctl *ctl; - - while (!list_empty(&dsp->ctl_list)) { - ctl = list_first_entry(&dsp->ctl_list, struct wm_coeff_ctl, - list); - list_del(&ctl->list); - wm_adsp_free_ctl_blk(ctl); - } + cs_dsp_remove(&dsp->cs_dsp); } EXPORT_SYMBOL_GPL(wm_adsp2_remove); @@ -3487,7 +1132,7 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) struct snd_soc_pcm_runtime *rtd = stream->private_data; int ret = 0; - mutex_lock(&dsp->pwr_lock); + mutex_lock(&dsp->cs_dsp.pwr_lock); if (wm_adsp_fw[dsp->fw].num_caps == 0) { adsp_err(dsp, "%s: Firmware does not support compressed API\n", @@ -3527,7 +1172,7 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) stream->runtime->private_data = compr; out: - mutex_unlock(&dsp->pwr_lock); + mutex_unlock(&dsp->cs_dsp.pwr_lock); return ret; } @@ -3539,7 +1184,7 @@ int wm_adsp_compr_free(struct snd_soc_component *component, struct wm_adsp_compr *compr = stream->runtime->private_data; struct wm_adsp *dsp = compr->dsp; - mutex_lock(&dsp->pwr_lock); + mutex_lock(&dsp->cs_dsp.pwr_lock); wm_adsp_compr_detach(compr); list_del(&compr->list); @@ -3547,7 +1192,7 @@ int wm_adsp_compr_free(struct snd_soc_component *component, kfree(compr->raw_buf); kfree(compr); - mutex_unlock(&dsp->pwr_lock); + mutex_unlock(&dsp->cs_dsp.pwr_lock); return 0; } @@ -3566,7 +1211,7 @@ static int wm_adsp_compr_check_params(struct snd_compr_stream *stream, params->buffer.fragment_size > WM_ADSP_MAX_FRAGMENT_SIZE || params->buffer.fragments < WM_ADSP_MIN_FRAGMENTS || params->buffer.fragments > WM_ADSP_MAX_FRAGMENTS || - params->buffer.fragment_size % WM_ADSP_DATA_WORD_SIZE) { + params->buffer.fragment_size % CS_DSP_DATA_WORD_SIZE) { compr_err(compr, "Invalid buffer fragsize=%d fragments=%d\n", params->buffer.fragment_size, params->buffer.fragments); @@ -3605,7 +1250,7 @@ static int wm_adsp_compr_check_params(struct snd_compr_stream *stream, static inline unsigned int wm_adsp_compr_frag_words(struct wm_adsp_compr *compr) { - return compr->size.fragment_size / WM_ADSP_DATA_WORD_SIZE; + return compr->size.fragment_size / CS_DSP_DATA_WORD_SIZE; } int wm_adsp_compr_set_params(struct snd_soc_component *component, @@ -3661,88 +1306,19 @@ int wm_adsp_compr_get_caps(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(wm_adsp_compr_get_caps); -static int wm_adsp_read_raw_data_block(struct wm_adsp *dsp, int mem_type, - unsigned int mem_addr, - unsigned int num_words, __be32 *data) -{ - struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type); - unsigned int reg; - int ret; - - if (!mem) - return -EINVAL; - - reg = dsp->ops->region_to_reg(mem, mem_addr); - - ret = regmap_raw_read(dsp->regmap, reg, data, - sizeof(*data) * num_words); - if (ret < 0) - return ret; - - return 0; -} - -static inline int wm_adsp_read_data_word(struct wm_adsp *dsp, int mem_type, - unsigned int mem_addr, u32 *data) -{ - __be32 raw; - int ret; - - ret = wm_adsp_read_raw_data_block(dsp, mem_type, mem_addr, 1, &raw); - if (ret < 0) - return ret; - - *data = be32_to_cpu(raw) & 0x00ffffffu; - - return 0; -} - -static int wm_adsp_write_data_word(struct wm_adsp *dsp, int mem_type, - unsigned int mem_addr, u32 data) -{ - struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type); - __be32 val = cpu_to_be32(data & 0x00ffffffu); - unsigned int reg; - - if (!mem) - return -EINVAL; - - reg = dsp->ops->region_to_reg(mem, mem_addr); - - return regmap_raw_write(dsp->regmap, reg, &val, sizeof(val)); -} - static inline int wm_adsp_buffer_read(struct wm_adsp_compr_buf *buf, unsigned int field_offset, u32 *data) { - return wm_adsp_read_data_word(buf->dsp, buf->host_buf_mem_type, - buf->host_buf_ptr + field_offset, data); + return cs_dsp_read_data_word(&buf->dsp->cs_dsp, buf->host_buf_mem_type, + buf->host_buf_ptr + field_offset, data); } static inline int wm_adsp_buffer_write(struct wm_adsp_compr_buf *buf, unsigned int field_offset, u32 data) { - return wm_adsp_write_data_word(buf->dsp, buf->host_buf_mem_type, - buf->host_buf_ptr + field_offset, data); -} - -static void wm_adsp_remove_padding(u32 *buf, int nwords) -{ - const __be32 *pack_in = (__be32 *)buf; - u8 *pack_out = (u8 *)buf; - int i; - - /* - * DSP words from the register map have pad bytes and the data bytes - * are in swapped order. This swaps back to the original little-endian - * order and strips the pad bytes. - */ - for (i = 0; i < nwords; i++) { - u32 word = be32_to_cpu(*pack_in++); - *pack_out++ = (u8)word; - *pack_out++ = (u8)(word >> 8); - *pack_out++ = (u8)(word >> 16); - } + return cs_dsp_write_data_word(&buf->dsp->cs_dsp, buf->host_buf_mem_type, + buf->host_buf_ptr + field_offset, + data); } static int wm_adsp_buffer_populate(struct wm_adsp_compr_buf *buf) @@ -3810,12 +1386,12 @@ static struct wm_adsp_compr_buf *wm_adsp_buffer_alloc(struct wm_adsp *dsp) static int wm_adsp_buffer_parse_legacy(struct wm_adsp *dsp) { - struct wm_adsp_alg_region *alg_region; + struct cs_dsp_alg_region *alg_region; struct wm_adsp_compr_buf *buf; u32 xmalg, addr, magic; int i, ret; - alg_region = wm_adsp_find_alg_region(dsp, WMFW_ADSP2_XM, dsp->fw_id); + alg_region = cs_dsp_find_alg_region(&dsp->cs_dsp, WMFW_ADSP2_XM, dsp->cs_dsp.fw_id); if (!alg_region) { adsp_err(dsp, "No algorithm region found\n"); return -EINVAL; @@ -3825,10 +1401,10 @@ static int wm_adsp_buffer_parse_legacy(struct wm_adsp *dsp) if (!buf) return -ENOMEM; - xmalg = dsp->ops->sys_config_size / sizeof(__be32); + xmalg = dsp->sys_config_size / sizeof(__be32); addr = alg_region->base + xmalg + ALG_XM_FIELD(magic); - ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, &magic); + ret = cs_dsp_read_data_word(&dsp->cs_dsp, WMFW_ADSP2_XM, addr, &magic); if (ret < 0) return ret; @@ -3837,8 +1413,8 @@ static int wm_adsp_buffer_parse_legacy(struct wm_adsp *dsp) addr = alg_region->base + xmalg + ALG_XM_FIELD(host_buf_ptr); for (i = 0; i < 5; ++i) { - ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, - &buf->host_buf_ptr); + ret = cs_dsp_read_data_word(&dsp->cs_dsp, WMFW_ADSP2_XM, addr, + &buf->host_buf_ptr); if (ret < 0) return ret; @@ -3862,40 +1438,36 @@ static int wm_adsp_buffer_parse_legacy(struct wm_adsp *dsp) return 0; } -static int wm_adsp_buffer_parse_coeff(struct wm_coeff_ctl *ctl) +static int wm_adsp_buffer_parse_coeff(struct cs_dsp_coeff_ctl *cs_ctl) { struct wm_adsp_host_buf_coeff_v1 coeff_v1; struct wm_adsp_compr_buf *buf; - unsigned int reg, version; - __be32 bufp; + struct wm_adsp *dsp = container_of(cs_ctl->dsp, struct wm_adsp, cs_dsp); + unsigned int version; int ret, i; - ret = wm_coeff_base_reg(ctl, ®); - if (ret) - return ret; - for (i = 0; i < 5; ++i) { - ret = regmap_raw_read(ctl->dsp->regmap, reg, &bufp, sizeof(bufp)); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, &coeff_v1, sizeof(coeff_v1)); if (ret < 0) return ret; - if (bufp) + if (coeff_v1.host_buf_ptr) break; usleep_range(1000, 2000); } - if (!bufp) { - adsp_err(ctl->dsp, "Failed to acquire host buffer\n"); + if (!coeff_v1.host_buf_ptr) { + adsp_err(dsp, "Failed to acquire host buffer\n"); return -EIO; } - buf = wm_adsp_buffer_alloc(ctl->dsp); + buf = wm_adsp_buffer_alloc(dsp); if (!buf) return -ENOMEM; - buf->host_buf_mem_type = ctl->alg_region.type; - buf->host_buf_ptr = be32_to_cpu(bufp); + buf->host_buf_mem_type = cs_ctl->alg_region.type; + buf->host_buf_ptr = be32_to_cpu(coeff_v1.host_buf_ptr); ret = wm_adsp_buffer_populate(buf); if (ret < 0) @@ -3905,29 +1477,24 @@ static int wm_adsp_buffer_parse_coeff(struct wm_coeff_ctl *ctl) * v0 host_buffer coefficients didn't have versioning, so if the * control is one word, assume version 0. */ - if (ctl->len == 4) { + if (cs_ctl->len == 4) { compr_dbg(buf, "host_buf_ptr=%x\n", buf->host_buf_ptr); return 0; } - ret = regmap_raw_read(ctl->dsp->regmap, reg, &coeff_v1, - sizeof(coeff_v1)); - if (ret < 0) - return ret; - version = be32_to_cpu(coeff_v1.versions) & HOST_BUF_COEFF_COMPAT_VER_MASK; version >>= HOST_BUF_COEFF_COMPAT_VER_SHIFT; if (version > HOST_BUF_COEFF_SUPPORTED_COMPAT_VER) { - adsp_err(ctl->dsp, + adsp_err(dsp, "Host buffer coeff ver %u > supported version %u\n", version, HOST_BUF_COEFF_SUPPORTED_COMPAT_VER); return -EINVAL; } - wm_adsp_remove_padding((u32 *)&coeff_v1.name, ARRAY_SIZE(coeff_v1.name)); + cs_dsp_remove_padding((u32 *)&coeff_v1.name, ARRAY_SIZE(coeff_v1.name)); - buf->name = kasprintf(GFP_KERNEL, "%s-dsp-%s", ctl->dsp->part, + buf->name = kasprintf(GFP_KERNEL, "%s-dsp-%s", dsp->part, (char *)&coeff_v1.name); compr_dbg(buf, "host_buf_ptr=%x coeff version %u\n", @@ -3938,17 +1505,17 @@ static int wm_adsp_buffer_parse_coeff(struct wm_coeff_ctl *ctl) static int wm_adsp_buffer_init(struct wm_adsp *dsp) { - struct wm_coeff_ctl *ctl; + struct cs_dsp_coeff_ctl *cs_ctl; int ret; - list_for_each_entry(ctl, &dsp->ctl_list, list) { - if (ctl->type != WMFW_CTL_TYPE_HOST_BUFFER) + list_for_each_entry(cs_ctl, &dsp->cs_dsp.ctl_list, list) { + if (cs_ctl->type != WMFW_CTL_TYPE_HOST_BUFFER) continue; - if (!ctl->enabled) + if (!cs_ctl->enabled) continue; - ret = wm_adsp_buffer_parse_coeff(ctl); + ret = wm_adsp_buffer_parse_coeff(cs_ctl); if (ret < 0) { adsp_err(dsp, "Failed to parse coeff: %d\n", ret); goto error; @@ -4016,7 +1583,7 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, compr_dbg(compr, "Trigger: %d\n", cmd); - mutex_lock(&dsp->pwr_lock); + mutex_lock(&dsp->cs_dsp.pwr_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -4052,7 +1619,7 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, break; } - mutex_unlock(&dsp->pwr_lock); + mutex_unlock(&dsp->cs_dsp.pwr_lock); return ret; } @@ -4101,7 +1668,7 @@ static int wm_adsp_buffer_update_avail(struct wm_adsp_compr_buf *buf) avail += wm_adsp_buffer_size(buf); compr_dbg(buf, "readindex=0x%x, writeindex=0x%x, avail=%d\n", - buf->read_index, write_index, avail * WM_ADSP_DATA_WORD_SIZE); + buf->read_index, write_index, avail * CS_DSP_DATA_WORD_SIZE); buf->avail = avail; @@ -4114,7 +1681,7 @@ int wm_adsp_compr_handle_irq(struct wm_adsp *dsp) struct wm_adsp_compr *compr; int ret = 0; - mutex_lock(&dsp->pwr_lock); + mutex_lock(&dsp->cs_dsp.pwr_lock); if (list_empty(&dsp->buffer_list)) { ret = -ENODEV; @@ -4152,7 +1719,7 @@ out_notify: } out: - mutex_unlock(&dsp->pwr_lock); + mutex_unlock(&dsp->cs_dsp.pwr_lock); return ret; } @@ -4182,7 +1749,7 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, compr_dbg(compr, "Pointer request\n"); - mutex_lock(&dsp->pwr_lock); + mutex_lock(&dsp->cs_dsp.pwr_lock); buf = compr->buf; @@ -4222,11 +1789,11 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, } tstamp->copied_total = compr->copied_total; - tstamp->copied_total += buf->avail * WM_ADSP_DATA_WORD_SIZE; + tstamp->copied_total += buf->avail * CS_DSP_DATA_WORD_SIZE; tstamp->sampling_rate = compr->sample_rate; out: - mutex_unlock(&dsp->pwr_lock); + mutex_unlock(&dsp->cs_dsp.pwr_lock); return ret; } @@ -4264,12 +1831,12 @@ static int wm_adsp_buffer_capture_block(struct wm_adsp_compr *compr, int target) return 0; /* Read data from DSP */ - ret = wm_adsp_read_raw_data_block(buf->dsp, mem_type, adsp_addr, - nwords, (__be32 *)compr->raw_buf); + ret = cs_dsp_read_raw_data_block(&buf->dsp->cs_dsp, mem_type, adsp_addr, + nwords, (__be32 *)compr->raw_buf); if (ret < 0) return ret; - wm_adsp_remove_padding(compr->raw_buf, nwords); + cs_dsp_remove_padding(compr->raw_buf, nwords); /* update read index to account for words read */ buf->read_index += nwords; @@ -4301,7 +1868,7 @@ static int wm_adsp_compr_read(struct wm_adsp_compr *compr, return -EIO; } - count /= WM_ADSP_DATA_WORD_SIZE; + count /= CS_DSP_DATA_WORD_SIZE; do { nwords = wm_adsp_buffer_capture_block(compr, count); @@ -4311,7 +1878,7 @@ static int wm_adsp_compr_read(struct wm_adsp_compr *compr, return nwords; } - nbytes = nwords * WM_ADSP_DATA_WORD_SIZE; + nbytes = nwords * CS_DSP_DATA_WORD_SIZE; compr_dbg(compr, "Read %d bytes\n", nbytes); @@ -4338,21 +1905,22 @@ int wm_adsp_compr_copy(struct snd_soc_component *component, struct wm_adsp *dsp = compr->dsp; int ret; - mutex_lock(&dsp->pwr_lock); + mutex_lock(&dsp->cs_dsp.pwr_lock); if (stream->direction == SND_COMPRESS_CAPTURE) ret = wm_adsp_compr_read(compr, buf, count); else ret = -ENOTSUPP; - mutex_unlock(&dsp->pwr_lock); + mutex_unlock(&dsp->cs_dsp.pwr_lock); return ret; } EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); -static void wm_adsp_fatal_error(struct wm_adsp *dsp) +static void wm_adsp_fatal_error(struct cs_dsp *cs_dsp) { + struct wm_adsp *dsp = container_of(cs_dsp, struct wm_adsp, cs_dsp); struct wm_adsp_compr *compr; dsp->fatal_error = true; @@ -4366,64 +1934,8 @@ static void wm_adsp_fatal_error(struct wm_adsp *dsp) irqreturn_t wm_adsp2_bus_error(int irq, void *data) { struct wm_adsp *dsp = (struct wm_adsp *)data; - unsigned int val; - struct regmap *regmap = dsp->regmap; - int ret = 0; - - mutex_lock(&dsp->pwr_lock); - - ret = regmap_read(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, &val); - if (ret) { - adsp_err(dsp, - "Failed to read Region Lock Ctrl register: %d\n", ret); - goto error; - } - - if (val & ADSP2_WDT_TIMEOUT_STS_MASK) { - adsp_err(dsp, "watchdog timeout error\n"); - dsp->ops->stop_watchdog(dsp); - wm_adsp_fatal_error(dsp); - } - - if (val & (ADSP2_ADDR_ERR_MASK | ADSP2_REGION_LOCK_ERR_MASK)) { - if (val & ADSP2_ADDR_ERR_MASK) - adsp_err(dsp, "bus error: address error\n"); - else - adsp_err(dsp, "bus error: region lock error\n"); - - ret = regmap_read(regmap, dsp->base + ADSP2_BUS_ERR_ADDR, &val); - if (ret) { - adsp_err(dsp, - "Failed to read Bus Err Addr register: %d\n", - ret); - goto error; - } - - adsp_err(dsp, "bus error address = 0x%x\n", - val & ADSP2_BUS_ERR_ADDR_MASK); - ret = regmap_read(regmap, - dsp->base + ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR, - &val); - if (ret) { - adsp_err(dsp, - "Failed to read Pmem Xmem Err Addr register: %d\n", - ret); - goto error; - } - - adsp_err(dsp, "xmem error address = 0x%x\n", - val & ADSP2_XMEM_ERR_ADDR_MASK); - adsp_err(dsp, "pmem error address = 0x%x\n", - (val & ADSP2_PMEM_ERR_ADDR_MASK) >> - ADSP2_PMEM_ERR_ADDR_SHIFT); - } - - regmap_update_bits(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, - ADSP2_CTRL_ERR_EINT, ADSP2_CTRL_ERR_EINT); - -error: - mutex_unlock(&dsp->pwr_lock); + cs_dsp_adsp2_bus_error(&dsp->cs_dsp); return IRQ_HANDLED; } @@ -4432,55 +1944,8 @@ EXPORT_SYMBOL_GPL(wm_adsp2_bus_error); irqreturn_t wm_halo_bus_error(int irq, void *data) { struct wm_adsp *dsp = (struct wm_adsp *)data; - struct regmap *regmap = dsp->regmap; - unsigned int fault[6]; - struct reg_sequence clear[] = { - { dsp->base + HALO_MPU_XM_VIO_STATUS, 0x0 }, - { dsp->base + HALO_MPU_YM_VIO_STATUS, 0x0 }, - { dsp->base + HALO_MPU_PM_VIO_STATUS, 0x0 }, - }; - int ret; - - mutex_lock(&dsp->pwr_lock); - - ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_1, - fault); - if (ret) { - adsp_warn(dsp, "Failed to read AHB DEBUG_1: %d\n", ret); - goto exit_unlock; - } - adsp_warn(dsp, "AHB: STATUS: 0x%x ADDR: 0x%x\n", - *fault & HALO_AHBM_FLAGS_ERR_MASK, - (*fault & HALO_AHBM_CORE_ERR_ADDR_MASK) >> - HALO_AHBM_CORE_ERR_ADDR_SHIFT); - - ret = regmap_read(regmap, dsp->base_sysinfo + HALO_AHBM_WINDOW_DEBUG_0, - fault); - if (ret) { - adsp_warn(dsp, "Failed to read AHB DEBUG_0: %d\n", ret); - goto exit_unlock; - } - - adsp_warn(dsp, "AHB: SYS_ADDR: 0x%x\n", *fault); - - ret = regmap_bulk_read(regmap, dsp->base + HALO_MPU_XM_VIO_ADDR, - fault, ARRAY_SIZE(fault)); - if (ret) { - adsp_warn(dsp, "Failed to read MPU fault info: %d\n", ret); - goto exit_unlock; - } - - adsp_warn(dsp, "XM: STATUS:0x%x ADDR:0x%x\n", fault[1], fault[0]); - adsp_warn(dsp, "YM: STATUS:0x%x ADDR:0x%x\n", fault[3], fault[2]); - adsp_warn(dsp, "PM: STATUS:0x%x ADDR:0x%x\n", fault[5], fault[4]); - - ret = regmap_multi_reg_write(dsp->regmap, clear, ARRAY_SIZE(clear)); - if (ret) - adsp_warn(dsp, "Failed to clear MPU status: %d\n", ret); - -exit_unlock: - mutex_unlock(&dsp->pwr_lock); + cs_dsp_halo_bus_error(&dsp->cs_dsp); return IRQ_HANDLED; } @@ -4490,99 +1955,23 @@ irqreturn_t wm_halo_wdt_expire(int irq, void *data) { struct wm_adsp *dsp = data; - mutex_lock(&dsp->pwr_lock); - - adsp_warn(dsp, "WDT Expiry Fault\n"); - dsp->ops->stop_watchdog(dsp); - wm_adsp_fatal_error(dsp); - - mutex_unlock(&dsp->pwr_lock); + cs_dsp_halo_wdt_expire(&dsp->cs_dsp); return IRQ_HANDLED; } EXPORT_SYMBOL_GPL(wm_halo_wdt_expire); -static const struct wm_adsp_ops wm_adsp1_ops = { - .validate_version = wm_adsp_validate_version, - .parse_sizes = wm_adsp1_parse_sizes, - .region_to_reg = wm_adsp_region_to_reg, +static const struct cs_dsp_client_ops wm_adsp1_client_ops = { + .control_add = wm_adsp_control_add, + .control_remove = wm_adsp_control_remove, }; -static const struct wm_adsp_ops wm_adsp2_ops[] = { - { - .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), - .parse_sizes = wm_adsp2_parse_sizes, - .validate_version = wm_adsp_validate_version, - .setup_algs = wm_adsp2_setup_algs, - .region_to_reg = wm_adsp_region_to_reg, - - .show_fw_status = wm_adsp2_show_fw_status, - - .enable_memory = wm_adsp2_enable_memory, - .disable_memory = wm_adsp2_disable_memory, - - .enable_core = wm_adsp2_enable_core, - .disable_core = wm_adsp2_disable_core, - - .start_core = wm_adsp2_start_core, - .stop_core = wm_adsp2_stop_core, - - }, - { - .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), - .parse_sizes = wm_adsp2_parse_sizes, - .validate_version = wm_adsp_validate_version, - .setup_algs = wm_adsp2_setup_algs, - .region_to_reg = wm_adsp_region_to_reg, - - .show_fw_status = wm_adsp2v2_show_fw_status, - - .enable_memory = wm_adsp2_enable_memory, - .disable_memory = wm_adsp2_disable_memory, - .lock_memory = wm_adsp2_lock, - - .enable_core = wm_adsp2v2_enable_core, - .disable_core = wm_adsp2v2_disable_core, - - .start_core = wm_adsp2_start_core, - .stop_core = wm_adsp2_stop_core, - }, - { - .sys_config_size = sizeof(struct wm_adsp_system_config_xm_hdr), - .parse_sizes = wm_adsp2_parse_sizes, - .validate_version = wm_adsp_validate_version, - .setup_algs = wm_adsp2_setup_algs, - .region_to_reg = wm_adsp_region_to_reg, - - .show_fw_status = wm_adsp2v2_show_fw_status, - .stop_watchdog = wm_adsp_stop_watchdog, - - .enable_memory = wm_adsp2_enable_memory, - .disable_memory = wm_adsp2_disable_memory, - .lock_memory = wm_adsp2_lock, - - .enable_core = wm_adsp2v2_enable_core, - .disable_core = wm_adsp2v2_disable_core, - - .start_core = wm_adsp2_start_core, - .stop_core = wm_adsp2_stop_core, - }, -}; - -static const struct wm_adsp_ops wm_halo_ops = { - .sys_config_size = sizeof(struct wm_halo_system_config_xm_hdr), - .parse_sizes = wm_adsp2_parse_sizes, - .validate_version = wm_halo_validate_version, - .setup_algs = wm_halo_setup_algs, - .region_to_reg = wm_halo_region_to_reg, - - .show_fw_status = wm_halo_show_fw_status, - .stop_watchdog = wm_halo_stop_watchdog, - - .lock_memory = wm_halo_configure_mpu, - - .start_core = wm_halo_start_core, - .stop_core = wm_halo_stop_core, +static const struct cs_dsp_client_ops wm_adsp2_client_ops = { + .control_add = wm_adsp_control_add, + .control_remove = wm_adsp_control_remove, + .post_run = wm_adsp_event_post_run, + .post_stop = wm_adsp_event_post_stop, + .watchdog_expired = wm_adsp_fatal_error, }; MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index f22131d9cc29..0e2f113bd342 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -10,128 +10,37 @@ #ifndef __WM_ADSP_H #define __WM_ADSP_H +#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> + #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/compress_driver.h> -#include "wmfw.h" - /* Return values for wm_adsp_compr_handle_irq */ #define WM_ADSP_COMPR_OK 0 #define WM_ADSP_COMPR_VOICE_TRIGGER 1 -#define WM_ADSP2_REGION_0 BIT(0) -#define WM_ADSP2_REGION_1 BIT(1) -#define WM_ADSP2_REGION_2 BIT(2) -#define WM_ADSP2_REGION_3 BIT(3) -#define WM_ADSP2_REGION_4 BIT(4) -#define WM_ADSP2_REGION_5 BIT(5) -#define WM_ADSP2_REGION_6 BIT(6) -#define WM_ADSP2_REGION_7 BIT(7) -#define WM_ADSP2_REGION_8 BIT(8) -#define WM_ADSP2_REGION_9 BIT(9) -#define WM_ADSP2_REGION_1_9 (WM_ADSP2_REGION_1 | \ - WM_ADSP2_REGION_2 | WM_ADSP2_REGION_3 | \ - WM_ADSP2_REGION_4 | WM_ADSP2_REGION_5 | \ - WM_ADSP2_REGION_6 | WM_ADSP2_REGION_7 | \ - WM_ADSP2_REGION_8 | WM_ADSP2_REGION_9) -#define WM_ADSP2_REGION_ALL (WM_ADSP2_REGION_0 | WM_ADSP2_REGION_1_9) - -struct wm_adsp_region { - int type; - unsigned int base; -}; - -struct wm_adsp_alg_region { - struct list_head list; - unsigned int alg; - int type; - unsigned int base; -}; - struct wm_adsp_compr; struct wm_adsp_compr_buf; -struct wm_adsp_ops; struct wm_adsp { + struct cs_dsp cs_dsp; const char *part; - const char *name; const char *fwf_name; - int rev; - int num; - int type; - struct device *dev; - struct regmap *regmap; struct snd_soc_component *component; - const struct wm_adsp_ops *ops; - - unsigned int base; - unsigned int base_sysinfo; - unsigned int sysclk_reg; - unsigned int sysclk_mask; - unsigned int sysclk_shift; - - struct list_head alg_regions; - - unsigned int fw_id; - unsigned int fw_id_version; - unsigned int fw_vendor_id; - - const struct wm_adsp_region *mem; - int num_mems; + unsigned int sys_config_size; int fw; - int fw_ver; + + struct work_struct boot_work; bool preloaded; - bool booted; - bool running; bool fatal_error; - struct list_head ctl_list; - - struct work_struct boot_work; - struct list_head compr_list; struct list_head buffer_list; - - struct mutex pwr_lock; - - unsigned int lock_regions; - -#ifdef CONFIG_DEBUG_FS - struct dentry *debugfs_root; - char *wmfw_file_name; - char *bin_file_name; -#endif - -}; - -struct wm_adsp_ops { - unsigned int sys_config_size; - - bool (*validate_version)(struct wm_adsp *dsp, unsigned int version); - unsigned int (*parse_sizes)(struct wm_adsp *dsp, - const char * const file, - unsigned int pos, - const struct firmware *firmware); - int (*setup_algs)(struct wm_adsp *dsp); - unsigned int (*region_to_reg)(struct wm_adsp_region const *mem, - unsigned int offset); - - void (*show_fw_status)(struct wm_adsp *dsp); - void (*stop_watchdog)(struct wm_adsp *dsp); - - int (*enable_memory)(struct wm_adsp *dsp); - void (*disable_memory)(struct wm_adsp *dsp); - int (*lock_memory)(struct wm_adsp *dsp, unsigned int lock_regions); - - int (*enable_core)(struct wm_adsp *dsp); - void (*disable_core)(struct wm_adsp *dsp); - - int (*start_core)(struct wm_adsp *dsp); - void (*stop_core)(struct wm_adsp *dsp); }; #define WM_ADSP1(wname, num) \ diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c index d21a72314d37..d20ec1571010 100644 --- a/sound/soc/codecs/zl38060.c +++ b/sound/soc/codecs/zl38060.c @@ -250,8 +250,8 @@ static int zl38_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: /* always 32 bits per frame (= 16 bits/channel, 2 channels) */ err = regmap_update_bits(priv->regmap, REG_TDMA_CFG_CLK, CFG_CLK_MASTER | CFG_CLK_PCLK_MASK, diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 33ce257ae198..5cb58929090d 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -356,25 +356,25 @@ static int dw_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); int ret = 0; - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: if (dev->capability & DW_I2S_SLAVE) ret = 0; else ret = -EINVAL; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: if (dev->capability & DW_I2S_MASTER) ret = 0; else ret = -EINVAL; break; - case SND_SOC_DAIFMT_CBM_CFS: - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBP_CFC: + case SND_SOC_DAIFMT_CBC_CFP: ret = -EINVAL; break; default: - dev_dbg(dev->dev, "dwc : Invalid master/slave format\n"); + dev_dbg(dev->dev, "dwc : Invalid clock provider format\n"); ret = -EINVAL; break; } diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index e13271ea84de..8b61582753c8 100644 --- a/sound/soc/fsl/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -70,7 +70,7 @@ static struct snd_soc_dai_link eukrea_tlv320_dai = { .name = "tlv320aic23", .stream_name = "TLV320AIC23", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM, + SND_SOC_DAIFMT_CBP_CFP, .ops = &eukrea_tlv320_snd_ops, SND_SOC_DAILINK_REG(hifi), }; diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 06107ae46e20..6e6494f9f399 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -356,8 +356,8 @@ static int fsl_asoc_card_audmux_init(struct device_node *np, * If only 4 wires are needed, just set SSI into * synchronous mode and enable 4 PADs in IOMUX. */ - switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + switch (priv->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | @@ -367,7 +367,7 @@ static int fsl_asoc_card_audmux_init(struct device_node *np, IMX_AUDMUX_V2_PTCR_TFSDIR | IMX_AUDMUX_V2_PTCR_TCLKDIR; break; - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBP_CFC: int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | IMX_AUDMUX_V2_PTCR_RCLKDIR | @@ -377,7 +377,7 @@ static int fsl_asoc_card_audmux_init(struct device_node *np, IMX_AUDMUX_V2_PTCR_RFSDIR | IMX_AUDMUX_V2_PTCR_TFSDIR; break; - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBC_CFP: int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | IMX_AUDMUX_V2_PTCR_RFSDIR | @@ -387,7 +387,7 @@ static int fsl_asoc_card_audmux_init(struct device_node *np, IMX_AUDMUX_V2_PTCR_RCLKDIR | IMX_AUDMUX_V2_PTCR_TCLKDIR; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) | IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) | IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | @@ -533,8 +533,8 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) struct device_node *cpu_np, *codec_np, *asrc_np; struct device_node *np = pdev->dev.of_node; struct platform_device *asrc_pdev = NULL; - struct device_node *bitclkmaster = NULL; - struct device_node *framemaster = NULL; + struct device_node *bitclkprovider = NULL; + struct device_node *frameprovider = NULL; struct platform_device *cpu_pdev; struct fsl_asoc_card_priv *priv; struct device *codec_dev = NULL; @@ -617,29 +617,29 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; priv->cpu_priv.slot_width = 32; - priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { codec_dai_name = "cs4271-hifi"; priv->codec_priv.mclk_id = CS427x_SYSCLK_MCLK; - priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { codec_dai_name = "sgtl5000"; priv->codec_priv.mclk_id = SGTL5000_SYSCLK; - priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) { codec_dai_name = "tlv320aic32x4-hifi"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { codec_dai_name = "wm8962"; priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK; priv->codec_priv.fll_id = WM8962_SYSCLK_FLL; priv->codec_priv.pll_id = WM8962_FLL; - priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { codec_dai_name = "wm8960-hifi"; priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO; priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO; - priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { codec_dai_name = "ac97-hifi"; priv->dai_fmt = SND_SOC_DAIFMT_AC97; @@ -648,7 +648,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) { codec_dai_name = "fsl-mqs-dai"; priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | - SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_NB_NF; priv->dai_link[1].dpcm_capture = 0; priv->dai_link[2].dpcm_capture = 0; @@ -656,7 +656,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) { codec_dai_name = "wm8524-hifi"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; priv->dai_link[1].dpcm_capture = 0; priv->dai_link[2].dpcm_capture = 0; priv->cpu_priv.slot_width = 32; @@ -664,12 +664,12 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) { codec_dai_name = "si476x-codec"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; priv->card.dapm_routes = audio_map_rx; priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) { codec_dai_name = "wm8994-aif1"; - priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; priv->codec_priv.mclk_id = WM8994_FLL_SRC_MCLK1; priv->codec_priv.fll_id = WM8994_SYSCLK_FLL1; priv->codec_priv.pll_id = WM8994_FLL1; @@ -683,29 +683,29 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } /* Format info from DT is optional. */ - snd_soc_daifmt_parse_clock_provider_as_phandle(np, NULL, &bitclkmaster, &framemaster); - if (bitclkmaster || framemaster) { + snd_soc_daifmt_parse_clock_provider_as_phandle(np, NULL, &bitclkprovider, &frameprovider); + if (bitclkprovider || frameprovider) { unsigned int daifmt = snd_soc_daifmt_parse_format(np, NULL); - if (codec_np == bitclkmaster) - daifmt |= (codec_np == framemaster) ? - SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; + if (codec_np == bitclkprovider) + daifmt |= (codec_np == frameprovider) ? + SND_SOC_DAIFMT_CBP_CFP : SND_SOC_DAIFMT_CBP_CFC; else - daifmt |= (codec_np == framemaster) ? - SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; + daifmt |= (codec_np == frameprovider) ? + SND_SOC_DAIFMT_CBC_CFP : SND_SOC_DAIFMT_CBC_CFC; /* Override dai_fmt with value from DT */ priv->dai_fmt = daifmt; } /* Change direction according to format */ - if (priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) { + if (priv->dai_fmt & SND_SOC_DAIFMT_CBP_CFP) { priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_IN; priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_IN; } - of_node_put(bitclkmaster); - of_node_put(framemaster); + of_node_put(bitclkprovider); + of_node_put(frameprovider); if (!fsl_asoc_card_is_ac97(priv) && !codec_dev) { dev_dbg(&pdev->dev, "failed to find codec device\n"); diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c index f931288e256c..6dbb8c99f626 100644 --- a/sound/soc/fsl/fsl_audmix.c +++ b/sound/soc/fsl/fsl_audmix.c @@ -257,10 +257,10 @@ static int fsl_audmix_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - /* For playback the AUDMIX is slave, and for record is master */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - case SND_SOC_DAIFMT_CBS_CFS: + /* For playback the AUDMIX is consumer, and for record is provider */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index bda66b30e063..3a9e2df4e16f 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -52,7 +52,7 @@ struct fsl_esai_soc_data { * @sck_rate: clock rate of desired SCKx clock * @hck_dir: the direction of HCKx pads * @sck_div: if using PSR/PM dividers for SCKx clock - * @slave_mode: if fully using DAI slave mode + * @consumer_mode: if fully using DAI clock consumer mode * @synchronous: if using tx/rx synchronous mode * @name: driver name */ @@ -78,7 +78,7 @@ struct fsl_esai { u32 sck_rate[2]; bool hck_dir[2]; bool sck_div[2]; - bool slave_mode; + bool consumer_mode; bool synchronous; char name[32]; }; @@ -366,8 +366,8 @@ static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) u32 sub, ratio = hck_rate / freq; int ret; - /* Don't apply for fully slave mode or unchanged bclk */ - if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq) + /* Don't apply for fully consumer mode or unchanged bclk */ + if (esai_priv->consumer_mode || esai_priv->sck_rate[tx] == freq) return 0; if (ratio * freq > hck_rate) @@ -476,20 +476,20 @@ static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - esai_priv->slave_mode = false; + esai_priv->consumer_mode = false; - /* DAI clock master masks */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - esai_priv->slave_mode = true; + /* DAI clock provider masks */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + esai_priv->consumer_mode = true; break; - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBC_CFP: xccr |= ESAI_xCCR_xCKD; break; - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBP_CFC: xccr |= ESAI_xCCR_xFSD; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; break; default: @@ -1016,8 +1016,8 @@ static int fsl_esai_probe(struct platform_device *pdev) /* Set a default slot number */ esai_priv->slots = 2; - /* Set a default master/slave state */ - esai_priv->slave_mode = true; + /* Set a default clock provider state */ + esai_priv->consumer_mode = true; /* Determine the FIFO depth */ iprop = of_get_property(np, "fsl,fifo-depth", NULL); diff --git a/sound/soc/fsl/fsl_mqs.c b/sound/soc/fsl/fsl_mqs.c index 69aeb0e71844..27b4536dce44 100644 --- a/sound/soc/fsl/fsl_mqs.c +++ b/sound/soc/fsl/fsl_mqs.c @@ -102,8 +102,8 @@ static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; diff --git a/sound/soc/fsl/fsl_rpmsg.c b/sound/soc/fsl/fsl_rpmsg.c index d60f4dac6c1b..8508bc7f239d 100644 --- a/sound/soc/fsl/fsl_rpmsg.c +++ b/sound/soc/fsl/fsl_rpmsg.c @@ -138,11 +138,43 @@ static const struct snd_soc_component_driver fsl_component = { .name = "fsl-rpmsg", }; +static const struct fsl_rpmsg_soc_data imx7ulp_data = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, +}; + +static const struct fsl_rpmsg_soc_data imx8mm_data = { + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_DSD_U8 | + SNDRV_PCM_FMTBIT_DSD_U16_LE | SNDRV_PCM_FMTBIT_DSD_U32_LE, +}; + +static const struct fsl_rpmsg_soc_data imx8mn_data = { + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, +}; + +static const struct fsl_rpmsg_soc_data imx8mp_data = { + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, +}; + static const struct of_device_id fsl_rpmsg_ids[] = { - { .compatible = "fsl,imx7ulp-rpmsg-audio"}, - { .compatible = "fsl,imx8mm-rpmsg-audio"}, - { .compatible = "fsl,imx8mn-rpmsg-audio"}, - { .compatible = "fsl,imx8mp-rpmsg-audio"}, + { .compatible = "fsl,imx7ulp-rpmsg-audio", .data = &imx7ulp_data}, + { .compatible = "fsl,imx8mm-rpmsg-audio", .data = &imx8mm_data}, + { .compatible = "fsl,imx8mn-rpmsg-audio", .data = &imx8mn_data}, + { .compatible = "fsl,imx8mp-rpmsg-audio", .data = &imx8mp_data}, + { .compatible = "fsl,imx8ulp-rpmsg-audio", .data = &imx7ulp_data}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, fsl_rpmsg_ids); @@ -157,6 +189,13 @@ static int fsl_rpmsg_probe(struct platform_device *pdev) if (!rpmsg) return -ENOMEM; + rpmsg->soc_data = of_device_get_match_data(&pdev->dev); + + fsl_rpmsg_dai.playback.rates = rpmsg->soc_data->rates; + fsl_rpmsg_dai.capture.rates = rpmsg->soc_data->rates; + fsl_rpmsg_dai.playback.formats = rpmsg->soc_data->formats; + fsl_rpmsg_dai.capture.formats = rpmsg->soc_data->formats; + if (of_property_read_bool(np, "fsl,enable-lpa")) { rpmsg->enable_lpa = 1; rpmsg->buffer_size = LPA_LARGE_BUFFER_SIZE; diff --git a/sound/soc/fsl/fsl_rpmsg.h b/sound/soc/fsl/fsl_rpmsg.h index 4f5b49eb18d8..b04086fbf828 100644 --- a/sound/soc/fsl/fsl_rpmsg.h +++ b/sound/soc/fsl/fsl_rpmsg.h @@ -7,6 +7,16 @@ #define __FSL_RPMSG_H /* + * struct fsl_rpmsg_soc_data + * @rates: supported rates + * @formats: supported formats + */ +struct fsl_rpmsg_soc_data { + int rates; + u64 formats; +}; + +/* * struct fsl_rpmsg - rpmsg private data * * @ipg: ipg clock for cpu dai (SAI) @@ -15,6 +25,7 @@ * @pll8k: parent clock for multiple of 8kHz frequency * @pll11k: parent clock for multiple of 11kHz frequency * @card_pdev: Platform_device pointer to register a sound card + * @soc_data: soc specific data * @mclk_streams: Active streams that are using baudclk * @force_lpa: force enable low power audio routine if condition satisfy * @enable_lpa: enable low power audio routine according to dts setting @@ -27,6 +38,7 @@ struct fsl_rpmsg { struct clk *pll8k; struct clk *pll11k; struct platform_device *card_pdev; + const struct fsl_rpmsg_soc_data *soc_data; unsigned int mclk_streams; int force_lpa; int enable_lpa; diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 38f6362099d5..10544fa27dc0 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -297,23 +297,23 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, return -EINVAL; } - /* DAI clock master masks */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + /* DAI clock provider masks */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = false; + sai->is_consumer_mode = false; break; - case SND_SOC_DAIFMT_CBM_CFM: - sai->is_slave_mode = true; + case SND_SOC_DAIFMT_CBP_CFP: + sai->is_consumer_mode = true; break; - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBC_CFP: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; - sai->is_slave_mode = false; + sai->is_consumer_mode = false; break; - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBP_CFC: val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = true; + sai->is_consumer_mode = true; break; default: return -EINVAL; @@ -356,8 +356,8 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) u32 id; int ret = 0; - /* Don't apply to slave mode */ - if (sai->is_slave_mode) + /* Don't apply to consumer mode */ + if (sai->is_consumer_mode) return 0; /* @@ -462,7 +462,7 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, pins = DIV_ROUND_UP(channels, slots); - if (!sai->is_slave_mode) { + if (!sai->is_consumer_mode) { if (sai->bclk_ratio) ret = fsl_sai_set_bclk(cpu_dai, tx, sai->bclk_ratio * @@ -502,12 +502,12 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, val_cr4 |= FSL_SAI_CR4_CHMOD; /* - * For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will + * For SAI provider mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will * generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4), * RCR5(TCR5) for playback(capture), or there will be sync error. */ - if (!sai->is_slave_mode && fsl_sai_dir_is_synced(sai, adir)) { + if (!sai->is_consumer_mode && fsl_sai_dir_is_synced(sai, adir)) { regmap_update_bits(sai->regmap, FSL_SAI_xCR4(!tx, ofs), FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | FSL_SAI_CR4_CHMOD_MASK, @@ -543,7 +543,7 @@ static int fsl_sai_hw_free(struct snd_pcm_substream *substream, regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs), FSL_SAI_CR3_TRCE_MASK, 0); - if (!sai->is_slave_mode && + if (!sai->is_consumer_mode && sai->mclk_streams & BIT(substream->stream)) { clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]); sai->mclk_streams &= ~BIT(substream->stream); @@ -577,7 +577,7 @@ static void fsl_sai_config_disable(struct fsl_sai *sai, int dir) * This is a hardware bug, and will be fix in the * next sai version. */ - if (!sai->is_slave_mode) { + if (!sai->is_consumer_mode) { /* Software Reset */ regmap_write(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_SR); /* Clear SR bit to finish the reset */ diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index bc60030967dd..9aaf231bc024 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -259,7 +259,7 @@ struct fsl_sai { struct clk *bus_clk; struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; - bool is_slave_mode; + bool is_consumer_mode; bool is_lsb_first; bool is_dsp_mode; bool synchronous[2]; diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index 1c53719bb61e..d178b479c8bd 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -111,6 +111,7 @@ struct spdif_mixer_control { * @dma_params_tx: DMA parameters for transmit channel * @dma_params_rx: DMA parameters for receive channel * @regcache_srpc: regcache for SRPC + * @bypass: status of bypass input to output */ struct fsl_spdif_priv { const struct fsl_spdif_soc_data *soc; @@ -133,6 +134,7 @@ struct fsl_spdif_priv { struct snd_dmaengine_dai_dma_data dma_params_rx; /* regcache for SRPC */ u32 regcache_srpc; + bool bypass; }; static struct fsl_spdif_soc_data fsl_spdif_vf610 = { @@ -186,6 +188,16 @@ static struct fsl_spdif_soc_data fsl_spdif_imx8mm = { .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, }; +static struct fsl_spdif_soc_data fsl_spdif_imx8ulp = { + .imx = true, + .shared_root_clock = true, + .raw_capture_mode = false, + .interrupts = 1, + .tx_burst = 2, /* Applied for EDMA */ + .rx_burst = 2, /* Applied for EDMA */ + .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, /* Applied for EDMA */ +}; + /* Check if clk is a root clock that does not share clock source with others */ static inline bool fsl_spdif_can_set_clk_rate(struct fsl_spdif_priv *spdif, int clk) { @@ -895,6 +907,69 @@ static int fsl_spdif_rx_rcm_put(struct snd_kcontrol *kcontrol, return 0; } +static int fsl_spdif_bypass_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *priv = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.integer.value[0] = priv->bypass ? 1 : 0; + + return 0; +} + +static int fsl_spdif_bypass_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_soc_card *card = dai->component->card; + bool set = (ucontrol->value.integer.value[0] != 0); + struct regmap *regmap = priv->regmap; + struct snd_soc_pcm_runtime *rtd; + u32 scr, mask; + int stream; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link); + + if (priv->bypass == set) + return 0; /* nothing to do */ + + if (snd_soc_dai_active(dai)) { + dev_err(dai->dev, "Cannot change BYPASS mode while stream is running.\n"); + return -EBUSY; + } + + pm_runtime_get_sync(dai->dev); + + if (set) { + /* Disable interrupts */ + regmap_update_bits(regmap, REG_SPDIF_SIE, 0xffffff, 0); + + /* Configure BYPASS mode */ + scr = SCR_TXSEL_RX | SCR_RXFIFO_OFF; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK | + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK | SCR_TXSEL_MASK; + /* Power up SPDIF module */ + mask |= SCR_LOW_POWER; + } else { + /* Power down SPDIF module, disable TX */ + scr = SCR_LOW_POWER | SCR_TXSEL_OFF; + mask = SCR_LOW_POWER | SCR_TXSEL_MASK; + } + + regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); + + /* Disable playback & capture if BYPASS mode is enabled, enable otherwise */ + for_each_pcm_streams(stream) + rtd->pcm->streams[stream].substream_count = (set ? 0 : 1); + + priv->bypass = set; + pm_runtime_put_sync(dai->dev); + + return 0; +} + /* DPLL lock information */ static int fsl_spdif_rxrate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) @@ -1065,6 +1140,15 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { .info = fsl_spdif_rxrate_info, .get = fsl_spdif_rxrate_get, }, + /* RX bypass controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Bypass Mode", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_bypass_get, + .put = fsl_spdif_bypass_put, + }, /* User bit sync mode set/get controller */ { .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1560,6 +1644,7 @@ static const struct of_device_id fsl_spdif_dt_ids[] = { { .compatible = "fsl,imx6sx-spdif", .data = &fsl_spdif_imx6sx, }, { .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, }, { .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, }, + { .compatible = "fsl,imx8ulp-spdif", .data = &fsl_spdif_imx8ulp, }, {} }; MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index ecbc1c365d5b..1169d1104b9e 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -350,16 +350,16 @@ static bool fsl_ssi_is_ac97(struct fsl_ssi *ssi) SND_SOC_DAIFMT_AC97; } -static bool fsl_ssi_is_i2s_master(struct fsl_ssi *ssi) +static bool fsl_ssi_is_i2s_clock_provider(struct fsl_ssi *ssi) { - return (ssi->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) == - SND_SOC_DAIFMT_CBS_CFS; + return (ssi->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) == + SND_SOC_DAIFMT_CBC_CFC; } -static bool fsl_ssi_is_i2s_cbm_cfs(struct fsl_ssi *ssi) +static bool fsl_ssi_is_i2s_cbp_cfc(struct fsl_ssi *ssi) { - return (ssi->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) == - SND_SOC_DAIFMT_CBM_CFS; + return (ssi->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) == + SND_SOC_DAIFMT_CBP_CFC; } /** @@ -808,7 +808,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, u32 wl = SSI_SxCCR_WL(sample_size); int ret; - if (fsl_ssi_is_i2s_master(ssi)) { + if (fsl_ssi_is_i2s_clock_provider(ssi)) { ret = fsl_ssi_set_bclk(substream, dai, hw_params); if (ret) return ret; @@ -841,7 +841,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, u8 i2s_net = ssi->i2s_net; /* Normal + Network mode to send 16-bit data in 32-bit frames */ - if (fsl_ssi_is_i2s_cbm_cfs(ssi) && sample_size == 16) + if (fsl_ssi_is_i2s_cbp_cfc(ssi) && sample_size == 16) i2s_net = SSI_SCR_I2S_MODE_NORMAL | SSI_SCR_NET; /* Use Normal mode to send mono data at 1st slot of 2 slots */ @@ -865,7 +865,7 @@ static int fsl_ssi_hw_free(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); - if (fsl_ssi_is_i2s_master(ssi) && + if (fsl_ssi_is_i2s_clock_provider(ssi) && ssi->baudclk_streams & BIT(substream->stream)) { clk_disable_unprepare(ssi->baudclk); ssi->baudclk_streams &= ~BIT(substream->stream); @@ -891,18 +891,18 @@ static int _fsl_ssi_set_dai_fmt(struct fsl_ssi *ssi, unsigned int fmt) ssi->i2s_net = SSI_SCR_NET; switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: if (IS_ERR(ssi->baudclk)) { dev_err(ssi->dev, "missing baudclk for master mode\n"); return -EINVAL; } fallthrough; - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBP_CFC: ssi->i2s_net |= SSI_SCR_I2S_MODE_MASTER; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: ssi->i2s_net |= SSI_SCR_I2S_MODE_SLAVE; break; default: @@ -962,17 +962,17 @@ static int _fsl_ssi_set_dai_fmt(struct fsl_ssi *ssi, unsigned int fmt) return -EINVAL; } - /* DAI clock master masks */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + /* DAI clock provider masks */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: /* Output bit and frame sync clocks */ strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR; scr |= SSI_SCR_SYS_CLK_EN; break; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: /* Input bit or frame sync clocks */ break; - case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBP_CFC: /* Input bit clock but output frame sync clock */ strcr |= SSI_STCR_TFDIR; break; @@ -1341,7 +1341,7 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev, } } - /* Do not error out for slave cases that live without a baud clock */ + /* Do not error out for consumer cases that live without a baud clock */ ssi->baudclk = devm_clk_get(dev, "baud"); if (IS_ERR(ssi->baudclk)) dev_dbg(dev, "failed to get baud clock: %ld\n", diff --git a/sound/soc/fsl/imx-audmix.c b/sound/soc/fsl/imx-audmix.c index a364e2415de0..502fe1b522ab 100644 --- a/sound/soc/fsl/imx-audmix.c +++ b/sound/soc/fsl/imx-audmix.c @@ -80,8 +80,8 @@ static int imx_audmix_fe_hw_params(struct snd_pcm_substream *substream, u32 channels = params_channels(params); int ret, dir; - /* For playback the AUDMIX is slave, and for record is master */ - fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + /* For playback the AUDMIX is consumer, and for record is provider */ + fmt |= tx ? SND_SOC_DAIFMT_CBC_CFC : SND_SOC_DAIFMT_CBP_CFP; dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; /* set DAI configuration */ @@ -121,8 +121,8 @@ static int imx_audmix_be_hw_params(struct snd_pcm_substream *substream, if (!tx) return 0; - /* For playback the AUDMIX is slave */ - fmt |= SND_SOC_DAIFMT_CBM_CFM; + /* For playback the AUDMIX is consumer */ + fmt |= SND_SOC_DAIFMT_CBP_CFP; /* set AUDMIX DAI configuration */ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), fmt); @@ -132,12 +132,12 @@ static int imx_audmix_be_hw_params(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_ops imx_audmix_fe_ops = { +static const struct snd_soc_ops imx_audmix_fe_ops = { .startup = imx_audmix_fe_startup, .hw_params = imx_audmix_fe_hw_params, }; -static struct snd_soc_ops imx_audmix_be_ops = { +static const struct snd_soc_ops imx_audmix_be_ops = { .hw_params = imx_audmix_be_hw_params, }; diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c index 58fd0639a069..6f06afd23b16 100644 --- a/sound/soc/fsl/imx-card.c +++ b/sound/soc/fsl/imx-card.c @@ -442,12 +442,12 @@ static int imx_aif_startup(struct snd_pcm_substream *substream) return ret; } -static struct snd_soc_ops imx_aif_ops = { +static const struct snd_soc_ops imx_aif_ops = { .hw_params = imx_aif_hw_params, .startup = imx_aif_startup, }; -static struct snd_soc_ops imx_aif_ops_be = { +static const struct snd_soc_ops imx_aif_ops_be = { .hw_params = imx_aif_hw_params, }; @@ -652,7 +652,7 @@ static int imx_card_parse_of(struct imx_card_data *data) NULL, &link->dai_fmt); if (ret) link->dai_fmt = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S; /* Get tdm slot */ diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c index 1981dcd7e930..09c674ee79f1 100644 --- a/sound/soc/fsl/imx-es8328.c +++ b/sound/soc/fsl/imx-es8328.c @@ -174,7 +174,7 @@ static int imx_es8328_probe(struct platform_device *pdev) data->dai.platforms->of_node = ssi_np; data->dai.init = &imx_es8328_dai_init; data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_CBP_CFP; data->card.dev = dev; data->card.dapm_widgets = imx_es8328_dapm_widgets; diff --git a/sound/soc/fsl/imx-hdmi.c b/sound/soc/fsl/imx-hdmi.c index 34a0dceae621..f10359a28800 100644 --- a/sound/soc/fsl/imx-hdmi.c +++ b/sound/soc/fsl/imx-hdmi.c @@ -59,7 +59,7 @@ static int imx_hdmi_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops imx_hdmi_ops = { +static const struct snd_soc_ops imx_hdmi_ops = { .hw_params = imx_hdmi_hw_params, }; @@ -171,7 +171,7 @@ static int imx_hdmi_probe(struct platform_device *pdev) data->dai.codecs->name = "hdmi-audio-codec.1"; data->dai.dai_fmt = data->dai_fmt | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_CBC_CFC; } if (hdmi_in) { @@ -181,7 +181,7 @@ static int imx_hdmi_probe(struct platform_device *pdev) data->dai.codecs->name = "hdmi-audio-codec.2"; data->dai.dai_fmt = data->dai_fmt | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_CBP_CFP; } data->card.dapm_widgets = imx_hdmi_widgets; diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c index f96fe4ff8425..2e117311e582 100644 --- a/sound/soc/fsl/imx-rpmsg.c +++ b/sound/soc/fsl/imx-rpmsg.c @@ -64,7 +64,7 @@ static int imx_rpmsg_probe(struct platform_device *pdev) data->dai.stream_name = "rpmsg hifi"; data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_CBC_CFC; /* Optional codec node */ ret = of_parse_phandle_with_fixed_args(np, "audio-codec", 0, 0, &args); diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c index f45cb4bbb6c4..2f1acd011042 100644 --- a/sound/soc/fsl/imx-sgtl5000.c +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -153,7 +153,7 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) data->dai.platforms->of_node = ssi_np; data->dai.init = &imx_sgtl5000_dai_init; data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_CBP_CFP; data->card.dev = &pdev->dev; ret = snd_soc_of_parse_card_name(&data->card, "model"); diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index 58b9ca3c4da0..e71a992fbf93 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -264,7 +264,7 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev) if (strcasecmp(sprop, "i2s-slave") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBP_CFP; machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; @@ -282,37 +282,37 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev) machine_data->clk_frequency = be32_to_cpup(iprop); } else if (strcasecmp(sprop, "i2s-master") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC; machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else if (strcasecmp(sprop, "lj-slave") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBP_CFP; machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; } else if (strcasecmp(sprop, "lj-master") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC; machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else if (strcasecmp(sprop, "rj-slave") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBP_CFP; machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; } else if (strcasecmp(sprop, "rj-master") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBC_CFC; machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else if (strcasecmp(sprop, "ac97-slave") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBP_CFP; machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; } else if (strcasecmp(sprop, "ac97-master") == 0) { machine_data->dai_format = - SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBC_CFC; machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else { diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c index 317c767b0099..b45742931b0d 100644 --- a/sound/soc/fsl/p1022_ds.c +++ b/sound/soc/fsl/p1022_ds.c @@ -275,7 +275,7 @@ static int p1022_ds_probe(struct platform_device *pdev) if (strcasecmp(sprop, "i2s-slave") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBP_CFP; mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; @@ -293,37 +293,37 @@ static int p1022_ds_probe(struct platform_device *pdev) mdata->clk_frequency = be32_to_cpup(iprop); } else if (strcasecmp(sprop, "i2s-master") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC; mdata->codec_clk_direction = SND_SOC_CLOCK_IN; mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else if (strcasecmp(sprop, "lj-slave") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBP_CFP; mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; } else if (strcasecmp(sprop, "lj-master") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC; mdata->codec_clk_direction = SND_SOC_CLOCK_IN; mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else if (strcasecmp(sprop, "rj-slave") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBP_CFP; mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; } else if (strcasecmp(sprop, "rj-master") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBC_CFC; mdata->codec_clk_direction = SND_SOC_CLOCK_IN; mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else if (strcasecmp(sprop, "ac97-slave") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBP_CFP; mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; } else if (strcasecmp(sprop, "ac97-master") == 0) { mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBC_CFC; mdata->codec_clk_direction = SND_SOC_CLOCK_IN; mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; } else { diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c index 714515b8081f..b395adabe823 100644 --- a/sound/soc/fsl/p1022_rdk.c +++ b/sound/soc/fsl/p1022_rdk.c @@ -265,7 +265,7 @@ static int p1022_rdk_probe(struct platform_device *pdev) * only one way to configure the SSI. */ mdata->dai_format = SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBP_CFP; mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index 4cafcf0e2bbf..b6df4e26bc4a 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -17,3 +17,23 @@ config SND_AUDIO_GRAPH_CARD This option enables generic simple sound card support with OF-graph DT bindings. It also support DPCM of multi CPU single Codec ststem. + +config SND_AUDIO_GRAPH_CARD2 + tristate "ASoC Audio Graph sound card2 support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple sound card2 support + with OF-graph DT bindings. + +config SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE + tristate "ASoC Audio Graph Card2 base custom sample support" + depends on SND_AUDIO_GRAPH_CARD2 + help + This option enables Audio Graph Card2 base custom sample + +config SND_TEST_COMPONENT + tristate "ASoC Test component sound support" + depends on OF + help + This option enables test component sound driver support. diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index 21c29e5e0671..084862156506 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -2,7 +2,13 @@ snd-soc-simple-card-utils-objs := simple-card-utils.o snd-soc-simple-card-objs := simple-card.o snd-soc-audio-graph-card-objs := audio-graph-card.o +snd-soc-audio-graph-card2-objs := audio-graph-card2.o +snd-soc-audio-graph-card2-custom-sample-objs := audio-graph-card2-custom-sample.o +snd-soc-test-component-objs := test-component.o obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o obj-$(CONFIG_SND_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o +obj-$(CONFIG_SND_AUDIO_GRAPH_CARD2) += snd-soc-audio-graph-card2.o +obj-$(CONFIG_SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE) += snd-soc-audio-graph-card2-custom-sample.o +obj-$(CONFIG_SND_TEST_COMPONENT) += snd-soc-test-component.o diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 546f6fd0609e..7eb027238327 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -310,8 +310,10 @@ static int graph_dai_link_of_dpcm(struct asoc_simple_priv *priv, * For example: FE <-> BE1 <-> BE2 <-> ... <-> BEn where * there are 'n' BE components in the path. */ - if (card->component_chaining && !soc_component_is_pcm(cpus)) + if (card->component_chaining && !soc_component_is_pcm(cpus)) { dai_link->no_pcm = 1; + dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup; + } asoc_simple_canonicalize_cpu(cpus, is_single_links); asoc_simple_canonicalize_platform(platforms, cpus); diff --git a/sound/soc/generic/audio-graph-card2-custom-sample.c b/sound/soc/generic/audio-graph-card2-custom-sample.c new file mode 100644 index 000000000000..4a2c743e286c --- /dev/null +++ b/sound/soc/generic/audio-graph-card2-custom-sample.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// audio-graph-card2-custom-sample.c +// +// Copyright (C) 2020 Renesas Electronics Corp. +// Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> +// +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <sound/graph_card.h> + +/* + * Custom driver can have own priv + * which includes asoc_simple_priv. + */ +struct custom_priv { + struct asoc_simple_priv simple_priv; + + /* custom driver's own params */ + int custom_params; +}; + +/* You can get custom_priv from simple_priv */ +#define simple_to_custom(simple) container_of((simple), struct custom_priv, simple_priv) + +static int custom_card_probe(struct snd_soc_card *card) +{ + struct asoc_simple_priv *simple_priv = snd_soc_card_get_drvdata(card); + struct custom_priv *custom_priv = simple_to_custom(simple_priv); + struct device *dev = simple_priv_to_dev(simple_priv); + + dev_info(dev, "custom probe\n"); + + custom_priv->custom_params = 1; + + /* you can use generic probe function */ + return asoc_graph_card_probe(card); +} + +static int custom_hook_pre(struct asoc_simple_priv *priv) +{ + struct device *dev = simple_priv_to_dev(priv); + + /* You can custom before parsing */ + dev_info(dev, "hook : %s\n", __func__); + + return 0; +} + +static int custom_hook_post(struct asoc_simple_priv *priv) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_card *card; + + /* You can custom after parsing */ + dev_info(dev, "hook : %s\n", __func__); + + /* overwrite .probe sample */ + card = simple_priv_to_card(priv); + card->probe = custom_card_probe; + + return 0; +} + +static int custom_normal(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device *dev = simple_priv_to_dev(priv); + + /* + * You can custom Normal parsing + * before/affter audio_graph2_link_normal() + */ + dev_info(dev, "hook : %s\n", __func__); + + return audio_graph2_link_normal(priv, lnk, li); +} + +static int custom_dpcm(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device *dev = simple_priv_to_dev(priv); + + /* + * You can custom DPCM parsing + * before/affter audio_graph2_link_dpcm() + */ + dev_info(dev, "hook : %s\n", __func__); + + return audio_graph2_link_dpcm(priv, lnk, li); +} + +static int custom_c2c(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device *dev = simple_priv_to_dev(priv); + + /* + * You can custom Codec2Codec parsing + * before/affter audio_graph2_link_c2c() + */ + dev_info(dev, "hook : %s\n", __func__); + + return audio_graph2_link_c2c(priv, lnk, li); +} + +/* + * audio-graph-card2 has many hooks for your customizing. + */ +static struct graph2_custom_hooks custom_hooks = { + .hook_pre = custom_hook_pre, + .hook_post = custom_hook_post, + .custom_normal = custom_normal, + .custom_dpcm = custom_dpcm, + .custom_c2c = custom_c2c, +}; + +static int custom_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct device *dev = simple_priv_to_dev(priv); + + dev_info(dev, "custom startup\n"); + + return asoc_simple_startup(substream); +} + +/* You can use custom ops */ +static const struct snd_soc_ops custom_ops = { + .startup = custom_startup, + .shutdown = asoc_simple_shutdown, + .hw_params = asoc_simple_hw_params, +}; + +static int custom_probe(struct platform_device *pdev) +{ + struct custom_priv *custom_priv; + struct asoc_simple_priv *simple_priv; + struct device *dev = &pdev->dev; + int ret; + + custom_priv = devm_kzalloc(dev, sizeof(*custom_priv), GFP_KERNEL); + if (!custom_priv) + return -ENOMEM; + + simple_priv = &custom_priv->simple_priv; + simple_priv->ops = &custom_ops; /* customize dai_link ops */ + + /* use audio-graph-card2 parsing with own custom hooks */ + ret = audio_graph2_parse_of(simple_priv, dev, &custom_hooks); + if (ret < 0) + return ret; + + /* customize more if needed */ + + return 0; +} + +static const struct of_device_id custom_of_match[] = { + { .compatible = "audio-graph-card2-custom-sample", }, + {}, +}; +MODULE_DEVICE_TABLE(of, custom_of_match); + +static struct platform_driver custom_card = { + .driver = { + .name = "audio-graph-card2-custom-sample", + .of_match_table = custom_of_match, + }, + .probe = custom_probe, + .remove = asoc_simple_remove, +}; +module_platform_driver(custom_card); + +MODULE_ALIAS("platform:asoc-audio-graph-card2-custom-sample"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Audio Graph Card2 Custom Sample"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/sound/soc/generic/audio-graph-card2-custom-sample.dtsi b/sound/soc/generic/audio-graph-card2-custom-sample.dtsi new file mode 100644 index 000000000000..8eee7b821ff7 --- /dev/null +++ b/sound/soc/generic/audio-graph-card2-custom-sample.dtsi @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * audio-graph-card2-custom-sample.dtsi + * + * Copyright (C) 2020 Renesas Electronics Corp. + * Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This sample indicates how to use audio-graph-card2 and its + * custom driver. "audio-graph-card2-custom-sample" is the custome driver + * which is using audio-graph-card2. + * + * You can easily use this sample by adding below line on your DT file, + * and add new CONFIG to your .config. + * + * #include "../../../../../sound/soc/generic/audio-graph-card2-custom-sample.dtsi" + * + * CONFIG_SND_AUDIO_GRAPH_CARD2 + * CONFIG_SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE + * CONFIG_SND_TEST_COMPONENT + */ +/ { + /* + * @ : used at links + * + * [Normal] + * cpu0 <-@-----------------> codec0 + * + * [Multi-CPU/Codec] + * +-+ +-+ + * cpu1 <--| |<-@--------->| |-> codec1 + * cpu2 <--| | | |-> codec2 + * +-+ +-+ + * + * [DPCM] + * FE BE + * **** + * cpu3 <-@--* *--@-> codec3 + * cpu4 <-@--* * + * **** + * + * [DPCM-Multi] + * + * --NOTE-- + * Multi-FE is not supported by ASoC. + * + * FE BE + * **** +-+ + * cpu5 <-@--* *--@-> | | -> codec4 + * cpu6 <-@--* * | | -> codec5 + * **** +-+ + * + * [Codec2Codec] + * +-@-> codec6 + * | + * +---> codec7 + * + * [Codec2Codec-Multi] + * + * --NOTE-- + * Multi connect N:M is not supported by ASoC. + * + * +-+ + * +-@->| |-> codec8 + * | | |-> codec9 + * | +-+ + * | +-+ + * +--->| |-> codec10 + * | |-> codec11 + * +-+ + */ + audio-graph-card2-custom-sample { + /* + * You can use audio-graph-card2 directly by using + * + * compatible = "audio-graph-card2"; + */ + compatible = "audio-graph-card2-custom-sample"; + + /* for [DPCM] */ + /* BE FE */ + routing = "TC DAI3 Playback", "DAI3 Playback", + "TC DAI3 Playback", "DAI4 Playback", + "DAI3 Capture", "TC DAI3 Capture", + "DAI4 Capture", "TC DAI3 Capture", + /* for [DPCM-Multi] */ + /* BE FE */ + "TC DAI4 Playback", "DAI5 Playback", + "TC DAI5 Playback", "DAI5 Playback", + "TC DAI4 Playback", "DAI6 Playback", + "TC DAI5 Playback", "DAI6 Playback", + "DAI5 Capture", "TC DAI4 Capture", + "DAI5 Capture", "TC DAI5 Capture", + "DAI6 Capture", "TC DAI4 Capture", + "DAI6 Capture", "TC DAI5 Capture", + /* for [Codec2Codec] */ + "TC OUT", "TC DAI7 Playback", + "TC DAI6 Capture", "TC IN", + /* for [Codec2Codec-Multi] */ + "TC OUT", "TC DAI10 Playback", + "TC DAI8 Capture", "TC IN", + "TC OUT", "TC DAI11 Playback", + "TC DAI9 Capture", "TC IN"; + + links = <&cpu0 /* normal: cpu side only */ + &mcpu0 /* multi: cpu side only */ + &fe00 &fe01 &be0 /* dpcm: both FE / BE */ + &fe10 &fe11 &be1 /* dpcm-m: both FE / BE */ + &c2c /* c2c: cpu side only */ + &c2c_m /* c2c: cpu side only */ + >; + + multi { + ports@0 { + mcpu0: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; + port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; + port@2 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; + }; + ports@1 { + port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; + port@1 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; + port@2 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; + }; + ports@2 { + port@0 { mbe_ep: endpoint { remote-endpoint = <&be10_ep>; }; }; + port@1 { mbe1_ep: endpoint { remote-endpoint = <&codec4_ep>; }; }; + port@2 { mbe2_ep: endpoint { remote-endpoint = <&codec5_ep>; }; }; + }; + ports@3 { + port@0 { mc2c0_ep: endpoint { remote-endpoint = <&c2cmf_ep>; }; }; + port@1 { mc2c00_ep: endpoint { remote-endpoint = <&codec8_ep>; }; }; + port@2 { mc2c01_ep: endpoint { remote-endpoint = <&codec9_ep>; }; }; + }; + ports@4 { + port@0 { mc2c1_ep: endpoint { remote-endpoint = <&c2cmb_ep>; }; }; + port@1 { mc2c10_ep: endpoint { remote-endpoint = <&codec10_ep>; }; }; + port@2 { mc2c11_ep: endpoint { remote-endpoint = <&codec11_ep>; }; }; + }; + }; + + dpcm { + /* FE */ + ports@0 { + fe00: port@0 { fe00_ep: endpoint { remote-endpoint = <&cpu3_ep>; }; }; + fe01: port@1 { fe01_ep: endpoint { remote-endpoint = <&cpu4_ep>; }; }; + fe10: port@2 { fe10_ep: endpoint { remote-endpoint = <&cpu5_ep>; }; }; + fe11: port@3 { fe11_ep: endpoint { remote-endpoint = <&cpu6_ep>; }; }; + }; + /* BE */ + ports@1 { + be0: port@0 { be00_ep: endpoint { remote-endpoint = <&codec3_ep>; }; }; + be1: port@1 { be10_ep: endpoint { remote-endpoint = <&mbe_ep>; }; }; + }; + }; + + codec2codec { + ports@0 { + rate = <48000>; + c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec6_ep>; }; }; + port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec7_ep>; }; }; + }; + ports@1 { + rate = <48000>; + c2c_m: port@0 { c2cmf_ep: endpoint { remote-endpoint = <&mc2c0_ep>; }; }; + port@1 { c2cmb_ep: endpoint { remote-endpoint = <&mc2c1_ep>; }; }; + }; + }; + }; + + test_cpu { + /* + * update compatible to indicate more detail behaviour + * if you want. see test-compatible for more detail. + * + * ex) + * - compatible = "test-cpu"; + * + compatible = "test-cpu-verbose"; + */ + compatible = "test-cpu"; + ports { + bitclock-master; + frame-master; + cpu0: port@0 { cpu0_ep: endpoint { remote-endpoint = <&codec0_ep>; }; }; + port@1 { cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; }; + port@2 { cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; }; + port@3 { cpu3_ep: endpoint { remote-endpoint = <&fe00_ep>; }; }; + port@4 { cpu4_ep: endpoint { remote-endpoint = <&fe01_ep>; }; }; + port@5 { cpu5_ep: endpoint { remote-endpoint = <&fe10_ep>; }; }; + port@6 { cpu6_ep: endpoint { remote-endpoint = <&fe11_ep>; }; }; + }; + }; + + test_codec { + /* + * update compatible to indicate more detail behaviour + * if you want. see test-compatible for more detail. + * + * ex) + * - compatible = "test-codec"; + * + compatible = "test-codec-verbose"; + */ + compatible = "test-codec"; + ports { + /* + * prefix can be added to *component*, + * see audio-graph-card2::routing + */ + prefix = "TC"; + + port@0 { codec0_ep: endpoint { remote-endpoint = <&cpu0_ep>; }; }; + port@1 { codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; }; + port@2 { codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; }; + port@3 { codec3_ep: endpoint { remote-endpoint = <&be00_ep>; }; }; + port@4 { codec4_ep: endpoint { remote-endpoint = <&mbe1_ep>; }; }; + port@5 { codec5_ep: endpoint { remote-endpoint = <&mbe2_ep>; }; }; + port@6 { bitclock-master; + frame-master; + codec6_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; }; + port@7 { codec7_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; }; + port@8 { bitclock-master; + frame-master; + codec8_ep: endpoint { remote-endpoint = <&mc2c00_ep>; }; }; + port@9 { codec9_ep: endpoint { remote-endpoint = <&mc2c01_ep>; }; }; + port@10 { codec10_ep: endpoint { remote-endpoint = <&mc2c10_ep>; }; }; + port@11 { codec11_ep: endpoint { remote-endpoint = <&mc2c11_ep>; }; }; + }; + }; +}; diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c new file mode 100644 index 000000000000..b6049bcfb771 --- /dev/null +++ b/sound/soc/generic/audio-graph-card2.c @@ -0,0 +1,1281 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ASoC Audio Graph Card2 support +// +// Copyright (C) 2020 Renesas Electronics Corp. +// Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> +// +// based on ${LINUX}/sound/soc/generic/audio-graph-card.c +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <sound/graph_card.h> + +/************************************ + daifmt + ************************************ + ports { + format = "left_j"; + port@0 { + bitclock-master; + sample0: endpoint@0 { + frame-master; + }; + sample1: endpoint@1 { + format = "i2s"; + }; + }; + ... + }; + + You can set daifmt at ports/port/endpoint. + It uses *latest* format, and *share* master settings. + In above case, + sample0: left_j, bitclock-master, frame-master + sample1: i2s, bitclock-master + + If there was no settings, *Codec* will be + bitclock/frame provider as default. + see + graph_parse_daifmt(). + + ************************************ + Normal Audio-Graph + ************************************ + + CPU <---> Codec + + sound { + compatible = "audio-graph-card2"; + links = <&cpu>; + }; + + CPU { + cpu: port { + bitclock-master; + frame-master; + cpu_ep: endpoint { remote-endpoint = <&codec_ep>; }; }; + }; + + Codec { + port { codec_ep: endpoint { remote-endpoint = <&cpu_ep>; }; }; + }; + + ************************************ + Multi-CPU/Codec + ************************************ + +It has connection part (= X) and list part (= y). +links indicates connection part of CPU side (= A). + + +-+ (A) +-+ + CPU1 --(y) | | <-(X)--(X)-> | | (y)-- Codec1 + CPU2 --(y) | | | | (y)-- Codec2 + +-+ +-+ + + sound { + compatible = "audio-graph-card2"; + +(A) links = <&mcpu>; + + multi { + ports@0 { +(X) (A) mcpu: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; +(y) port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; +(y) port@1 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; + }; + ports@1 { +(X) port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; +(y) port@0 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; +(y) port@1 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; + }; + }; + }; + + CPU { + ports { + bitclock-master; + frame-master; + port@0 { cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; }; + port@1 { cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; }; + }; + }; + + Codec { + ports { + port@0 { codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; }; + port@1 { codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; }; + }; + }; + + ************************************ + DPCM + ************************************ + + DSP + ************ + PCM0 <--> * fe0 be0 * <--> DAI0: Codec Headset + PCM1 <--> * fe1 be1 * <--> DAI1: Codec Speakers + PCM2 <--> * fe2 be2 * <--> DAI2: MODEM + PCM3 <--> * fe3 be3 * <--> DAI3: BT + * be4 * <--> DAI4: DMIC + * be5 * <--> DAI5: FM + ************ + + sound { + compatible = "audio-graph-card2"; + + // indicate routing + routing = "xxx Playback", "xxx Playback", + "xxx Playback", "xxx Playback", + "xxx Playback", "xxx Playback"; + + // indicate all Front-End, Back-End + links = <&fe0, &fe1, ..., + &be0, &be1, ...>; + + dpcm { + // Front-End + ports@0 { + fe0: port@0 { fe0_ep: endpoint { remote-endpoint = <&pcm0_ep>; }; }; + fe1: port@1 { fe1_ep: endpoint { remote-endpoint = <&pcm1_ep>; }; }; + ... + }; + // Back-End + ports@1 { + be0: port@0 { be0_ep: endpoint { remote-endpoint = <&dai0_ep>; }; }; + be1: port@1 { be1_ep: endpoint { remote-endpoint = <&dai1_ep>; }; }; + ... + }; + }; + }; + + CPU { + ports { + bitclock-master; + frame-master; + port@0 { pcm0_ep: endpoint { remote-endpoint = <&fe0_ep>; }; }; + port@1 { pcm1_ep: endpoint { remote-endpoint = <&fe1_ep>; }; }; + ... + }; + }; + + Codec { + ports { + port@0 { dai0_ep: endpoint { remote-endpoint = <&be0_ep>; }; }; + port@1 { dai1_ep: endpoint { remote-endpoint = <&be1_ep>; }; }; + ... + }; + }; + + ************************************ + Codec to Codec + ************************************ + + +--+ + | |<-- Codec0 <- IN + | |--> Codec1 -> OUT + +--+ + + sound { + compatible = "audio-graph-card2"; + + routing = "OUT" ,"DAI1 Playback", + "DAI0 Capture", "IN"; + + links = <&c2c>; + + codec2codec { + ports { + rate = <48000>; + c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec0_ep>; }; }; + port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; + }; + }; + + Codec { + ports { + port@0 { + bitclock-master; + frame-master; + codec0_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; }; + port@1 { codec1_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; }; + }; + }; + +*/ + +enum graph_type { + GRAPH_NORMAL, + GRAPH_DPCM, + GRAPH_C2C, + + GRAPH_MULTI, /* don't use ! Use this only in __graph_get_type() */ +}; + +#define GRAPH_NODENAME_MULTI "multi" +#define GRAPH_NODENAME_DPCM "dpcm" +#define GRAPH_NODENAME_C2C "codec2codec" + +#define port_to_endpoint(port) of_get_child_by_name(port, "endpoint") + +static enum graph_type __graph_get_type(struct device_node *lnk) +{ + struct device_node *np; + + /* + * target { + * ports { + * => lnk: port@0 { ... }; + * port@1 { ... }; + * }; + * }; + */ + np = of_get_parent(lnk); + if (of_node_name_eq(np, "ports")) + np = of_get_parent(np); + + if (of_node_name_eq(np, GRAPH_NODENAME_MULTI)) + return GRAPH_MULTI; + + if (of_node_name_eq(np, GRAPH_NODENAME_DPCM)) + return GRAPH_DPCM; + + if (of_node_name_eq(np, GRAPH_NODENAME_C2C)) + return GRAPH_C2C; + + return GRAPH_NORMAL; +} + +static enum graph_type graph_get_type(struct asoc_simple_priv *priv, + struct device_node *lnk) +{ + enum graph_type type = __graph_get_type(lnk); + + /* GRAPH_MULTI here means GRAPH_NORMAL */ + if (type == GRAPH_MULTI) + type = GRAPH_NORMAL; + +#ifdef DEBUG + { + struct device *dev = simple_priv_to_dev(priv); + const char *str = "Normal"; + + switch (type) { + case GRAPH_DPCM: + if (asoc_graph_is_ports0(lnk)) + str = "DPCM Front-End"; + else + str = "DPCM Back-End"; + break; + case GRAPH_C2C: + str = "Codec2Codec"; + break; + default: + break; + } + + dev_dbg(dev, "%pOF (%s)", lnk, str); + } +#endif + return type; +} + +static int graph_lnk_is_multi(struct device_node *lnk) +{ + return __graph_get_type(lnk) == GRAPH_MULTI; +} + +static struct device_node *graph_get_next_multi_ep(struct device_node **port) +{ + struct device_node *ports = of_get_parent(*port); + struct device_node *ep = NULL; + struct device_node *rep = NULL; + + /* + * multi { + * ports { + * => lnk: port@0 { ... }; + * port@1 { ep { ... = rep0 } }; + * port@2 { ep { ... = rep1 } }; + * ... + * }; + * }; + * + * xxx { + * port@0 { rep0 }; + * port@1 { rep1 }; + * }; + */ + do { + *port = of_get_next_child(ports, *port); + if (!*port) + break; + } while (!of_node_name_eq(*port, "port")); + + if (*port) { + ep = port_to_endpoint(*port); + rep = of_graph_get_remote_endpoint(ep); + } + + of_node_put(ep); + of_node_put(ports); + + return rep; +} + +static const struct snd_soc_ops graph_ops = { + .startup = asoc_simple_startup, + .shutdown = asoc_simple_shutdown, + .hw_params = asoc_simple_hw_params, +}; + +static int graph_get_dai_id(struct device_node *ep) +{ + struct device_node *node; + struct device_node *endpoint; + struct of_endpoint info; + int i, id; + const u32 *reg; + int ret; + + /* use driver specified DAI ID if exist */ + ret = snd_soc_get_dai_id(ep); + if (ret != -ENOTSUPP) + return ret; + + /* use endpoint/port reg if exist */ + ret = of_graph_parse_endpoint(ep, &info); + if (ret == 0) { + /* + * Because it will count port/endpoint if it doesn't have "reg". + * But, we can't judge whether it has "no reg", or "reg = <0>" + * only of_graph_parse_endpoint(). + * We need to check "reg" property + */ + if (of_get_property(ep, "reg", NULL)) + return info.id; + + node = of_get_parent(ep); + reg = of_get_property(node, "reg", NULL); + of_node_put(node); + if (reg) + return info.port; + } + node = of_graph_get_port_parent(ep); + + /* + * Non HDMI sound case, counting port/endpoint on its DT + * is enough. Let's count it. + */ + i = 0; + id = -1; + for_each_endpoint_of_node(node, endpoint) { + if (endpoint == ep) + id = i; + i++; + } + + of_node_put(node); + + if (id < 0) + return -ENODEV; + + return id; +} + +static int asoc_simple_parse_dai(struct device_node *ep, + struct snd_soc_dai_link_component *dlc, + int *is_single_link) +{ + struct device_node *node; + struct of_phandle_args args; + int ret; + + if (!ep) + return 0; + + node = of_graph_get_port_parent(ep); + + /* Get dai->name */ + args.np = node; + args.args[0] = graph_get_dai_id(ep); + args.args_count = (of_graph_get_endpoint_count(node) > 1); + + /* + * FIXME + * + * Here, dlc->dai_name is pointer to CPU/Codec DAI name. + * If user unbinded CPU or Codec driver, but not for Sound Card, + * dlc->dai_name is keeping unbinded CPU or Codec + * driver's pointer. + * + * If user re-bind CPU or Codec driver again, ALSA SoC will try + * to rebind Card via snd_soc_try_rebind_card(), but because of + * above reason, it might can't bind Sound Card. + * Because Sound Card is pointing to released dai_name pointer. + * + * To avoid this rebind Card issue, + * 1) It needs to alloc memory to keep dai_name eventhough + * CPU or Codec driver was unbinded, or + * 2) user need to rebind Sound Card everytime + * if he unbinded CPU or Codec. + */ + ret = snd_soc_get_dai_name(&args, &dlc->dai_name); + if (ret < 0) + return ret; + + dlc->of_node = node; + + if (is_single_link) + *is_single_link = of_graph_get_endpoint_count(node) == 1; + + return 0; +} + +static void graph_parse_convert(struct device_node *ep, + struct simple_dai_props *props) +{ + struct device_node *port = of_get_parent(ep); + struct device_node *ports = of_get_parent(port); + struct asoc_simple_data *adata = &props->adata; + + if (of_node_name_eq(ports, "ports")) + asoc_simple_parse_convert(ports, NULL, adata); + asoc_simple_parse_convert(port, NULL, adata); + asoc_simple_parse_convert(ep, NULL, adata); + + of_node_put(port); + of_node_put(ports); +} + +static void graph_parse_mclk_fs(struct device_node *ep, + struct simple_dai_props *props) +{ + struct device_node *port = of_get_parent(ep); + struct device_node *ports = of_get_parent(port); + + if (of_node_name_eq(ports, "ports")) + of_property_read_u32(ports, "mclk-fs", &props->mclk_fs); + of_property_read_u32(port, "mclk-fs", &props->mclk_fs); + of_property_read_u32(ep, "mclk-fs", &props->mclk_fs); + + of_node_put(port); + of_node_put(ports); +} + +static int __graph_parse_node(struct asoc_simple_priv *priv, + enum graph_type gtype, + struct device_node *ep, + struct link_info *li, + int is_cpu, int idx) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); + struct snd_soc_dai_link_component *dlc; + struct asoc_simple_dai *dai; + int ret, is_single_links = 0; + + if (is_cpu) { + dlc = asoc_link_to_cpu(dai_link, idx); + dai = simple_props_to_dai_cpu(dai_props, idx); + } else { + dlc = asoc_link_to_codec(dai_link, idx); + dai = simple_props_to_dai_codec(dai_props, idx); + } + + graph_parse_mclk_fs(ep, dai_props); + + ret = asoc_simple_parse_dai(ep, dlc, &is_single_links); + if (ret < 0) + return ret; + + ret = asoc_simple_parse_tdm(ep, dai); + if (ret < 0) + return ret; + + ret = asoc_simple_parse_clk(dev, ep, dai, dlc); + if (ret < 0) + return ret; + + /* + * set DAI Name + */ + if (!dai_link->name) { + struct snd_soc_dai_link_component *cpus = dlc; + struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, idx); + char *cpu_multi = ""; + char *codec_multi = ""; + + if (dai_link->num_cpus > 1) + cpu_multi = "_multi"; + if (dai_link->num_codecs > 1) + codec_multi = "_multi"; + + switch (gtype) { + case GRAPH_NORMAL: + /* run is_cpu only. see audio_graph2_link_normal() */ + if (is_cpu) + asoc_simple_set_dailink_name(dev, dai_link, "%s%s-%s%s", + cpus->dai_name, cpu_multi, + codecs->dai_name, codec_multi); + break; + case GRAPH_DPCM: + if (is_cpu) + asoc_simple_set_dailink_name(dev, dai_link, "fe.%pOFP.%s%s", + cpus->of_node, cpus->dai_name, cpu_multi); + else + asoc_simple_set_dailink_name(dev, dai_link, "be.%pOFP.%s%s", + codecs->of_node, codecs->dai_name, codec_multi); + break; + case GRAPH_C2C: + /* run is_cpu only. see audio_graph2_link_c2c() */ + if (is_cpu) + asoc_simple_set_dailink_name(dev, dai_link, "c2c.%s%s-%s%s", + cpus->dai_name, cpu_multi, + codecs->dai_name, codec_multi); + break; + default: + break; + } + } + + /* + * Check "prefix" from top node + * if DPCM-BE case + */ + if (!is_cpu && gtype == GRAPH_DPCM) { + struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, idx); + struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, idx); + struct device_node *rport = of_get_parent(ep); + struct device_node *rports = of_get_parent(rport); + + if (of_node_name_eq(rports, "ports")) + snd_soc_of_parse_node_prefix(rports, cconf, codecs->of_node, "prefix"); + snd_soc_of_parse_node_prefix(rport, cconf, codecs->of_node, "prefix"); + + of_node_put(rport); + of_node_put(rports); + } + + if (is_cpu) { + struct snd_soc_dai_link_component *cpus = dlc; + struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, idx); + + asoc_simple_canonicalize_cpu(cpus, is_single_links); + asoc_simple_canonicalize_platform(platforms, cpus); + } + + return 0; +} + +static int graph_parse_node(struct asoc_simple_priv *priv, + enum graph_type gtype, + struct device_node *port, + struct link_info *li, int is_cpu) +{ + struct device_node *ep; + int ret = 0; + + if (graph_lnk_is_multi(port)) { + int idx; + + of_node_get(port); + + for (idx = 0;; idx++) { + ep = graph_get_next_multi_ep(&port); + if (!ep) + break; + + ret = __graph_parse_node(priv, gtype, ep, + li, is_cpu, idx); + of_node_put(ep); + if (ret < 0) + break; + } + } else { + /* Single CPU / Codec */ + ep = port_to_endpoint(port); + ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, 0); + of_node_put(ep); + } + + return ret; +} + +static void graph_parse_daifmt(struct device_node *node, + unsigned int *daifmt, unsigned int *bit_frame) +{ + unsigned int fmt; + + /* + * see also above "daifmt" explanation + * and samples. + */ + + /* + * ports { + * (A) + * port { + * (B) + * endpoint { + * (C) + * }; + * }; + * }; + * }; + */ + + /* + * clock_provider: + * + * It can be judged it is provider + * if (A) or (B) or (C) has bitclock-master / frame-master flag. + * + * use "or" + */ + *bit_frame |= snd_soc_daifmt_parse_clock_provider_as_bitmap(node, NULL); + +#define update_daifmt(name) \ + if (!(*daifmt & SND_SOC_DAIFMT_##name##_MASK) && \ + (fmt & SND_SOC_DAIFMT_##name##_MASK)) \ + *daifmt |= fmt & SND_SOC_DAIFMT_##name##_MASK + + /* + * format + * + * This function is called by (C) -> (B) -> (A) order. + * Set if applicable part was not yet set. + */ + fmt = snd_soc_daifmt_parse_format(node, NULL); + update_daifmt(FORMAT); + update_daifmt(CLOCK); + update_daifmt(INV); +} + +static void graph_link_init(struct asoc_simple_priv *priv, + struct device_node *port, + struct link_info *li, + int is_cpu_node) +{ + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct device_node *ep; + struct device_node *ports; + unsigned int daifmt = 0, daiclk = 0; + unsigned int bit_frame = 0; + + if (graph_lnk_is_multi(port)) { + of_node_get(port); + ep = graph_get_next_multi_ep(&port); + port = of_get_parent(ep); + } else { + ep = port_to_endpoint(port); + } + + ports = of_get_parent(port); + + /* + * ports { + * (A) + * port { + * (B) + * endpoint { + * (C) + * }; + * }; + * }; + * }; + */ + graph_parse_daifmt(ep, &daifmt, &bit_frame); /* (C) */ + graph_parse_daifmt(port, &daifmt, &bit_frame); /* (B) */ + if (of_node_name_eq(ports, "ports")) + graph_parse_daifmt(ports, &daifmt, &bit_frame); /* (A) */ + + /* + * convert bit_frame + * We need to flip clock_provider if it was CPU node, + * because it is Codec base. + */ + daiclk = snd_soc_daifmt_clock_provider_from_bitmap(bit_frame); + if (is_cpu_node) + daiclk = snd_soc_daifmt_clock_provider_fliped(daiclk); + + dai_link->dai_fmt = daifmt | daiclk; + dai_link->init = asoc_simple_dai_init; + dai_link->ops = &graph_ops; + if (priv->ops) + dai_link->ops = priv->ops; +} + +int audio_graph2_link_normal(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device_node *cpu_port = lnk; + struct device_node *cpu_ep = port_to_endpoint(cpu_port); + struct device_node *codec_port = of_graph_get_remote_port(cpu_ep); + int ret; + + /* + * call Codec first. + * see + * __graph_parse_node() :: DAI Naming + */ + ret = graph_parse_node(priv, GRAPH_NORMAL, codec_port, li, 0); + if (ret < 0) + goto err; + + /* + * call CPU, and set DAI Name + */ + ret = graph_parse_node(priv, GRAPH_NORMAL, cpu_port, li, 1); + if (ret < 0) + goto err; + + graph_link_init(priv, cpu_port, li, 1); +err: + of_node_put(codec_port); + of_node_put(cpu_ep); + + return ret; +} +EXPORT_SYMBOL_GPL(audio_graph2_link_normal); + +int audio_graph2_link_dpcm(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device_node *ep = port_to_endpoint(lnk); + struct device_node *rep = of_graph_get_remote_endpoint(ep); + struct device_node *rport = of_graph_get_remote_port(ep); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); + int is_cpu = asoc_graph_is_ports0(lnk); + int ret; + + if (is_cpu) { + /* + * dpcm { + * // Front-End + * ports@0 { + * => lnk: port@0 { ep: { ... = rep }; }; + * ... + * }; + * // Back-End + * ports@0 { + * ... + * }; + * }; + * + * CPU { + * rports: ports { + * rport: port@0 { rep: { ... = ep } }; + * } + * } + */ + /* + * setup CPU here, Codec is already set as dummy. + * see + * asoc_simple_init_priv() + */ + dai_link->dynamic = 1; + dai_link->dpcm_merged_format = 1; + + ret = graph_parse_node(priv, GRAPH_DPCM, rport, li, 1); + if (ret) + goto err; + } else { + /* + * dpcm { + * // Front-End + * ports@0 { + * ... + * }; + * // Back-End + * ports@0 { + * => lnk: port@0 { ep: { ... = rep; }; }; + * ... + * }; + * }; + * + * Codec { + * rports: ports { + * rport: port@0 { rep: { ... = ep; }; }; + * } + * } + */ + /* + * setup Codec here, CPU is already set as dummy. + * see + * asoc_simple_init_priv() + */ + + /* BE settings */ + dai_link->no_pcm = 1; + dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup; + + ret = graph_parse_node(priv, GRAPH_DPCM, rport, li, 0); + if (ret < 0) + goto err; + } + + graph_parse_convert(rep, dai_props); + + snd_soc_dai_link_set_capabilities(dai_link); + + graph_link_init(priv, rport, li, is_cpu); +err: + of_node_put(ep); + of_node_put(rep); + of_node_put(rport); + + return ret; +} +EXPORT_SYMBOL_GPL(audio_graph2_link_dpcm); + +int audio_graph2_link_c2c(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); + struct snd_soc_pcm_stream *c2c_conf = dai_props->c2c_conf; + struct device_node *port0, *port1, *ports; + struct device_node *codec0_port, *codec1_port; + struct device_node *ep0, *ep1; + u32 val; + int ret = -EINVAL; + + /* + * codec2codec { + * ports { + * rate = <48000>; + * => lnk: port@0 { c2c0_ep: { ... = codec0_ep; }; }; + * port@1 { c2c1_ep: { ... = codec1_ep; }; }; + * }; + * }; + * + * Codec { + * ports { + * port@0 { codec0_ep: ... }; }; + * port@1 { codec1_ep: ... }; }; + * }; + * }; + */ + of_node_get(lnk); + port0 = lnk; + ports = of_get_parent(port0); + port1 = of_get_next_child(ports, lnk); + + if (!of_get_property(ports, "rate", &val)) { + struct device *dev = simple_priv_to_dev(priv); + + dev_err(dev, "Codec2Codec needs rate settings\n"); + goto err1; + } + + c2c_conf->formats = SNDRV_PCM_FMTBIT_S32_LE; /* update ME */ + c2c_conf->rate_min = + c2c_conf->rate_max = val; + c2c_conf->channels_min = + c2c_conf->channels_max = 2; /* update ME */ + dai_link->params = c2c_conf; + + ep0 = port_to_endpoint(port0); + ep1 = port_to_endpoint(port1); + + codec0_port = of_graph_get_remote_port(ep0); + codec1_port = of_graph_get_remote_port(ep1); + + /* + * call Codec first. + * see + * __graph_parse_node() :: DAI Naming + */ + ret = graph_parse_node(priv, GRAPH_C2C, codec1_port, li, 0); + if (ret < 0) + goto err2; + + /* + * call CPU, and set DAI Name + */ + ret = graph_parse_node(priv, GRAPH_C2C, codec0_port, li, 1); + if (ret < 0) + goto err2; + + graph_link_init(priv, codec0_port, li, 1); +err2: + of_node_put(ep0); + of_node_put(ep1); + of_node_put(codec0_port); + of_node_put(codec1_port); +err1: + of_node_put(ports); + of_node_put(port0); + of_node_put(port1); + + return ret; +} +EXPORT_SYMBOL_GPL(audio_graph2_link_c2c); + +static int graph_link(struct asoc_simple_priv *priv, + struct graph2_custom_hooks *hooks, + enum graph_type gtype, + struct device_node *lnk, + struct link_info *li) +{ + struct device *dev = simple_priv_to_dev(priv); + GRAPH2_CUSTOM func = NULL; + int ret = -EINVAL; + + switch (gtype) { + case GRAPH_NORMAL: + if (hooks && hooks->custom_normal) + func = hooks->custom_normal; + else + func = audio_graph2_link_normal; + break; + case GRAPH_DPCM: + if (hooks && hooks->custom_dpcm) + func = hooks->custom_dpcm; + else + func = audio_graph2_link_dpcm; + break; + case GRAPH_C2C: + if (hooks && hooks->custom_c2c) + func = hooks->custom_c2c; + else + func = audio_graph2_link_c2c; + break; + default: + break; + } + + if (!func) { + dev_err(dev, "non supported gtype (%d)\n", gtype); + goto err; + } + + ret = func(priv, lnk, li); + if (ret < 0) + goto err; + + li->link++; +err: + return ret; +} + +static int graph_counter(struct device_node *lnk) +{ + /* + * Multi CPU / Codec + * + * multi { + * ports { + * => lnk: port@0 { ... }; + * port@1 { ... }; + * port@2 { ... }; + * ... + * }; + * }; + * + * ignore first lnk part + */ + if (graph_lnk_is_multi(lnk)) + return of_graph_get_endpoint_count(of_get_parent(lnk)) - 1; + /* + * Single CPU / Codec + */ + else + return 1; +} + +static int graph_count_normal(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device_node *cpu_port = lnk; + struct device_node *cpu_ep = port_to_endpoint(cpu_port); + struct device_node *codec_port = of_graph_get_remote_port(cpu_ep); + + /* + * CPU { + * => lnk: port { endpoint { .. }; }; + * }; + */ + li->num[li->link].cpus = + li->num[li->link].platforms = graph_counter(cpu_port); + li->num[li->link].codecs = graph_counter(codec_port); + + of_node_put(cpu_ep); + of_node_put(codec_port); + + return 0; +} + +static int graph_count_dpcm(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device_node *ep = port_to_endpoint(lnk); + struct device_node *rport = of_graph_get_remote_port(ep); + + /* + * dpcm { + * // Front-End + * ports@0 { + * => lnk: port@0 { endpoint { ... }; }; + * ... + * }; + * // Back-End + * ports@1 { + * => lnk: port@0 { endpoint { ... }; }; + * ... + * }; + * }; + */ + + if (asoc_graph_is_ports0(lnk)) { + li->num[li->link].cpus = graph_counter(rport); /* FE */ + li->num[li->link].platforms = graph_counter(rport); + } else { + li->num[li->link].codecs = graph_counter(rport); /* BE */ + } + + of_node_put(ep); + of_node_put(rport); + + return 0; +} + +static int graph_count_c2c(struct asoc_simple_priv *priv, + struct device_node *lnk, + struct link_info *li) +{ + struct device_node *ports = of_get_parent(lnk); + struct device_node *port0 = lnk; + struct device_node *port1 = of_get_next_child(ports, lnk); + struct device_node *ep0 = port_to_endpoint(port0); + struct device_node *ep1 = port_to_endpoint(port1); + struct device_node *codec0 = of_graph_get_remote_port(ep0); + struct device_node *codec1 = of_graph_get_remote_port(ep1); + + of_node_get(lnk); + + /* + * codec2codec { + * ports { + * => lnk: port@0 { endpoint { ... }; }; + * port@1 { endpoint { ... }; }; + * }; + * }; + */ + li->num[li->link].cpus = + li->num[li->link].platforms = graph_counter(codec0); + li->num[li->link].codecs = graph_counter(codec1); + li->num[li->link].c2c = 1; + + of_node_put(ports); + of_node_put(port1); + of_node_put(ep0); + of_node_put(ep1); + of_node_put(codec0); + of_node_put(codec1); + + return 0; +} + +static int graph_count(struct asoc_simple_priv *priv, + struct graph2_custom_hooks *hooks, + enum graph_type gtype, + struct device_node *lnk, + struct link_info *li) +{ + struct device *dev = simple_priv_to_dev(priv); + GRAPH2_CUSTOM func = NULL; + int ret = -EINVAL; + + if (li->link >= SNDRV_MAX_LINKS) { + dev_err(dev, "too many links\n"); + return ret; + } + + switch (gtype) { + case GRAPH_NORMAL: + func = graph_count_normal; + break; + case GRAPH_DPCM: + func = graph_count_dpcm; + break; + case GRAPH_C2C: + func = graph_count_c2c; + break; + default: + break; + } + + if (!func) { + dev_err(dev, "non supported gtype (%d)\n", gtype); + goto err; + } + + ret = func(priv, lnk, li); + if (ret < 0) + goto err; + + li->link++; +err: + return ret; +} + +static int graph_for_each_link(struct asoc_simple_priv *priv, + struct graph2_custom_hooks *hooks, + struct link_info *li, + int (*func)(struct asoc_simple_priv *priv, + struct graph2_custom_hooks *hooks, + enum graph_type gtype, + struct device_node *lnk, + struct link_info *li)) +{ + struct of_phandle_iterator it; + struct device *dev = simple_priv_to_dev(priv); + struct device_node *node = dev->of_node; + struct device_node *lnk; + enum graph_type gtype; + int rc, ret; + + /* loop for all listed CPU port */ + of_for_each_phandle(&it, rc, node, "links", NULL, 0) { + lnk = it.node; + + gtype = graph_get_type(priv, lnk); + + ret = func(priv, hooks, gtype, lnk, li); + if (ret < 0) + return ret; + } + + return 0; +} + +int audio_graph2_parse_of(struct asoc_simple_priv *priv, struct device *dev, + struct graph2_custom_hooks *hooks) +{ + struct snd_soc_card *card = simple_priv_to_card(priv); + struct link_info *li; + int ret; + + dev_warn(dev, "Audio Graph Card2 is still under Experimental stage\n"); + + li = devm_kzalloc(dev, sizeof(*li), GFP_KERNEL); + if (!li) + return -ENOMEM; + + card->probe = asoc_graph_card_probe; + card->owner = THIS_MODULE; + card->dev = dev; + + if ((hooks) && (hooks)->hook_pre) { + ret = (hooks)->hook_pre(priv); + if (ret < 0) + goto err; + } + + ret = graph_for_each_link(priv, hooks, li, graph_count); + if (!li->link) + ret = -EINVAL; + if (ret < 0) + goto err; + + ret = asoc_simple_init_priv(priv, li); + if (ret < 0) + goto err; + + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); + if (IS_ERR(priv->pa_gpio)) { + ret = PTR_ERR(priv->pa_gpio); + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); + goto err; + } + + ret = asoc_simple_parse_widgets(card, NULL); + if (ret < 0) + goto err; + + ret = asoc_simple_parse_routing(card, NULL); + if (ret < 0) + goto err; + + memset(li, 0, sizeof(*li)); + ret = graph_for_each_link(priv, hooks, li, graph_link); + if (ret < 0) + goto err; + + ret = asoc_simple_parse_card_name(card, NULL); + if (ret < 0) + goto err; + + snd_soc_card_set_drvdata(card, priv); + + if ((hooks) && (hooks)->hook_post) { + ret = (hooks)->hook_post(priv); + if (ret < 0) + goto err; + } + + asoc_simple_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +err: + devm_kfree(dev, li); + + if ((ret < 0) && (ret != -EPROBE_DEFER)) + dev_err(dev, "parse error %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(audio_graph2_parse_of); + +static int graph_probe(struct platform_device *pdev) +{ + struct asoc_simple_priv *priv; + struct device *dev = &pdev->dev; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + return audio_graph2_parse_of(priv, dev, NULL); +} + +static const struct of_device_id graph_of_match[] = { + { .compatible = "audio-graph-card2", }, + {}, +}; +MODULE_DEVICE_TABLE(of, graph_of_match); + +static struct platform_driver graph_card = { + .driver = { + .name = "asoc-audio-graph-card2", + .pm = &snd_soc_pm_ops, + .of_match_table = graph_of_match, + }, + .probe = graph_probe, + .remove = asoc_simple_remove, +}; +module_platform_driver(graph_card); + +MODULE_ALIAS("platform:asoc-audio-graph-card2"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Audio Graph Card2"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 10c63b73900c..850e968677f1 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -355,9 +355,9 @@ static int asoc_simple_init_dai_link_params(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hardware hw; int i, ret, stream; - /* Only codecs should have non_legacy_dai_naming set. */ + /* Only Codecs */ for_each_rtd_components(rtd, i, component) { - if (!component->driver->non_legacy_dai_naming) + if (!snd_soc_component_is_codec(component)) return 0; } @@ -619,7 +619,8 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, struct asoc_simple_dai *dais; struct snd_soc_dai_link_component *dlcs; struct snd_soc_codec_conf *cconf = NULL; - int i, dai_num = 0, dlc_num = 0, cnf_num = 0; + struct snd_soc_pcm_stream *c2c_conf = NULL; + int i, dai_num = 0, dlc_num = 0, cnf_num = 0, c2c_num = 0; dai_props = devm_kcalloc(dev, li->link, sizeof(*dai_props), GFP_KERNEL); dai_link = devm_kcalloc(dev, li->link, sizeof(*dai_link), GFP_KERNEL); @@ -638,6 +639,8 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, if (!li->num[i].cpus) cnf_num += li->num[i].codecs; + + c2c_num += li->num[i].c2c; } dais = devm_kcalloc(dev, dai_num, sizeof(*dais), GFP_KERNEL); @@ -651,6 +654,12 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, return -ENOMEM; } + if (c2c_num) { + c2c_conf = devm_kcalloc(dev, c2c_num, sizeof(*c2c_conf), GFP_KERNEL); + if (!c2c_conf) + return -ENOMEM; + } + dev_dbg(dev, "link %d, dais %d, ccnf %d\n", li->link, dai_num, cnf_num); @@ -664,6 +673,7 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, priv->dais = dais; priv->dlcs = dlcs; priv->codec_conf = cconf; + priv->c2c_conf = c2c_conf; card->dai_link = priv->dai_link; card->num_links = li->link; @@ -681,6 +691,12 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, dlcs += li->num[i].cpus; dais += li->num[i].cpus; + + if (li->num[i].c2c) { + /* Codec2Codec */ + dai_props[i].c2c_conf = c2c_conf; + c2c_conf += li->num[i].c2c; + } } else { /* DPCM Be's CPU = dummy */ dai_props[i].cpus = @@ -759,6 +775,34 @@ int asoc_graph_card_probe(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(asoc_graph_card_probe); +int asoc_graph_is_ports0(struct device_node *np) +{ + struct device_node *port, *ports, *ports0, *top; + int ret; + + /* np is "endpoint" or "port" */ + if (of_node_name_eq(np, "endpoint")) { + port = of_get_parent(np); + } else { + port = np; + of_node_get(port); + } + + ports = of_get_parent(port); + top = of_get_parent(ports); + ports0 = of_get_child_by_name(top, "ports"); + + ret = ports0 == ports; + + of_node_put(port); + of_node_put(ports); + of_node_put(ports0); + of_node_put(top); + + return ret; +} +EXPORT_SYMBOL_GPL(asoc_graph_is_ports0); + /* Module information */ MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); MODULE_DESCRIPTION("ALSA SoC Simple Card Utils"); diff --git a/sound/soc/generic/test-component.c b/sound/soc/generic/test-component.c new file mode 100644 index 000000000000..85385a771d80 --- /dev/null +++ b/sound/soc/generic/test-component.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// test-component.c -- Test Audio Component driver +// +// Copyright (C) 2020 Renesas Electronics Corporation +// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + +#include <linux/slab.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/module.h> +#include <linux/workqueue.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#define TEST_NAME_LEN 32 +struct test_dai_name { + char name[TEST_NAME_LEN]; + char name_playback[TEST_NAME_LEN]; + char name_capture[TEST_NAME_LEN]; +}; + +struct test_priv { + struct device *dev; + struct snd_pcm_substream *substream; + struct delayed_work dwork; + struct snd_soc_component_driver *component_driver; + struct snd_soc_dai_driver *dai_driver; + struct test_dai_name *name; +}; + +struct test_adata { + u32 is_cpu:1; + u32 cmp_v:1; + u32 dai_v:1; +}; + +#define mile_stone(d) dev_info((d)->dev, "%s() : %s", __func__, (d)->driver->name) +#define mile_stone_x(dev) dev_info(dev, "%s()", __func__) + +static int test_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + mile_stone(dai); + + return 0; +} + +static int test_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + mile_stone(dai); + + return 0; +} + +static int test_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) +{ + mile_stone(dai); + + return 0; +} + +static int test_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + unsigned int format = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + unsigned int clock = fmt & SND_SOC_DAIFMT_CLOCK_MASK; + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int master = fmt & SND_SOC_DAIFMT_MASTER_MASK; + char *str; + + dev_info(dai->dev, "name : %s", dai->name); + + str = "unknown"; + switch (format) { + case SND_SOC_DAIFMT_I2S: + str = "i2s"; + break; + case SND_SOC_DAIFMT_RIGHT_J: + str = "right_j"; + break; + case SND_SOC_DAIFMT_LEFT_J: + str = "left_j"; + break; + case SND_SOC_DAIFMT_DSP_A: + str = "dsp_a"; + break; + case SND_SOC_DAIFMT_DSP_B: + str = "dsp_b"; + break; + case SND_SOC_DAIFMT_AC97: + str = "ac97"; + break; + case SND_SOC_DAIFMT_PDM: + str = "pdm"; + break; + } + dev_info(dai->dev, "format : %s", str); + + if (clock == SND_SOC_DAIFMT_CONT) + str = "continuous"; + else + str = "gated"; + dev_info(dai->dev, "clock : %s", str); + + str = "unknown"; + switch (master) { + case SND_SOC_DAIFMT_CBP_CFP: + str = "clk provider, frame provider"; + break; + case SND_SOC_DAIFMT_CBC_CFP: + str = "clk consumer, frame provider"; + break; + case SND_SOC_DAIFMT_CBP_CFC: + str = "clk provider, frame consumer"; + break; + case SND_SOC_DAIFMT_CBC_CFC: + str = "clk consumer, frame consumer"; + break; + } + dev_info(dai->dev, "clock : codec is %s", str); + + str = "unknown"; + switch (inv) { + case SND_SOC_DAIFMT_NB_NF: + str = "normal bit, normal frame"; + break; + case SND_SOC_DAIFMT_NB_IF: + str = "normal bit, invert frame"; + break; + case SND_SOC_DAIFMT_IB_NF: + str = "invert bit, normal frame"; + break; + case SND_SOC_DAIFMT_IB_IF: + str = "invert bit, invert frame"; + break; + } + dev_info(dai->dev, "signal : %s", str); + + return 0; +} + +static int test_dai_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + mile_stone(dai); + + return 0; +} + +static int test_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + mile_stone(dai); + + return 0; +} + +static void test_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + mile_stone(dai); +} + +static int test_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + mile_stone(dai); + + return 0; +} + +static int test_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + mile_stone(dai); + + return 0; +} + +static int test_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +{ + mile_stone(dai); + + return 0; +} + +static int test_dai_bespoke_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + mile_stone(dai); + + return 0; +} + +static u64 test_dai_formats = + /* + * Select below from Sound Card, not auto + * SND_SOC_POSSIBLE_DAIFMT_CBP_CFP + * SND_SOC_POSSIBLE_DAIFMT_CBC_CFP + * SND_SOC_POSSIBLE_DAIFMT_CBP_CFC + * SND_SOC_POSSIBLE_DAIFMT_CBC_CFC + */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_AC97 | + SND_SOC_POSSIBLE_DAIFMT_PDM | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + +static const struct snd_soc_dai_ops test_ops = { + .set_fmt = test_dai_set_fmt, + .startup = test_dai_startup, + .shutdown = test_dai_shutdown, + .auto_selectable_formats = &test_dai_formats, + .num_auto_selectable_formats = 1, +}; + +static const struct snd_soc_dai_ops test_verbose_ops = { + .set_sysclk = test_dai_set_sysclk, + .set_pll = test_dai_set_pll, + .set_clkdiv = test_dai_set_clkdiv, + .set_fmt = test_dai_set_fmt, + .mute_stream = test_dai_mute_stream, + .startup = test_dai_startup, + .shutdown = test_dai_shutdown, + .hw_params = test_dai_hw_params, + .hw_free = test_dai_hw_free, + .trigger = test_dai_trigger, + .bespoke_trigger = test_dai_bespoke_trigger, + .auto_selectable_formats = &test_dai_formats, + .num_auto_selectable_formats = 1, +}; + +#define STUB_RATES SNDRV_PCM_RATE_8000_384000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_U32_LE) + +static int test_component_probe(struct snd_soc_component *component) +{ + mile_stone(component); + + return 0; +} + +static void test_component_remove(struct snd_soc_component *component) +{ + mile_stone(component); +} + +static int test_component_suspend(struct snd_soc_component *component) +{ + mile_stone(component); + + return 0; +} + +static int test_component_resume(struct snd_soc_component *component) +{ + mile_stone(component); + + return 0; +} + +#define PREALLOC_BUFFER (32 * 1024) +static int test_component_pcm_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + mile_stone(component); + + snd_pcm_set_managed_buffer_all( + rtd->pcm, + SNDRV_DMA_TYPE_DEV, + rtd->card->snd_card->dev, + PREALLOC_BUFFER, PREALLOC_BUFFER); + + return 0; +} + +static void test_component_pcm_destruct(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + mile_stone(component); +} + +static int test_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + mile_stone(component); + + return 0; +} + +static int test_component_set_pll(struct snd_soc_component *component, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + mile_stone(component); + + return 0; +} + +static int test_component_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + mile_stone(component); + + return 0; +} + +static void test_component_seq_notifier(struct snd_soc_component *component, + enum snd_soc_dapm_type type, int subseq) +{ + mile_stone(component); +} + +static int test_component_stream_event(struct snd_soc_component *component, int event) +{ + mile_stone(component); + + return 0; +} + +static int test_component_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + mile_stone(component); + + return 0; +} + +static const struct snd_pcm_hardware test_component_hardware = { + /* Random values to keep userspace happy when checking constraints */ + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = 32 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 128, + .fifo_size = 256, +}; + +static int test_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + + mile_stone(component); + + /* BE's dont need dummy params */ + if (!rtd->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &test_component_hardware); + + return 0; +} + +static int test_component_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + mile_stone(component); + + return 0; +} + +static int test_component_ioctl(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + mile_stone(component); + + return 0; +} + +static int test_component_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + mile_stone(component); + + return 0; +} + +static int test_component_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + mile_stone(component); + + return 0; +} + +static int test_component_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + mile_stone(component); + + return 0; +} + +static void test_component_timer_stop(struct test_priv *priv) +{ + cancel_delayed_work(&priv->dwork); +} + +static void test_component_timer_start(struct test_priv *priv) +{ + schedule_delayed_work(&priv->dwork, msecs_to_jiffies(10)); +} + +static void test_component_dwork(struct work_struct *work) +{ + struct test_priv *priv = container_of(work, struct test_priv, dwork.work); + + if (priv->substream) + snd_pcm_period_elapsed(priv->substream); + + test_component_timer_start(priv); +} + +static int test_component_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct test_priv *priv = dev_get_drvdata(component->dev); + + mile_stone(component); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + test_component_timer_start(priv); + priv->substream = substream; /* set substream later */ + break; + case SNDRV_PCM_TRIGGER_STOP: + priv->substream = NULL; + test_component_timer_stop(priv); + } + + return 0; +} + +static int test_component_sync_stop(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + mile_stone(component); + + return 0; +} + +static snd_pcm_uframes_t test_component_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static int pointer; + + if (!runtime) + return 0; + + pointer += 10; + if (pointer > PREALLOC_BUFFER) + pointer = 0; + + /* mile_stone(component); */ + + return bytes_to_frames(runtime, pointer); +} + +static int test_component_get_time_info(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct timespec64 *system_ts, + struct timespec64 *audio_ts, + struct snd_pcm_audio_tstamp_config *audio_tstamp_config, + struct snd_pcm_audio_tstamp_report *audio_tstamp_report) +{ + mile_stone(component); + + return 0; +} + +static int test_component_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + mile_stone_x(rtd->dev); + + return 0; +} + +/* CPU */ +static const struct test_adata test_cpu = { .is_cpu = 1, .cmp_v = 0, .dai_v = 0, }; +static const struct test_adata test_cpu_vv = { .is_cpu = 1, .cmp_v = 1, .dai_v = 1, }; +static const struct test_adata test_cpu_nv = { .is_cpu = 1, .cmp_v = 0, .dai_v = 1, }; +static const struct test_adata test_cpu_vn = { .is_cpu = 1, .cmp_v = 1, .dai_v = 0, }; +/* Codec */ +static const struct test_adata test_codec = { .is_cpu = 0, .cmp_v = 0, .dai_v = 0, }; +static const struct test_adata test_codec_vv = { .is_cpu = 0, .cmp_v = 1, .dai_v = 1, }; +static const struct test_adata test_codec_nv = { .is_cpu = 0, .cmp_v = 0, .dai_v = 1, }; +static const struct test_adata test_codec_vn = { .is_cpu = 0, .cmp_v = 1, .dai_v = 0, }; + +static const struct of_device_id test_of_match[] = { + { .compatible = "test-cpu", .data = (void *)&test_cpu, }, + { .compatible = "test-cpu-verbose", .data = (void *)&test_cpu_vv, }, + { .compatible = "test-cpu-verbose-dai", .data = (void *)&test_cpu_nv, }, + { .compatible = "test-cpu-verbose-component", .data = (void *)&test_cpu_vn, }, + { .compatible = "test-codec", .data = (void *)&test_codec, }, + { .compatible = "test-codec-verbose", .data = (void *)&test_codec_vv, }, + { .compatible = "test-codec-verbose-dai", .data = (void *)&test_codec_nv, }, + { .compatible = "test-codec-verbose-component", .data = (void *)&test_codec_vn, }, + {}, +}; +MODULE_DEVICE_TABLE(of, test_of_match); + +static const struct snd_soc_dapm_widget widgets[] = { + /* + * FIXME + * + * Just IN/OUT is OK for now, + * but need to be updated ? + */ + SND_SOC_DAPM_INPUT("IN"), + SND_SOC_DAPM_OUTPUT("OUT"), +}; + +static int test_driver_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct device_node *ep; + const struct of_device_id *of_id = of_match_device(test_of_match, &pdev->dev); + const struct test_adata *adata = of_id->data; + struct snd_soc_component_driver *cdriv; + struct snd_soc_dai_driver *ddriv; + struct test_dai_name *dname; + struct test_priv *priv; + int num, ret, i; + + num = of_graph_get_endpoint_count(node); + if (!num) { + dev_err(dev, "no port exits\n"); + return -EINVAL; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + cdriv = devm_kzalloc(dev, sizeof(*cdriv), GFP_KERNEL); + ddriv = devm_kzalloc(dev, sizeof(*ddriv) * num, GFP_KERNEL); + dname = devm_kzalloc(dev, sizeof(*dname) * num, GFP_KERNEL); + if (!priv || !cdriv || !ddriv || !dname) + return -EINVAL; + + priv->dev = dev; + priv->component_driver = cdriv; + priv->dai_driver = ddriv; + priv->name = dname; + + INIT_DELAYED_WORK(&priv->dwork, test_component_dwork); + dev_set_drvdata(dev, priv); + + if (adata->is_cpu) { + cdriv->name = "test_cpu"; + cdriv->pcm_construct = test_component_pcm_construct; + cdriv->pointer = test_component_pointer; + cdriv->trigger = test_component_trigger; + } else { + cdriv->name = "test_codec"; + cdriv->idle_bias_on = 1; + cdriv->endianness = 1; + cdriv->non_legacy_dai_naming = 1; + } + + cdriv->open = test_component_open; + cdriv->dapm_widgets = widgets; + cdriv->num_dapm_widgets = ARRAY_SIZE(widgets); + + if (adata->cmp_v) { + cdriv->probe = test_component_probe; + cdriv->remove = test_component_remove; + cdriv->suspend = test_component_suspend; + cdriv->resume = test_component_resume; + cdriv->set_sysclk = test_component_set_sysclk; + cdriv->set_pll = test_component_set_pll; + cdriv->set_jack = test_component_set_jack; + cdriv->seq_notifier = test_component_seq_notifier; + cdriv->stream_event = test_component_stream_event; + cdriv->set_bias_level = test_component_set_bias_level; + cdriv->close = test_component_close; + cdriv->ioctl = test_component_ioctl; + cdriv->hw_params = test_component_hw_params; + cdriv->hw_free = test_component_hw_free; + cdriv->prepare = test_component_prepare; + cdriv->sync_stop = test_component_sync_stop; + cdriv->get_time_info = test_component_get_time_info; + cdriv->be_hw_params_fixup = test_component_be_hw_params_fixup; + + if (adata->is_cpu) + cdriv->pcm_destruct = test_component_pcm_destruct; + } + + i = 0; + for_each_endpoint_of_node(node, ep) { + snprintf(dname[i].name, TEST_NAME_LEN, "%s.%d", node->name, i); + ddriv[i].name = dname[i].name; + + snprintf(dname[i].name_playback, TEST_NAME_LEN, "DAI%d Playback", i); + ddriv[i].playback.stream_name = dname[i].name_playback; + ddriv[i].playback.channels_min = 1; + ddriv[i].playback.channels_max = 384; + ddriv[i].playback.rates = STUB_RATES; + ddriv[i].playback.formats = STUB_FORMATS; + + snprintf(dname[i].name_capture, TEST_NAME_LEN, "DAI%d Capture", i); + ddriv[i].capture.stream_name = dname[i].name_capture; + ddriv[i].capture.channels_min = 1; + ddriv[i].capture.channels_max = 384; + ddriv[i].capture.rates = STUB_RATES; + ddriv[i].capture.formats = STUB_FORMATS; + + if (adata->dai_v) + ddriv[i].ops = &test_verbose_ops; + else + ddriv[i].ops = &test_ops; + + i++; + } + + ret = devm_snd_soc_register_component(dev, cdriv, ddriv, num); + if (ret < 0) + return ret; + + mile_stone_x(dev); + + return 0; +} + +static int test_driver_remove(struct platform_device *pdev) +{ + mile_stone_x(&pdev->dev); + + return 0; +} + +static struct platform_driver test_driver = { + .driver = { + .name = "test-component", + .of_match_table = test_of_match, + }, + .probe = test_driver_probe, + .remove = test_driver_remove, +}; +module_platform_driver(test_driver); + +MODULE_ALIAS("platform:asoc-test-component"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); +MODULE_DESCRIPTION("ASoC Test Component"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 61b71d6c44cf..2dd5ff7e35ce 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -371,7 +371,7 @@ config SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH config SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH tristate "KBL with DA7219 and MAX98357A in I2S Mode" - depends on I2C && ACPI + depends on I2C && ACPI && GPIOLIB depends on MFD_INTEL_LPSS || COMPILE_TEST select SND_SOC_INTEL_DA7219_MAX98357A_GENERIC help @@ -427,6 +427,7 @@ config SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH depends on MFD_INTEL_LPSS || COMPILE_TEST depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC select SND_SOC_RT5682_I2C + select SND_SOC_RT5682S select SND_SOC_MAX98357A select SND_SOC_DMIC select SND_SOC_HDAC_HDMI @@ -470,6 +471,7 @@ config SND_SOC_INTEL_SOF_RT5682_MACH select SND_SOC_RT1015 select SND_SOC_RT1015P select SND_SOC_RT5682_I2C + select SND_SOC_RT5682S select SND_SOC_DMIC select SND_SOC_HDAC_HDMI select SND_SOC_INTEL_HDA_DSP_COMMON @@ -511,6 +513,20 @@ config SND_SOC_INTEL_SOF_PCM512x_MACH Say Y or m if you have such a device. If unsure select "N". +config SND_SOC_INTEL_SOF_ES8336_MACH + tristate "SOF with ES8336 codec in I2S mode" + depends on I2C && ACPI && GPIOLIB + depends on MFD_INTEL_LPSS || COMPILE_TEST + depends on SND_HDA_CODEC_HDMI && SND_SOC_SOF_HDA_AUDIO_CODEC + select SND_SOC_ES8316 + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + help + This adds support for ASoC machine driver for SOF platforms + with es8336 codec. + Say Y if you have such a device. + If unsure select "N". + endif ## SND_SOC_SOF_HDA_LINK || SND_SOC_SOF_BAYTRAIL if (SND_SOC_SOF_COMETLAKE && SND_SOC_SOF_HDA_LINK) diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index ed21b82a4cf6..9ee8ed864f5d 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -21,6 +21,7 @@ snd-soc-sst-byt-cht-es8316-objs := bytcht_es8316.o snd-soc-sst-byt-cht-nocodec-objs := bytcht_nocodec.o snd-soc-sof_rt5682-objs := sof_rt5682.o sof_realtek_common.o snd-soc-sof_cs42l42-objs := sof_cs42l42.o +snd-soc-sof_es8336-objs := sof_es8336.o snd-soc-cml_rt1011_rt5682-objs := cml_rt1011_rt5682.o snd-soc-kbl_da7219_max98357a-objs := kbl_da7219_max98357a.o snd-soc-kbl_da7219_max98927-objs := kbl_da7219_max98927.o @@ -42,6 +43,7 @@ snd-soc-sof-sdw-objs += sof_sdw.o \ sof_sdw_dmic.o sof_sdw_hdmi.o obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o obj-$(CONFIG_SND_SOC_INTEL_SOF_CS42L42_MACH) += snd-soc-sof_cs42l42.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_ES8336_MACH) += snd-soc-sof_es8336.o obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o obj-$(CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_COMMON) += snd-soc-sst-bxt-da7219_max98357a.o obj-$(CONFIG_SND_SOC_INTEL_BXT_RT298_MACH) += snd-soc-sst-bxt-rt298.o diff --git a/sound/soc/intel/boards/bdw-rt5650.c b/sound/soc/intel/boards/bdw-rt5650.c index c5122d3b0e6c..6cba5552f7a2 100644 --- a/sound/soc/intel/boards/bdw-rt5650.c +++ b/sound/soc/intel/boards/bdw-rt5650.c @@ -251,7 +251,7 @@ static struct snd_soc_dai_link bdw_rt5650_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broadwell_ssp0_fixup, .ops = &bdw_rt5650_ops, diff --git a/sound/soc/intel/boards/bdw-rt5677.c b/sound/soc/intel/boards/bdw-rt5677.c index e01b7a90ca6c..119c441f4c10 100644 --- a/sound/soc/intel/boards/bdw-rt5677.c +++ b/sound/soc/intel/boards/bdw-rt5677.c @@ -351,7 +351,7 @@ static struct snd_soc_dai_link bdw_rt5677_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broadwell_ssp0_fixup, .ops = &bdw_rt5677_ops, diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c index 3c3aff9c61cc..618d0645ed8d 100644 --- a/sound/soc/intel/boards/broadwell.c +++ b/sound/soc/intel/boards/broadwell.c @@ -217,7 +217,7 @@ static struct snd_soc_dai_link broadwell_rt286_dais[] = { .no_pcm = 1, .init = broadwell_rt286_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broadwell_ssp0_fixup, .ops = &broadwell_rt286_ops, diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c index e67ddfb8e469..b768d9b8ec02 100644 --- a/sound/soc/intel/boards/bxt_da7219_max98357a.c +++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c @@ -572,7 +572,7 @@ static struct snd_soc_dai_link broxton_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp_fixup, .dpcm_playback = 1, @@ -585,7 +585,7 @@ static struct snd_soc_dai_link broxton_dais[] = { .no_pcm = 1, .init = broxton_da7219_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp_fixup, .dpcm_playback = 1, diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c index 47f6b1523ae6..920e575b4314 100644 --- a/sound/soc/intel/boards/bxt_rt298.c +++ b/sound/soc/intel/boards/bxt_rt298.c @@ -468,7 +468,7 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .no_pcm = 1, .init = broxton_rt298_codec_init, .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp5_fixup, .ops = &broxton_rt298_ops, diff --git a/sound/soc/intel/boards/bytcht_cx2072x.c b/sound/soc/intel/boards/bytcht_cx2072x.c index a9e51bbf018c..0a736308052a 100644 --- a/sound/soc/intel/boards/bytcht_cx2072x.c +++ b/sound/soc/intel/boards/bytcht_cx2072x.c @@ -126,7 +126,7 @@ static int byt_cht_cx2072x_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_CBC_CFC); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; @@ -195,7 +195,7 @@ static struct snd_soc_dai_link byt_cht_cx2072x_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .init = byt_cht_cx2072x_init, .be_hw_params_fixup = byt_cht_cx2072x_fixup, .dpcm_playback = 1, diff --git a/sound/soc/intel/boards/bytcht_da7213.c b/sound/soc/intel/boards/bytcht_da7213.c index a28773fb7892..fae1e7e785b0 100644 --- a/sound/soc/intel/boards/bytcht_da7213.c +++ b/sound/soc/intel/boards/bytcht_da7213.c @@ -81,7 +81,7 @@ static int codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_CBC_CFC); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; @@ -195,7 +195,7 @@ static struct snd_soc_dai_link dailink[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = codec_fixup, .dpcm_playback = 1, .dpcm_capture = 1, diff --git a/sound/soc/intel/boards/bytcht_es8316.c b/sound/soc/intel/boards/bytcht_es8316.c index 4d313d0d0f23..9d86fea51a7d 100644 --- a/sound/soc/intel/boards/bytcht_es8316.c +++ b/sound/soc/intel/boards/bytcht_es8316.c @@ -265,7 +265,7 @@ static int byt_cht_es8316_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_CBC_CFC ); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); @@ -336,7 +336,7 @@ static struct snd_soc_dai_link byt_cht_es8316_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = byt_cht_es8316_codec_fixup, .dpcm_playback = 1, .dpcm_capture = 1, diff --git a/sound/soc/intel/boards/bytcht_nocodec.c b/sound/soc/intel/boards/bytcht_nocodec.c index 9b48fe701a2c..67b3c4e97864 100644 --- a/sound/soc/intel/boards/bytcht_nocodec.c +++ b/sound/soc/intel/boards/bytcht_nocodec.c @@ -61,7 +61,7 @@ static int codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_CBC_CFC); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); @@ -141,7 +141,7 @@ static struct snd_soc_dai_link dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = codec_fixup, .ignore_suspend = 1, .dpcm_playback = 1, diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index a6e837290c7d..a0c5f0e9c22a 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -269,13 +269,10 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, return -EIO; if (SND_SOC_DAPM_EVENT_ON(event)) { - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { - ret = clk_prepare_enable(priv->mclk); - if (ret < 0) { - dev_err(card->dev, - "could not configure MCLK state\n"); - return ret; - } + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, "could not configure MCLK state\n"); + return ret; } ret = byt_rt5640_prepare_and_enable_pll1(codec_dai, 48000); } else { @@ -287,10 +284,8 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_RCCLK, 48000 * 512, SND_SOC_CLOCK_IN); - if (!ret) { - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) - clk_disable_unprepare(priv->mclk); - } + if (!ret) + clk_disable_unprepare(priv->mclk); } if (ret < 0) { @@ -1217,30 +1212,25 @@ static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) return ret; } - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { - /* - * The firmware might enable the clock at - * boot (this information may or may not - * be reflected in the enable clock register). - * To change the rate we must disable the clock - * first to cover these cases. Due to common - * clock framework restrictions that do not allow - * to disable a clock that has not been enabled, - * we need to enable the clock first. - */ - ret = clk_prepare_enable(priv->mclk); - if (!ret) - clk_disable_unprepare(priv->mclk); - - if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) - ret = clk_set_rate(priv->mclk, 25000000); - else - ret = clk_set_rate(priv->mclk, 19200000); + /* + * The firmware might enable the clock at boot (this information + * may or may not be reflected in the enable clock register). + * To change the rate we must disable the clock first to cover + * these cases. Due to common clock framework restrictions that + * do not allow to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); - if (ret) { - dev_err(card->dev, "unable to set MCLK rate\n"); - return ret; - } + if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) + ret = clk_set_rate(priv->mclk, 25000000); + else + ret = clk_set_rate(priv->mclk, 19200000); + if (ret) { + dev_err(card->dev, "unable to set MCLK rate\n"); + return ret; } if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) { @@ -1336,7 +1326,7 @@ static int byt_rt5640_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_CBC_CFC); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; @@ -1411,7 +1401,7 @@ static struct snd_soc_dai_link byt_rt5640_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = byt_rt5640_codec_fixup, .dpcm_playback = 1, .dpcm_capture = 1, @@ -1495,12 +1485,12 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; static const char * const map_name[] = { "dmic1", "dmic2", "in1", "in3", "none" }; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); __maybe_unused const char *spk_type; const struct dmi_system_id *dmi_id; const char *headset2_string = ""; const char *lineout_string = ""; struct byt_rt5640_private *priv; - struct snd_soc_acpi_mach *mach; const char *platform_name; struct acpi_device *adev; struct device *codec_dev; @@ -1511,13 +1501,12 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) int aif; is_bytcr = false; - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* register the soc card */ - byt_rt5640_card.dev = &pdev->dev; - mach = byt_rt5640_card.dev->platform_data; + byt_rt5640_card.dev = dev; snd_soc_card_set_drvdata(&byt_rt5640_card, priv); /* fix index of codec dai */ @@ -1537,7 +1526,7 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) put_device(&adev->dev); byt_rt5640_dais[dai_index].codecs->name = byt_rt5640_codec_name; } else { - dev_err(&pdev->dev, "Error cannot find '%s' dev\n", mach->id); + dev_err(dev, "Error cannot find '%s' dev\n", mach->id); return -ENXIO; } @@ -1580,13 +1569,13 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) &pkg_ctx); if (pkg_found) { if (chan_package.aif_value == 1) { - dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); + dev_info(dev, "BIOS Routing: AIF1 connected\n"); byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF1; } else if (chan_package.aif_value == 2) { - dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); + dev_info(dev, "BIOS Routing: AIF2 connected\n"); byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2; } else { - dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); + dev_info(dev, "BIOS Routing isn't valid, ignored\n"); pkg_found = false; } } @@ -1610,7 +1599,7 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) if (dmi_id) byt_rt5640_quirk = (unsigned long)dmi_id->driver_data; if (quirk_override != -1) { - dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n", byt_rt5640_quirk, quirk_override); byt_rt5640_quirk = quirk_override; } @@ -1624,12 +1613,12 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) acpi_dev_add_driver_gpios(ACPI_COMPANION(priv->codec_dev), byt_rt5640_hp_elitepad_1000g2_gpios); - priv->hsmic_detect = devm_fwnode_gpiod_get(&pdev->dev, codec_dev->fwnode, + priv->hsmic_detect = devm_fwnode_gpiod_get(dev, codec_dev->fwnode, "headset-mic-detect", GPIOD_IN, "headset-mic-detect"); if (IS_ERR(priv->hsmic_detect)) { - ret_val = PTR_ERR(priv->hsmic_detect); - dev_err_probe(&pdev->dev, ret_val, "getting hsmic-detect GPIO\n"); + ret_val = dev_err_probe(dev, PTR_ERR(priv->hsmic_detect), + "getting hsmic-detect GPIO\n"); goto err_device; } } @@ -1639,7 +1628,7 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) if (ret_val) goto err_remove_gpios; - log_quirks(&pdev->dev); + log_quirks(dev); if ((byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) || (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) { @@ -1654,23 +1643,18 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) byt_rt5640_dais[dai_index].cpus->dai_name = "ssp0-port"; if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { - priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + priv->mclk = devm_clk_get_optional(dev, "pmc_plt_clk_3"); if (IS_ERR(priv->mclk)) { - ret_val = PTR_ERR(priv->mclk); - - dev_err(&pdev->dev, - "Failed to get MCLK from pmc_plt_clk_3: %d\n", - ret_val); - - /* - * Fall back to bit clock usage for -ENOENT (clock not - * available likely due to missing dependencies), bail - * for all other errors, including -EPROBE_DEFER - */ - if (ret_val != -ENOENT) - goto err; - byt_rt5640_quirk &= ~BYT_RT5640_MCLK_EN; + ret_val = dev_err_probe(dev, PTR_ERR(priv->mclk), + "Failed to get MCLK from pmc_plt_clk_3\n"); + goto err; } + /* + * Fall back to bit clock usage when clock is not + * available likely due to missing dependencies. + */ + if (!priv->mclk) + byt_rt5640_quirk &= ~BYT_RT5640_MCLK_EN; } if (byt_rt5640_quirk & BYT_RT5640_NO_SPEAKERS) { @@ -1714,7 +1698,7 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) if (ret_val) goto err; - sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); + sof_parent = snd_soc_acpi_sof_parent(dev); /* set card and driver name */ if (sof_parent) { @@ -1729,11 +1713,9 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) if (sof_parent) dev->driver->pm = &snd_soc_pm_ops; - ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5640_card); - + ret_val = devm_snd_soc_register_card(dev, &byt_rt5640_card); if (ret_val) { - dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", - ret_val); + dev_err(dev, "devm_snd_soc_register_card failed %d\n", ret_val); goto err; } platform_set_drvdata(pdev, &byt_rt5640_card); diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index e94c9124d4f4..5e9c53dadbc7 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -188,13 +188,10 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, } if (SND_SOC_DAPM_EVENT_ON(event)) { - if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { - ret = clk_prepare_enable(priv->mclk); - if (ret < 0) { - dev_err(card->dev, - "could not configure MCLK state"); - return ret; - } + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, "could not configure MCLK state"); + return ret; } ret = byt_rt5651_prepare_and_enable_pll1(codec_dai, 48000, 50); } else { @@ -207,8 +204,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, 48000 * 512, SND_SOC_CLOCK_IN); if (!ret) - if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) - clk_disable_unprepare(priv->mclk); + clk_disable_unprepare(priv->mclk); } if (ret < 0) { @@ -629,29 +625,25 @@ static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) return ret; } - if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { - /* - * The firmware might enable the clock at - * boot (this information may or may not - * be reflected in the enable clock register). - * To change the rate we must disable the clock - * first to cover these cases. Due to common - * clock framework restrictions that do not allow - * to disable a clock that has not been enabled, - * we need to enable the clock first. - */ - ret = clk_prepare_enable(priv->mclk); - if (!ret) - clk_disable_unprepare(priv->mclk); + /* + * The firmware might enable the clock at boot (this information + * may or may not be reflected in the enable clock register). + * To change the rate we must disable the clock first to cover + * these cases. Due to common clock framework restrictions that + * do not allow to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); - if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) - ret = clk_set_rate(priv->mclk, 25000000); - else - ret = clk_set_rate(priv->mclk, 19200000); + if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) + ret = clk_set_rate(priv->mclk, 25000000); + else + ret = clk_set_rate(priv->mclk, 19200000); - if (ret) - dev_err(card->dev, "unable to set MCLK rate\n"); - } + if (ret) + dev_err(card->dev, "unable to set MCLK rate\n"); report = 0; if (BYT_RT5651_JDSRC(byt_rt5651_quirk)) @@ -713,7 +705,7 @@ static int byt_rt5651_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_CBC_CFC ); if (ret < 0) { @@ -798,7 +790,7 @@ static struct snd_soc_dai_link byt_rt5651_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = byt_rt5651_codec_fixup, .dpcm_playback = 1, .dpcm_capture = 1, @@ -894,9 +886,10 @@ struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; static const char * const mic_name[] = { "dmic", "in1", "in2", "in12" }; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); struct byt_rt5651_private *priv; - struct snd_soc_acpi_mach *mach; const char *platform_name; struct acpi_device *adev; struct device *codec_dev; @@ -906,14 +899,12 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) int dai_index = 0; int i; - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* register the soc card */ - byt_rt5651_card.dev = &pdev->dev; - - mach = byt_rt5651_card.dev->platform_data; + byt_rt5651_card.dev = dev; snd_soc_card_set_drvdata(&byt_rt5651_card, priv); /* fix index of codec dai */ @@ -933,7 +924,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) put_device(&adev->dev); byt_rt5651_dais[dai_index].codecs->name = byt_rt5651_codec_name; } else { - dev_err(&pdev->dev, "Error cannot find '%s' dev\n", mach->id); + dev_err(dev, "Error cannot find '%s' dev\n", mach->id); return -ENXIO; } @@ -981,13 +972,13 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) &pkg_ctx); if (pkg_found) { if (chan_package.aif_value == 1) { - dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); + dev_info(dev, "BIOS Routing: AIF1 connected\n"); byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF1; } else if (chan_package.aif_value == 2) { - dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); + dev_info(dev, "BIOS Routing: AIF2 connected\n"); byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF2; } else { - dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); + dev_info(dev, "BIOS Routing isn't valid, ignored\n"); pkg_found = false; } } @@ -1002,7 +993,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) dmi_check_system(byt_rt5651_quirk_table); if (quirk_override != -1) { - dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n", byt_rt5651_quirk, quirk_override); byt_rt5651_quirk = quirk_override; } @@ -1018,8 +1009,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) if (byt_rt5651_gpios) { devm_acpi_dev_add_driver_gpios(codec_dev, byt_rt5651_gpios); - priv->ext_amp_gpio = devm_fwnode_gpiod_get(&pdev->dev, - codec_dev->fwnode, + priv->ext_amp_gpio = devm_fwnode_gpiod_get(dev, codec_dev->fwnode, "ext-amp-enable", GPIOD_OUT_LOW, "speaker-amp"); @@ -1030,15 +1020,13 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) priv->ext_amp_gpio = NULL; break; default: - dev_err(&pdev->dev, "Failed to get ext-amp-enable GPIO: %d\n", - ret_val); + dev_err(dev, "Failed to get ext-amp-enable GPIO: %d\n", ret_val); fallthrough; case -EPROBE_DEFER: goto err; } } - priv->hp_detect = devm_fwnode_gpiod_get(&pdev->dev, - codec_dev->fwnode, + priv->hp_detect = devm_fwnode_gpiod_get(dev, codec_dev->fwnode, "hp-detect", GPIOD_IN, "hp-detect"); @@ -1049,8 +1037,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) priv->hp_detect = NULL; break; default: - dev_err(&pdev->dev, "Failed to get hp-detect GPIO: %d\n", - ret_val); + dev_err(dev, "Failed to get hp-detect GPIO: %d\n", ret_val); fallthrough; case -EPROBE_DEFER: goto err; @@ -1058,7 +1045,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) } } - log_quirks(&pdev->dev); + log_quirks(dev); if ((byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2) || (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2)) @@ -1069,21 +1056,18 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) byt_rt5651_dais[dai_index].cpus->dai_name = "ssp0-port"; if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { - priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + priv->mclk = devm_clk_get_optional(dev, "pmc_plt_clk_3"); if (IS_ERR(priv->mclk)) { - ret_val = PTR_ERR(priv->mclk); - dev_err(&pdev->dev, - "Failed to get MCLK from pmc_plt_clk_3: %d\n", - ret_val); - /* - * Fall back to bit clock usage for -ENOENT (clock not - * available likely due to missing dependencies), bail - * for all other errors, including -EPROBE_DEFER - */ - if (ret_val != -ENOENT) - goto err; - byt_rt5651_quirk &= ~BYT_RT5651_MCLK_EN; + ret_val = dev_err_probe(dev, PTR_ERR(priv->mclk), + "Failed to get MCLK from pmc_plt_clk_3\n"); + goto err; } + /* + * Fall back to bit clock usage when clock is not + * available likely due to missing dependencies. + */ + if (!priv->mclk) + byt_rt5651_quirk &= ~BYT_RT5651_MCLK_EN; } snprintf(byt_rt5651_components, sizeof(byt_rt5651_components), @@ -1112,7 +1096,7 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) if (ret_val) goto err; - sof_parent = snd_soc_acpi_sof_parent(&pdev->dev); + sof_parent = snd_soc_acpi_sof_parent(dev); /* set card and driver name */ if (sof_parent) { @@ -1125,13 +1109,11 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) /* set pm ops */ if (sof_parent) - pdev->dev.driver->pm = &snd_soc_pm_ops; - - ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5651_card); + dev->driver->pm = &snd_soc_pm_ops; + ret_val = devm_snd_soc_register_card(dev, &byt_rt5651_card); if (ret_val) { - dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", - ret_val); + dev_err(dev, "devm_snd_soc_register_card failed %d\n", ret_val); goto err; } platform_set_drvdata(pdev, &byt_rt5651_card); diff --git a/sound/soc/intel/boards/bytcr_wm5102.c b/sound/soc/intel/boards/bytcr_wm5102.c index 580d5fddae5a..504ef4cab111 100644 --- a/sound/soc/intel/boards/bytcr_wm5102.c +++ b/sound/soc/intel/boards/bytcr_wm5102.c @@ -265,7 +265,7 @@ static int byt_wm5102_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_CBC_CFC); if (ret) { dev_err(rtd->dev, "Error setting format to I2S: %d\n", ret); return ret; @@ -349,7 +349,7 @@ static struct snd_soc_dai_link byt_wm5102_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .be_hw_params_fixup = byt_wm5102_codec_fixup, .dpcm_playback = 1, .dpcm_capture = 1, diff --git a/sound/soc/intel/boards/cht_bsw_max98090_ti.c b/sound/soc/intel/boards/cht_bsw_max98090_ti.c index 131882378a59..1bc21434c9de 100644 --- a/sound/soc/intel/boards/cht_bsw_max98090_ti.c +++ b/sound/soc/intel/boards/cht_bsw_max98090_ti.c @@ -264,7 +264,7 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, } fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS; + | SND_SOC_DAIFMT_CBC_CFC; ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), fmt); if (ret < 0) { @@ -372,7 +372,7 @@ static struct snd_soc_dai_link cht_dailink[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .init = cht_codec_init, .be_hw_params_fixup = cht_codec_fixup, .dpcm_playback = 1, diff --git a/sound/soc/intel/boards/cht_bsw_nau8824.c b/sound/soc/intel/boards/cht_bsw_nau8824.c index da5a5cbc8759..bad32d2bdf89 100644 --- a/sound/soc/intel/boards/cht_bsw_nau8824.c +++ b/sound/soc/intel/boards/cht_bsw_nau8824.c @@ -214,7 +214,7 @@ static struct snd_soc_dai_link cht_dailink[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF - | SND_SOC_DAIFMT_CBS_CFS, + | SND_SOC_DAIFMT_CBC_CFC, .init = cht_codec_init, .be_hw_params_fixup = cht_codec_fixup, .dpcm_playback = 1, @@ -278,6 +278,8 @@ static int snd_cht_mc_probe(struct platform_device *pdev) snd_soc_card_cht.driver_name = DRIVER_NAME; } + snd_soc_card_cht.components = nau8824_components(); + /* set pm ops */ if (sof_parent) pdev->dev.driver->pm = &snd_soc_pm_ops; diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c index 804dbc7911d5..e182012d0c60 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5645.c +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -362,7 +362,7 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_CBC_CFC ); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); @@ -372,7 +372,7 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS + SND_SOC_DAIFMT_CBC_CFC ); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); @@ -396,7 +396,7 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 0), SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_CBC_CFC); if (ret < 0) { dev_err(rtd->dev, "can't set format to TDM %d\n", ret); return ret; diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c index 9509b6e161b8..26eb8ad0d262 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5672.c +++ b/sound/soc/intel/boards/cht_bsw_rt5672.c @@ -300,7 +300,7 @@ static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_CBC_CFC); if (ret < 0) { dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); return ret; diff --git a/sound/soc/intel/boards/glk_rt5682_max98357a.c b/sound/soc/intel/boards/glk_rt5682_max98357a.c index 71fe26a1b701..bad3829e52ca 100644 --- a/sound/soc/intel/boards/glk_rt5682_max98357a.c +++ b/sound/soc/intel/boards/glk_rt5682_max98357a.c @@ -18,14 +18,18 @@ #include <sound/soc.h> #include <sound/soc-acpi.h> #include "../../codecs/rt5682.h" +#include "../../codecs/rt5682s.h" #include "../../codecs/hdac_hdmi.h" #include "hda_dsp_common.h" /* The platform clock outputs 19.2Mhz clock to codec as I2S MCLK */ #define GLK_PLAT_CLK_FREQ 19200000 #define RT5682_PLL_FREQ (48000 * 512) -#define GLK_REALTEK_CODEC_DAI "rt5682-aif1" +#define RT5682_DAI_NAME "rt5682-aif1" +#define RT5682S_DAI_NAME "rt5682s-aif1" #define GLK_MAXIM_CODEC_DAI "HiFi" +#define RT5682_DEV0_NAME "i2c-10EC5682:00" +#define RT5682S_DEV0_NAME "i2c-RTL5682:00" #define MAXIM_DEV0_NAME "MX98357A:00" #define DUAL_CHANNEL 2 #define QUAD_CHANNEL 4 @@ -43,6 +47,7 @@ struct glk_card_private { struct snd_soc_jack geminilake_headset; struct list_head hdmi_pcm_list; bool common_hdmi_codec_drv; + int is_rt5682s; }; enum { @@ -139,9 +144,19 @@ static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct snd_soc_jack *jack; - int ret; + int pll_id, pll_source, clk_id, ret; + + if (ctx->is_rt5682s) { + pll_id = RT5682S_PLL2; + pll_source = RT5682S_PLL_S_MCLK; + clk_id = RT5682S_SCLK_S_PLL2; + } else { + pll_id = RT5682_PLL1; + pll_source = RT5682_PLL1_S_MCLK; + clk_id = RT5682_SCLK_S_PLL1; + } - ret = snd_soc_dai_set_pll(codec_dai, 0, RT5682_PLL1_S_MCLK, + ret = snd_soc_dai_set_pll(codec_dai, pll_id, pll_source, GLK_PLAT_CLK_FREQ, RT5682_PLL_FREQ); if (ret < 0) { dev_err(rtd->dev, "can't set codec pll: %d\n", ret); @@ -149,7 +164,7 @@ static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) } /* Configure sysclk for codec */ - ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, + ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); if (ret < 0) dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); @@ -344,9 +359,12 @@ SND_SOC_DAILINK_DEF(ssp1_codec, SND_SOC_DAILINK_DEF(ssp2_pin, DAILINK_COMP_ARRAY(COMP_CPU("SSP2 Pin"))); -SND_SOC_DAILINK_DEF(ssp2_codec, - DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5682:00", - GLK_REALTEK_CODEC_DAI))); +SND_SOC_DAILINK_DEF(ssp2_codec_5682, + DAILINK_COMP_ARRAY(COMP_CODEC(RT5682_DEV0_NAME, + RT5682_DAI_NAME))); +SND_SOC_DAILINK_DEF(ssp2_codec_5682s, + DAILINK_COMP_ARRAY(COMP_CODEC(RT5682S_DEV0_NAME, + RT5682S_DAI_NAME))); SND_SOC_DAILINK_DEF(dmic_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin"))); @@ -473,7 +491,7 @@ static struct snd_soc_dai_link geminilake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = geminilake_ssp_fixup, .dpcm_playback = 1, @@ -486,13 +504,13 @@ static struct snd_soc_dai_link geminilake_dais[] = { .no_pcm = 1, .init = geminilake_rt5682_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = geminilake_ssp_fixup, .ops = &geminilake_rt5682_ops, .dpcm_playback = 1, .dpcm_capture = 1, - SND_SOC_DAILINK_REG(ssp2_pin, ssp2_codec, platform), + SND_SOC_DAILINK_REG(ssp2_pin, ssp2_codec_5682, platform), }, { .name = "dmic01", @@ -592,12 +610,28 @@ static int geminilake_audio_probe(struct platform_device *pdev) struct snd_soc_acpi_mach *mach; const char *platform_name; struct snd_soc_card *card; - int ret; + int ret, i; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; + /* Detect the headset codec variant */ + if (acpi_dev_present("RTL5682", NULL, -1)) { + /* ALC5682I-VS is detected */ + ctx->is_rt5682s = 1; + + for (i = 0; i < glk_audio_card_rt5682_m98357a.num_links; i++) { + if (strcmp(geminilake_dais[i].name, "SSP2-Codec")) + continue; + + /* update the dai link to use rt5682s codec */ + geminilake_dais[i].codecs = ssp2_codec_5682s; + geminilake_dais[i].num_codecs = ARRAY_SIZE(ssp2_codec_5682s); + break; + } + } + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); card = &glk_audio_card_rt5682_m98357a; diff --git a/sound/soc/intel/boards/haswell.c b/sound/soc/intel/boards/haswell.c index c763bfeb1f38..36e136acbef5 100644 --- a/sound/soc/intel/boards/haswell.c +++ b/sound/soc/intel/boards/haswell.c @@ -145,7 +145,7 @@ static struct snd_soc_dai_link haswell_rt5640_dais[] = { .id = 0, .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = haswell_ssp0_fixup, .ops = &haswell_rt5640_ops, diff --git a/sound/soc/intel/boards/kbl_da7219_max98357a.c b/sound/soc/intel/boards/kbl_da7219_max98357a.c index 14b625e947f5..a4bdf634e9b9 100644 --- a/sound/soc/intel/boards/kbl_da7219_max98357a.c +++ b/sound/soc/intel/boards/kbl_da7219_max98357a.c @@ -518,7 +518,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -531,7 +531,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_da7219_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, diff --git a/sound/soc/intel/boards/kbl_da7219_max98927.c b/sound/soc/intel/boards/kbl_da7219_max98927.c index 2b43459adc33..620a9fbcb08f 100644 --- a/sound/soc/intel/boards/kbl_da7219_max98927.c +++ b/sound/soc/intel/boards/kbl_da7219_max98927.c @@ -764,7 +764,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .dpcm_playback = 1, .dpcm_capture = 1, .ignore_pmdown_time = 1, @@ -779,7 +779,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_da7219_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -907,7 +907,7 @@ static struct snd_soc_dai_link kabylake_max98_927_373_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .dpcm_playback = 1, .dpcm_capture = 1, .ignore_pmdown_time = 1, diff --git a/sound/soc/intel/boards/kbl_rt5660.c b/sound/soc/intel/boards/kbl_rt5660.c index 289ca39b8206..1cb56ec363e8 100644 --- a/sound/soc/intel/boards/kbl_rt5660.c +++ b/sound/soc/intel/boards/kbl_rt5660.c @@ -436,7 +436,7 @@ static struct snd_soc_dai_link kabylake_rt5660_dais[] = { .exit = kabylake_rt5660_codec_exit, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp0_fixup, .ops = &kabylake_rt5660_ops, diff --git a/sound/soc/intel/boards/kbl_rt5663_max98927.c b/sound/soc/intel/boards/kbl_rt5663_max98927.c index a3e040a249f6..f24e0ce5d49f 100644 --- a/sound/soc/intel/boards/kbl_rt5663_max98927.c +++ b/sound/soc/intel/boards/kbl_rt5663_max98927.c @@ -767,7 +767,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -781,7 +781,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_rt5663_max98927_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .ops = &kabylake_rt5663_ops, @@ -879,7 +879,7 @@ static struct snd_soc_dai_link kabylake_5663_dais[] = { .no_pcm = 1, .init = kabylake_rt5663_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .ops = &kabylake_rt5663_ops, diff --git a/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c index dd38fdaf2ff5..6874e981c8df 100644 --- a/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c +++ b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c @@ -639,7 +639,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .dpcm_playback = 1, @@ -653,7 +653,7 @@ static struct snd_soc_dai_link kabylake_dais[] = { .no_pcm = 1, .init = kabylake_rt5663_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = kabylake_ssp_fixup, .ops = &kabylake_rt5663_ops, diff --git a/sound/soc/intel/boards/skl_nau88l25_max98357a.c b/sound/soc/intel/boards/skl_nau88l25_max98357a.c index e3a1f04a8b53..7297eb05613c 100644 --- a/sound/soc/intel/boards/skl_nau88l25_max98357a.c +++ b/sound/soc/intel/boards/skl_nau88l25_max98357a.c @@ -539,7 +539,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, .dpcm_playback = 1, @@ -552,7 +552,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .init = skylake_nau8825_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, .ops = &skylake_nau8825_ops, diff --git a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c index adf5992a9ec5..68efde1633b3 100644 --- a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c +++ b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c @@ -578,7 +578,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .init = skylake_ssm4567_codec_init, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, @@ -593,7 +593,7 @@ static struct snd_soc_dai_link skylake_dais[] = { .no_pcm = 1, .init = skylake_nau8825_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp_fixup, .ops = &skylake_nau8825_ops, diff --git a/sound/soc/intel/boards/skl_rt286.c b/sound/soc/intel/boards/skl_rt286.c index 75dab5405380..eca4a78668af 100644 --- a/sound/soc/intel/boards/skl_rt286.c +++ b/sound/soc/intel/boards/skl_rt286.c @@ -434,7 +434,7 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = { .init = skylake_rt286_codec_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBS_CFS, + SND_SOC_DAIFMT_CBC_CFC, .ignore_pmdown_time = 1, .be_hw_params_fixup = skylake_ssp0_fixup, .ops = &skylake_rt286_ops, diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c new file mode 100644 index 000000000000..20d577eaab6d --- /dev/null +++ b/sound/soc/intel/boards/sof_es8336.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2021 Intel Corporation. + +/* + * Intel SOF Machine Driver with es8336 Codec + */ + +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/machine.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "hda_dsp_common.h" + +#define SOF_ES8336_SSP_CODEC(quirk) ((quirk) & GENMASK(3, 0)) +#define SOF_ES8336_SSP_CODEC_MASK (GENMASK(3, 0)) + +#define SOF_ES8336_TGL_GPIO_QUIRK BIT(4) +#define SOF_ES8336_ENABLE_DMIC BIT(5) + +static unsigned long quirk; + +static int quirk_override = -1; +module_param_named(quirk, quirk_override, int, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); + +struct sof_es8336_private { + struct device *codec_dev; + struct gpio_desc *gpio_pa; + struct snd_soc_jack jack; + struct list_head hdmi_pcm_list; + bool speaker_en; +}; + +struct sof_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +static const struct acpi_gpio_params pa_enable_gpio = { 0, 0, true }; +static const struct acpi_gpio_mapping acpi_es8336_gpios[] = { + { "pa-enable-gpios", &pa_enable_gpio, 1 }, + { } +}; + +static const struct acpi_gpio_params quirk_pa_enable_gpio = { 1, 0, true }; +static const struct acpi_gpio_mapping quirk_acpi_es8336_gpios[] = { + { "pa-enable-gpios", &quirk_pa_enable_gpio, 1 }, + { } +}; + +static const struct acpi_gpio_mapping *gpio_mapping = acpi_es8336_gpios; + +static void log_quirks(struct device *dev) +{ + dev_info(dev, "quirk SSP%ld", SOF_ES8336_SSP_CODEC(quirk)); +} + +static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + + if (SND_SOC_DAPM_EVENT_ON(event)) + priv->speaker_en = false; + else + priv->speaker_en = true; + + gpiod_set_value_cansleep(priv->gpio_pa, priv->speaker_en); + + return 0; +} + +static const struct snd_soc_dapm_widget sof_es8316_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + + SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0, + sof_es8316_speaker_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +}; + +static const struct snd_soc_dapm_widget dmic_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +static const struct snd_soc_dapm_route sof_es8316_audio_map[] = { + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + + /* + * There is no separate speaker output instead the speakers are muxed to + * the HP outputs. The mux is controlled by the "Speaker Power" supply. + */ + {"Speaker", NULL, "HPOL"}, + {"Speaker", NULL, "HPOR"}, + {"Speaker", NULL, "Speaker Power"}, +}; + +static const struct snd_soc_dapm_route sof_es8316_intmic_in1_map[] = { + {"MIC1", NULL, "Internal Mic"}, + {"MIC2", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route dmic_map[] = { + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, +}; + +static const struct snd_kcontrol_new sof_es8316_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static struct snd_soc_jack_pin sof_es8316_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int dmic_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets, + ARRAY_SIZE(dmic_widgets)); + if (ret) { + dev_err(card->dev, "DMic widget addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map, + ARRAY_SIZE(dmic_map)); + if (ret) + dev_err(card->dev, "DMic map addition failed: %d\n", ret); + + return ret; +} + +static int sof_hdmi_init(struct snd_soc_pcm_runtime *runtime) +{ + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(runtime->card); + struct snd_soc_dai *dai = asoc_rtd_to_codec(runtime, 0); + struct sof_hdmi_pcm *pcm; + + pcm = devm_kzalloc(runtime->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = runtime->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &priv->hdmi_pcm_list); + + return 0; +} + +static int sof_es8316_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *codec = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_card *card = runtime->card; + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + const struct snd_soc_dapm_route *custom_map; + int num_routes; + int ret; + + card->dapm.idle_bias_off = true; + + custom_map = sof_es8316_intmic_in1_map; + num_routes = ARRAY_SIZE(sof_es8316_intmic_in1_map); + + ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); + if (ret) + return ret; + + ret = snd_soc_card_jack_new(card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &priv->jack, sof_es8316_jack_pins, + ARRAY_SIZE(sof_es8316_jack_pins)); + if (ret) { + dev_err(card->dev, "jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + + snd_soc_component_set_jack(codec, &priv->jack, NULL); + + return 0; +} + +static void sof_es8316_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_set_jack(component, NULL, NULL); +} + +static int sof_es8336_quirk_cb(const struct dmi_system_id *id) +{ + quirk = (unsigned long)id->driver_data; + + if (quirk & SOF_ES8336_TGL_GPIO_QUIRK) + gpio_mapping = quirk_acpi_es8336_gpios; + + return 1; +} + +static const struct dmi_system_id sof_es8336_quirk_table[] = { + { + .callback = sof_es8336_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "CHUWI Innovation And Technology"), + DMI_MATCH(DMI_BOARD_NAME, "Hi10 X"), + }, + .driver_data = (void *)SOF_ES8336_SSP_CODEC(2) + }, + { + .callback = sof_es8336_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "IP3 tech"), + DMI_MATCH(DMI_BOARD_NAME, "WN1"), + }, + .driver_data = (void *)(SOF_ES8336_SSP_CODEC(0) | + SOF_ES8336_TGL_GPIO_QUIRK | + SOF_ES8336_ENABLE_DMIC) + }, + {} +}; + +static int sof_es8336_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + const int sysclk = 19200000; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 1, sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->dev, "%s, Failed to set ES8336 SYSCLK: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +/* machine stream operations */ +static struct snd_soc_ops sof_es8336_ops = { + .hw_params = sof_es8336_hw_params, +}; + +static struct snd_soc_dai_link_component platform_component[] = { + { + /* name might be overridden during probe */ + .name = "0000:00:1f.3" + } +}; + +SND_SOC_DAILINK_DEF(ssp1_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("i2c-ESSX8336:00", "ES8316 HiFi"))); + +static struct snd_soc_dai_link_component dmic_component[] = { + { + .name = "dmic-codec", + .dai_name = "dmic-hifi", + } +}; + +static int sof_es8336_late_probe(struct snd_soc_card *card) +{ + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + struct sof_hdmi_pcm *pcm; + + if (list_empty(&priv->hdmi_pcm_list)) + return -ENOENT; + + pcm = list_first_entry(&priv->hdmi_pcm_list, struct sof_hdmi_pcm, head); + + return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component); +} + +/* SoC card */ +static struct snd_soc_card sof_es8336_card = { + .name = "essx8336", /* sof- prefix added automatically */ + .owner = THIS_MODULE, + .dapm_widgets = sof_es8316_widgets, + .num_dapm_widgets = ARRAY_SIZE(sof_es8316_widgets), + .dapm_routes = sof_es8316_audio_map, + .num_dapm_routes = ARRAY_SIZE(sof_es8316_audio_map), + .controls = sof_es8316_controls, + .num_controls = ARRAY_SIZE(sof_es8316_controls), + .fully_routed = true, + .late_probe = sof_es8336_late_probe, + .num_links = 1, +}; + +static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, + int ssp_codec, + int dmic_be_num, + int hdmi_num) +{ + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link *links; + struct snd_soc_dai_link_component *idisp_components; + int hdmi_id_offset = 0; + int id = 0; + int i; + + links = devm_kcalloc(dev, sof_es8336_card.num_links, + sizeof(struct snd_soc_dai_link), GFP_KERNEL); + cpus = devm_kcalloc(dev, sof_es8336_card.num_links, + sizeof(struct snd_soc_dai_link_component), GFP_KERNEL); + if (!links || !cpus) + goto devm_err; + + /* codec SSP */ + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d-Codec", ssp_codec); + if (!links[id].name) + goto devm_err; + + links[id].id = id; + links[id].codecs = ssp1_codec; + links[id].num_codecs = ARRAY_SIZE(ssp1_codec); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_es8316_init; + links[id].exit = sof_es8316_exit; + links[id].ops = &sof_es8336_ops; + links[id].nonatomic = true; + links[id].dpcm_playback = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "SSP%d Pin", + ssp_codec); + if (!links[id].cpus->dai_name) + goto devm_err; + + id++; + + /* dmic */ + if (dmic_be_num > 0) { + /* at least we have dmic01 */ + links[id].name = "dmic01"; + links[id].cpus = &cpus[id]; + links[id].cpus->dai_name = "DMIC01 Pin"; + links[id].init = dmic_init; + if (dmic_be_num > 1) { + /* set up 2 BE links at most */ + links[id + 1].name = "dmic16k"; + links[id + 1].cpus = &cpus[id + 1]; + links[id + 1].cpus->dai_name = "DMIC16k Pin"; + dmic_be_num = 2; + } + } else { + /* HDMI dai link starts at 3 according to current topology settings */ + hdmi_id_offset = 2; + } + + for (i = 0; i < dmic_be_num; i++) { + links[id].id = id; + links[id].num_cpus = 1; + links[id].codecs = dmic_component; + links[id].num_codecs = ARRAY_SIZE(dmic_component); + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].ignore_suspend = 1; + links[id].dpcm_capture = 1; + links[id].no_pcm = 1; + + id++; + } + + /* HDMI */ + if (hdmi_num > 0) { + idisp_components = devm_kzalloc(dev, + sizeof(struct snd_soc_dai_link_component) * + hdmi_num, GFP_KERNEL); + if (!idisp_components) + goto devm_err; + } + + for (i = 1; i <= hdmi_num; i++) { + links[id].name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d", i); + if (!links[id].name) + goto devm_err; + + links[id].id = id + hdmi_id_offset; + links[id].cpus = &cpus[id]; + links[id].num_cpus = 1; + links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, + "iDisp%d Pin", i); + if (!links[id].cpus->dai_name) + goto devm_err; + + idisp_components[i - 1].name = "ehdaudio0D2"; + idisp_components[i - 1].dai_name = devm_kasprintf(dev, + GFP_KERNEL, + "intel-hdmi-hifi%d", + i); + if (!idisp_components[i - 1].dai_name) + goto devm_err; + + links[id].codecs = &idisp_components[i - 1]; + links[id].num_codecs = 1; + links[id].platforms = platform_component; + links[id].num_platforms = ARRAY_SIZE(platform_component); + links[id].init = sof_hdmi_init; + links[id].dpcm_playback = 1; + links[id].no_pcm = 1; + + id++; + } + + return links; + +devm_err: + return NULL; +} + + /* i2c-<HID>:00 with HID being 8 chars */ +static char codec_name[SND_ACPI_I2C_ID_LEN]; + +static int sof_es8336_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + struct snd_soc_acpi_mach *mach = pdev->dev.platform_data; + struct sof_es8336_private *priv; + struct acpi_device *adev; + struct snd_soc_dai_link *dai_links; + struct device *codec_dev; + int dmic_be_num = 0; + int hdmi_num = 3; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card = &sof_es8336_card; + card->dev = dev; + + if (!dmi_check_system(sof_es8336_quirk_table)) + quirk = SOF_ES8336_SSP_CODEC(2); + + if (quirk & SOF_ES8336_ENABLE_DMIC) + dmic_be_num = 2; + + if (quirk_override != -1) { + dev_info(dev, "Overriding quirk 0x%lx => 0x%x\n", + quirk, quirk_override); + quirk = quirk_override; + } + log_quirks(dev); + + sof_es8336_card.num_links += dmic_be_num + hdmi_num; + dai_links = sof_card_dai_links_create(dev, + SOF_ES8336_SSP_CODEC(quirk), + dmic_be_num, hdmi_num); + if (!dai_links) + return -ENOMEM; + + sof_es8336_card.dai_link = dai_links; + + /* fixup codec name based on HID */ + adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); + if (adev) { + snprintf(codec_name, sizeof(codec_name), + "i2c-%s", acpi_dev_name(adev)); + put_device(&adev->dev); + dai_links[0].codecs->name = codec_name; + } + + ret = snd_soc_fixup_dai_links_platform_name(&sof_es8336_card, + mach->mach_params.platform); + if (ret) + return ret; + + /* get speaker enable GPIO */ + codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, codec_name); + if (!codec_dev) + return -EPROBE_DEFER; + + ret = devm_acpi_dev_add_driver_gpios(codec_dev, gpio_mapping); + if (ret) + dev_warn(codec_dev, "unable to add GPIO mapping table\n"); + + priv->gpio_pa = gpiod_get(codec_dev, "pa-enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpio_pa)) { + ret = PTR_ERR(priv->gpio_pa); + dev_err(codec_dev, "%s, could not get pa-enable: %d\n", + __func__, ret); + goto err; + } + + priv->codec_dev = codec_dev; + INIT_LIST_HEAD(&priv->hdmi_pcm_list); + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + gpiod_put(priv->gpio_pa); + dev_err(dev, "snd_soc_register_card failed: %d\n", ret); + goto err; + } + platform_set_drvdata(pdev, &sof_es8336_card); + return 0; + +err: + put_device(codec_dev); + return ret; +} + +static int sof_es8336_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); + + gpiod_put(priv->gpio_pa); + put_device(priv->codec_dev); + + return 0; +} + +static struct platform_driver sof_es8336_driver = { + .driver = { + .name = "sof-essx8336", + .pm = &snd_soc_pm_ops, + }, + .probe = sof_es8336_probe, + .remove = sof_es8336_remove, +}; +module_platform_driver(sof_es8336_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) SOF + ES8336 Machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sof-essx8336"); +MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index f096bd6d69be..c41f386b4138 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -18,9 +18,11 @@ #include <sound/soc.h> #include <sound/sof.h> #include <sound/rt5682.h> +#include <sound/rt5682s.h> #include <sound/soc-acpi.h> #include "../../codecs/rt1015.h" #include "../../codecs/rt5682.h" +#include "../../codecs/rt5682s.h" #include "../../codecs/hdac_hdmi.h" #include "../common/soc-intel-quirks.h" #include "hda_dsp_common.h" @@ -56,6 +58,7 @@ #define SOF_BT_OFFLOAD_SSP(quirk) \ (((quirk) << SOF_BT_OFFLOAD_SSP_SHIFT) & SOF_BT_OFFLOAD_SSP_MASK) #define SOF_SSP_BT_OFFLOAD_PRESENT BIT(22) +#define SOF_RT5682S_HEADPHONE_CODEC_PRESENT BIT(23) /* Default: MCLK on, MCLK 19.2M, SSP0 */ static unsigned long sof_rt5682_quirk = SOF_RT5682_MCLK_EN | @@ -208,9 +211,16 @@ static int sof_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) /* need to enable ASRC function for 24MHz mclk rate */ if ((sof_rt5682_quirk & SOF_RT5682_MCLK_EN) && (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ)) { - rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER | - RT5682_AD_STEREO1_FILTER, - RT5682_CLK_SEL_I2S1_ASRC); + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) + rt5682s_sel_asrc_clk_src(component, + RT5682S_DA_STEREO1_FILTER | + RT5682S_AD_STEREO1_FILTER, + RT5682S_CLK_SEL_I2S1_ASRC); + else + rt5682_sel_asrc_clk_src(component, + RT5682_DA_STEREO1_FILTER | + RT5682_AD_STEREO1_FILTER, + RT5682_CLK_SEL_I2S1_ASRC); } if (sof_rt5682_quirk & SOF_RT5682_MCLK_BYTCHT_EN) { @@ -277,7 +287,7 @@ static int sof_rt5682_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - int clk_id, clk_freq, pll_out, ret; + int pll_id, pll_source, pll_in, pll_out, clk_id, ret; if (sof_rt5682_quirk & SOF_RT5682_MCLK_EN) { if (sof_rt5682_quirk & SOF_RT5682_MCLK_BYTCHT_EN) { @@ -289,35 +299,52 @@ static int sof_rt5682_hw_params(struct snd_pcm_substream *substream, } } - clk_id = RT5682_PLL1_S_MCLK; + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) + pll_source = RT5682S_PLL_S_MCLK; + else + pll_source = RT5682_PLL1_S_MCLK; /* get the tplg configured mclk. */ - clk_freq = sof_dai_get_mclk(rtd); + pll_in = sof_dai_get_mclk(rtd); /* mclk from the quirk is the first choice */ if (sof_rt5682_quirk & SOF_RT5682_MCLK_24MHZ) { - if (clk_freq != 24000000) + if (pll_in != 24000000) dev_warn(rtd->dev, "configure wrong mclk in tplg, please use 24MHz.\n"); - clk_freq = 24000000; - } else if (clk_freq == 0) { + pll_in = 24000000; + } else if (pll_in == 0) { /* use default mclk if not specified correct in topology */ - clk_freq = 19200000; - } else if (clk_freq < 0) { - return clk_freq; + pll_in = 19200000; + } else if (pll_in < 0) { + return pll_in; } } else { - clk_id = RT5682_PLL1_S_BCLK1; - clk_freq = params_rate(params) * 50; + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) + pll_source = RT5682S_PLL_S_BCLK1; + else + pll_source = RT5682_PLL1_S_BCLK1; + + pll_in = params_rate(params) * 50; + } + + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) { + pll_id = RT5682S_PLL2; + clk_id = RT5682S_SCLK_S_PLL2; + } else { + pll_id = RT5682_PLL1; + clk_id = RT5682_SCLK_S_PLL1; } pll_out = params_rate(params) * 512; - ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out); + /* Configure pll for codec */ + ret = snd_soc_dai_set_pll(codec_dai, pll_id, pll_source, pll_in, + pll_out); if (ret < 0) dev_err(rtd->dev, "snd_soc_dai_set_pll err = %d\n", ret); /* Configure sysclk for codec */ - ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, + ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, pll_out, SND_SOC_CLOCK_IN); if (ret < 0) dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); @@ -560,6 +587,13 @@ static struct snd_soc_dai_link_component rt5682_component[] = { } }; +static struct snd_soc_dai_link_component rt5682s_component[] = { + { + .name = "i2c-RTL5682:00", + .dai_name = "rt5682s-aif1", + } +}; + static struct snd_soc_dai_link_component dmic_component[] = { { .name = "dmic-codec", @@ -610,8 +644,13 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, goto devm_err; links[id].id = id; - links[id].codecs = rt5682_component; - links[id].num_codecs = ARRAY_SIZE(rt5682_component); + if (sof_rt5682_quirk & SOF_RT5682S_HEADPHONE_CODEC_PRESENT) { + links[id].codecs = rt5682s_component; + links[id].num_codecs = ARRAY_SIZE(rt5682s_component); + } else { + links[id].codecs = rt5682_component; + links[id].num_codecs = ARRAY_SIZE(rt5682_component); + } links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); links[id].init = sof_rt5682_codec_init; @@ -825,6 +864,10 @@ static int sof_audio_probe(struct platform_device *pdev) if ((sof_rt5682_quirk & SOF_SPEAKER_AMP_PRESENT) && !mach->quirk_data) sof_rt5682_quirk &= ~SOF_SPEAKER_AMP_PRESENT; + /* Detect the headset codec variant */ + if (acpi_dev_present("RTL5682", NULL, -1)) + sof_rt5682_quirk |= SOF_RT5682S_HEADPHONE_CODEC_PRESENT; + if (soc_intel_is_byt() || soc_intel_is_cht()) { is_legacy_cpu = 1; dmic_be_num = 0; @@ -920,7 +963,7 @@ static const struct platform_device_id board_ids[] = { .name = "sof_rt5682", }, { - .name = "tgl_mx98357a_rt5682", + .name = "tgl_mx98357_rt5682", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | @@ -950,7 +993,7 @@ static const struct platform_device_id board_ids[] = { SOF_SSP_BT_OFFLOAD_PRESENT), }, { - .name = "jsl_rt5682_mx98360a", + .name = "jsl_rt5682_mx98360", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | SOF_RT5682_MCLK_24MHZ | SOF_RT5682_SSP_CODEC(0) | @@ -1000,13 +1043,24 @@ static const struct platform_device_id board_ids[] = { SOF_SSP_BT_OFFLOAD_PRESENT), }, { - .name = "adl_mx98357a_rt5682", + .name = "adl_mx98357_rt5682", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | SOF_RT5682_SSP_AMP(2) | SOF_RT5682_NUM_HDMIDEV(4)), }, + { + .name = "adl_mx98360_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, { } }; MODULE_DEVICE_TABLE(platform, board_ids); diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 6b06248a9327..f10496206cee 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -213,6 +213,16 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { SOF_RT715_DAI_ID_FIX | SOF_SDW_FOUR_SPK), }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A45") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_RT715_DAI_ID_FIX), + }, /* AlderLake devices */ { .callback = sof_sdw_quirk_cb, diff --git a/sound/soc/intel/common/soc-acpi-intel-adl-match.c b/sound/soc/intel/common/soc-acpi-intel-adl-match.c index a0f6a69c7038..06f503452aa5 100644 --- a/sound/soc/intel/common/soc-acpi-intel-adl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-adl-match.c @@ -280,9 +280,19 @@ static const struct snd_soc_acpi_codecs adl_max98357a_amp = { .codecs = {"MX98357A"} }; +static const struct snd_soc_acpi_codecs adl_max98360a_amp = { + .num_codecs = 1, + .codecs = {"MX98360A"} +}; + +static const struct snd_soc_acpi_codecs adl_rt5682_rt5682s_hp = { + .num_codecs = 2, + .codecs = {"10EC5682", "RTL5682"}, +}; + struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_machines[] = { { - .id = "10EC5682", + .comp_ids = &adl_rt5682_rt5682s_hp, .drv_name = "adl_mx98373_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &adl_max98373_amp, @@ -290,13 +300,21 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_machines[] = { .sof_tplg_filename = "sof-adl-max98373-rt5682.tplg", }, { - .id = "10EC5682", - .drv_name = "adl_mx98357a_rt5682", + .comp_ids = &adl_rt5682_rt5682s_hp, + .drv_name = "adl_mx98357_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &adl_max98357a_amp, .sof_fw_filename = "sof-adl.ri", .sof_tplg_filename = "sof-adl-max98357a-rt5682.tplg", }, + { + .comp_ids = &adl_rt5682_rt5682s_hp, + .drv_name = "adl_mx98360_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &adl_max98360a_amp, + .sof_fw_filename = "sof-adl.ri", + .sof_tplg_filename = "sof-adl-max98360a-rt5682.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_adl_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-bxt-match.c b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c index 576407b5daf2..342d34052204 100644 --- a/sound/soc/intel/common/soc-acpi-intel-bxt-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c @@ -41,7 +41,7 @@ static struct snd_soc_acpi_mach *apl_quirk(void *arg) return mach; } -static struct snd_soc_acpi_codecs bxt_codecs = { +static const struct snd_soc_acpi_codecs bxt_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; @@ -82,6 +82,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_bxt_machines[] = { .sof_fw_filename = "sof-apl.ri", .sof_tplg_filename = "sof-apl-tdf8532.tplg", }, + { + .id = "ESSX8336", + .drv_name = "sof-essx8336", + .sof_fw_filename = "sof-apl.ri", + .sof_tplg_filename = "sof-apl-es8336.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_bxt_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-byt-match.c b/sound/soc/intel/common/soc-acpi-intel-byt-match.c index 510a5f38b7f1..142000991813 100644 --- a/sound/soc/intel/common/soc-acpi-intel-byt-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-byt-match.c @@ -120,9 +120,29 @@ static struct snd_soc_acpi_mach *byt_quirk(void *arg) } } +static const struct snd_soc_acpi_codecs rt5640_comp_ids = { + .num_codecs = 3, + .codecs = { "10EC5640", "10EC5642", "INTCCFFD"}, +}; + +static const struct snd_soc_acpi_codecs wm5102_comp_ids = { + .num_codecs = 2, + .codecs = { "WM510204", "WM510205"}, +}; + +static const struct snd_soc_acpi_codecs da7213_comp_ids = { + .num_codecs = 2, + .codecs = { "DGLS7212", "DGLS7213"}, +}; + +static const struct snd_soc_acpi_codecs rt5645_comp_ids = { + .num_codecs = 2, + .codecs = { "10EC5645", "10EC5648"}, +}; + struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { { - .id = "10EC5640", + .comp_ids = &rt5640_comp_ids, .drv_name = "bytcr_rt5640", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcr_rt5640", @@ -131,22 +151,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .sof_tplg_filename = "sof-byt-rt5640.tplg", }, { - .id = "10EC5642", - .drv_name = "bytcr_rt5640", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "bytcr_rt5640", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-rt5640.tplg", - }, - { - .id = "INTCCFFD", - .drv_name = "bytcr_rt5640", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "bytcr_rt5640", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-rt5640.tplg", - }, - { .id = "10EC5651", .drv_name = "bytcr_rt5651", .fw_filename = "intel/fw_sst_0f28.bin", @@ -155,7 +159,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .sof_tplg_filename = "sof-byt-rt5651.tplg", }, { - .id = "WM510204", + .comp_ids = &wm5102_comp_ids, .drv_name = "bytcr_wm5102", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcr_wm5102", @@ -163,23 +167,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { .sof_tplg_filename = "sof-byt-wm5102.tplg", }, { - .id = "WM510205", - .drv_name = "bytcr_wm5102", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "bytcr_wm5102", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-wm5102.tplg", - }, - { - .id = "DLGS7212", - .drv_name = "bytcht_da7213", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "bytcht_da7213", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-da7213.tplg", - }, - { - .id = "DLGS7213", + .comp_ids = &da7213_comp_ids, .drv_name = "bytcht_da7213", .fw_filename = "intel/fw_sst_0f28.bin", .board = "bytcht_da7213", @@ -202,15 +190,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { }, /* some Baytrail platforms rely on RT5645, use CHT machine driver */ { - .id = "10EC5645", - .drv_name = "cht-bsw-rt5645", - .fw_filename = "intel/fw_sst_0f28.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-byt.ri", - .sof_tplg_filename = "sof-byt-rt5645.tplg", - }, - { - .id = "10EC5648", + .comp_ids = &rt5645_comp_ids, .drv_name = "cht-bsw-rt5645", .fw_filename = "intel/fw_sst_0f28.bin", .board = "cht-bsw", diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index 227424236fd5..c60a5e8e7bc9 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -51,18 +51,31 @@ static struct snd_soc_acpi_mach *cht_quirk(void *arg) return mach; } +static const struct snd_soc_acpi_codecs rt5640_comp_ids = { + .num_codecs = 2, + .codecs = { "10EC5640", "10EC3276" }, +}; + +static const struct snd_soc_acpi_codecs rt5670_comp_ids = { + .num_codecs = 2, + .codecs = { "10EC5670", "10EC5672" }, +}; + +static const struct snd_soc_acpi_codecs rt5645_comp_ids = { + .num_codecs = 3, + .codecs = { "10EC5645", "10EC5650", "10EC3270" }, +}; + +static const struct snd_soc_acpi_codecs da7213_comp_ids = { + .num_codecs = 2, + .codecs = { "DGLS7212", "DGLS7213"}, + +}; + /* Cherryview-based platforms: CherryTrail and Braswell */ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { { - .id = "10EC5670", - .drv_name = "cht-bsw-rt5672", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5670.tplg", - }, - { - .id = "10EC5672", + .comp_ids = &rt5670_comp_ids, .drv_name = "cht-bsw-rt5672", .fw_filename = "intel/fw_sst_22a8.bin", .board = "cht-bsw", @@ -70,23 +83,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .sof_tplg_filename = "sof-cht-rt5670.tplg", }, { - .id = "10EC5645", - .drv_name = "cht-bsw-rt5645", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5645.tplg", - }, - { - .id = "10EC5650", - .drv_name = "cht-bsw-rt5645", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "cht-bsw", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5645.tplg", - }, - { - .id = "10EC3270", + .comp_ids = &rt5645_comp_ids, .drv_name = "cht-bsw-rt5645", .fw_filename = "intel/fw_sst_22a8.bin", .board = "cht-bsw", @@ -110,15 +107,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .sof_tplg_filename = "sof-cht-nau8824.tplg", }, { - .id = "DLGS7212", - .drv_name = "bytcht_da7213", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "bytcht_da7213", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-da7213.tplg", - }, - { - .id = "DLGS7213", + .comp_ids = &da7213_comp_ids, .drv_name = "bytcht_da7213", .fw_filename = "intel/fw_sst_22a8.bin", .board = "bytcht_da7213", @@ -135,7 +124,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { }, /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ { - .id = "10EC5640", + .comp_ids = &rt5640_comp_ids, .drv_name = "bytcr_rt5640", .fw_filename = "intel/fw_sst_22a8.bin", .board = "bytcr_rt5640", @@ -144,14 +133,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .sof_tplg_filename = "sof-cht-rt5640.tplg", }, { - .id = "10EC3276", - .drv_name = "bytcr_rt5640", - .fw_filename = "intel/fw_sst_22a8.bin", - .board = "bytcr_rt5640", - .sof_fw_filename = "sof-cht.ri", - .sof_tplg_filename = "sof-cht-rt5640.tplg", - }, - { .id = "10EC5682", .drv_name = "sof_rt5682", .sof_fw_filename = "sof-cht.ri", diff --git a/sound/soc/intel/common/soc-acpi-intel-cml-match.c b/sound/soc/intel/common/soc-acpi-intel-cml-match.c index b591c6fd13fd..b4eb0c97edf1 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cml-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cml-match.c @@ -9,22 +9,22 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> -static struct snd_soc_acpi_codecs rt1011_spk_codecs = { +static const struct snd_soc_acpi_codecs rt1011_spk_codecs = { .num_codecs = 1, .codecs = {"10EC1011"} }; -static struct snd_soc_acpi_codecs rt1015_spk_codecs = { +static const struct snd_soc_acpi_codecs rt1015_spk_codecs = { .num_codecs = 1, .codecs = {"10EC1015"} }; -static struct snd_soc_acpi_codecs max98357a_spk_codecs = { +static const struct snd_soc_acpi_codecs max98357a_spk_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; -static struct snd_soc_acpi_codecs max98390_spk_codecs = { +static const struct snd_soc_acpi_codecs max98390_spk_codecs = { .num_codecs = 1, .codecs = {"MX98390"} }; diff --git a/sound/soc/intel/common/soc-acpi-intel-glk-match.c b/sound/soc/intel/common/soc-acpi-intel-glk-match.c index da1e151190b4..8492b7e2a945 100644 --- a/sound/soc/intel/common/soc-acpi-intel-glk-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-glk-match.c @@ -9,7 +9,7 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> -static struct snd_soc_acpi_codecs glk_codecs = { +static const struct snd_soc_acpi_codecs glk_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; @@ -41,6 +41,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_machines[] = { .sof_tplg_filename = "sof-glk-rt5682.tplg", }, { + .id = "RTL5682", + .drv_name = "glk_rt5682_max98357a", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &glk_codecs, + .sof_fw_filename = "sof-glk.ri", + .sof_tplg_filename = "sof-glk-rt5682.tplg", + }, + { .id = "10134242", .drv_name = "glk_cs4242_mx98357a", .fw_filename = "intel/dsp_fw_glk.bin", @@ -49,7 +57,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_machines[] = { .sof_fw_filename = "sof-glk.ri", .sof_tplg_filename = "sof-glk-cs42l42.tplg", }, - + { + .id = "ESSX8336", + .drv_name = "sof-essx8336", + .sof_fw_filename = "sof-glk.ri", + .sof_tplg_filename = "sof-glk-es8336.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_glk_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-jsl-match.c b/sound/soc/intel/common/soc-acpi-intel-jsl-match.c index 69ff7286d357..278ec196da7b 100644 --- a/sound/soc/intel/common/soc-acpi-intel-jsl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-jsl-match.c @@ -9,26 +9,31 @@ #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> -static struct snd_soc_acpi_codecs jsl_7219_98373_codecs = { +static const struct snd_soc_acpi_codecs jsl_7219_98373_codecs = { .num_codecs = 1, .codecs = {"MX98373"} }; -static struct snd_soc_acpi_codecs rt1015_spk = { +static const struct snd_soc_acpi_codecs rt1015_spk = { .num_codecs = 1, .codecs = {"10EC1015"} }; -static struct snd_soc_acpi_codecs rt1015p_spk = { +static const struct snd_soc_acpi_codecs rt1015p_spk = { .num_codecs = 1, .codecs = {"RTL1015"} }; -static struct snd_soc_acpi_codecs mx98360a_spk = { +static const struct snd_soc_acpi_codecs mx98360a_spk = { .num_codecs = 1, .codecs = {"MX98360A"} }; +static const struct snd_soc_acpi_codecs rt5682_rt5682s_hp = { + .num_codecs = 2, + .codecs = {"10EC5682", "RTL5682"}, +}; + /* * When adding new entry to the snd_soc_acpi_intel_jsl_machines array, * use .quirk_data member to distinguish different machine driver, @@ -50,7 +55,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[] = { .sof_tplg_filename = "sof-jsl-da7219-mx98360a.tplg", }, { - .id = "10EC5682", + .comp_ids = &rt5682_rt5682s_hp, .drv_name = "jsl_rt5682_rt1015", .sof_fw_filename = "sof-jsl.ri", .machine_quirk = snd_soc_acpi_codec_list, @@ -58,7 +63,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[] = { .sof_tplg_filename = "sof-jsl-rt5682-rt1015.tplg", }, { - .id = "10EC5682", + .comp_ids = &rt5682_rt5682s_hp, .drv_name = "jsl_rt5682_rt1015p", .sof_fw_filename = "sof-jsl.ri", .machine_quirk = snd_soc_acpi_codec_list, @@ -66,8 +71,8 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[] = { .sof_tplg_filename = "sof-jsl-rt5682-rt1015.tplg", }, { - .id = "10EC5682", - .drv_name = "jsl_rt5682_mx98360a", + .comp_ids = &rt5682_rt5682s_hp, + .drv_name = "jsl_rt5682_mx98360", .sof_fw_filename = "sof-jsl.ri", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &mx98360a_spk, @@ -81,6 +86,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[] = { .quirk_data = &mx98360a_spk, .sof_tplg_filename = "sof-jsl-cs42l42-mx98360a.tplg", }, + { + .id = "ESSX8336", + .drv_name = "sof-essx8336", + .sof_fw_filename = "sof-jsl.ri", + .sof_tplg_filename = "sof-jsl-es8336.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_jsl_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-kbl-match.c b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c index 741bf2f9e081..4e817f559d38 100644 --- a/sound/soc/intel/common/soc-acpi-intel-kbl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c @@ -12,32 +12,32 @@ static struct skl_machine_pdata skl_dmic_data; -static struct snd_soc_acpi_codecs kbl_codecs = { +static const struct snd_soc_acpi_codecs kbl_codecs = { .num_codecs = 1, .codecs = {"10508825"} }; -static struct snd_soc_acpi_codecs kbl_poppy_codecs = { +static const struct snd_soc_acpi_codecs kbl_poppy_codecs = { .num_codecs = 1, .codecs = {"10EC5663"} }; -static struct snd_soc_acpi_codecs kbl_5663_5514_codecs = { +static const struct snd_soc_acpi_codecs kbl_5663_5514_codecs = { .num_codecs = 2, .codecs = {"10EC5663", "10EC5514"} }; -static struct snd_soc_acpi_codecs kbl_7219_98357_codecs = { +static const struct snd_soc_acpi_codecs kbl_7219_98357_codecs = { .num_codecs = 1, .codecs = {"MX98357A"} }; -static struct snd_soc_acpi_codecs kbl_7219_98927_codecs = { +static const struct snd_soc_acpi_codecs kbl_7219_98927_codecs = { .num_codecs = 1, .codecs = {"MX98927"} }; -static struct snd_soc_acpi_codecs kbl_7219_98373_codecs = { +static const struct snd_soc_acpi_codecs kbl_7219_98373_codecs = { .num_codecs = 1, .codecs = {"MX98373"} }; diff --git a/sound/soc/intel/common/soc-acpi-intel-skl-match.c b/sound/soc/intel/common/soc-acpi-intel-skl-match.c index 961df8d6b5e4..75302e956742 100644 --- a/sound/soc/intel/common/soc-acpi-intel-skl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-skl-match.c @@ -12,7 +12,7 @@ static struct skl_machine_pdata skl_dmic_data; -static struct snd_soc_acpi_codecs skl_codecs = { +static const struct snd_soc_acpi_codecs skl_codecs = { .num_codecs = 1, .codecs = {"10508825"} }; diff --git a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c index 785d5f5f8a9c..da31bb3cca17 100644 --- a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c @@ -156,6 +156,15 @@ static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { } }; +static const struct snd_soc_acpi_adr_device rt1316_1_single_adr[] = { + { + .adr = 0x000131025D131601ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1316-1" + } +}; + static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = { { .adr = 0x000131025D131601ull, /* unique ID is set for some reason */ @@ -320,6 +329,25 @@ static const struct snd_soc_acpi_link_adr tgl_3_in_1_sdca[] = { {} }; +static const struct snd_soc_acpi_link_adr tgl_3_in_1_sdca_mono[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_single_adr), + .adr_d = rt1316_1_single_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, + }, + {} +}; + static const struct snd_soc_acpi_codecs tgl_max98373_amp = { .num_codecs = 1, .codecs = {"MX98373"} @@ -330,17 +358,22 @@ static const struct snd_soc_acpi_codecs tgl_rt1011_amp = { .codecs = {"10EC1011"} }; +static const struct snd_soc_acpi_codecs tgl_rt5682_rt5682s_hp = { + .num_codecs = 2, + .codecs = {"10EC5682", "RTL5682"}, +}; + struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[] = { { - .id = "10EC5682", - .drv_name = "tgl_mx98357a_rt5682", + .comp_ids = &tgl_rt5682_rt5682s_hp, + .drv_name = "tgl_mx98357_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &tgl_codecs, .sof_fw_filename = "sof-tgl.ri", .sof_tplg_filename = "sof-tgl-max98357a-rt5682.tplg", }, { - .id = "10EC5682", + .comp_ids = &tgl_rt5682_rt5682s_hp, .drv_name = "tgl_mx98373_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &tgl_max98373_amp, @@ -348,13 +381,19 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[] = { .sof_tplg_filename = "sof-tgl-max98373-rt5682.tplg", }, { - .id = "10EC5682", + .comp_ids = &tgl_rt5682_rt5682s_hp, .drv_name = "tgl_rt1011_rt5682", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &tgl_rt1011_amp, .sof_fw_filename = "sof-tgl.ri", .sof_tplg_filename = "sof-tgl-rt1011-rt5682.tplg", }, + { + .id = "ESSX8336", + .drv_name = "sof-essx8336", + .sof_fw_filename = "sof-tgl.ri", + .sof_tplg_filename = "sof-tgl-es8336.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_tgl_machines); @@ -413,6 +452,19 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_sdw_machines[] = { .sof_tplg_filename = "sof-tgl-rt711-rt1316-rt714.tplg", }, { + /* + * link_mask should be 0xB, but all links are enabled by BIOS. + * This entry will be selected if there is no rt1316 amplifier exposed + * on link2 since it will fail to match the above entry. + */ + + .link_mask = 0xF, /* 4 active links required */ + .links = tgl_3_in_1_sdca_mono, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt711-l0-rt1316-l1-mono-rt714-l3.tplg", + }, + + { .link_mask = 0x3, /* rt711 on link 0 and 1 rt1308 on link 1 */ .links = tgl_hp, .drv_name = "sof_sdw", diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c index b036852d6889..89e4231304dd 100644 --- a/sound/soc/intel/skylake/skl-topology.c +++ b/sound/soc/intel/skylake/skl-topology.c @@ -3637,7 +3637,7 @@ static int skl_manifest_load(struct snd_soc_component *cmpnt, int index, return 0; } -static void skl_tplg_complete(struct snd_soc_component *component) +static int skl_tplg_complete(struct snd_soc_component *component) { struct snd_soc_dobj *dobj; struct snd_soc_acpi_mach *mach; @@ -3646,7 +3646,7 @@ static void skl_tplg_complete(struct snd_soc_component *component) val = kmalloc(sizeof(*val), GFP_KERNEL); if (!val) - return; + return -ENOMEM; mach = dev_get_platdata(component->card->dev); list_for_each_entry(dobj, &component->dobj_list, list) { @@ -3671,7 +3671,9 @@ static void skl_tplg_complete(struct snd_soc_component *component) } } } + kfree(val); + return 0; } static struct snd_soc_tplg_ops skl_tplg_ops = { diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 81ad2dcee9eb..3b1ddea26a9e 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -120,7 +120,7 @@ config SND_SOC_MT8183 config SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A tristate "ASoC Audio driver for MT8183 with MT6358 TS3A227E MAX98357A RT1015 codec" - depends on I2C + depends on I2C && GPIOLIB depends on SND_SOC_MT8183 select SND_SOC_MT6358 select SND_SOC_MAX98357A @@ -138,7 +138,7 @@ config SND_SOC_MT8183_MT6358_TS3A227E_MAX98357A config SND_SOC_MT8183_DA7219_MAX98357A tristate "ASoC Audio driver for MT8183 with DA7219 MAX98357A RT1015 codec" - depends on SND_SOC_MT8183 && I2C + depends on SND_SOC_MT8183 && I2C && GPIOLIB select SND_SOC_MT6358 select SND_SOC_MAX98357A select SND_SOC_RT1015 @@ -173,7 +173,7 @@ config SND_SOC_MT8192 config SND_SOC_MT8192_MT6359_RT1015_RT5682 tristate "ASoC Audio driver for MT8192 with MT6359 RT1015 RT5682 codec" - depends on I2C + depends on I2C && GPIOLIB depends on SND_SOC_MT8192 && MTK_PMIC_WRAP select SND_SOC_MT6359 select SND_SOC_RT1015 @@ -200,7 +200,7 @@ config SND_SOC_MT8195 config SND_SOC_MT8195_MT6359_RT1019_RT5682 tristate "ASoC Audio driver for MT8195 with MT6359 RT1019 RT5682 codec" - depends on I2C + depends on I2C && GPIOLIB depends on SND_SOC_MT8195 && MTK_PMIC_WRAP select SND_SOC_MT6359 select SND_SOC_RT1015P @@ -212,3 +212,18 @@ config SND_SOC_MT8195_MT6359_RT1019_RT5682 with the MT6359 RT1019 RT5682 audio codec. Select Y if you have such device. If unsure select "N". + +config SND_SOC_MT8195_MT6359_RT1011_RT5682 + tristate "ASoC Audio driver for MT8195 with MT6359 RT1011 RT5682 codec" + depends on I2C + depends on SND_SOC_MT8195 && MTK_PMIC_WRAP + select SND_SOC_MT6359 + select SND_SOC_RT1011 + select SND_SOC_RT5682_I2C + select SND_SOC_DMIC + select SND_SOC_HDMI_CODEC + help + This adds ASoC driver for Mediatek MT8195 boards + with the MT6359 RT1011 RT5682 audio codec. + Select Y if you have such device. + If unsure select "N". diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index e95c7c018e7d..395be97f13ae 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -288,7 +288,6 @@ const struct snd_soc_dai_ops mtk_afe_fe_ops = { }; EXPORT_SYMBOL_GPL(mtk_afe_fe_ops); -static DEFINE_MUTEX(irqs_lock); int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe) { int i; @@ -351,7 +350,7 @@ int mtk_afe_resume(struct snd_soc_component *component) struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); struct device *dev = afe->dev; struct regmap *regmap = afe->regmap; - int i = 0; + int i; if (pm_runtime_status_suspended(dev) || !afe->suspended) return 0; diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c index 44a8d5cfb0aa..d9fd6eb786aa 100644 --- a/sound/soc/mediatek/mt2701/mt2701-cs42448.c +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -146,7 +146,7 @@ static int mt2701_cs42448_be_ops_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt2701_cs42448_be_ops = { +static const struct snd_soc_ops mt2701_cs42448_be_ops = { .hw_params = mt2701_cs42448_be_ops_hw_params }; diff --git a/sound/soc/mediatek/mt2701/mt2701-wm8960.c b/sound/soc/mediatek/mt2701/mt2701-wm8960.c index 414e422c0eba..f56de1b918bf 100644 --- a/sound/soc/mediatek/mt2701/mt2701-wm8960.c +++ b/sound/soc/mediatek/mt2701/mt2701-wm8960.c @@ -40,7 +40,7 @@ static int mt2701_wm8960_be_ops_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt2701_wm8960_be_ops = { +static const struct snd_soc_ops mt2701_wm8960_be_ops = { .hw_params = mt2701_wm8960_be_ops_hw_params }; diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c index 94dcbd36c869..aeb1af86047e 100644 --- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c @@ -335,7 +335,7 @@ static void mt8183_mt6358_tdm_shutdown(struct snd_pcm_substream *substream) __func__, ret); } -static struct snd_soc_ops mt8183_mt6358_tdm_ops = { +static const struct snd_soc_ops mt8183_mt6358_tdm_ops = { .startup = mt8183_mt6358_tdm_startup, .shutdown = mt8183_mt6358_tdm_shutdown, }; diff --git a/sound/soc/mediatek/mt8195/Makefile b/sound/soc/mediatek/mt8195/Makefile index 44775f400b40..e5f0df5010b6 100644 --- a/sound/soc/mediatek/mt8195/Makefile +++ b/sound/soc/mediatek/mt8195/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_SND_SOC_MT8195) += snd-soc-mt8195-afe.o # machine driver obj-$(CONFIG_SND_SOC_MT8195_MT6359_RT1019_RT5682) += mt8195-mt6359-rt1019-rt5682.o +obj-$(CONFIG_SND_SOC_MT8195_MT6359_RT1011_RT5682) += mt8195-mt6359-rt1011-rt5682.o diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index 6635c3f72ecc..2bb05a828e8d 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -2232,7 +2232,7 @@ static const struct mtk_base_memif_data memif_data[MT8195_AFE_MEMIF_NUM] = { }, }; -static const struct mtk_base_irq_data irq_data[MT8195_AFE_IRQ_NUM] = { +static const struct mtk_base_irq_data irq_data_array[MT8195_AFE_IRQ_NUM] = { [MT8195_AFE_IRQ_1] = { .id = MT8195_AFE_IRQ_1, .irq_cnt_reg = -1, @@ -3057,7 +3057,6 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) { struct mtk_base_afe *afe; struct mt8195_afe_private *afe_priv; - struct resource *res; struct device *dev = &pdev->dev; int i, irq_id, ret; struct snd_soc_component *component; @@ -3078,8 +3077,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) afe_priv = afe->platform_priv; afe->dev = &pdev->dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - afe->base_addr = devm_ioremap_resource(&pdev->dev, res); + afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) return PTR_ERR(afe->base_addr); @@ -3102,7 +3100,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) return -ENOMEM; for (i = 0; i < afe->irqs_size; i++) - afe->irqs[i].irq_data = &irq_data[i]; + afe->irqs[i].irq_data = &irq_data_array[i]; /* init memif */ afe->memif_size = MT8195_AFE_MEMIF_NUM; @@ -3266,9 +3264,7 @@ static struct platform_driver mt8195_afe_pcm_driver = { .driver = { .name = "mt8195-audio", .of_match_table = mt8195_afe_pcm_dt_match, -#ifdef CONFIG_PM .pm = &mt8195_afe_pm_ops, -#endif }, .probe = mt8195_afe_pcm_dev_probe, .remove = mt8195_afe_pcm_dev_remove, diff --git a/sound/soc/mediatek/mt8195/mt8195-audsys-clk.c b/sound/soc/mediatek/mt8195/mt8195-audsys-clk.c index 740aa6ddda0e..e0670e0dbd5b 100644 --- a/sound/soc/mediatek/mt8195/mt8195-audsys-clk.c +++ b/sound/soc/mediatek/mt8195/mt8195-audsys-clk.c @@ -59,93 +59,93 @@ struct afe_gate { static const struct afe_gate aud_clks[CLK_AUD_NR_CLK] = { /* AUD0 */ - GATE_AUD0(CLK_AUD_AFE, "aud_afe", "a1sys_hp_sel", 2), - GATE_AUD0(CLK_AUD_LRCK_CNT, "aud_lrck_cnt", "a1sys_hp_sel", 4), - GATE_AUD0(CLK_AUD_SPDIFIN_TUNER_APLL, "aud_spdifin_tuner_apll", "apll4_sel", 10), - GATE_AUD0(CLK_AUD_SPDIFIN_TUNER_DBG, "aud_spdifin_tuner_dbg", "apll4_sel", 11), - GATE_AUD0(CLK_AUD_UL_TML, "aud_ul_tml", "a1sys_hp_sel", 18), - GATE_AUD0(CLK_AUD_APLL1_TUNER, "aud_apll1_tuner", "apll1_sel", 19), - GATE_AUD0(CLK_AUD_APLL2_TUNER, "aud_apll2_tuner", "apll2_sel", 20), - GATE_AUD0(CLK_AUD_TOP0_SPDF, "aud_top0_spdf", "aud_iec_sel", 21), - GATE_AUD0(CLK_AUD_APLL, "aud_apll", "apll1_sel", 23), - GATE_AUD0(CLK_AUD_APLL2, "aud_apll2", "apll2_sel", 24), - GATE_AUD0(CLK_AUD_DAC, "aud_dac", "a1sys_hp_sel", 25), - GATE_AUD0(CLK_AUD_DAC_PREDIS, "aud_dac_predis", "a1sys_hp_sel", 26), - GATE_AUD0(CLK_AUD_TML, "aud_tml", "a1sys_hp_sel", 27), - GATE_AUD0(CLK_AUD_ADC, "aud_adc", "a1sys_hp_sel", 28), - GATE_AUD0(CLK_AUD_DAC_HIRES, "aud_dac_hires", "audio_h_sel", 31), + GATE_AUD0(CLK_AUD_AFE, "aud_afe", "top_a1sys_hp", 2), + GATE_AUD0(CLK_AUD_LRCK_CNT, "aud_lrck_cnt", "top_a1sys_hp", 4), + GATE_AUD0(CLK_AUD_SPDIFIN_TUNER_APLL, "aud_spdifin_tuner_apll", "top_apll4", 10), + GATE_AUD0(CLK_AUD_SPDIFIN_TUNER_DBG, "aud_spdifin_tuner_dbg", "top_apll4", 11), + GATE_AUD0(CLK_AUD_UL_TML, "aud_ul_tml", "top_a1sys_hp", 18), + GATE_AUD0(CLK_AUD_APLL1_TUNER, "aud_apll1_tuner", "top_apll1", 19), + GATE_AUD0(CLK_AUD_APLL2_TUNER, "aud_apll2_tuner", "top_apll2", 20), + GATE_AUD0(CLK_AUD_TOP0_SPDF, "aud_top0_spdf", "top_aud_iec_clk", 21), + GATE_AUD0(CLK_AUD_APLL, "aud_apll", "top_apll1", 23), + GATE_AUD0(CLK_AUD_APLL2, "aud_apll2", "top_apll2", 24), + GATE_AUD0(CLK_AUD_DAC, "aud_dac", "top_a1sys_hp", 25), + GATE_AUD0(CLK_AUD_DAC_PREDIS, "aud_dac_predis", "top_a1sys_hp", 26), + GATE_AUD0(CLK_AUD_TML, "aud_tml", "top_a1sys_hp", 27), + GATE_AUD0(CLK_AUD_ADC, "aud_adc", "top_a1sys_hp", 28), + GATE_AUD0(CLK_AUD_DAC_HIRES, "aud_dac_hires", "top_audio_h", 31), /* AUD1 */ - GATE_AUD1(CLK_AUD_A1SYS_HP, "aud_a1sys_hp", "a1sys_hp_sel", 2), - GATE_AUD1(CLK_AUD_AFE_DMIC1, "aud_afe_dmic1", "a1sys_hp_sel", 10), - GATE_AUD1(CLK_AUD_AFE_DMIC2, "aud_afe_dmic2", "a1sys_hp_sel", 11), - GATE_AUD1(CLK_AUD_AFE_DMIC3, "aud_afe_dmic3", "a1sys_hp_sel", 12), - GATE_AUD1(CLK_AUD_AFE_DMIC4, "aud_afe_dmic4", "a1sys_hp_sel", 13), - GATE_AUD1(CLK_AUD_AFE_26M_DMIC_TM, "aud_afe_26m_dmic_tm", "a1sys_hp_sel", 14), - GATE_AUD1(CLK_AUD_UL_TML_HIRES, "aud_ul_tml_hires", "audio_h_sel", 16), - GATE_AUD1(CLK_AUD_ADC_HIRES, "aud_adc_hires", "audio_h_sel", 17), - GATE_AUD1(CLK_AUD_ADDA6_ADC, "aud_adda6_adc", "a1sys_hp_sel", 18), - GATE_AUD1(CLK_AUD_ADDA6_ADC_HIRES, "aud_adda6_adc_hires", "audio_h_sel", 19), + GATE_AUD1(CLK_AUD_A1SYS_HP, "aud_a1sys_hp", "top_a1sys_hp", 2), + GATE_AUD1(CLK_AUD_AFE_DMIC1, "aud_afe_dmic1", "top_a1sys_hp", 10), + GATE_AUD1(CLK_AUD_AFE_DMIC2, "aud_afe_dmic2", "top_a1sys_hp", 11), + GATE_AUD1(CLK_AUD_AFE_DMIC3, "aud_afe_dmic3", "top_a1sys_hp", 12), + GATE_AUD1(CLK_AUD_AFE_DMIC4, "aud_afe_dmic4", "top_a1sys_hp", 13), + GATE_AUD1(CLK_AUD_AFE_26M_DMIC_TM, "aud_afe_26m_dmic_tm", "top_a1sys_hp", 14), + GATE_AUD1(CLK_AUD_UL_TML_HIRES, "aud_ul_tml_hires", "top_audio_h", 16), + GATE_AUD1(CLK_AUD_ADC_HIRES, "aud_adc_hires", "top_audio_h", 17), + GATE_AUD1(CLK_AUD_ADDA6_ADC, "aud_adda6_adc", "top_a1sys_hp", 18), + GATE_AUD1(CLK_AUD_ADDA6_ADC_HIRES, "aud_adda6_adc_hires", "top_audio_h", 19), /* AUD3 */ - GATE_AUD3(CLK_AUD_LINEIN_TUNER, "aud_linein_tuner", "apll5_sel", 5), - GATE_AUD3(CLK_AUD_EARC_TUNER, "aud_earc_tuner", "apll3_sel", 7), + GATE_AUD3(CLK_AUD_LINEIN_TUNER, "aud_linein_tuner", "top_apll5", 5), + GATE_AUD3(CLK_AUD_EARC_TUNER, "aud_earc_tuner", "top_apll3", 7), /* AUD4 */ - GATE_AUD4(CLK_AUD_I2SIN, "aud_i2sin", "a1sys_hp_sel", 0), - GATE_AUD4(CLK_AUD_TDM_IN, "aud_tdm_in", "a1sys_hp_sel", 1), - GATE_AUD4(CLK_AUD_I2S_OUT, "aud_i2s_out", "a1sys_hp_sel", 6), - GATE_AUD4(CLK_AUD_TDM_OUT, "aud_tdm_out", "a1sys_hp_sel", 7), - GATE_AUD4(CLK_AUD_HDMI_OUT, "aud_hdmi_out", "a1sys_hp_sel", 8), - GATE_AUD4(CLK_AUD_ASRC11, "aud_asrc11", "a1sys_hp_sel", 16), - GATE_AUD4(CLK_AUD_ASRC12, "aud_asrc12", "a1sys_hp_sel", 17), + GATE_AUD4(CLK_AUD_I2SIN, "aud_i2sin", "top_a1sys_hp", 0), + GATE_AUD4(CLK_AUD_TDM_IN, "aud_tdm_in", "top_a1sys_hp", 1), + GATE_AUD4(CLK_AUD_I2S_OUT, "aud_i2s_out", "top_a1sys_hp", 6), + GATE_AUD4(CLK_AUD_TDM_OUT, "aud_tdm_out", "top_a1sys_hp", 7), + GATE_AUD4(CLK_AUD_HDMI_OUT, "aud_hdmi_out", "top_a1sys_hp", 8), + GATE_AUD4(CLK_AUD_ASRC11, "aud_asrc11", "top_a1sys_hp", 16), + GATE_AUD4(CLK_AUD_ASRC12, "aud_asrc12", "top_a1sys_hp", 17), GATE_AUD4(CLK_AUD_MULTI_IN, "aud_multi_in", "mphone_slave_b", 19), - GATE_AUD4(CLK_AUD_INTDIR, "aud_intdir", "intdir_sel", 20), - GATE_AUD4(CLK_AUD_A1SYS, "aud_a1sys", "a1sys_hp_sel", 21), - GATE_AUD4(CLK_AUD_A2SYS, "aud_a2sys", "a2sys_sel", 22), - GATE_AUD4(CLK_AUD_PCMIF, "aud_pcmif", "a1sys_hp_sel", 24), - GATE_AUD4(CLK_AUD_A3SYS, "aud_a3sys", "a3sys_sel", 30), - GATE_AUD4(CLK_AUD_A4SYS, "aud_a4sys", "a4sys_sel", 31), + GATE_AUD4(CLK_AUD_INTDIR, "aud_intdir", "top_intdir", 20), + GATE_AUD4(CLK_AUD_A1SYS, "aud_a1sys", "top_a1sys_hp", 21), + GATE_AUD4(CLK_AUD_A2SYS, "aud_a2sys", "top_a2sys_hf", 22), + GATE_AUD4(CLK_AUD_PCMIF, "aud_pcmif", "top_a1sys_hp", 24), + GATE_AUD4(CLK_AUD_A3SYS, "aud_a3sys", "top_a3sys_hf", 30), + GATE_AUD4(CLK_AUD_A4SYS, "aud_a4sys", "top_a4sys_hf", 31), /* AUD5 */ - GATE_AUD5(CLK_AUD_MEMIF_UL1, "aud_memif_ul1", "a1sys_hp_sel", 0), - GATE_AUD5(CLK_AUD_MEMIF_UL2, "aud_memif_ul2", "a1sys_hp_sel", 1), - GATE_AUD5(CLK_AUD_MEMIF_UL3, "aud_memif_ul3", "a1sys_hp_sel", 2), - GATE_AUD5(CLK_AUD_MEMIF_UL4, "aud_memif_ul4", "a1sys_hp_sel", 3), - GATE_AUD5(CLK_AUD_MEMIF_UL5, "aud_memif_ul5", "a1sys_hp_sel", 4), - GATE_AUD5(CLK_AUD_MEMIF_UL6, "aud_memif_ul6", "a1sys_hp_sel", 5), - GATE_AUD5(CLK_AUD_MEMIF_UL8, "aud_memif_ul8", "a1sys_hp_sel", 7), - GATE_AUD5(CLK_AUD_MEMIF_UL9, "aud_memif_ul9", "a1sys_hp_sel", 8), - GATE_AUD5(CLK_AUD_MEMIF_UL10, "aud_memif_ul10", "a1sys_hp_sel", 9), - GATE_AUD5(CLK_AUD_MEMIF_DL2, "aud_memif_dl2", "a1sys_hp_sel", 18), - GATE_AUD5(CLK_AUD_MEMIF_DL3, "aud_memif_dl3", "a1sys_hp_sel", 19), - GATE_AUD5(CLK_AUD_MEMIF_DL6, "aud_memif_dl6", "a1sys_hp_sel", 22), - GATE_AUD5(CLK_AUD_MEMIF_DL7, "aud_memif_dl7", "a1sys_hp_sel", 23), - GATE_AUD5(CLK_AUD_MEMIF_DL8, "aud_memif_dl8", "a1sys_hp_sel", 24), - GATE_AUD5(CLK_AUD_MEMIF_DL10, "aud_memif_dl10", "a1sys_hp_sel", 26), - GATE_AUD5(CLK_AUD_MEMIF_DL11, "aud_memif_dl11", "a1sys_hp_sel", 27), + GATE_AUD5(CLK_AUD_MEMIF_UL1, "aud_memif_ul1", "top_a1sys_hp", 0), + GATE_AUD5(CLK_AUD_MEMIF_UL2, "aud_memif_ul2", "top_a1sys_hp", 1), + GATE_AUD5(CLK_AUD_MEMIF_UL3, "aud_memif_ul3", "top_a1sys_hp", 2), + GATE_AUD5(CLK_AUD_MEMIF_UL4, "aud_memif_ul4", "top_a1sys_hp", 3), + GATE_AUD5(CLK_AUD_MEMIF_UL5, "aud_memif_ul5", "top_a1sys_hp", 4), + GATE_AUD5(CLK_AUD_MEMIF_UL6, "aud_memif_ul6", "top_a1sys_hp", 5), + GATE_AUD5(CLK_AUD_MEMIF_UL8, "aud_memif_ul8", "top_a1sys_hp", 7), + GATE_AUD5(CLK_AUD_MEMIF_UL9, "aud_memif_ul9", "top_a1sys_hp", 8), + GATE_AUD5(CLK_AUD_MEMIF_UL10, "aud_memif_ul10", "top_a1sys_hp", 9), + GATE_AUD5(CLK_AUD_MEMIF_DL2, "aud_memif_dl2", "top_a1sys_hp", 18), + GATE_AUD5(CLK_AUD_MEMIF_DL3, "aud_memif_dl3", "top_a1sys_hp", 19), + GATE_AUD5(CLK_AUD_MEMIF_DL6, "aud_memif_dl6", "top_a1sys_hp", 22), + GATE_AUD5(CLK_AUD_MEMIF_DL7, "aud_memif_dl7", "top_a1sys_hp", 23), + GATE_AUD5(CLK_AUD_MEMIF_DL8, "aud_memif_dl8", "top_a1sys_hp", 24), + GATE_AUD5(CLK_AUD_MEMIF_DL10, "aud_memif_dl10", "top_a1sys_hp", 26), + GATE_AUD5(CLK_AUD_MEMIF_DL11, "aud_memif_dl11", "top_a1sys_hp", 27), /* AUD6 */ - GATE_AUD6(CLK_AUD_GASRC0, "aud_gasrc0", "asm_h_sel", 0), - GATE_AUD6(CLK_AUD_GASRC1, "aud_gasrc1", "asm_h_sel", 1), - GATE_AUD6(CLK_AUD_GASRC2, "aud_gasrc2", "asm_h_sel", 2), - GATE_AUD6(CLK_AUD_GASRC3, "aud_gasrc3", "asm_h_sel", 3), - GATE_AUD6(CLK_AUD_GASRC4, "aud_gasrc4", "asm_h_sel", 4), - GATE_AUD6(CLK_AUD_GASRC5, "aud_gasrc5", "asm_h_sel", 5), - GATE_AUD6(CLK_AUD_GASRC6, "aud_gasrc6", "asm_h_sel", 6), - GATE_AUD6(CLK_AUD_GASRC7, "aud_gasrc7", "asm_h_sel", 7), - GATE_AUD6(CLK_AUD_GASRC8, "aud_gasrc8", "asm_h_sel", 8), - GATE_AUD6(CLK_AUD_GASRC9, "aud_gasrc9", "asm_h_sel", 9), - GATE_AUD6(CLK_AUD_GASRC10, "aud_gasrc10", "asm_h_sel", 10), - GATE_AUD6(CLK_AUD_GASRC11, "aud_gasrc11", "asm_h_sel", 11), - GATE_AUD6(CLK_AUD_GASRC12, "aud_gasrc12", "asm_h_sel", 12), - GATE_AUD6(CLK_AUD_GASRC13, "aud_gasrc13", "asm_h_sel", 13), - GATE_AUD6(CLK_AUD_GASRC14, "aud_gasrc14", "asm_h_sel", 14), - GATE_AUD6(CLK_AUD_GASRC15, "aud_gasrc15", "asm_h_sel", 15), - GATE_AUD6(CLK_AUD_GASRC16, "aud_gasrc16", "asm_h_sel", 16), - GATE_AUD6(CLK_AUD_GASRC17, "aud_gasrc17", "asm_h_sel", 17), - GATE_AUD6(CLK_AUD_GASRC18, "aud_gasrc18", "asm_h_sel", 18), - GATE_AUD6(CLK_AUD_GASRC19, "aud_gasrc19", "asm_h_sel", 19), + GATE_AUD6(CLK_AUD_GASRC0, "aud_gasrc0", "top_asm_h", 0), + GATE_AUD6(CLK_AUD_GASRC1, "aud_gasrc1", "top_asm_h", 1), + GATE_AUD6(CLK_AUD_GASRC2, "aud_gasrc2", "top_asm_h", 2), + GATE_AUD6(CLK_AUD_GASRC3, "aud_gasrc3", "top_asm_h", 3), + GATE_AUD6(CLK_AUD_GASRC4, "aud_gasrc4", "top_asm_h", 4), + GATE_AUD6(CLK_AUD_GASRC5, "aud_gasrc5", "top_asm_h", 5), + GATE_AUD6(CLK_AUD_GASRC6, "aud_gasrc6", "top_asm_h", 6), + GATE_AUD6(CLK_AUD_GASRC7, "aud_gasrc7", "top_asm_h", 7), + GATE_AUD6(CLK_AUD_GASRC8, "aud_gasrc8", "top_asm_h", 8), + GATE_AUD6(CLK_AUD_GASRC9, "aud_gasrc9", "top_asm_h", 9), + GATE_AUD6(CLK_AUD_GASRC10, "aud_gasrc10", "top_asm_h", 10), + GATE_AUD6(CLK_AUD_GASRC11, "aud_gasrc11", "top_asm_h", 11), + GATE_AUD6(CLK_AUD_GASRC12, "aud_gasrc12", "top_asm_h", 12), + GATE_AUD6(CLK_AUD_GASRC13, "aud_gasrc13", "top_asm_h", 13), + GATE_AUD6(CLK_AUD_GASRC14, "aud_gasrc14", "top_asm_h", 14), + GATE_AUD6(CLK_AUD_GASRC15, "aud_gasrc15", "top_asm_h", 15), + GATE_AUD6(CLK_AUD_GASRC16, "aud_gasrc16", "top_asm_h", 16), + GATE_AUD6(CLK_AUD_GASRC17, "aud_gasrc17", "top_asm_h", 17), + GATE_AUD6(CLK_AUD_GASRC18, "aud_gasrc18", "top_asm_h", 18), + GATE_AUD6(CLK_AUD_GASRC19, "aud_gasrc19", "top_asm_h", 19), }; int mt8195_audsys_clk_register(struct mtk_base_afe *afe) diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-adda.c b/sound/soc/mediatek/mt8195/mt8195-dai-adda.c index 878dec0b69ed..f04bd1781356 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-adda.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-adda.c @@ -788,9 +788,11 @@ static int init_adda_priv_data(struct mtk_base_afe *afe) { struct mt8195_afe_private *afe_priv = afe->platform_priv; struct mtk_dai_adda_priv *adda_priv; - int adda_dai_list[] = { MT8195_AFE_IO_DL_SRC, - MT8195_AFE_IO_UL_SRC1, - MT8195_AFE_IO_UL_SRC2}; + static const int adda_dai_list[] = { + MT8195_AFE_IO_DL_SRC, + MT8195_AFE_IO_UL_SRC1, + MT8195_AFE_IO_UL_SRC2 + }; int i; for (i = 0; i < ARRAY_SIZE(adda_dai_list); i++) { diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c index 7378e42f2766..c02c10da3600 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c @@ -1316,7 +1316,7 @@ static int mt8195_afe_disable_etdm(struct mtk_base_afe *afe, int dai_id) } out: spin_unlock_irqrestore(&afe_priv->afe_ctrl_lock, flags); - return 0; + return ret; } static int etdm_cowork_slv_sel(int id, int slave_mode) @@ -2094,7 +2094,7 @@ static int mtk_dai_etdm_set_sysclk(struct snd_soc_dai *dai, { struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); struct mt8195_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id]; + struct mtk_dai_etdm_priv *etdm_data; int dai_id; dev_dbg(dai->dev, "%s id %d freq %u, dir %d\n", diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359-rt1011-rt5682.c b/sound/soc/mediatek/mt8195/mt8195-mt6359-rt1011-rt5682.c new file mode 100644 index 000000000000..e103102d7ef6 --- /dev/null +++ b/sound/soc/mediatek/mt8195/mt8195-mt6359-rt1011-rt5682.c @@ -0,0 +1,1155 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt8195-mt6359-rt1011-rt5682.c -- +// MT8195-MT6359-RT1011-RT5682 ALSA SoC machine driver +// +// Copyright (c) 2021 MediaTek Inc. +// Author: Trevor Wu <trevor.wu@mediatek.com> +// + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/rt5682.h> +#include <sound/soc.h> +#include "../../codecs/mt6359.h" +#include "../../codecs/rt1011.h" +#include "../../codecs/rt5682.h" +#include "../common/mtk-afe-platform-driver.h" +#include "mt8195-afe-common.h" + +#define RT1011_CODEC_DAI "rt1011-aif" +#define RT1011_DEV0_NAME "rt1011.2-0038" +#define RT1011_DEV1_NAME "rt1011.2-0039" + +#define RT5682_CODEC_DAI "rt5682-aif1" +#define RT5682_DEV0_NAME "rt5682.2-001a" + +struct mt8195_mt6359_rt1011_rt5682_priv { + struct device_node *platform_node; + struct device_node *hdmi_node; + struct device_node *dp_node; + struct snd_soc_jack headset_jack; + struct snd_soc_jack dp_jack; + struct snd_soc_jack hdmi_jack; +}; + +static const struct snd_soc_dapm_widget +mt8195_mt6359_rt1011_rt5682_widgets[] = { + SND_SOC_DAPM_SPK("Left Speaker", NULL), + SND_SOC_DAPM_SPK("Right Speaker", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8195_mt6359_rt1011_rt5682_routes[] = { + /* speaker */ + { "Left Speaker", NULL, "Left SPO" }, + { "Right Speaker", NULL, "Right SPO" }, + /* headset */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + { "IN1P", NULL, "Headset Mic" }, +}; + +static const struct snd_kcontrol_new mt8195_mt6359_rt1011_rt5682_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Speaker"), + SOC_DAPM_PIN_SWITCH("Right Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8195_rt5682_etdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + unsigned int rate = params_rate(params); + int bitwidth; + int ret; + + bitwidth = snd_pcm_format_width(params_format(params)); + if (bitwidth < 0) { + dev_err(card->dev, "invalid bit width: %d\n", bitwidth); + return bitwidth; + } + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x00, 0x0, 0x2, bitwidth); + if (ret) { + dev_err(card->dev, "failed to set tdm slot\n"); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, RT5682_PLL1, RT5682_PLL1_S_BCLK1, + rate * 64, rate * 512); + if (ret) { + dev_err(card->dev, "failed to set pll\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, + rate * 512, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "failed to set sysclk\n"); + return ret; + } + + return snd_soc_dai_set_sysclk(cpu_dai, 0, rate * 128, + SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops mt8195_rt5682_etdm_ops = { + .hw_params = mt8195_rt5682_etdm_hw_params, +}; + +static int mt8195_rt1011_etdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + struct snd_soc_card *card = rtd->card; + int srate, i, ret = 0; + + srate = params_rate(params); + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_pll(codec_dai, 0, RT1011_PLL1_S_BCLK, + 64 * srate, 256 * srate); + if (ret < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + RT1011_FS_SYS_PRE_S_PLL1, + 256 * srate, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return ret; + } + } + return ret; +} + +static const struct snd_soc_ops mt8195_rt1011_etdm_ops = { + .hw_params = mt8195_rt1011_etdm_hw_params, +}; + +#define CKSYS_AUD_TOP_CFG 0x032c +#define CKSYS_AUD_TOP_MON 0x0330 + +static int mt8195_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *cmpnt_afe = + snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct snd_soc_component *cmpnt_codec = + asoc_rtd_to_codec(rtd, 0)->component; + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt_afe); + struct mt8195_afe_private *afe_priv = afe->platform_priv; + struct mtkaif_param *param = &afe_priv->mtkaif_params; + int chosen_phase_1, chosen_phase_2, chosen_phase_3; + int prev_cycle_1, prev_cycle_2, prev_cycle_3; + int test_done_1, test_done_2, test_done_3; + int cycle_1, cycle_2, cycle_3; + int mtkaif_chosen_phase[MT8195_MTKAIF_MISO_NUM]; + int mtkaif_phase_cycle[MT8195_MTKAIF_MISO_NUM]; + int mtkaif_calibration_num_phase; + bool mtkaif_calibration_ok; + unsigned int monitor; + int counter; + int phase; + int i; + + dev_dbg(afe->dev, "%s(), start\n", __func__); + + param->mtkaif_calibration_ok = false; + for (i = 0; i < MT8195_MTKAIF_MISO_NUM; i++) { + param->mtkaif_chosen_phase[i] = -1; + param->mtkaif_phase_cycle[i] = 0; + mtkaif_chosen_phase[i] = -1; + mtkaif_phase_cycle[i] = 0; + } + + if (IS_ERR(afe_priv->topckgen)) { + dev_info(afe->dev, "%s() Cannot find topckgen controller\n", + __func__); + return 0; + } + + pm_runtime_get_sync(afe->dev); + mt6359_mtkaif_calibration_enable(cmpnt_codec); + + /* set test type to synchronizer pulse */ + regmap_update_bits(afe_priv->topckgen, + CKSYS_AUD_TOP_CFG, 0xffff, 0x4); + mtkaif_calibration_num_phase = 42; /* mt6359: 0 ~ 42 */ + mtkaif_calibration_ok = true; + + for (phase = 0; + phase <= mtkaif_calibration_num_phase && mtkaif_calibration_ok; + phase++) { + mt6359_set_mtkaif_calibration_phase(cmpnt_codec, + phase, phase, phase); + + regmap_update_bits(afe_priv->topckgen, + CKSYS_AUD_TOP_CFG, 0x1, 0x1); + + test_done_1 = 0; + test_done_2 = 0; + test_done_3 = 0; + cycle_1 = -1; + cycle_2 = -1; + cycle_3 = -1; + counter = 0; + while (!(test_done_1 & test_done_2 & test_done_3)) { + regmap_read(afe_priv->topckgen, + CKSYS_AUD_TOP_MON, &monitor); + test_done_1 = (monitor >> 28) & 0x1; + test_done_2 = (monitor >> 29) & 0x1; + test_done_3 = (monitor >> 30) & 0x1; + if (test_done_1 == 1) + cycle_1 = monitor & 0xf; + + if (test_done_2 == 1) + cycle_2 = (monitor >> 4) & 0xf; + + if (test_done_3 == 1) + cycle_3 = (monitor >> 8) & 0xf; + + /* handle if never test done */ + if (++counter > 10000) { + dev_info(afe->dev, "%s(), test fail, cycle_1 %d, cycle_2 %d, cycle_3 %d, monitor 0x%x\n", + __func__, + cycle_1, cycle_2, cycle_3, monitor); + mtkaif_calibration_ok = false; + break; + } + } + + if (phase == 0) { + prev_cycle_1 = cycle_1; + prev_cycle_2 = cycle_2; + prev_cycle_3 = cycle_3; + } + + if (cycle_1 != prev_cycle_1 && + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_0] < 0) { + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_0] = phase - 1; + mtkaif_phase_cycle[MT8195_MTKAIF_MISO_0] = prev_cycle_1; + } + + if (cycle_2 != prev_cycle_2 && + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_1] < 0) { + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_1] = phase - 1; + mtkaif_phase_cycle[MT8195_MTKAIF_MISO_1] = prev_cycle_2; + } + + if (cycle_3 != prev_cycle_3 && + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_2] < 0) { + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_2] = phase - 1; + mtkaif_phase_cycle[MT8195_MTKAIF_MISO_2] = prev_cycle_3; + } + + regmap_update_bits(afe_priv->topckgen, + CKSYS_AUD_TOP_CFG, 0x1, 0x0); + + if (mtkaif_chosen_phase[MT8195_MTKAIF_MISO_0] >= 0 && + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_1] >= 0 && + mtkaif_chosen_phase[MT8195_MTKAIF_MISO_2] >= 0) + break; + } + + if (mtkaif_chosen_phase[MT8195_MTKAIF_MISO_0] < 0) { + mtkaif_calibration_ok = false; + chosen_phase_1 = 0; + } else { + chosen_phase_1 = mtkaif_chosen_phase[MT8195_MTKAIF_MISO_0]; + } + + if (mtkaif_chosen_phase[MT8195_MTKAIF_MISO_1] < 0) { + mtkaif_calibration_ok = false; + chosen_phase_2 = 0; + } else { + chosen_phase_2 = mtkaif_chosen_phase[MT8195_MTKAIF_MISO_1]; + } + + if (mtkaif_chosen_phase[MT8195_MTKAIF_MISO_2] < 0) { + mtkaif_calibration_ok = false; + chosen_phase_3 = 0; + } else { + chosen_phase_3 = mtkaif_chosen_phase[MT8195_MTKAIF_MISO_2]; + } + + mt6359_set_mtkaif_calibration_phase(cmpnt_codec, + chosen_phase_1, + chosen_phase_2, + chosen_phase_3); + + mt6359_mtkaif_calibration_disable(cmpnt_codec); + pm_runtime_put(afe->dev); + + param->mtkaif_calibration_ok = mtkaif_calibration_ok; + param->mtkaif_chosen_phase[MT8195_MTKAIF_MISO_0] = chosen_phase_1; + param->mtkaif_chosen_phase[MT8195_MTKAIF_MISO_1] = chosen_phase_2; + param->mtkaif_chosen_phase[MT8195_MTKAIF_MISO_2] = chosen_phase_3; + for (i = 0; i < MT8195_MTKAIF_MISO_NUM; i++) + param->mtkaif_phase_cycle[i] = mtkaif_phase_cycle[i]; + + dev_info(afe->dev, "%s(), end, calibration ok %d\n", + __func__, param->mtkaif_calibration_ok); + + return 0; +} + +static int mt8195_mt6359_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *cmpnt_codec = + asoc_rtd_to_codec(rtd, 0)->component; + + /* set mtkaif protocol */ + mt6359_set_mtkaif_protocol(cmpnt_codec, + MT6359_MTKAIF_PROTOCOL_2_CLK_P2); + + /* mtkaif calibration */ + mt8195_mt6359_mtkaif_calibration(rtd); + + return 0; +} + +static int mt8195_rt5682_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *cmpnt_codec = + asoc_rtd_to_codec(rtd, 0)->component; + struct mt8195_mt6359_rt1011_rt5682_priv *priv = + snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_jack *jack = &priv->headset_jack; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + jack, NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(cmpnt_codec, jack, NULL); + if (ret) { + dev_err(rtd->dev, "Headset Jack set failed: %d\n", ret); + return ret; + } + + return 0; +}; + +static int mt8195_etdm_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int mt8195_hdmitx_dptx_startup(struct snd_pcm_substream *substream) +{ + static const unsigned int rates[] = { + 48000 + }; + static const unsigned int channels[] = { + 2, 4, 6, 8 + }; + static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, + }; + + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + if (ret < 0) { + dev_err(rtd->dev, "hw_constraint_list rate failed\n"); + return ret; + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + if (ret < 0) { + dev_err(rtd->dev, "hw_constraint_list channel failed\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops mt8195_hdmitx_dptx_playback_ops = { + .startup = mt8195_hdmitx_dptx_startup, +}; + +static int mt8195_dptx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + + return snd_soc_dai_set_sysclk(cpu_dai, 0, params_rate(params) * 256, + SND_SOC_CLOCK_OUT); +} + +static struct snd_soc_ops mt8195_dptx_ops = { + .hw_params = mt8195_dptx_hw_params, +}; + +static int mt8195_dptx_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mt8195_mt6359_rt1011_rt5682_priv *priv = + snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *cmpnt_codec = + asoc_rtd_to_codec(rtd, 0)->component; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "DP Jack", SND_JACK_LINEOUT, + &priv->dp_jack, NULL, 0); + if (ret) + return ret; + + return snd_soc_component_set_jack(cmpnt_codec, &priv->dp_jack, NULL); +} + +static int mt8195_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mt8195_mt6359_rt1011_rt5682_priv *priv = + snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *cmpnt_codec = + asoc_rtd_to_codec(rtd, 0)->component; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "HDMI Jack", SND_JACK_LINEOUT, + &priv->hdmi_jack, NULL, 0); + if (ret) + return ret; + + return snd_soc_component_set_jack(cmpnt_codec, &priv->hdmi_jack, NULL); +} + +static int mt8195_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) + +{ + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int mt8195_playback_startup(struct snd_pcm_substream *substream) +{ + static const unsigned int rates[] = { + 48000 + }; + static const unsigned int channels[] = { + 2 + }; + static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, + }; + + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + if (ret < 0) { + dev_err(rtd->dev, "hw_constraint_list rate failed\n"); + return ret; + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + if (ret < 0) { + dev_err(rtd->dev, "hw_constraint_list channel failed\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops mt8195_playback_ops = { + .startup = mt8195_playback_startup, +}; + +static int mt8195_capture_startup(struct snd_pcm_substream *substream) +{ + static const unsigned int rates[] = { + 48000 + }; + static const unsigned int channels[] = { + 1, 2 + }; + static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, + }; + + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + if (ret < 0) { + dev_err(rtd->dev, "hw_constraint_list rate failed\n"); + return ret; + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + if (ret < 0) { + dev_err(rtd->dev, "hw_constraint_list channel failed\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops mt8195_capture_ops = { + .startup = mt8195_capture_startup, +}; + +enum { + DAI_LINK_DL2_FE, + DAI_LINK_DL3_FE, + DAI_LINK_DL6_FE, + DAI_LINK_DL7_FE, + DAI_LINK_DL8_FE, + DAI_LINK_DL10_FE, + DAI_LINK_DL11_FE, + DAI_LINK_UL1_FE, + DAI_LINK_UL2_FE, + DAI_LINK_UL3_FE, + DAI_LINK_UL4_FE, + DAI_LINK_UL5_FE, + DAI_LINK_UL6_FE, + DAI_LINK_UL8_FE, + DAI_LINK_UL9_FE, + DAI_LINK_UL10_FE, + DAI_LINK_DL_SRC_BE, + DAI_LINK_DPTX_BE, + DAI_LINK_ETDM1_IN_BE, + DAI_LINK_ETDM2_IN_BE, + DAI_LINK_ETDM1_OUT_BE, + DAI_LINK_ETDM2_OUT_BE, + DAI_LINK_ETDM3_OUT_BE, + DAI_LINK_PCM1_BE, + DAI_LINK_UL_SRC1_BE, + DAI_LINK_UL_SRC2_BE, +}; + +/* FE */ +SND_SOC_DAILINK_DEFS(DL2_FE, + DAILINK_COMP_ARRAY(COMP_CPU("DL2")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(DL3_FE, + DAILINK_COMP_ARRAY(COMP_CPU("DL3")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(DL6_FE, + DAILINK_COMP_ARRAY(COMP_CPU("DL6")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(DL7_FE, + DAILINK_COMP_ARRAY(COMP_CPU("DL7")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(DL8_FE, + DAILINK_COMP_ARRAY(COMP_CPU("DL8")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(DL10_FE, + DAILINK_COMP_ARRAY(COMP_CPU("DL10")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(DL11_FE, + DAILINK_COMP_ARRAY(COMP_CPU("DL11")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL1_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL2_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL2")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL3_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL3")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL4_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL4")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL5_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL5")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL6_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL6")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL8_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL8")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL9_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL9")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL10_FE, + DAILINK_COMP_ARRAY(COMP_CPU("UL10")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +/* BE */ +SND_SOC_DAILINK_DEFS(DL_SRC_BE, + DAILINK_COMP_ARRAY(COMP_CPU("DL_SRC")), + DAILINK_COMP_ARRAY(COMP_CODEC("mt6359-sound", + "mt6359-snd-codec-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(DPTX_BE, + DAILINK_COMP_ARRAY(COMP_CPU("DPTX")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(ETDM1_IN_BE, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM1_IN")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(ETDM2_IN_BE, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM2_IN")), + DAILINK_COMP_ARRAY(COMP_CODEC(RT5682_DEV0_NAME, + RT5682_CODEC_DAI)), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(ETDM1_OUT_BE, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM1_OUT")), + DAILINK_COMP_ARRAY(COMP_CODEC(RT5682_DEV0_NAME, + RT5682_CODEC_DAI)), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(ETDM2_OUT_BE, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM2_OUT")), + DAILINK_COMP_ARRAY(COMP_CODEC(RT1011_DEV0_NAME, + RT1011_CODEC_DAI), + COMP_CODEC(RT1011_DEV1_NAME, + RT1011_CODEC_DAI)), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(ETDM3_OUT_BE, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM3_OUT")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(PCM1_BE, + DAILINK_COMP_ARRAY(COMP_CPU("PCM1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL_SRC1_BE, + DAILINK_COMP_ARRAY(COMP_CPU("UL_SRC1")), + DAILINK_COMP_ARRAY(COMP_CODEC("mt6359-sound", + "mt6359-snd-codec-aif1"), + COMP_CODEC("dmic-codec", + "dmic-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(UL_SRC2_BE, + DAILINK_COMP_ARRAY(COMP_CPU("UL_SRC2")), + DAILINK_COMP_ARRAY(COMP_CODEC("mt6359-sound", + "mt6359-snd-codec-aif2")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link mt8195_mt6359_rt1011_rt5682_dai_links[] = { + /* FE */ + [DAI_LINK_DL2_FE] = { + .name = "DL2_FE", + .stream_name = "DL2 Playback", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &mt8195_playback_ops, + SND_SOC_DAILINK_REG(DL2_FE), + }, + [DAI_LINK_DL3_FE] = { + .name = "DL3_FE", + .stream_name = "DL3 Playback", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &mt8195_playback_ops, + SND_SOC_DAILINK_REG(DL3_FE), + }, + [DAI_LINK_DL6_FE] = { + .name = "DL6_FE", + .stream_name = "DL6 Playback", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &mt8195_playback_ops, + SND_SOC_DAILINK_REG(DL6_FE), + }, + [DAI_LINK_DL7_FE] = { + .name = "DL7_FE", + .stream_name = "DL7 Playback", + .trigger = { + SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE, + }, + .dynamic = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(DL7_FE), + }, + [DAI_LINK_DL8_FE] = { + .name = "DL8_FE", + .stream_name = "DL8 Playback", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &mt8195_playback_ops, + SND_SOC_DAILINK_REG(DL8_FE), + }, + [DAI_LINK_DL10_FE] = { + .name = "DL10_FE", + .stream_name = "DL10 Playback", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &mt8195_hdmitx_dptx_playback_ops, + SND_SOC_DAILINK_REG(DL10_FE), + }, + [DAI_LINK_DL11_FE] = { + .name = "DL11_FE", + .stream_name = "DL11 Playback", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &mt8195_playback_ops, + SND_SOC_DAILINK_REG(DL11_FE), + }, + [DAI_LINK_UL1_FE] = { + .name = "UL1_FE", + .stream_name = "UL1 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE, + }, + .dynamic = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(UL1_FE), + }, + [DAI_LINK_UL2_FE] = { + .name = "UL2_FE", + .stream_name = "UL2 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8195_capture_ops, + SND_SOC_DAILINK_REG(UL2_FE), + }, + [DAI_LINK_UL3_FE] = { + .name = "UL3_FE", + .stream_name = "UL3 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8195_capture_ops, + SND_SOC_DAILINK_REG(UL3_FE), + }, + [DAI_LINK_UL4_FE] = { + .name = "UL4_FE", + .stream_name = "UL4 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8195_capture_ops, + SND_SOC_DAILINK_REG(UL4_FE), + }, + [DAI_LINK_UL5_FE] = { + .name = "UL5_FE", + .stream_name = "UL5 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8195_capture_ops, + SND_SOC_DAILINK_REG(UL5_FE), + }, + [DAI_LINK_UL6_FE] = { + .name = "UL6_FE", + .stream_name = "UL6 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE, + }, + .dynamic = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(UL6_FE), + }, + [DAI_LINK_UL8_FE] = { + .name = "UL8_FE", + .stream_name = "UL8 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8195_capture_ops, + SND_SOC_DAILINK_REG(UL8_FE), + }, + [DAI_LINK_UL9_FE] = { + .name = "UL9_FE", + .stream_name = "UL9 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8195_capture_ops, + SND_SOC_DAILINK_REG(UL9_FE), + }, + [DAI_LINK_UL10_FE] = { + .name = "UL10_FE", + .stream_name = "UL10 Capture", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST, + }, + .dynamic = 1, + .dpcm_capture = 1, + .ops = &mt8195_capture_ops, + SND_SOC_DAILINK_REG(UL10_FE), + }, + /* BE */ + [DAI_LINK_DL_SRC_BE] = { + .name = "DL_SRC_BE", + .init = mt8195_mt6359_init, + .no_pcm = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(DL_SRC_BE), + }, + [DAI_LINK_DPTX_BE] = { + .name = "DPTX_BE", + .no_pcm = 1, + .dpcm_playback = 1, + .ops = &mt8195_dptx_ops, + .be_hw_params_fixup = mt8195_dptx_hw_params_fixup, + SND_SOC_DAILINK_REG(DPTX_BE), + }, + [DAI_LINK_ETDM1_IN_BE] = { + .name = "ETDM1_IN_BE", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(ETDM1_IN_BE), + }, + [DAI_LINK_ETDM2_IN_BE] = { + .name = "ETDM2_IN_BE", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_capture = 1, + .init = mt8195_rt5682_init, + .ops = &mt8195_rt5682_etdm_ops, + .be_hw_params_fixup = mt8195_etdm_hw_params_fixup, + SND_SOC_DAILINK_REG(ETDM2_IN_BE), + }, + [DAI_LINK_ETDM1_OUT_BE] = { + .name = "ETDM1_OUT_BE", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_playback = 1, + .ops = &mt8195_rt5682_etdm_ops, + .be_hw_params_fixup = mt8195_etdm_hw_params_fixup, + SND_SOC_DAILINK_REG(ETDM1_OUT_BE), + }, + [DAI_LINK_ETDM2_OUT_BE] = { + .name = "ETDM2_OUT_BE", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_playback = 1, + .ops = &mt8195_rt1011_etdm_ops, + .be_hw_params_fixup = mt8195_etdm_hw_params_fixup, + SND_SOC_DAILINK_REG(ETDM2_OUT_BE), + }, + [DAI_LINK_ETDM3_OUT_BE] = { + .name = "ETDM3_OUT_BE", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(ETDM3_OUT_BE), + }, + [DAI_LINK_PCM1_BE] = { + .name = "PCM1_BE", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(PCM1_BE), + }, + [DAI_LINK_UL_SRC1_BE] = { + .name = "UL_SRC1_BE", + .no_pcm = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(UL_SRC1_BE), + }, + [DAI_LINK_UL_SRC2_BE] = { + .name = "UL_SRC2_BE", + .no_pcm = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(UL_SRC2_BE), + }, +}; + +static struct snd_soc_codec_conf rt1011_amp_conf[] = { + { + .dlc = COMP_CODEC_CONF(RT1011_DEV0_NAME), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF(RT1011_DEV1_NAME), + .name_prefix = "Right", + }, +}; + +static struct snd_soc_card mt8195_mt6359_rt1011_rt5682_soc_card = { + .name = "mt8195_r1011_5682", + .owner = THIS_MODULE, + .dai_link = mt8195_mt6359_rt1011_rt5682_dai_links, + .num_links = ARRAY_SIZE(mt8195_mt6359_rt1011_rt5682_dai_links), + .controls = mt8195_mt6359_rt1011_rt5682_controls, + .num_controls = ARRAY_SIZE(mt8195_mt6359_rt1011_rt5682_controls), + .dapm_widgets = mt8195_mt6359_rt1011_rt5682_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8195_mt6359_rt1011_rt5682_widgets), + .dapm_routes = mt8195_mt6359_rt1011_rt5682_routes, + .num_dapm_routes = ARRAY_SIZE(mt8195_mt6359_rt1011_rt5682_routes), + .codec_conf = rt1011_amp_conf, + .num_configs = ARRAY_SIZE(rt1011_amp_conf), +}; + +static int mt8195_mt6359_rt1011_rt5682_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8195_mt6359_rt1011_rt5682_soc_card; + struct snd_soc_dai_link *dai_link; + struct mt8195_mt6359_rt1011_rt5682_priv *priv; + int ret, i; + + card->dev = &pdev->dev; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!priv->platform_node) { + dev_dbg(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + if (!dai_link->platforms->name) + dai_link->platforms->of_node = priv->platform_node; + + if (strcmp(dai_link->name, "DPTX_BE") == 0) { + priv->dp_node = + of_parse_phandle(pdev->dev.of_node, + "mediatek,dptx-codec", 0); + + if (!priv->dp_node) { + dev_dbg(&pdev->dev, "No property 'dptx-codec'\n"); + } else { + dai_link->codecs->of_node = priv->dp_node; + dai_link->codecs->name = NULL; + dai_link->codecs->dai_name = "i2s-hifi"; + dai_link->init = mt8195_dptx_codec_init; + } + } + + if (strcmp(dai_link->name, "ETDM3_OUT_BE") == 0) { + priv->hdmi_node = + of_parse_phandle(pdev->dev.of_node, + "mediatek,hdmi-codec", 0); + if (!priv->hdmi_node) { + dev_dbg(&pdev->dev, "No property 'hdmi-codec'\n"); + } else { + dai_link->codecs->of_node = priv->hdmi_node; + dai_link->codecs->name = NULL; + dai_link->codecs->dai_name = "i2s-hifi"; + dai_link->init = mt8195_hdmi_codec_init; + } + } + } + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + of_node_put(priv->hdmi_node); + of_node_put(priv->dp_node); + of_node_put(priv->platform_node); + } + + return ret; +} + +static int mt8195_mt6359_rt1011_rt5682_dev_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct mt8195_mt6359_rt1011_rt5682_priv *priv = + snd_soc_card_get_drvdata(card); + + of_node_put(priv->hdmi_node); + of_node_put(priv->dp_node); + of_node_put(priv->platform_node); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt8195_mt6359_rt1011_rt5682_dt_match[] = { + {.compatible = "mediatek,mt8195_mt6359_rt1011_rt5682",}, + {} +}; +#endif + +static const struct dev_pm_ops mt8195_mt6359_rt1011_rt5682_pm_ops = { + .poweroff = snd_soc_poweroff, + .restore = snd_soc_resume, +}; + +static struct platform_driver mt8195_mt6359_rt1011_rt5682_driver = { + .driver = { + .name = "mt8195_mt6359_rt1011_rt5682", +#ifdef CONFIG_OF + .of_match_table = mt8195_mt6359_rt1011_rt5682_dt_match, +#endif + .pm = &mt8195_mt6359_rt1011_rt5682_pm_ops, + }, + .probe = mt8195_mt6359_rt1011_rt5682_dev_probe, + .remove = mt8195_mt6359_rt1011_rt5682_dev_remove, +}; + +module_platform_driver(mt8195_mt6359_rt1011_rt5682_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8195-MT6359-RT1011-RT5682 ALSA SoC machine driver"); +MODULE_AUTHOR("Trevor Wu <trevor.wu@mediatek.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("mt8195_mt6359_rt1011_rt5682 soc card"); diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359-rt1019-rt5682.c b/sound/soc/mediatek/mt8195/mt8195-mt6359-rt1019-rt5682.c index de09f67c0450..95abaadcd842 100644 --- a/sound/soc/mediatek/mt8195/mt8195-mt6359-rt1019-rt5682.c +++ b/sound/soc/mediatek/mt8195/mt8195-mt6359-rt1019-rt5682.c @@ -26,6 +26,9 @@ #define RT5682_DEV0_NAME "rt5682.2-001a" struct mt8195_mt6359_rt1019_rt5682_priv { + struct device_node *platform_node; + struct device_node *hdmi_node; + struct device_node *dp_node; struct snd_soc_jack headset_jack; struct snd_soc_jack dp_jack; struct snd_soc_jack hdmi_jack; @@ -994,31 +997,36 @@ static struct snd_soc_card mt8195_mt6359_rt1019_rt5682_soc_card = { static int mt8195_mt6359_rt1019_rt5682_dev_probe(struct platform_device *pdev) { struct snd_soc_card *card = &mt8195_mt6359_rt1019_rt5682_soc_card; - struct device_node *platform_node; struct snd_soc_dai_link *dai_link; - struct mt8195_mt6359_rt1019_rt5682_priv *priv = NULL; + struct mt8195_mt6359_rt1019_rt5682_priv *priv; int ret, i; card->dev = &pdev->dev; - platform_node = of_parse_phandle(pdev->dev.of_node, - "mediatek,platform", 0); - if (!platform_node) { + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!priv->platform_node) { dev_dbg(&pdev->dev, "Property 'platform' missing or invalid\n"); return -EINVAL; } for_each_card_prelinks(card, i, dai_link) { if (!dai_link->platforms->name) - dai_link->platforms->of_node = platform_node; + dai_link->platforms->of_node = priv->platform_node; if (strcmp(dai_link->name, "DPTX_BE") == 0) { - dai_link->codecs->of_node = + priv->dp_node = of_parse_phandle(pdev->dev.of_node, "mediatek,dptx-codec", 0); - if (!dai_link->codecs->of_node) { + + if (!priv->dp_node) { dev_dbg(&pdev->dev, "No property 'dptx-codec'\n"); } else { + dai_link->codecs->of_node = priv->dp_node; dai_link->codecs->name = NULL; dai_link->codecs->dai_name = "i2s-hifi"; dai_link->init = mt8195_dptx_codec_init; @@ -1026,12 +1034,13 @@ static int mt8195_mt6359_rt1019_rt5682_dev_probe(struct platform_device *pdev) } if (strcmp(dai_link->name, "ETDM3_OUT_BE") == 0) { - dai_link->codecs->of_node = + priv->hdmi_node = of_parse_phandle(pdev->dev.of_node, "mediatek,hdmi-codec", 0); - if (!dai_link->codecs->of_node) { + if (!priv->hdmi_node) { dev_dbg(&pdev->dev, "No property 'hdmi-codec'\n"); } else { + dai_link->codecs->of_node = priv->hdmi_node; dai_link->codecs->name = NULL; dai_link->codecs->dai_name = "i2s-hifi"; dai_link->init = mt8195_hdmi_codec_init; @@ -1039,19 +1048,33 @@ static int mt8195_mt6359_rt1019_rt5682_dev_probe(struct platform_device *pdev) } } - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - snd_soc_card_set_drvdata(card, priv); ret = devm_snd_soc_register_card(&pdev->dev, card); - if (ret) + if (ret) { dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", __func__, ret); + of_node_put(priv->hdmi_node); + of_node_put(priv->dp_node); + of_node_put(priv->platform_node); + } + return ret; } +static int mt8195_mt6359_rt1019_rt5682_dev_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct mt8195_mt6359_rt1019_rt5682_priv *priv = + snd_soc_card_get_drvdata(card); + + of_node_put(priv->hdmi_node); + of_node_put(priv->dp_node); + of_node_put(priv->platform_node); + + return 0; +} + #ifdef CONFIG_OF static const struct of_device_id mt8195_mt6359_rt1019_rt5682_dt_match[] = { {.compatible = "mediatek,mt8195_mt6359_rt1019_rt5682",}, @@ -1073,6 +1096,7 @@ static struct platform_driver mt8195_mt6359_rt1019_rt5682_driver = { .pm = &mt8195_mt6359_rt1019_rt5682_pm_ops, }, .probe = mt8195_mt6359_rt1019_rt5682_dev_probe, + .remove = mt8195_mt6359_rt1019_rt5682_dev_remove, }; module_platform_driver(mt8195_mt6359_rt1019_rt5682_driver); diff --git a/sound/soc/meson/aiu-encoder-spdif.c b/sound/soc/meson/aiu-encoder-spdif.c index de850913975f..97da60db2c4d 100644 --- a/sound/soc/meson/aiu-encoder-spdif.c +++ b/sound/soc/meson/aiu-encoder-spdif.c @@ -113,7 +113,7 @@ static int aiu_encoder_spdif_hw_params(struct snd_pcm_substream *substream, val |= AIU_958_MISC_MODE_32BITS; break; default: - dev_err(dai->dev, "Unsupport physical width\n"); + dev_err(dai->dev, "Unsupported physical width\n"); return -EINVAL; } diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c index 2b77010c2c5c..cbbaa55d92a6 100644 --- a/sound/soc/meson/axg-card.c +++ b/sound/soc/meson/axg-card.c @@ -320,6 +320,7 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, dai_link->cpus = cpu; dai_link->num_cpus = 1; + dai_link->nonatomic = true; ret = meson_card_parse_dai(card, np, &dai_link->cpus->of_node, &dai_link->cpus->dai_name); diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c index 87cac440b369..db077773af7a 100644 --- a/sound/soc/meson/axg-tdm-interface.c +++ b/sound/soc/meson/axg-tdm-interface.c @@ -351,13 +351,29 @@ static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, return 0; } -static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream, +static int axg_tdm_iface_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) { - struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct axg_tdm_stream *ts = + snd_soc_dai_get_dma_data(dai, substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + axg_tdm_stream_start(ts); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + axg_tdm_stream_stop(ts); + break; + default: + return -EINVAL; + } - /* Force all attached formatters to update */ - return axg_tdm_stream_reset(ts); + return 0; } static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai) @@ -397,8 +413,8 @@ static const struct snd_soc_dai_ops axg_tdm_iface_ops = { .set_fmt = axg_tdm_iface_set_fmt, .startup = axg_tdm_iface_startup, .hw_params = axg_tdm_iface_hw_params, - .prepare = axg_tdm_iface_prepare, .hw_free = axg_tdm_iface_hw_free, + .trigger = axg_tdm_iface_trigger, }; /* TDM Backend DAIs */ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c index 415cc0046e4b..29b0174f4b5c 100644 --- a/sound/soc/meson/meson-card-utils.c +++ b/sound/soc/meson/meson-card-utils.c @@ -302,6 +302,7 @@ int meson_card_probe(struct platform_device *pdev) priv->card.owner = THIS_MODULE; priv->card.dev = dev; + priv->card.driver_name = dev->driver->name; priv->match_data = data; ret = snd_soc_of_parse_card_name(&priv->card, "model"); diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c index d07270d17cee..2870cfad813a 100644 --- a/sound/soc/meson/meson-codec-glue.c +++ b/sound/soc/meson/meson-codec-glue.c @@ -113,9 +113,6 @@ int meson_codec_glue_output_startup(struct snd_pcm_substream *substream, /* Replace link params with the input params */ rtd->dai_link->params = &in_data->params; - if (!in_data->fmt) - return 0; - return snd_soc_runtime_set_dai_fmt(rtd, in_data->fmt); } EXPORT_SYMBOL_GPL(meson_codec_glue_output_startup); diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index cc7c1de2f1d9..b2173847dc47 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -38,6 +38,7 @@ config SND_SOC_LPASS_SC7180 config SND_SOC_STORM tristate "ASoC I2S support for Storm boards" + depends on GPIOLIB select SND_SOC_LPASS_IPQ806X select SND_SOC_MAX98357A help @@ -84,6 +85,25 @@ config SND_SOC_QDSP6_ASM_DAI select SND_SOC_COMPRESS tristate +config SND_SOC_QDSP6_APM_DAI + tristate + select SND_SOC_COMPRESS + +config SND_SOC_QDSP6_APM_LPASS_DAI + tristate + +config SND_SOC_QDSP6_APM + tristate + select SND_SOC_QDSP6_APM_DAI + select SND_SOC_QDSP6_APM_LPASS_DAI + +config SND_SOC_QDSP6_PRM_LPASS_CLOCKS + tristate + +config SND_SOC_QDSP6_PRM + tristate + select SND_SOC_QDSP6_PRM_LPASS_CLOCKS + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR @@ -97,6 +117,9 @@ config SND_SOC_QDSP6 select SND_SOC_QDSP6_ROUTING select SND_SOC_QDSP6_ASM select SND_SOC_QDSP6_ASM_DAI + select SND_SOC_TOPOLOGY + select SND_SOC_QDSP6_APM + select SND_SOC_QDSP6_PRM help To add support for MSM QDSP6 Soc Audio. This will enable sound soc platform specific @@ -141,7 +164,7 @@ config SND_SOC_SM8250 config SND_SOC_SC7180 tristate "SoC Machine driver for SC7180 boards" - depends on I2C + depends on I2C && GPIOLIB select SND_SOC_QCOM_COMMON select SND_SOC_LPASS_SC7180 select SND_SOC_MAX98357A diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c index 1a69baefc5ce..c7b7d0864d1a 100644 --- a/sound/soc/qcom/apq8096.c +++ b/sound/soc/qcom/apq8096.c @@ -60,7 +60,7 @@ end: return ret; } -static struct snd_soc_ops apq8096_ops = { +static const struct snd_soc_ops apq8096_ops = { .hw_params = msm_snd_hw_params, }; diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index 09af00700700..2e1c618f7529 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -44,7 +44,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) return ret; /* Populate links */ - num_links = of_get_child_count(dev->of_node); + num_links = of_get_available_child_count(dev->of_node); /* Allocate the DAI link array */ card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL); @@ -54,7 +54,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) card->num_links = num_links; link = card->dai_link; - for_each_child_of_node(dev->of_node, np) { + for_each_available_child_of_node(dev->of_node, np) { dlc = devm_kzalloc(dev, 2 * sizeof(*dlc), GFP_KERNEL); if (!dlc) { ret = -ENOMEM; diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 3c1dd9f32f1d..3963bf234664 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,5 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o +snd-q6dsp-common-objs := q6dsp-common.o q6dsp-lpass-ports.o q6dsp-lpass-clocks.o +snd-q6apm-objs := q6apm.o audioreach.o topology.o + +obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += snd-q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o @@ -8,3 +11,9 @@ obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o + +obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o +obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_APM_LPASS_DAI) += q6apm-lpass-dais.o +obj-$(CONFIG_SND_SOC_QDSP6_PRM) += q6prm.o +obj-$(CONFIG_SND_SOC_QDSP6_PRM_LPASS_CLOCKS) += q6prm-clocks.o diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c new file mode 100644 index 000000000000..98c0efa1d0fe --- /dev/null +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -0,0 +1,1130 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/soc/qcom/apr.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <dt-bindings/soc/qcom,gpr.h> +#include "q6apm.h" +#include "audioreach.h" + +/* SubGraph Config */ +struct apm_sub_graph_data { + struct apm_sub_graph_cfg sub_graph_cfg; + struct apm_prop_data perf_data; + struct apm_sg_prop_id_perf_mode perf; + struct apm_prop_data dir_data; + struct apm_sg_prop_id_direction dir; + struct apm_prop_data sid_data; + struct apm_sg_prop_id_scenario_id sid; + +} __packed; + +#define APM_SUB_GRAPH_CFG_NPROP 3 + +struct apm_sub_graph_params { + struct apm_module_param_data param_data; + uint32_t num_sub_graphs; + struct apm_sub_graph_data sg_cfg[]; +} __packed; + +#define APM_SUB_GRAPH_PSIZE(p, n) ALIGN(struct_size(p, sg_cfg, n), 8) + +/* container config */ +struct apm_container_obj { + struct apm_container_cfg container_cfg; + /* Capability ID list */ + struct apm_prop_data cap_data; + uint32_t num_capability_id; + uint32_t capability_id; + + /* Container graph Position */ + struct apm_prop_data pos_data; + struct apm_cont_prop_id_graph_pos pos; + + /* Container Stack size */ + struct apm_prop_data stack_data; + struct apm_cont_prop_id_stack_size stack; + + /* Container proc domain id */ + struct apm_prop_data domain_data; + struct apm_cont_prop_id_domain domain; +} __packed; + +struct apm_container_params { + struct apm_module_param_data param_data; + uint32_t num_containers; + struct apm_container_obj cont_obj[]; +} __packed; + +#define APM_CONTAINER_PSIZE(p, n) ALIGN(struct_size(p, cont_obj, n), 8) + +/* Module List config */ +struct apm_mod_list_obj { + /* Modules list cfg */ + uint32_t sub_graph_id; + uint32_t container_id; + uint32_t num_modules; + struct apm_module_obj mod_cfg[]; +} __packed; + +#define APM_MOD_LIST_OBJ_PSIZE(p, n) struct_size(p, mod_cfg, n) + +struct apm_module_list_params { + struct apm_module_param_data param_data; + uint32_t num_modules_list; + /* Module list config array */ + struct apm_mod_list_obj mod_list_obj[]; +} __packed; + + +/* Module Properties */ +struct apm_mod_prop_obj { + u32 instance_id; + u32 num_props; + struct apm_prop_data prop_data_1; + struct apm_module_prop_id_port_info prop_id_port; +} __packed; + +struct apm_prop_list_params { + struct apm_module_param_data param_data; + u32 num_modules_prop_cfg; + struct apm_mod_prop_obj mod_prop_obj[]; + +} __packed; + +#define APM_MOD_PROP_PSIZE(p, n) ALIGN(struct_size(p, mod_prop_obj, n), 8) + +/* Module Connections */ +struct apm_mod_conn_list_params { + struct apm_module_param_data param_data; + u32 num_connections; + struct apm_module_conn_obj conn_obj[]; + +} __packed; + +#define APM_MOD_CONN_PSIZE(p, n) ALIGN(struct_size(p, conn_obj, n), 8) + +struct apm_graph_open_params { + struct apm_cmd_header *cmd_header; + struct apm_sub_graph_params *sg_data; + struct apm_container_params *cont_data; + struct apm_module_list_params *mod_list_data; + struct apm_prop_list_params *mod_prop_data; + struct apm_mod_conn_list_params *mod_conn_list_data; +} __packed; + +struct apm_pcm_module_media_fmt_cmd { + struct apm_module_param_data param_data; + struct param_id_pcm_output_format_cfg header; + struct payload_pcm_output_format_cfg media_cfg; +} __packed; + +struct apm_rd_shmem_module_config_cmd { + struct apm_module_param_data param_data; + struct param_id_rd_sh_mem_cfg cfg; +} __packed; + +struct apm_sh_module_media_fmt_cmd { + struct media_format header; + struct payload_media_fmt_pcm cfg; +} __packed; + +#define APM_SHMEM_FMT_CFG_PSIZE(ch) ALIGN( \ + sizeof(struct apm_sh_module_media_fmt_cmd) + \ + ch * sizeof(uint8_t), 8) + +/* num of channels as argument */ +#define APM_PCM_MODULE_FMT_CMD_PSIZE(ch) ALIGN( \ + sizeof(struct apm_pcm_module_media_fmt_cmd) + \ + ch * sizeof(uint8_t), 8) + +#define APM_PCM_OUT_FMT_CFG_PSIZE(p, n) ALIGN(struct_size(p, channel_mapping, n), 4) + +struct apm_i2s_module_intf_cfg { + struct apm_module_param_data param_data; + struct param_id_i2s_intf_cfg cfg; +} __packed; + +#define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8) + +struct apm_module_hw_ep_mf_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_mf mf; +} __packed; + +#define APM_HW_EP_CFG_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_mf_cfg), 8) + +struct apm_module_frame_size_factor_cfg { + struct apm_module_param_data param_data; + uint32_t frame_size_factor; +} __packed; + +#define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8) + +struct apm_module_hw_ep_power_mode_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_power_mode_cfg power_mode; +} __packed; + +#define APM_HW_EP_PMODE_CFG_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_power_mode_cfg), 8) + +struct apm_module_hw_ep_dma_data_align_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_dma_data_align align; +} __packed; + +#define APM_HW_EP_DALIGN_CFG_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_dma_data_align_cfg), 8) + +struct apm_gain_module_cfg { + struct apm_module_param_data param_data; + struct param_id_gain_cfg gain_cfg; +} __packed; + +#define APM_GAIN_CFG_PSIZE ALIGN(sizeof(struct apm_gain_module_cfg), 8) + +struct apm_codec_dma_module_intf_cfg { + struct apm_module_param_data param_data; + struct param_id_codec_dma_intf_cfg cfg; +} __packed; + +#define APM_CDMA_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_codec_dma_module_intf_cfg), 8) + +static void *__audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, + uint32_t src_port, uint32_t dest_port, bool has_cmd_hdr) +{ + struct gpr_pkt *pkt; + void *p; + int pkt_size = GPR_HDR_SIZE + payload_size; + + if (has_cmd_hdr) + pkt_size += APM_CMD_HDR_SIZE; + + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + pkt = p; + pkt->hdr.version = GPR_PKT_VER; + pkt->hdr.hdr_size = GPR_PKT_HEADER_WORD_SIZE; + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.dest_port = dest_port; + pkt->hdr.src_port = src_port; + + pkt->hdr.dest_domain = GPR_DOMAIN_ID_ADSP; + pkt->hdr.src_domain = GPR_DOMAIN_ID_APPS; + pkt->hdr.token = token; + pkt->hdr.opcode = opcode; + + if (has_cmd_hdr) { + struct apm_cmd_header *cmd_header; + + p = p + GPR_HDR_SIZE; + cmd_header = p; + cmd_header->payload_size = payload_size; + } + + return pkt; +} + +void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, + uint32_t src_port, uint32_t dest_port) +{ + return __audioreach_alloc_pkt(payload_size, opcode, token, src_port, dest_port, false); +} +EXPORT_SYMBOL_GPL(audioreach_alloc_pkt); + +void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token, uint32_t src_port) +{ + return __audioreach_alloc_pkt(pkt_size, opcode, token, src_port, APM_MODULE_INSTANCE_ID, + false); +} +EXPORT_SYMBOL_GPL(audioreach_alloc_apm_pkt); + +void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, uint32_t token, + uint32_t src_port, uint32_t dest_port) +{ + return __audioreach_alloc_pkt(payload_size, opcode, token, src_port, dest_port, true); +} +EXPORT_SYMBOL_GPL(audioreach_alloc_cmd_pkt); + +void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t token) +{ + return __audioreach_alloc_pkt(pkt_size, opcode, token, GPR_APM_MODULE_IID, + APM_MODULE_INSTANCE_ID, true); +} +EXPORT_SYMBOL_GPL(audioreach_alloc_apm_cmd_pkt); + +static void apm_populate_container_config(struct apm_container_obj *cfg, + struct audioreach_container *cont) +{ + + /* Container Config */ + cfg->container_cfg.container_id = cont->container_id; + cfg->container_cfg.num_prop = 4; + + /* Capability list */ + cfg->cap_data.prop_id = APM_CONTAINER_PROP_ID_CAPABILITY_LIST; + cfg->cap_data.prop_size = APM_CONTAINER_PROP_ID_CAPABILITY_SIZE; + cfg->num_capability_id = 1; + cfg->capability_id = cont->capability_id; + + /* Graph Position */ + cfg->pos_data.prop_id = APM_CONTAINER_PROP_ID_GRAPH_POS; + cfg->pos_data.prop_size = sizeof(struct apm_cont_prop_id_graph_pos); + cfg->pos.graph_pos = cont->graph_pos; + + /* Stack size */ + cfg->stack_data.prop_id = APM_CONTAINER_PROP_ID_STACK_SIZE; + cfg->stack_data.prop_size = sizeof(struct apm_cont_prop_id_stack_size); + cfg->stack.stack_size = cont->stack_size; + + /* Proc domain */ + cfg->domain_data.prop_id = APM_CONTAINER_PROP_ID_PROC_DOMAIN; + cfg->domain_data.prop_size = sizeof(struct apm_cont_prop_id_domain); + cfg->domain.proc_domain = cont->proc_domain; +} + +static void apm_populate_sub_graph_config(struct apm_sub_graph_data *cfg, + struct audioreach_sub_graph *sg) +{ + cfg->sub_graph_cfg.sub_graph_id = sg->sub_graph_id; + cfg->sub_graph_cfg.num_sub_graph_prop = APM_SUB_GRAPH_CFG_NPROP; + + /* Perf Mode */ + cfg->perf_data.prop_id = APM_SUB_GRAPH_PROP_ID_PERF_MODE; + cfg->perf_data.prop_size = APM_SG_PROP_ID_PERF_MODE_SIZE; + cfg->perf.perf_mode = sg->perf_mode; + + /* Direction */ + cfg->dir_data.prop_id = APM_SUB_GRAPH_PROP_ID_DIRECTION; + cfg->dir_data.prop_size = APM_SG_PROP_ID_DIR_SIZE; + cfg->dir.direction = sg->direction; + + /* Scenario ID */ + cfg->sid_data.prop_id = APM_SUB_GRAPH_PROP_ID_SCENARIO_ID; + cfg->sid_data.prop_size = APM_SG_PROP_ID_SID_SIZE; + cfg->sid.scenario_id = sg->scenario_id; +} + +static void apm_populate_connection_obj(struct apm_module_conn_obj *obj, + struct audioreach_module *module) +{ + obj->src_mod_inst_id = module->src_mod_inst_id; + obj->src_mod_op_port_id = module->src_mod_op_port_id; + obj->dst_mod_inst_id = module->instance_id; + obj->dst_mod_ip_port_id = module->in_port; +} + +static void apm_populate_module_prop_obj(struct apm_mod_prop_obj *obj, + struct audioreach_module *module) +{ + + obj->instance_id = module->instance_id; + obj->num_props = 1; + obj->prop_data_1.prop_id = APM_MODULE_PROP_ID_PORT_INFO; + obj->prop_data_1.prop_size = APM_MODULE_PROP_ID_PORT_INFO_SZ; + obj->prop_id_port.max_ip_port = module->max_ip_port; + obj->prop_id_port.max_op_port = module->max_op_port; +} + +struct audioreach_module *audioreach_get_container_last_module( + struct audioreach_container *container) +{ + struct audioreach_module *module; + + list_for_each_entry(module, &container->modules_list, node) { + if (module->dst_mod_inst_id == 0) + return module; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(audioreach_get_container_last_module); + +static bool is_module_in_container(struct audioreach_container *container, int module_iid) +{ + struct audioreach_module *module; + + list_for_each_entry(module, &container->modules_list, node) { + if (module->instance_id == module_iid) + return true; + } + + return false; +} + +struct audioreach_module *audioreach_get_container_first_module( + struct audioreach_container *container) +{ + struct audioreach_module *module; + + /* get the first module from both connected or un-connected containers */ + list_for_each_entry(module, &container->modules_list, node) { + if (module->src_mod_inst_id == 0 || + !is_module_in_container(container, module->src_mod_inst_id)) + return module; + } + return NULL; +} +EXPORT_SYMBOL_GPL(audioreach_get_container_first_module); + +struct audioreach_module *audioreach_get_container_next_module( + struct audioreach_container *container, + struct audioreach_module *module) +{ + int nmodule_iid = module->dst_mod_inst_id; + struct audioreach_module *nmodule; + + list_for_each_entry(nmodule, &container->modules_list, node) { + if (nmodule->instance_id == nmodule_iid) + return nmodule; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(audioreach_get_container_next_module); + +static void apm_populate_module_list_obj(struct apm_mod_list_obj *obj, + struct audioreach_container *container, + int sub_graph_id) +{ + struct audioreach_module *module; + int i; + + obj->sub_graph_id = sub_graph_id; + obj->container_id = container->container_id; + obj->num_modules = container->num_modules; + i = 0; + list_for_each_container_module(module, container) { + obj->mod_cfg[i].module_id = module->module_id; + obj->mod_cfg[i].instance_id = module->instance_id; + i++; + } +} + +static void audioreach_populate_graph(struct apm_graph_open_params *open, + struct list_head *sg_list, + int num_sub_graphs) +{ + struct apm_mod_conn_list_params *mc_data = open->mod_conn_list_data; + struct apm_module_list_params *ml_data = open->mod_list_data; + struct apm_prop_list_params *mp_data = open->mod_prop_data; + struct apm_container_params *c_data = open->cont_data; + struct apm_sub_graph_params *sg_data = open->sg_data; + int ncontainer = 0, nmodule = 0, nconn = 0; + struct apm_mod_prop_obj *module_prop_obj; + struct audioreach_container *container; + struct apm_module_conn_obj *conn_obj; + struct audioreach_module *module; + struct audioreach_sub_graph *sg; + struct apm_container_obj *cobj; + struct apm_mod_list_obj *mlobj; + int i = 0; + + mlobj = &ml_data->mod_list_obj[0]; + + list_for_each_entry(sg, sg_list, node) { + struct apm_sub_graph_data *sg_cfg = &sg_data->sg_cfg[i++]; + + apm_populate_sub_graph_config(sg_cfg, sg); + + list_for_each_entry(container, &sg->container_list, node) { + cobj = &c_data->cont_obj[ncontainer]; + + apm_populate_container_config(cobj, container); + apm_populate_module_list_obj(mlobj, container, sg->sub_graph_id); + + list_for_each_container_module(module, container) { + uint32_t src_mod_inst_id; + + src_mod_inst_id = module->src_mod_inst_id; + + module_prop_obj = &mp_data->mod_prop_obj[nmodule]; + apm_populate_module_prop_obj(module_prop_obj, module); + + if (src_mod_inst_id) { + conn_obj = &mc_data->conn_obj[nconn]; + apm_populate_connection_obj(conn_obj, module); + nconn++; + } + + nmodule++; + } + mlobj = (void *) mlobj + APM_MOD_LIST_OBJ_PSIZE(mlobj, container->num_modules); + + ncontainer++; + } + } +} + +void *audioreach_alloc_graph_pkt(struct q6apm *apm, struct list_head *sg_list, int graph_id) +{ + int payload_size, sg_sz, cont_sz, ml_sz, mp_sz, mc_sz; + struct apm_module_param_data *param_data; + struct apm_container_params *cont_params; + struct audioreach_container *container; + struct apm_sub_graph_params *sg_params; + struct apm_mod_conn_list_params *mcon; + struct apm_graph_open_params params; + struct apm_prop_list_params *mprop; + struct audioreach_module *module; + struct audioreach_sub_graph *sgs; + struct apm_mod_list_obj *mlobj; + int num_modules_per_list; + int num_connections = 0; + int num_containers = 0; + int num_sub_graphs = 0; + int num_modules = 0; + int num_modules_list; + struct gpr_pkt *pkt; + void *p; + + list_for_each_entry(sgs, sg_list, node) { + num_sub_graphs++; + list_for_each_entry(container, &sgs->container_list, node) { + num_containers++; + num_modules += container->num_modules; + list_for_each_container_module(module, container) { + if (module->src_mod_inst_id) + num_connections++; + } + } + } + + num_modules_list = num_containers; + num_modules_per_list = num_modules/num_containers; + sg_sz = APM_SUB_GRAPH_PSIZE(sg_params, num_sub_graphs); + cont_sz = APM_CONTAINER_PSIZE(cont_params, num_containers); + ml_sz = ALIGN(sizeof(struct apm_module_list_params) + + num_modules_list * APM_MOD_LIST_OBJ_PSIZE(mlobj, num_modules_per_list), 8); + mp_sz = APM_MOD_PROP_PSIZE(mprop, num_modules); + mc_sz = APM_MOD_CONN_PSIZE(mcon, num_connections); + + payload_size = sg_sz + cont_sz + ml_sz + mp_sz + mc_sz; + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_GRAPH_OPEN, 0); + if (IS_ERR(pkt)) + return pkt; + + p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + /* SubGraph */ + params.sg_data = p; + param_data = ¶ms.sg_data->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_SUB_GRAPH_CONFIG; + param_data->param_size = sg_sz - APM_MODULE_PARAM_DATA_SIZE; + params.sg_data->num_sub_graphs = num_sub_graphs; + p += sg_sz; + + /* Container */ + params.cont_data = p; + param_data = ¶ms.cont_data->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_CONTAINER_CONFIG; + param_data->param_size = cont_sz - APM_MODULE_PARAM_DATA_SIZE; + params.cont_data->num_containers = num_containers; + p += cont_sz; + + /* Module List*/ + params.mod_list_data = p; + param_data = ¶ms.mod_list_data->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_MODULE_LIST; + param_data->param_size = ml_sz - APM_MODULE_PARAM_DATA_SIZE; + params.mod_list_data->num_modules_list = num_sub_graphs; + p += ml_sz; + + /* Module Properties */ + params.mod_prop_data = p; + param_data = ¶ms.mod_prop_data->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_MODULE_PROP; + param_data->param_size = mp_sz - APM_MODULE_PARAM_DATA_SIZE; + params.mod_prop_data->num_modules_prop_cfg = num_modules; + p += mp_sz; + + /* Module Connections */ + params.mod_conn_list_data = p; + param_data = ¶ms.mod_conn_list_data->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_MODULE_CONN; + param_data->param_size = mc_sz - APM_MODULE_PARAM_DATA_SIZE; + params.mod_conn_list_data->num_connections = num_connections; + p += mc_sz; + + audioreach_populate_graph(¶ms, sg_list, num_sub_graphs); + + return pkt; +} +EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt); + +int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, + struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, + gpr_port_t *port, wait_queue_head_t *cmd_wait, + struct gpr_pkt *pkt, uint32_t rsp_opcode) +{ + + struct gpr_hdr *hdr = &pkt->hdr; + int rc; + + mutex_lock(cmd_lock); + result->opcode = 0; + result->status = 0; + + if (port) + rc = gpr_send_port_pkt(port, pkt); + else if (gdev) + rc = gpr_send_pkt(gdev, pkt); + else + rc = -EINVAL; + + if (rc < 0) + goto err; + + if (rsp_opcode) + rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode) || + (result->opcode == rsp_opcode), 5 * HZ); + else + rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), 5 * HZ); + + if (!rc) { + dev_err(dev, "CMD timeout for [%x] opcode\n", hdr->opcode); + rc = -ETIMEDOUT; + } else if (result->status > 0) { + dev_err(dev, "DSP returned error[%x] %x\n", hdr->opcode, result->status); + rc = -EINVAL; + } else { + /* DSP successfully finished the command */ + rc = 0; + } + +err: + mutex_unlock(cmd_lock); + return rc; +} +EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync); + +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, + uint32_t rsp_opcode) +{ + + return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock, + graph->port, &graph->cmd_wait, pkt, rsp_opcode); +} +EXPORT_SYMBOL_GPL(audioreach_graph_send_cmd_sync); + +/* LPASS Codec DMA port Module Media Format Setup */ +static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module, + struct audioreach_module_config *cfg) +{ + struct apm_codec_dma_module_intf_cfg *intf_cfg; + struct apm_module_frame_size_factor_cfg *fs_cfg; + struct apm_module_hw_ep_power_mode_cfg *pm_cfg; + struct apm_module_param_data *param_data; + struct apm_module_hw_ep_mf_cfg *hw_cfg; + int ic_sz, ep_sz, fs_sz, pm_sz, dl_sz; + int rc, payload_size; + struct gpr_pkt *pkt; + void *p; + + ic_sz = APM_CDMA_INTF_CFG_PSIZE; + ep_sz = APM_HW_EP_CFG_PSIZE; + fs_sz = APM_FS_CFG_PSIZE; + pm_sz = APM_HW_EP_PMODE_CFG_PSIZE; + dl_sz = 0; + + payload_size = ic_sz + ep_sz + fs_sz + pm_sz + dl_sz; + + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + hw_cfg = p; + param_data = &hw_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_MF_CFG; + param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; + + hw_cfg->mf.sample_rate = cfg->sample_rate; + hw_cfg->mf.bit_width = cfg->bit_width; + hw_cfg->mf.num_channels = cfg->num_channels; + hw_cfg->mf.data_format = module->data_format; + p += ep_sz; + + fs_cfg = p; + param_data = &fs_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_FRAME_SIZE_FACTOR; + param_data->param_size = fs_sz - APM_MODULE_PARAM_DATA_SIZE; + fs_cfg->frame_size_factor = 1; + p += fs_sz; + + intf_cfg = p; + param_data = &intf_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_CODEC_DMA_INTF_CFG; + param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; + + intf_cfg->cfg.lpaif_type = module->hw_interface_type; + intf_cfg->cfg.intf_index = module->hw_interface_idx; + intf_cfg->cfg.active_channels_mask = (1 << cfg->num_channels) - 1; + p += ic_sz; + + pm_cfg = p; + param_data = &pm_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_POWER_MODE_CFG; + param_data->param_size = pm_sz - APM_MODULE_PARAM_DATA_SIZE; + pm_cfg->power_mode.power_mode = 0; + + rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); + + kfree(pkt); + + return rc; +} + +static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module, + struct audioreach_module_config *cfg) +{ + struct apm_module_frame_size_factor_cfg *fs_cfg; + struct apm_module_param_data *param_data; + struct apm_i2s_module_intf_cfg *intf_cfg; + struct apm_module_hw_ep_mf_cfg *hw_cfg; + int ic_sz, ep_sz, fs_sz; + int rc, payload_size; + struct gpr_pkt *pkt; + void *p; + + ic_sz = APM_I2S_INTF_CFG_PSIZE; + ep_sz = APM_HW_EP_CFG_PSIZE; + fs_sz = APM_FS_CFG_PSIZE; + + payload_size = ic_sz + ep_sz + fs_sz; + + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + intf_cfg = p; + + param_data = &intf_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_I2S_INTF_CFG; + param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; + + intf_cfg->cfg.intf_idx = module->hw_interface_idx; + intf_cfg->cfg.sd_line_idx = module->sd_line_idx; + + switch (cfg->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + intf_cfg->cfg.ws_src = CONFIG_I2S_WS_SRC_INTERNAL; + break; + case SND_SOC_DAIFMT_CBP_CFP: + /* CPU is slave */ + intf_cfg->cfg.ws_src = CONFIG_I2S_WS_SRC_EXTERNAL; + break; + default: + break; + } + + p += ic_sz; + hw_cfg = p; + param_data = &hw_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_MF_CFG; + param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; + + hw_cfg->mf.sample_rate = cfg->sample_rate; + hw_cfg->mf.bit_width = cfg->bit_width; + hw_cfg->mf.num_channels = cfg->num_channels; + hw_cfg->mf.data_format = module->data_format; + + p += ep_sz; + fs_cfg = p; + param_data = &fs_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_FRAME_SIZE_FACTOR; + param_data->param_size = fs_sz - APM_MODULE_PARAM_DATA_SIZE; + fs_cfg->frame_size_factor = 1; + + rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); + + kfree(pkt); + + return rc; +} + +static int audioreach_logging_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module) +{ + struct apm_module_param_data *param_data; + struct data_logging_config *cfg; + int rc, payload_size; + struct gpr_pkt *pkt; + void *p; + + payload_size = sizeof(*cfg) + APM_MODULE_PARAM_DATA_SIZE; + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + param_data = p; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_DATA_LOGGING_CONFIG; + param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE; + + p = p + APM_MODULE_PARAM_DATA_SIZE; + cfg = p; + cfg->log_code = module->log_code; + cfg->log_tap_point_id = module->log_tap_point_id; + cfg->mode = module->log_mode; + + rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); + + kfree(pkt); + + return rc; +} + +static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module, + struct audioreach_module_config *mcfg) +{ + struct payload_pcm_output_format_cfg *media_cfg; + uint32_t num_channels = mcfg->num_channels; + struct apm_pcm_module_media_fmt_cmd *cfg; + struct apm_module_param_data *param_data; + int rc, payload_size; + struct gpr_pkt *pkt; + + if (num_channels > 2) { + dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels); + return -EINVAL; + } + + payload_size = APM_PCM_MODULE_FMT_CMD_PSIZE(num_channels); + + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + cfg = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + param_data = &cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_PCM_OUTPUT_FORMAT_CFG; + param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE; + + cfg->header.data_format = DATA_FORMAT_FIXED_POINT; + cfg->header.fmt_id = MEDIA_FMT_ID_PCM; + cfg->header.payload_size = APM_PCM_OUT_FMT_CFG_PSIZE(media_cfg, num_channels); + + media_cfg = &cfg->media_cfg; + media_cfg->alignment = PCM_LSB_ALIGNED; + media_cfg->bit_width = mcfg->bit_width; + media_cfg->endianness = PCM_LITTLE_ENDIAN; + media_cfg->interleaved = module->interleave_type; + media_cfg->num_channels = mcfg->num_channels; + media_cfg->q_factor = mcfg->bit_width - 1; + media_cfg->bits_per_sample = mcfg->bit_width; + + if (num_channels == 1) { + media_cfg->channel_mapping[0] = PCM_CHANNEL_L; + } else if (num_channels == 2) { + media_cfg->channel_mapping[0] = PCM_CHANNEL_L; + media_cfg->channel_mapping[1] = PCM_CHANNEL_R; + + } + + rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); + + kfree(pkt); + + return rc; +} + +static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module, + struct audioreach_module_config *mcfg) +{ + uint32_t num_channels = mcfg->num_channels; + struct apm_module_param_data *param_data; + struct payload_media_fmt_pcm *cfg; + struct media_format *header; + int rc, payload_size; + struct gpr_pkt *pkt; + void *p; + + if (num_channels > 2) { + dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels); + return -EINVAL; + } + + payload_size = APM_SHMEM_FMT_CFG_PSIZE(num_channels) + APM_MODULE_PARAM_DATA_SIZE; + + pkt = audioreach_alloc_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0, + graph->port->id, module->instance_id); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + param_data = p; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_MEDIA_FORMAT; + param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE; + p = p + APM_MODULE_PARAM_DATA_SIZE; + + header = p; + header->data_format = DATA_FORMAT_FIXED_POINT; + header->fmt_id = MEDIA_FMT_ID_PCM; + header->payload_size = payload_size - sizeof(*header); + + p = p + sizeof(*header); + cfg = p; + cfg->sample_rate = mcfg->sample_rate; + cfg->bit_width = mcfg->bit_width; + cfg->alignment = PCM_LSB_ALIGNED; + cfg->bits_per_sample = mcfg->bit_width; + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; + + if (mcfg->num_channels == 1) { + cfg->channel_mapping[0] = PCM_CHANNEL_L; + } else if (num_channels == 2) { + cfg->channel_mapping[0] = PCM_CHANNEL_L; + cfg->channel_mapping[1] = PCM_CHANNEL_R; + } + + rc = audioreach_graph_send_cmd_sync(graph, pkt, 0); + + kfree(pkt); + + return rc; +} + +int audioreach_gain_set_vol_ctrl(struct q6apm *apm, struct audioreach_module *module, int vol) +{ + struct param_id_vol_ctrl_master_gain *cfg; + struct apm_module_param_data *param_data; + int rc, payload_size; + struct gpr_pkt *pkt; + void *p; + + payload_size = sizeof(*cfg) + APM_MODULE_PARAM_DATA_SIZE; + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + param_data = p; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_VOL_CTRL_MASTER_GAIN; + param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE; + + p = p + APM_MODULE_PARAM_DATA_SIZE; + cfg = p; + cfg->master_gain = vol; + rc = q6apm_send_cmd_sync(apm, pkt, 0); + + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(audioreach_gain_set_vol_ctrl); + +static int audioreach_gain_set(struct q6apm_graph *graph, struct audioreach_module *module) +{ + struct apm_module_param_data *param_data; + struct apm_gain_module_cfg *cfg; + int rc, payload_size; + struct gpr_pkt *pkt; + + payload_size = APM_GAIN_CFG_PSIZE; + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + cfg = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + param_data = &cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = APM_PARAM_ID_GAIN; + param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE; + + cfg->gain_cfg.gain = module->gain; + + rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); + + kfree(pkt); + + return rc; +} + +int audioreach_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module, + struct audioreach_module_config *cfg) +{ + int rc; + + switch (module->module_id) { + case MODULE_ID_DATA_LOGGING: + rc = audioreach_logging_set_media_format(graph, module); + break; + case MODULE_ID_PCM_DEC: + case MODULE_ID_PCM_ENC: + case MODULE_ID_PCM_CNV: + rc = audioreach_pcm_set_media_format(graph, module, cfg); + break; + case MODULE_ID_I2S_SOURCE: + case MODULE_ID_I2S_SINK: + rc = audioreach_i2s_set_media_format(graph, module, cfg); + break; + case MODULE_ID_WR_SHARED_MEM_EP: + rc = audioreach_shmem_set_media_format(graph, module, cfg); + break; + case MODULE_ID_GAIN: + rc = audioreach_gain_set(graph, module); + break; + case MODULE_ID_CODEC_DMA_SINK: + case MODULE_ID_CODEC_DMA_SOURCE: + rc = audioreach_codec_dma_set_media_format(graph, module, cfg); + break; + default: + rc = 0; + } + + return rc; +} +EXPORT_SYMBOL_GPL(audioreach_set_media_format); + +void audioreach_graph_free_buf(struct q6apm_graph *graph) +{ + struct audioreach_graph_data *port; + + mutex_lock(&graph->lock); + port = &graph->rx_data; + port->num_periods = 0; + kfree(port->buf); + port->buf = NULL; + + port = &graph->tx_data; + port->num_periods = 0; + kfree(port->buf); + port->buf = NULL; + mutex_unlock(&graph->lock); +} +EXPORT_SYMBOL_GPL(audioreach_graph_free_buf); + +int audioreach_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, size_t period_sz, + unsigned int periods, bool is_contiguous) +{ + struct apm_shared_map_region_payload *mregions; + struct apm_cmd_shared_mem_map_regions *cmd; + uint32_t num_regions, buf_sz, payload_size; + struct audioreach_graph_data *data; + struct gpr_pkt *pkt; + void *p; + int rc, i; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + data = &graph->rx_data; + else + data = &graph->tx_data; + + if (is_contiguous) { + num_regions = 1; + buf_sz = period_sz * periods; + } else { + buf_sz = period_sz; + num_regions = periods; + } + + /* DSP expects size should be aligned to 4K */ + buf_sz = ALIGN(buf_sz, 4096); + + payload_size = sizeof(*cmd) + (sizeof(*mregions) * num_regions); + + pkt = audioreach_alloc_apm_pkt(payload_size, APM_CMD_SHARED_MEM_MAP_REGIONS, dir, + graph->port->id); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + p = (void *)pkt + GPR_HDR_SIZE; + cmd = p; + cmd->mem_pool_id = APM_MEMORY_MAP_SHMEM8_4K_POOL; + cmd->num_regions = num_regions; + + cmd->property_flag = 0x0; + + mregions = p + sizeof(*cmd); + + mutex_lock(&graph->lock); + + for (i = 0; i < num_regions; i++) { + struct audio_buffer *ab; + + ab = &data->buf[i]; + mregions->shm_addr_lsw = lower_32_bits(ab->phys); + mregions->shm_addr_msw = upper_32_bits(ab->phys); + mregions->mem_size_bytes = buf_sz; + ++mregions; + } + mutex_unlock(&graph->lock); + + rc = audioreach_graph_send_cmd_sync(graph, pkt, APM_CMD_RSP_SHARED_MEM_MAP_REGIONS); + + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(audioreach_map_memory_regions); + +int audioreach_shared_memory_send_eos(struct q6apm_graph *graph) +{ + struct data_cmd_wr_sh_mem_ep_eos *eos; + struct gpr_pkt *pkt; + int rc = 0, iid; + + iid = q6apm_graph_get_rx_shmem_module_iid(graph); + pkt = audioreach_alloc_cmd_pkt(sizeof(*eos), DATA_CMD_WR_SH_MEM_EP_EOS, 0, + graph->port->id, iid); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + eos = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + eos->policy = WR_SH_MEM_EP_EOS_POLICY_LAST; + + rc = gpr_send_port_pkt(graph->port, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(audioreach_shared_memory_send_eos); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h new file mode 100644 index 000000000000..4f693a2660b5 --- /dev/null +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -0,0 +1,726 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __AUDIOREACH_H__ +#define __AUDIOREACH_H__ +#include <linux/types.h> +#include <linux/soc/qcom/apr.h> +#include <sound/soc.h> +struct q6apm; +struct q6apm_graph; + +/* Module IDs */ +#define MODULE_ID_WR_SHARED_MEM_EP 0x07001000 +#define MODULE_ID_RD_SHARED_MEM_EP 0x07001001 +#define MODULE_ID_GAIN 0x07001002 +#define MODULE_ID_PCM_CNV 0x07001003 +#define MODULE_ID_PCM_ENC 0x07001004 +#define MODULE_ID_PCM_DEC 0x07001005 +#define MODULE_ID_CODEC_DMA_SINK 0x07001023 +#define MODULE_ID_CODEC_DMA_SOURCE 0x07001024 +#define MODULE_ID_I2S_SINK 0x0700100A +#define MODULE_ID_I2S_SOURCE 0x0700100B +#define MODULE_ID_DATA_LOGGING 0x0700101A + +#define APM_CMD_GET_SPF_STATE 0x01001021 +#define APM_CMD_RSP_GET_SPF_STATE 0x02001007 + +#define APM_MODULE_INSTANCE_ID 0x00000001 +#define PRM_MODULE_INSTANCE_ID 0x00000002 +#define AMDB_MODULE_INSTANCE_ID 0x00000003 +#define VCPM_MODULE_INSTANCE_ID 0x00000004 +#define AR_MODULE_INSTANCE_ID_START 0x00006000 +#define AR_MODULE_INSTANCE_ID_END 0x00007000 +#define AR_MODULE_DYNAMIC_INSTANCE_ID_START 0x00007000 +#define AR_MODULE_DYNAMIC_INSTANCE_ID_END 0x00008000 +#define AR_CONT_INSTANCE_ID_START 0x00005000 +#define AR_CONT_INSTANCE_ID_END 0x00006000 +#define AR_SG_INSTANCE_ID_START 0x00004000 + +#define APM_CMD_GRAPH_OPEN 0x01001000 +#define APM_CMD_GRAPH_PREPARE 0x01001001 +#define APM_CMD_GRAPH_START 0x01001002 +#define APM_CMD_GRAPH_STOP 0x01001003 +#define APM_CMD_GRAPH_CLOSE 0x01001004 +#define APM_CMD_GRAPH_FLUSH 0x01001005 +#define APM_CMD_SET_CFG 0x01001006 +#define APM_CMD_GET_CFG 0x01001007 +#define APM_CMD_SHARED_MEM_MAP_REGIONS 0x0100100C +#define APM_CMD_SHARED_MEM_UNMAP_REGIONS 0x0100100D +#define APM_CMD_RSP_SHARED_MEM_MAP_REGIONS 0x02001001 +#define APM_CMD_RSP_GET_CFG 0x02001000 +#define APM_CMD_CLOSE_ALL 0x01001013 +#define APM_CMD_REGISTER_SHARED_CFG 0x0100100A + +#define APM_MEMORY_MAP_SHMEM8_4K_POOL 3 + +struct apm_cmd_shared_mem_map_regions { + uint16_t mem_pool_id; + uint16_t num_regions; + uint32_t property_flag; +} __packed; + +struct apm_shared_map_region_payload { + uint32_t shm_addr_lsw; + uint32_t shm_addr_msw; + uint32_t mem_size_bytes; +} __packed; + +struct apm_cmd_shared_mem_unmap_regions { + uint32_t mem_map_handle; +} __packed; + +struct apm_cmd_rsp_shared_mem_map_regions { + uint32_t mem_map_handle; +} __packed; + +/* APM module */ +#define APM_PARAM_ID_SUB_GRAPH_LIST 0x08001005 + +#define APM_PARAM_ID_MODULE_LIST 0x08001002 + +struct apm_param_id_modules_list { + uint32_t num_modules_list; +} __packed; + +#define APM_PARAM_ID_MODULE_PROP 0x08001003 + +struct apm_param_id_module_prop { + uint32_t num_modules_prop_cfg; +} __packed; + +struct apm_module_prop_cfg { + uint32_t instance_id; + uint32_t num_props; +} __packed; + +#define APM_PARAM_ID_MODULE_CONN 0x08001004 + +struct apm_param_id_module_conn { + uint32_t num_connections; +} __packed; + +struct apm_module_conn_obj { + uint32_t src_mod_inst_id; + uint32_t src_mod_op_port_id; + uint32_t dst_mod_inst_id; + uint32_t dst_mod_ip_port_id; +} __packed; + +#define APM_PARAM_ID_GAIN 0x08001006 + +struct param_id_gain_cfg { + uint16_t gain; + uint16_t reserved; +} __packed; + +#define PARAM_ID_PCM_OUTPUT_FORMAT_CFG 0x08001008 + +struct param_id_pcm_output_format_cfg { + uint32_t data_format; + uint32_t fmt_id; + uint32_t payload_size; +} __packed; + +struct payload_pcm_output_format_cfg { + uint16_t bit_width; + uint16_t alignment; + uint16_t bits_per_sample; + uint16_t q_factor; + uint16_t endianness; + uint16_t interleaved; + uint16_t reserved; + uint16_t num_channels; + uint8_t channel_mapping[]; +} __packed; + +#define PARAM_ID_ENC_BITRATE 0x08001052 + +struct param_id_enc_bitrate_param { + uint32_t bitrate; +} __packed; + +#define DATA_FORMAT_FIXED_POINT 1 +#define PCM_LSB_ALIGNED 1 +#define PCM_MSB_ALIGNED 2 +#define PCM_LITTLE_ENDIAN 1 +#define PCM_BIT_ENDIAN 2 + +#define MEDIA_FMT_ID_PCM 0x09001000 +#define PCM_CHANNEL_L 1 +#define PCM_CHANNEL_R 2 +#define SAMPLE_RATE_48K 48000 +#define BIT_WIDTH_16 16 + +#define APM_PARAM_ID_PROP_PORT_INFO 0x08001015 + +struct apm_modules_prop_info { + uint32_t max_ip_port; + uint32_t max_op_port; +} __packed; + +/* Shared memory module */ +#define DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER 0x04001000 +#define WR_SH_MEM_EP_TIMESTAMP_VALID_FLAG BIT(31) +#define WR_SH_MEM_EP_LAST_BUFFER_FLAG BIT(30) +#define WR_SH_MEM_EP_TS_CONTINUE_FLAG BIT(29) +#define WR_SH_MEM_EP_EOF_FLAG BIT(4) + +struct apm_data_cmd_wr_sh_mem_ep_data_buffer { + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t buf_size; + uint32_t timestamp_lsw; + uint32_t timestamp_msw; + uint32_t flags; +} __packed; + +#define DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2 0x0400100A + +struct apm_data_cmd_wr_sh_mem_ep_data_buffer_v2 { + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t buf_size; + uint32_t timestamp_lsw; + uint32_t timestamp_msw; + uint32_t flags; + uint32_t md_addr_lsw; + uint32_t md_addr_msw; + uint32_t md_map_handle; + uint32_t md_buf_size; +} __packed; + +#define DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE 0x05001000 + +struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done { + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t status; + +} __packed; + +#define DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE_V2 0x05001004 + +struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done_v2 { + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t status; + uint32_t md_buf_addr_lsw; + uint32_t md_buf_addr_msw; + uint32_t md_mem_map_handle; + uint32_t md_status; +} __packed; + +#define PARAM_ID_MEDIA_FORMAT 0x0800100C +#define DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT 0x04001001 + +struct apm_media_format { + uint32_t data_format; + uint32_t fmt_id; + uint32_t payload_size; +} __packed; + +#define DATA_CMD_WR_SH_MEM_EP_EOS 0x04001002 +#define WR_SH_MEM_EP_EOS_POLICY_LAST 1 +#define WR_SH_MEM_EP_EOS_POLICY_EACH 2 + +struct data_cmd_wr_sh_mem_ep_eos { + uint32_t policy; + +} __packed; + +#define DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER 0x04001003 + +struct data_cmd_rd_sh_mem_ep_data_buffer { + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t buf_size; +} __packed; + +#define DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER 0x05001002 + +struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done { + uint32_t status; + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t data_size; + uint32_t offset; + uint32_t timestamp_lsw; + uint32_t timestamp_msw; + uint32_t flags; + uint32_t num_frames; +} __packed; + +#define DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2 0x0400100B + +struct data_cmd_rd_sh_mem_ep_data_buffer_v2 { + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t buf_size; + uint32_t md_buf_addr_lsw; + uint32_t md_buf_addr_msw; + uint32_t md_mem_map_handle; + uint32_t md_buf_size; +} __packed; + +#define DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER_V2 0x05001005 + +struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 { + uint32_t status; + uint32_t buf_addr_lsw; + uint32_t buf_addr_msw; + uint32_t mem_map_handle; + uint32_t data_size; + uint32_t offset; + uint32_t timestamp_lsw; + uint32_t timestamp_msw; + uint32_t flags; + uint32_t num_frames; + uint32_t md_status; + uint32_t md_buf_addr_lsw; + uint32_t md_buf_addr_msw; + uint32_t md_mem_map_handle; + uint32_t md_size; +} __packed; + +#define PARAM_ID_RD_SH_MEM_CFG 0x08001007 + +struct param_id_rd_sh_mem_cfg { + uint32_t num_frames_per_buffer; + uint32_t metadata_control_flags; + +} __packed; + +#define DATA_CMD_WR_SH_MEM_EP_EOS_RENDERED 0x05001001 + +struct data_cmd_wr_sh_mem_ep_eos_rendered { + uint32_t module_instance_id; + uint32_t render_status; +} __packed; + +#define MODULE_ID_WR_SHARED_MEM_EP 0x07001000 + +struct apm_cmd_header { + uint32_t payload_address_lsw; + uint32_t payload_address_msw; + uint32_t mem_map_handle; + uint32_t payload_size; +} __packed; + +#define APM_CMD_HDR_SIZE sizeof(struct apm_cmd_header) + +struct apm_module_param_data { + uint32_t module_instance_id; + uint32_t param_id; + uint32_t param_size; + uint32_t error_code; +} __packed; + +#define APM_MODULE_PARAM_DATA_SIZE sizeof(struct apm_module_param_data) + +struct apm_module_param_shared_data { + uint32_t param_id; + uint32_t param_size; +} __packed; + +struct apm_prop_data { + uint32_t prop_id; + uint32_t prop_size; +} __packed; + +/* Sub-Graph Properties */ +#define APM_PARAM_ID_SUB_GRAPH_CONFIG 0x08001001 + +struct apm_param_id_sub_graph_cfg { + uint32_t num_sub_graphs; +} __packed; + +struct apm_sub_graph_cfg { + uint32_t sub_graph_id; + uint32_t num_sub_graph_prop; +} __packed; + +#define APM_SUB_GRAPH_PROP_ID_PERF_MODE 0x0800100E + +struct apm_sg_prop_id_perf_mode { + uint32_t perf_mode; +} __packed; + +#define APM_SG_PROP_ID_PERF_MODE_SIZE 4 + +#define APM_SUB_GRAPH_PROP_ID_DIRECTION 0x0800100F + +struct apm_sg_prop_id_direction { + uint32_t direction; +} __packed; + +#define APM_SG_PROP_ID_DIR_SIZE 4 + +#define APM_SUB_GRAPH_PROP_ID_SCENARIO_ID 0x08001010 +#define APM_SUB_GRAPH_SID_AUDIO_PLAYBACK 0x1 +#define APM_SUB_GRAPH_SID_AUDIO_RECORD 0x2 +#define APM_SUB_GRAPH_SID_AUDIO_VOICE_CALL 0x3 + +struct apm_sg_prop_id_scenario_id { + uint32_t scenario_id; +} __packed; + +#define APM_SG_PROP_ID_SID_SIZE 4 +/* container api */ +#define APM_PARAM_ID_CONTAINER_CONFIG 0x08001000 + +struct apm_param_id_container_cfg { + uint32_t num_containers; +} __packed; + +struct apm_container_cfg { + uint32_t container_id; + uint32_t num_prop; +} __packed; + +struct apm_cont_capability { + uint32_t capability_id; +} __packed; + +#define APM_CONTAINER_PROP_ID_CAPABILITY_LIST 0x08001011 +#define APM_CONTAINER_PROP_ID_CAPABILITY_SIZE 8 + +#define APM_PROP_ID_INVALID 0x0 +#define APM_CONTAINER_CAP_ID_PP 0x1 +#define APM_CONTAINER_CAP_ID_PP 0x1 + +struct apm_cont_prop_id_cap_list { + uint32_t num_capability_id; +} __packed; + +#define APM_CONTAINER_PROP_ID_GRAPH_POS 0x08001012 + +struct apm_cont_prop_id_graph_pos { + uint32_t graph_pos; +} __packed; + +#define APM_CONTAINER_PROP_ID_STACK_SIZE 0x08001013 + +struct apm_cont_prop_id_stack_size { + uint32_t stack_size; +} __packed; + +#define APM_CONTAINER_PROP_ID_PROC_DOMAIN 0x08001014 + +struct apm_cont_prop_id_domain { + uint32_t proc_domain; +} __packed; + +#define CONFIG_I2S_WS_SRC_EXTERNAL 0x0 +#define CONFIG_I2S_WS_SRC_INTERNAL 0x1 + +#define PARAM_ID_I2S_INTF_CFG 0x08001019 +struct param_id_i2s_intf_cfg { + uint32_t lpaif_type; + uint32_t intf_idx; + uint16_t sd_line_idx; + uint16_t ws_src; +} __packed; + +#define I2S_INTF_TYPE_PRIMARY 0 +#define I2S_INTF_TYPE_SECOINDARY 1 +#define I2S_INTF_TYPE_TERTINARY 2 +#define I2S_INTF_TYPE_QUATERNARY 3 +#define I2S_INTF_TYPE_QUINARY 4 +#define I2S_SD0 1 +#define I2S_SD1 2 +#define I2S_SD2 3 +#define I2S_SD3 4 + +#define PORT_ID_I2S_INPUT 2 +#define PORT_ID_I2S_OUPUT 1 +#define I2S_STACK_SIZE 2048 + +#define PARAM_ID_HW_EP_MF_CFG 0x08001017 +struct param_id_hw_ep_mf { + uint32_t sample_rate; + uint16_t bit_width; + uint16_t num_channels; + uint32_t data_format; +} __packed; + +#define PARAM_ID_HW_EP_FRAME_SIZE_FACTOR 0x08001018 + +struct param_id_fram_size_factor { + uint32_t frame_size_factor; +} __packed; + +#define APM_CONTAINER_PROP_ID_PARENT_CONTAINER_ID 0x080010CB + +struct apm_cont_prop_id_parent_container { + uint32_t parent_container_id; +} __packed; + +#define APM_CONTAINER_PROP_ID_HEAP_ID 0x08001174 +#define APM_CONT_HEAP_DEFAULT 0x1 +#define APM_CONT_HEAP_LOW_POWER 0x2 + +struct apm_cont_prop_id_headp_id { + uint32_t heap_id; +} __packed; + +struct apm_modules_list { + uint32_t sub_graph_id; + uint32_t container_id; + uint32_t num_modules; +} __packed; + +struct apm_module_obj { + uint32_t module_id; + uint32_t instance_id; +} __packed; + +#define APM_MODULE_PROP_ID_PORT_INFO 0x08001015 +#define APM_MODULE_PROP_ID_PORT_INFO_SZ 8 +struct apm_module_prop_id_port_info { + uint32_t max_ip_port; + uint32_t max_op_port; +} __packed; + +#define DATA_LOGGING_MAX_INPUT_PORTS 0x1 +#define DATA_LOGGING_MAX_OUTPUT_PORTS 0x1 +#define DATA_LOGGING_STACK_SIZE 2048 +#define PARAM_ID_DATA_LOGGING_CONFIG 0x08001031 + +struct data_logging_config { + uint32_t log_code; + uint32_t log_tap_point_id; + uint32_t mode; +} __packed; + +#define PARAM_ID_MFC_OUTPUT_MEDIA_FORMAT 0x08001024 + +struct param_id_mfc_media_format { + uint32_t sample_rate; + uint16_t bit_width; + uint16_t num_channels; + uint16_t channel_mapping[]; +} __packed; + +struct media_format { + uint32_t data_format; + uint32_t fmt_id; + uint32_t payload_size; +} __packed; + +struct payload_media_fmt_pcm { + uint32_t sample_rate; + uint16_t bit_width; + uint16_t alignment; + uint16_t bits_per_sample; + uint16_t q_factor; + uint16_t endianness; + uint16_t num_channels; + uint8_t channel_mapping[]; +} __packed; + +#define PARAM_ID_CODEC_DMA_INTF_CFG 0x08001063 + +struct param_id_codec_dma_intf_cfg { + /* 1 - RXTX + * 2 - WSA + * 3 - VA + * 4 - AXI + */ + uint32_t lpaif_type; + /* + * RX0 | TX0 = 1 + * RX1 | TX1 = 2 + * RX2 | TX2 = 3... so on + */ + uint32_t intf_index; + uint32_t active_channels_mask; +} __packed; + +struct audio_hw_clk_cfg { + uint32_t clock_id; + uint32_t clock_freq; + uint32_t clock_attri; + uint32_t clock_root; +} __packed; + +#define PARAM_ID_HW_EP_POWER_MODE_CFG 0x8001176 +#define AR_HW_EP_POWER_MODE_0 0 /* default */ +#define AR_HW_EP_POWER_MODE_1 1 /* XO Shutdown allowed */ +#define AR_HW_EP_POWER_MODE_2 2 /* XO Shutdown not allowed */ + +struct param_id_hw_ep_power_mode_cfg { + uint32_t power_mode; +} __packed; + +#define PARAM_ID_HW_EP_DMA_DATA_ALIGN 0x08001233 +#define AR_HW_EP_DMA_DATA_ALIGN_MSB 0 +#define AR_HW_EP_DMA_DATA_ALIGN_LSB 1 +#define AR_PCM_MAX_NUM_CHANNEL 8 + +struct param_id_hw_ep_dma_data_align { + uint32_t dma_data_align; +} __packed; + +#define PARAM_ID_VOL_CTRL_MASTER_GAIN 0x08001035 +#define VOL_CTRL_DEFAULT_GAIN 0x2000 + +struct param_id_vol_ctrl_master_gain { + uint16_t master_gain; + uint16_t reserved; +} __packed; + + +/* Graph */ +struct audioreach_connection { + /* Connections */ + uint32_t src_mod_inst_id; + uint32_t src_mod_op_port_id; + uint32_t dst_mod_inst_id; + uint32_t dst_mod_ip_port_id; + struct list_head node; +}; + +struct audioreach_graph_info { + int id; + uint32_t num_sub_graphs; + struct list_head sg_list; + struct list_head connection_list; +}; + +struct audioreach_sub_graph { + uint32_t sub_graph_id; + uint32_t perf_mode; + uint32_t direction; + uint32_t scenario_id; + struct list_head node; + + struct audioreach_graph_info *info; + uint32_t num_containers; + struct list_head container_list; +}; + +struct audioreach_container { + uint32_t container_id; + uint32_t capability_id; + uint32_t graph_pos; + uint32_t stack_size; + uint32_t proc_domain; + struct list_head node; + + uint32_t num_modules; + struct list_head modules_list; + struct audioreach_sub_graph *sub_graph; +}; + +struct audioreach_module { + uint32_t module_id; + uint32_t instance_id; + + uint32_t max_ip_port; + uint32_t max_op_port; + + uint32_t in_port; + uint32_t out_port; + + /* Connections */ + uint32_t src_mod_inst_id; + uint32_t src_mod_op_port_id; + uint32_t dst_mod_inst_id; + uint32_t dst_mod_ip_port_id; + + /* Format specifics */ + uint32_t ch_fmt; + uint32_t rate; + uint32_t bit_depth; + + /* I2S module */ + uint32_t hw_interface_idx; + uint32_t sd_line_idx; + uint32_t ws_src; + uint32_t frame_size_factor; + uint32_t data_format; + uint32_t hw_interface_type; + + /* PCM module specific */ + uint32_t interleave_type; + + /* GAIN/Vol Control Module */ + uint16_t gain; + + /* Logging */ + uint32_t log_code; + uint32_t log_tap_point_id; + uint32_t log_mode; + + /* bookkeeping */ + struct list_head node; + struct audioreach_container *container; + struct snd_soc_dapm_widget *widget; +}; + +struct audioreach_module_config { + int direction; + u32 sample_rate; + u16 bit_width; + u16 bits_per_sample; + + u16 data_format; + u16 num_channels; + u16 active_channels_mask; + u32 sd_line_mask; + int fmt; + u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; +}; + +/* Packet Allocation routines */ +void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t + token); +void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, + uint32_t token, uint32_t src_port, + uint32_t dest_port); +void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token, + uint32_t src_port); +void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, + uint32_t token, uint32_t src_port, + uint32_t dest_port); +void *audioreach_alloc_graph_pkt(struct q6apm *apm, + struct list_head *sg_list, + int graph_id); +/* Topology specific */ +int audioreach_tplg_init(struct snd_soc_component *component); + +/* Module specific */ +void audioreach_graph_free_buf(struct q6apm_graph *graph); +int audioreach_map_memory_regions(struct q6apm_graph *graph, + unsigned int dir, size_t period_sz, + unsigned int periods, + bool is_contiguous); +int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, + struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, + struct gpr_pkt *pkt, uint32_t rsp_opcode); +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, + uint32_t rsp_opcode); +int audioreach_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module, + struct audioreach_module_config *cfg); +int audioreach_shared_memory_send_eos(struct q6apm_graph *graph); +int audioreach_gain_set_vol_ctrl(struct q6apm *apm, + struct audioreach_module *module, int vol); +struct audioreach_module *audioreach_get_container_last_module( + struct audioreach_container *container); +struct audioreach_module *audioreach_get_container_first_module( + struct audioreach_container *container); +struct audioreach_module *audioreach_get_container_next_module( + struct audioreach_container *container, + struct audioreach_module *module); +#define list_for_each_container_module(mod, cont) \ + for (mod = audioreach_get_container_first_module(cont); mod != NULL; \ + mod = audioreach_get_container_next_module(cont, mod)) +#endif /* __AUDIOREACH_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6afe-clocks.c b/sound/soc/qcom/qdsp6/q6afe-clocks.c index 9431656283cd..1ccab64ff00b 100644 --- a/sound/soc/qcom/qdsp6/q6afe-clocks.c +++ b/sound/soc/qcom/qdsp6/q6afe-clocks.c @@ -7,115 +7,18 @@ #include <linux/module.h> #include <linux/device.h> #include <linux/platform_device.h> -#include <linux/of.h> -#include <linux/slab.h> +#include "q6dsp-lpass-clocks.h" #include "q6afe.h" #define Q6AFE_CLK(id) { \ .clk_id = id, \ - .afe_clk_id = Q6AFE_##id, \ + .q6dsp_clk_id = Q6AFE_##id, \ .name = #id, \ .rate = 19200000, \ } -#define Q6AFE_VOTE_CLK(id, blkid, n) { \ - .clk_id = id, \ - .afe_clk_id = blkid, \ - .name = n, \ - } - -struct q6afe_clk_init { - int clk_id; - int afe_clk_id; - char *name; - int rate; -}; - -struct q6afe_clk { - struct device *dev; - int afe_clk_id; - int attributes; - int rate; - uint32_t handle; - struct clk_hw hw; -}; - -#define to_q6afe_clk(_hw) container_of(_hw, struct q6afe_clk, hw) - -struct q6afe_cc { - struct device *dev; - struct q6afe_clk *clks[Q6AFE_MAX_CLK_ID]; -}; - -static int clk_q6afe_prepare(struct clk_hw *hw) -{ - struct q6afe_clk *clk = to_q6afe_clk(hw); - - return q6afe_set_lpass_clock(clk->dev, clk->afe_clk_id, clk->attributes, - Q6AFE_LPASS_CLK_ROOT_DEFAULT, clk->rate); -} - -static void clk_q6afe_unprepare(struct clk_hw *hw) -{ - struct q6afe_clk *clk = to_q6afe_clk(hw); - - q6afe_set_lpass_clock(clk->dev, clk->afe_clk_id, clk->attributes, - Q6AFE_LPASS_CLK_ROOT_DEFAULT, 0); -} - -static int clk_q6afe_set_rate(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - struct q6afe_clk *clk = to_q6afe_clk(hw); - - clk->rate = rate; - - return 0; -} - -static unsigned long clk_q6afe_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct q6afe_clk *clk = to_q6afe_clk(hw); - - return clk->rate; -} - -static long clk_q6afe_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) -{ - return rate; -} - -static const struct clk_ops clk_q6afe_ops = { - .prepare = clk_q6afe_prepare, - .unprepare = clk_q6afe_unprepare, - .set_rate = clk_q6afe_set_rate, - .round_rate = clk_q6afe_round_rate, - .recalc_rate = clk_q6afe_recalc_rate, -}; - -static int clk_vote_q6afe_block(struct clk_hw *hw) -{ - struct q6afe_clk *clk = to_q6afe_clk(hw); - - return q6afe_vote_lpass_core_hw(clk->dev, clk->afe_clk_id, - clk_hw_get_name(&clk->hw), &clk->handle); -} -static void clk_unvote_q6afe_block(struct clk_hw *hw) -{ - struct q6afe_clk *clk = to_q6afe_clk(hw); - - q6afe_unvote_lpass_core_hw(clk->dev, clk->afe_clk_id, clk->handle); -} - -static const struct clk_ops clk_vote_q6afe_ops = { - .prepare = clk_vote_q6afe_block, - .unprepare = clk_unvote_q6afe_block, -}; - -static const struct q6afe_clk_init q6afe_clks[] = { +static const struct q6dsp_clk_init q6afe_clks[] = { Q6AFE_CLK(LPASS_CLK_ID_PRI_MI2S_IBIT), Q6AFE_CLK(LPASS_CLK_ID_PRI_MI2S_EBIT), Q6AFE_CLK(LPASS_CLK_ID_SEC_MI2S_IBIT), @@ -176,88 +79,28 @@ static const struct q6afe_clk_init q6afe_clks[] = { Q6AFE_CLK(LPASS_CLK_ID_RX_CORE_MCLK), Q6AFE_CLK(LPASS_CLK_ID_RX_CORE_NPL_MCLK), Q6AFE_CLK(LPASS_CLK_ID_VA_CORE_2X_MCLK), - Q6AFE_VOTE_CLK(LPASS_HW_AVTIMER_VOTE, + Q6DSP_VOTE_CLK(LPASS_HW_AVTIMER_VOTE, Q6AFE_LPASS_CORE_AVTIMER_BLOCK, "LPASS_AVTIMER_MACRO"), - Q6AFE_VOTE_CLK(LPASS_HW_MACRO_VOTE, + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6AFE_LPASS_CORE_HW_MACRO_BLOCK, "LPASS_HW_MACRO"), - Q6AFE_VOTE_CLK(LPASS_HW_DCODEC_VOTE, + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6AFE_LPASS_CORE_HW_DCODEC_BLOCK, "LPASS_HW_DCODEC"), }; -static struct clk_hw *q6afe_of_clk_hw_get(struct of_phandle_args *clkspec, - void *data) -{ - struct q6afe_cc *cc = data; - unsigned int idx = clkspec->args[0]; - unsigned int attr = clkspec->args[1]; - - if (idx >= Q6AFE_MAX_CLK_ID || attr > LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR) { - dev_err(cc->dev, "Invalid clk specifier (%d, %d)\n", idx, attr); - return ERR_PTR(-EINVAL); - } - - if (cc->clks[idx]) { - cc->clks[idx]->attributes = attr; - return &cc->clks[idx]->hw; - } - - return ERR_PTR(-ENOENT); -} - -static int q6afe_clock_dev_probe(struct platform_device *pdev) -{ - struct q6afe_cc *cc; - struct device *dev = &pdev->dev; - int i, ret; - - cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); - if (!cc) - return -ENOMEM; - - cc->dev = dev; - for (i = 0; i < ARRAY_SIZE(q6afe_clks); i++) { - unsigned int id = q6afe_clks[i].clk_id; - struct clk_init_data init = { - .name = q6afe_clks[i].name, - }; - struct q6afe_clk *clk; - - clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL); - if (!clk) - return -ENOMEM; - - clk->dev = dev; - clk->afe_clk_id = q6afe_clks[i].afe_clk_id; - clk->rate = q6afe_clks[i].rate; - clk->hw.init = &init; - - if (clk->rate) - init.ops = &clk_q6afe_ops; - else - init.ops = &clk_vote_q6afe_ops; - - cc->clks[id] = clk; - - ret = devm_clk_hw_register(dev, &clk->hw); - if (ret) - return ret; - } - - ret = devm_of_clk_add_hw_provider(dev, q6afe_of_clk_hw_get, cc); - if (ret) - return ret; - - dev_set_drvdata(dev, cc); - - return 0; -} +static const struct q6dsp_clk_desc q6dsp_clk_q6afe __maybe_unused = { + .clks = q6afe_clks, + .num_clks = ARRAY_SIZE(q6afe_clks), + .lpass_set_clk = q6afe_set_lpass_clock, + .lpass_vote_clk = q6afe_vote_lpass_core_hw, + .lpass_unvote_clk = q6afe_unvote_lpass_core_hw, +}; #ifdef CONFIG_OF static const struct of_device_id q6afe_clock_device_id[] = { - { .compatible = "qcom,q6afe-clocks" }, + { .compatible = "qcom,q6afe-clocks", .data = &q6dsp_clk_q6afe }, {}, }; MODULE_DEVICE_TABLE(of, q6afe_clock_device_id); @@ -268,7 +111,7 @@ static struct platform_driver q6afe_clock_platform_driver = { .name = "q6afe-clock", .of_match_table = of_match_ptr(q6afe_clock_device_id), }, - .probe = q6afe_clock_dev_probe, + .probe = q6dsp_clock_dev_probe, }; module_platform_driver(q6afe_clock_platform_driver); diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index ac8f7324e94b..8bb7452b8f18 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -11,91 +11,9 @@ #include <sound/pcm.h> #include <sound/soc.h> #include <sound/pcm_params.h> +#include "q6dsp-lpass-ports.h" #include "q6afe.h" -#define Q6AFE_TDM_PB_DAI(pre, num, did) { \ - .playback = { \ - .stream_name = pre" TDM"#num" Playback", \ - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ - SNDRV_PCM_RATE_176400, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FMTBIT_S32_LE, \ - .channels_min = 1, \ - .channels_max = 8, \ - .rate_min = 8000, \ - .rate_max = 176400, \ - }, \ - .name = #did, \ - .ops = &q6tdm_ops, \ - .id = did, \ - .probe = msm_dai_q6_dai_probe, \ - .remove = msm_dai_q6_dai_remove, \ - } - -#define Q6AFE_TDM_CAP_DAI(pre, num, did) { \ - .capture = { \ - .stream_name = pre" TDM"#num" Capture", \ - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ - SNDRV_PCM_RATE_176400, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FMTBIT_S32_LE, \ - .channels_min = 1, \ - .channels_max = 8, \ - .rate_min = 8000, \ - .rate_max = 176400, \ - }, \ - .name = #did, \ - .ops = &q6tdm_ops, \ - .id = did, \ - .probe = msm_dai_q6_dai_probe, \ - .remove = msm_dai_q6_dai_remove, \ - } - -#define Q6AFE_CDC_DMA_RX_DAI(did) { \ - .playback = { \ - .stream_name = #did" Playback", \ - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ - SNDRV_PCM_RATE_176400, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FMTBIT_S32_LE, \ - .channels_min = 1, \ - .channels_max = 8, \ - .rate_min = 8000, \ - .rate_max = 176400, \ - }, \ - .name = #did, \ - .ops = &q6dma_ops, \ - .id = did, \ - .probe = msm_dai_q6_dai_probe, \ - .remove = msm_dai_q6_dai_remove, \ - } - -#define Q6AFE_CDC_DMA_TX_DAI(did) { \ - .capture = { \ - .stream_name = #did" Capture", \ - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ - SNDRV_PCM_RATE_176400, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE | \ - SNDRV_PCM_FMTBIT_S24_LE | \ - SNDRV_PCM_FMTBIT_S32_LE, \ - .channels_min = 1, \ - .channels_max = 8, \ - .rate_min = 8000, \ - .rate_max = 176400, \ - }, \ - .name = #did, \ - .ops = &q6dma_ops, \ - .id = did, \ - .probe = msm_dai_q6_dai_probe, \ - .remove = msm_dai_q6_dai_remove, \ - } struct q6afe_dai_priv_data { uint32_t sd_line_mask; @@ -784,591 +702,6 @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) return 0; } -static struct snd_soc_dai_driver q6afe_dais[] = { - { - .playback = { - .stream_name = "HDMI Playback", - .rates = SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 2, - .channels_max = 8, - .rate_max = 192000, - .rate_min = 48000, - }, - .ops = &q6hdmi_ops, - .id = HDMI_RX, - .name = "HDMI", - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .name = "SLIMBUS_0_RX", - .ops = &q6slim_ops, - .id = SLIMBUS_0_RX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .playback = { - .stream_name = "Slimbus Playback", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .name = "SLIMBUS_0_TX", - .ops = &q6slim_ops, - .id = SLIMBUS_0_TX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .capture = { - .stream_name = "Slimbus Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .playback = { - .stream_name = "Slimbus1 Playback", - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 2, - .rate_min = 8000, - .rate_max = 192000, - }, - .name = "SLIMBUS_1_RX", - .ops = &q6slim_ops, - .id = SLIMBUS_1_RX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .name = "SLIMBUS_1_TX", - .ops = &q6slim_ops, - .id = SLIMBUS_1_TX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .capture = { - .stream_name = "Slimbus1 Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .playback = { - .stream_name = "Slimbus2 Playback", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - .name = "SLIMBUS_2_RX", - .ops = &q6slim_ops, - .id = SLIMBUS_2_RX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - - }, { - .name = "SLIMBUS_2_TX", - .ops = &q6slim_ops, - .id = SLIMBUS_2_TX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .capture = { - .stream_name = "Slimbus2 Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .playback = { - .stream_name = "Slimbus3 Playback", - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 2, - .rate_min = 8000, - .rate_max = 192000, - }, - .name = "SLIMBUS_3_RX", - .ops = &q6slim_ops, - .id = SLIMBUS_3_RX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - - }, { - .name = "SLIMBUS_3_TX", - .ops = &q6slim_ops, - .id = SLIMBUS_3_TX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .capture = { - .stream_name = "Slimbus3 Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .playback = { - .stream_name = "Slimbus4 Playback", - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 2, - .rate_min = 8000, - .rate_max = 192000, - }, - .name = "SLIMBUS_4_RX", - .ops = &q6slim_ops, - .id = SLIMBUS_4_RX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - - }, { - .name = "SLIMBUS_4_TX", - .ops = &q6slim_ops, - .id = SLIMBUS_4_TX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .capture = { - .stream_name = "Slimbus4 Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .playback = { - .stream_name = "Slimbus5 Playback", - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 2, - .rate_min = 8000, - .rate_max = 192000, - }, - .name = "SLIMBUS_5_RX", - .ops = &q6slim_ops, - .id = SLIMBUS_5_RX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - - }, { - .name = "SLIMBUS_5_TX", - .ops = &q6slim_ops, - .id = SLIMBUS_5_TX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .capture = { - .stream_name = "Slimbus5 Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .playback = { - .stream_name = "Slimbus6 Playback", - .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 2, - .rate_min = 8000, - .rate_max = 192000, - }, - .ops = &q6slim_ops, - .name = "SLIMBUS_6_RX", - .id = SLIMBUS_6_RX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - - }, { - .name = "SLIMBUS_6_TX", - .ops = &q6slim_ops, - .id = SLIMBUS_6_TX, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - .capture = { - .stream_name = "Slimbus6 Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - }, { - .playback = { - .stream_name = "Primary MI2S Playback", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .id = PRIMARY_MI2S_RX, - .name = "PRI_MI2S_RX", - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .capture = { - .stream_name = "Primary MI2S Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .id = PRIMARY_MI2S_TX, - .name = "PRI_MI2S_TX", - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .playback = { - .stream_name = "Secondary MI2S Playback", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .name = "SEC_MI2S_RX", - .id = SECONDARY_MI2S_RX, - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .capture = { - .stream_name = "Secondary MI2S Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .id = SECONDARY_MI2S_TX, - .name = "SEC_MI2S_TX", - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .playback = { - .stream_name = "Tertiary MI2S Playback", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .name = "TERT_MI2S_RX", - .id = TERTIARY_MI2S_RX, - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .capture = { - .stream_name = "Tertiary MI2S Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .id = TERTIARY_MI2S_TX, - .name = "TERT_MI2S_TX", - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .playback = { - .stream_name = "Quaternary MI2S Playback", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .name = "QUAT_MI2S_RX", - .id = QUATERNARY_MI2S_RX, - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .capture = { - .stream_name = "Quaternary MI2S Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .id = QUATERNARY_MI2S_TX, - .name = "QUAT_MI2S_TX", - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .playback = { - .stream_name = "Quinary MI2S Playback", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 192000, - }, - .id = QUINARY_MI2S_RX, - .name = "QUIN_MI2S_RX", - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, { - .capture = { - .stream_name = "Quinary MI2S Capture", - .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | - SNDRV_PCM_RATE_16000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .channels_min = 1, - .channels_max = 8, - .rate_min = 8000, - .rate_max = 48000, - }, - .id = QUINARY_MI2S_TX, - .name = "QUIN_MI2S_TX", - .ops = &q6i2s_ops, - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, - Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), - Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), - Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), - Q6AFE_TDM_PB_DAI("Primary", 3, PRIMARY_TDM_RX_3), - Q6AFE_TDM_PB_DAI("Primary", 4, PRIMARY_TDM_RX_4), - Q6AFE_TDM_PB_DAI("Primary", 5, PRIMARY_TDM_RX_5), - Q6AFE_TDM_PB_DAI("Primary", 6, PRIMARY_TDM_RX_6), - Q6AFE_TDM_PB_DAI("Primary", 7, PRIMARY_TDM_RX_7), - Q6AFE_TDM_CAP_DAI("Primary", 0, PRIMARY_TDM_TX_0), - Q6AFE_TDM_CAP_DAI("Primary", 1, PRIMARY_TDM_TX_1), - Q6AFE_TDM_CAP_DAI("Primary", 2, PRIMARY_TDM_TX_2), - Q6AFE_TDM_CAP_DAI("Primary", 3, PRIMARY_TDM_TX_3), - Q6AFE_TDM_CAP_DAI("Primary", 4, PRIMARY_TDM_TX_4), - Q6AFE_TDM_CAP_DAI("Primary", 5, PRIMARY_TDM_TX_5), - Q6AFE_TDM_CAP_DAI("Primary", 6, PRIMARY_TDM_TX_6), - Q6AFE_TDM_CAP_DAI("Primary", 7, PRIMARY_TDM_TX_7), - Q6AFE_TDM_PB_DAI("Secondary", 0, SECONDARY_TDM_RX_0), - Q6AFE_TDM_PB_DAI("Secondary", 1, SECONDARY_TDM_RX_1), - Q6AFE_TDM_PB_DAI("Secondary", 2, SECONDARY_TDM_RX_2), - Q6AFE_TDM_PB_DAI("Secondary", 3, SECONDARY_TDM_RX_3), - Q6AFE_TDM_PB_DAI("Secondary", 4, SECONDARY_TDM_RX_4), - Q6AFE_TDM_PB_DAI("Secondary", 5, SECONDARY_TDM_RX_5), - Q6AFE_TDM_PB_DAI("Secondary", 6, SECONDARY_TDM_RX_6), - Q6AFE_TDM_PB_DAI("Secondary", 7, SECONDARY_TDM_RX_7), - Q6AFE_TDM_CAP_DAI("Secondary", 0, SECONDARY_TDM_TX_0), - Q6AFE_TDM_CAP_DAI("Secondary", 1, SECONDARY_TDM_TX_1), - Q6AFE_TDM_CAP_DAI("Secondary", 2, SECONDARY_TDM_TX_2), - Q6AFE_TDM_CAP_DAI("Secondary", 3, SECONDARY_TDM_TX_3), - Q6AFE_TDM_CAP_DAI("Secondary", 4, SECONDARY_TDM_TX_4), - Q6AFE_TDM_CAP_DAI("Secondary", 5, SECONDARY_TDM_TX_5), - Q6AFE_TDM_CAP_DAI("Secondary", 6, SECONDARY_TDM_TX_6), - Q6AFE_TDM_CAP_DAI("Secondary", 7, SECONDARY_TDM_TX_7), - Q6AFE_TDM_PB_DAI("Tertiary", 0, TERTIARY_TDM_RX_0), - Q6AFE_TDM_PB_DAI("Tertiary", 1, TERTIARY_TDM_RX_1), - Q6AFE_TDM_PB_DAI("Tertiary", 2, TERTIARY_TDM_RX_2), - Q6AFE_TDM_PB_DAI("Tertiary", 3, TERTIARY_TDM_RX_3), - Q6AFE_TDM_PB_DAI("Tertiary", 4, TERTIARY_TDM_RX_4), - Q6AFE_TDM_PB_DAI("Tertiary", 5, TERTIARY_TDM_RX_5), - Q6AFE_TDM_PB_DAI("Tertiary", 6, TERTIARY_TDM_RX_6), - Q6AFE_TDM_PB_DAI("Tertiary", 7, TERTIARY_TDM_RX_7), - Q6AFE_TDM_CAP_DAI("Tertiary", 0, TERTIARY_TDM_TX_0), - Q6AFE_TDM_CAP_DAI("Tertiary", 1, TERTIARY_TDM_TX_1), - Q6AFE_TDM_CAP_DAI("Tertiary", 2, TERTIARY_TDM_TX_2), - Q6AFE_TDM_CAP_DAI("Tertiary", 3, TERTIARY_TDM_TX_3), - Q6AFE_TDM_CAP_DAI("Tertiary", 4, TERTIARY_TDM_TX_4), - Q6AFE_TDM_CAP_DAI("Tertiary", 5, TERTIARY_TDM_TX_5), - Q6AFE_TDM_CAP_DAI("Tertiary", 6, TERTIARY_TDM_TX_6), - Q6AFE_TDM_CAP_DAI("Tertiary", 7, TERTIARY_TDM_TX_7), - Q6AFE_TDM_PB_DAI("Quaternary", 0, QUATERNARY_TDM_RX_0), - Q6AFE_TDM_PB_DAI("Quaternary", 1, QUATERNARY_TDM_RX_1), - Q6AFE_TDM_PB_DAI("Quaternary", 2, QUATERNARY_TDM_RX_2), - Q6AFE_TDM_PB_DAI("Quaternary", 3, QUATERNARY_TDM_RX_3), - Q6AFE_TDM_PB_DAI("Quaternary", 4, QUATERNARY_TDM_RX_4), - Q6AFE_TDM_PB_DAI("Quaternary", 5, QUATERNARY_TDM_RX_5), - Q6AFE_TDM_PB_DAI("Quaternary", 6, QUATERNARY_TDM_RX_6), - Q6AFE_TDM_PB_DAI("Quaternary", 7, QUATERNARY_TDM_RX_7), - Q6AFE_TDM_CAP_DAI("Quaternary", 0, QUATERNARY_TDM_TX_0), - Q6AFE_TDM_CAP_DAI("Quaternary", 1, QUATERNARY_TDM_TX_1), - Q6AFE_TDM_CAP_DAI("Quaternary", 2, QUATERNARY_TDM_TX_2), - Q6AFE_TDM_CAP_DAI("Quaternary", 3, QUATERNARY_TDM_TX_3), - Q6AFE_TDM_CAP_DAI("Quaternary", 4, QUATERNARY_TDM_TX_4), - Q6AFE_TDM_CAP_DAI("Quaternary", 5, QUATERNARY_TDM_TX_5), - Q6AFE_TDM_CAP_DAI("Quaternary", 6, QUATERNARY_TDM_TX_6), - Q6AFE_TDM_CAP_DAI("Quaternary", 7, QUATERNARY_TDM_TX_7), - Q6AFE_TDM_PB_DAI("Quinary", 0, QUINARY_TDM_RX_0), - Q6AFE_TDM_PB_DAI("Quinary", 1, QUINARY_TDM_RX_1), - Q6AFE_TDM_PB_DAI("Quinary", 2, QUINARY_TDM_RX_2), - Q6AFE_TDM_PB_DAI("Quinary", 3, QUINARY_TDM_RX_3), - Q6AFE_TDM_PB_DAI("Quinary", 4, QUINARY_TDM_RX_4), - Q6AFE_TDM_PB_DAI("Quinary", 5, QUINARY_TDM_RX_5), - Q6AFE_TDM_PB_DAI("Quinary", 6, QUINARY_TDM_RX_6), - Q6AFE_TDM_PB_DAI("Quinary", 7, QUINARY_TDM_RX_7), - Q6AFE_TDM_CAP_DAI("Quinary", 0, QUINARY_TDM_TX_0), - Q6AFE_TDM_CAP_DAI("Quinary", 1, QUINARY_TDM_TX_1), - Q6AFE_TDM_CAP_DAI("Quinary", 2, QUINARY_TDM_TX_2), - Q6AFE_TDM_CAP_DAI("Quinary", 3, QUINARY_TDM_TX_3), - Q6AFE_TDM_CAP_DAI("Quinary", 4, QUINARY_TDM_TX_4), - Q6AFE_TDM_CAP_DAI("Quinary", 5, QUINARY_TDM_TX_5), - Q6AFE_TDM_CAP_DAI("Quinary", 6, QUINARY_TDM_TX_6), - Q6AFE_TDM_CAP_DAI("Quinary", 7, QUINARY_TDM_TX_7), - { - .playback = { - .stream_name = "Display Port Playback", - .rates = SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - .channels_min = 2, - .channels_max = 8, - .rate_max = 192000, - .rate_min = 48000, - }, - .ops = &q6hdmi_ops, - .id = DISPLAY_PORT_RX, - .name = "DISPLAY_PORT", - .probe = msm_dai_q6_dai_probe, - .remove = msm_dai_q6_dai_remove, - }, - Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_0), - Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_0), - Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_1), - Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_1), - Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_2), - Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_0), - Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_1), - Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_2), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_0), - Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_0), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_1), - Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_1), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_2), - Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_2), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_3), - Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_3), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_4), - Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_4), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_5), - Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_5), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_6), - Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_7), -}; - -static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, - const struct of_phandle_args *args, - const char **dai_name) -{ - int id = args->args[0]; - int ret = -EINVAL; - int i; - - for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) { - if (q6afe_dais[i].id == id) { - *dai_name = q6afe_dais[i].name; - ret = 0; - break; - } - } - - return ret; -} - static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { SND_SOC_DAPM_AIF_IN("HDMI_RX", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("SLIMBUS_0_RX", NULL, 0, SND_SOC_NOPM, 0, 0), @@ -1627,7 +960,7 @@ static const struct snd_soc_component_driver q6afe_dai_component = { .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets), .dapm_routes = q6afe_dapm_routes, .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes), - .of_xlate_dai_name = q6afe_of_xlate_dai_name, + .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, }; @@ -1715,19 +1048,29 @@ static void of_q6afe_parse_dai_data(struct device *dev, static int q6afe_dai_dev_probe(struct platform_device *pdev) { + struct q6dsp_audio_port_dai_driver_config cfg; + struct snd_soc_dai_driver *dais; struct q6afe_dai_data *dai_data; struct device *dev = &pdev->dev; + int num_dais; dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); if (!dai_data) return -ENOMEM; dev_set_drvdata(dev, dai_data); - of_q6afe_parse_dai_data(dev, dai_data); - return devm_snd_soc_register_component(dev, &q6afe_dai_component, - q6afe_dais, ARRAY_SIZE(q6afe_dais)); + cfg.probe = msm_dai_q6_dai_probe; + cfg.remove = msm_dai_q6_dai_remove; + cfg.q6hdmi_ops = &q6hdmi_ops; + cfg.q6slim_ops = &q6slim_ops; + cfg.q6i2s_ops = &q6i2s_ops; + cfg.q6tdm_ops = &q6tdm_ops; + cfg.q6dma_ops = &q6dma_ops; + dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + + return devm_snd_soc_register_component(dev, &q6afe_dai_component, dais, num_dais); } #ifdef CONFIG_OF @@ -1747,5 +1090,5 @@ static struct platform_driver q6afe_dai_platform_driver = { }; module_platform_driver(q6afe_dai_platform_driver); -MODULE_DESCRIPTION("Q6 Audio Fronend dai driver"); +MODULE_DESCRIPTION("Q6 Audio Frontend dai driver"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c new file mode 100644 index 000000000000..eb1c3aec479b --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021, Linaro Limited + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <asm/dma.h> +#include <linux/dma-mapping.h> +#include <linux/of_device.h> +#include <sound/pcm_params.h> +#include "q6apm.h" + +#define DRV_NAME "q6apm-dai" + +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 65536 +#define PLAYBACK_MIN_PERIOD_SIZE 128 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 8 +#define CAPTURE_MAX_PERIOD_SIZE 4096 +#define CAPTURE_MIN_PERIOD_SIZE 320 +#define BUFFER_BYTES_MAX (PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE) +#define BUFFER_BYTES_MIN (PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE) +#define SID_MASK_DEFAULT 0xF + +enum stream_state { + Q6APM_STREAM_IDLE = 0, + Q6APM_STREAM_STOPPED, + Q6APM_STREAM_RUNNING, +}; + +struct q6apm_dai_rtd { + struct snd_pcm_substream *substream; + struct snd_compr_stream *cstream; + struct snd_compr_params codec_param; + struct snd_dma_buffer dma_buffer; + phys_addr_t phys; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pos; /* Buffer position */ + unsigned int periods; + unsigned int bytes_sent; + unsigned int bytes_received; + unsigned int copied_total; + uint16_t bits_per_sample; + uint16_t source; /* Encoding source bit mask */ + uint16_t session_id; + enum stream_state state; + struct q6apm_graph *graph; +}; + +struct q6apm_dai_data { + long long sid; +}; + +static struct snd_pcm_hardware q6apm_dai_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 4, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware q6apm_dai_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = (PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE), + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +static void event_handler(uint32_t opcode, uint32_t token, uint32_t *payload, void *priv) +{ + struct q6apm_dai_rtd *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + + switch (opcode) { + case APM_CLIENT_EVENT_CMD_EOS_DONE: + prtd->state = Q6APM_STREAM_STOPPED; + break; + case APM_CLIENT_EVENT_DATA_WRITE_DONE: + prtd->pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == Q6APM_STREAM_RUNNING) + q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, 0); + + break; + case APM_CLIENT_EVENT_DATA_READ_DONE: + prtd->pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == Q6APM_STREAM_RUNNING) + q6apm_read(prtd->graph); + + break; + default: + break; + } +} + +static int q6apm_dai_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct audioreach_module_config cfg; + struct device *dev = component->dev; + struct q6apm_dai_data *pdata; + int ret; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + if (!prtd || !prtd->graph) { + dev_err(dev, "%s: private data null or audio client freed\n", __func__); + return -EINVAL; + } + + cfg.direction = substream->stream; + cfg.sample_rate = runtime->rate; + cfg.num_channels = runtime->channels; + cfg.bit_width = prtd->bits_per_sample; + + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pos = 0; + /* rate and channels are sent to audio driver */ + ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg); + if (ret < 0) { + dev_err(dev, "%s: q6apm_open_write failed\n", __func__); + return ret; + } + + ret = q6apm_graph_media_format_pcm(prtd->graph, &cfg); + if (ret < 0) + dev_err(dev, "%s: CMD Format block failed\n", __func__); + + ret = q6apm_map_memory_regions(prtd->graph, substream->stream, prtd->phys, + (prtd->pcm_size / prtd->periods), prtd->periods); + + if (ret < 0) { + dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); + return -ENOMEM; + } + + ret = q6apm_graph_prepare(prtd->graph); + if (ret) { + dev_err(dev, "Failed to prepare Graph %d\n", ret); + return ret; + } + + ret = q6apm_graph_start(prtd->graph); + if (ret) { + dev_err(dev, "Failed to Start Graph %d\n", ret); + return ret; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + int i; + /* Queue the buffers for Capture ONLY after graph is started */ + for (i = 0; i < runtime->periods; i++) + q6apm_read(prtd->graph); + + } + + /* Now that graph as been prepared and started update the internal state accordingly */ + prtd->state = Q6APM_STREAM_RUNNING; + + return 0; +} + +static int q6apm_dai_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* start writing buffers for playback only as we already queued capture buffers */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + /* TODO support be handled via SoftPause Module */ + prtd->state = Q6APM_STREAM_STOPPED; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6apm_dai_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_prtd, 0); + struct device *dev = component->dev; + struct q6apm_dai_data *pdata; + struct q6apm_dai_rtd *prtd; + int graph_id, ret; + + graph_id = cpu_dai->driver->id; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) { + dev_err(dev, "Drv data not found ..\n"); + return -EINVAL; + } + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + prtd->substream = substream; + prtd->graph = q6apm_graph_open(dev, (q6apm_cb)event_handler, prtd, graph_id); + if (IS_ERR(prtd->graph)) { + dev_err(dev, "%s: Could not allocate memory\n", __func__); + ret = PTR_ERR(prtd->graph); + goto err; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = q6apm_dai_hardware_playback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + runtime->hw = q6apm_dai_hardware_capture; + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "snd_pcm_hw_constraint_integer failed\n"); + goto err; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + BUFFER_BYTES_MIN, BUFFER_BYTES_MAX); + if (ret < 0) { + dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret); + goto err; + } + } + + ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret < 0) { + dev_err(dev, "constraint for period bytes step ret = %d\n", ret); + goto err; + } + + ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret < 0) { + dev_err(dev, "constraint for buffer bytes step ret = %d\n", ret); + goto err; + } + + runtime->private_data = prtd; + runtime->dma_bytes = BUFFER_BYTES_MAX; + if (pdata->sid < 0) + prtd->phys = substream->dma_buffer.addr; + else + prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); + + return 0; +err: + kfree(prtd); + + return ret; +} + +static int q6apm_dai_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + + q6apm_graph_stop(prtd->graph); + q6apm_unmap_memory_regions(prtd->graph, substream->stream); + q6apm_graph_close(prtd->graph); + prtd->graph = NULL; + kfree(prtd); + runtime->private_data = NULL; + + return 0; +} + +static snd_pcm_uframes_t q6apm_dai_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + + if (prtd->pos == prtd->pcm_size) + prtd->pos = 0; + + return bytes_to_frames(runtime, prtd->pos); +} + +static int q6apm_dai_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + + prtd->pcm_size = params_buffer_bytes(params); + prtd->periods = params_periods(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + prtd->bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + prtd->bits_per_sample = 24; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) +{ + int size = BUFFER_BYTES_MAX; + + return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, component->dev, size); +} + +static const struct snd_soc_component_driver q6apm_fe_dai_component = { + .name = DRV_NAME, + .open = q6apm_dai_open, + .close = q6apm_dai_close, + .prepare = q6apm_dai_prepare, + .pcm_construct = q6apm_dai_pcm_new, + .hw_params = q6apm_dai_hw_params, + .pointer = q6apm_dai_pointer, + .trigger = q6apm_dai_trigger, +}; + +static int q6apm_dai_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct q6apm_dai_data *pdata; + struct of_phandle_args args; + int rc; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); + if (rc < 0) + pdata->sid = -1; + else + pdata->sid = args.args[0] & SID_MASK_DEFAULT; + + dev_set_drvdata(dev, pdata); + + return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, NULL, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id q6apm_dai_device_id[] = { + { .compatible = "qcom,q6apm-dais" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6apm_dai_device_id); +#endif + +static struct platform_driver q6apm_dai_platform_driver = { + .driver = { + .name = "q6apm-dai", + .of_match_table = of_match_ptr(q6apm_dai_device_id), + }, + .probe = q6apm_dai_probe, +}; +module_platform_driver(q6apm_dai_platform_driver); + +MODULE_DESCRIPTION("Q6APM dai driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c new file mode 100644 index 000000000000..ce9e5646d8f3 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021, Linaro Limited + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "q6dsp-lpass-ports.h" +#include "audioreach.h" +#include "q6apm.h" + +#define AUDIOREACH_BE_PCM_BASE 16 + +struct q6apm_lpass_dai_data { + struct q6apm_graph *graph[APM_PORT_MAX]; + bool is_port_started[APM_PORT_MAX]; + struct audioreach_module_config module_config[APM_PORT_MAX]; +}; + +static int q6dma_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_ch_mask, + unsigned int rx_num, unsigned int *rx_ch_mask) +{ + + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; + int ch_mask; + + switch (dai->id) { + case WSA_CODEC_DMA_TX_0: + case WSA_CODEC_DMA_TX_1: + case WSA_CODEC_DMA_TX_2: + case VA_CODEC_DMA_TX_0: + case VA_CODEC_DMA_TX_1: + case VA_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_3: + case TX_CODEC_DMA_TX_4: + case TX_CODEC_DMA_TX_5: + if (!tx_ch_mask) { + dev_err(dai->dev, "tx slot not found\n"); + return -EINVAL; + } + + if (tx_num > AR_PCM_MAX_NUM_CHANNEL) { + dev_err(dai->dev, "invalid tx num %d\n", + tx_num); + return -EINVAL; + } + ch_mask = *tx_ch_mask; + + break; + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + case RX_CODEC_DMA_RX_0: + case RX_CODEC_DMA_RX_1: + case RX_CODEC_DMA_RX_2: + case RX_CODEC_DMA_RX_3: + case RX_CODEC_DMA_RX_4: + case RX_CODEC_DMA_RX_5: + case RX_CODEC_DMA_RX_6: + case RX_CODEC_DMA_RX_7: + /* rx */ + if (!rx_ch_mask) { + dev_err(dai->dev, "rx slot not found\n"); + return -EINVAL; + } + if (rx_num > APM_PORT_MAX_AUDIO_CHAN_CNT) { + dev_err(dai->dev, "invalid rx num %d\n", + rx_num); + return -EINVAL; + } + ch_mask = *rx_ch_mask; + + break; + default: + dev_err(dai->dev, "%s: invalid dai id 0x%x\n", + __func__, dai->id); + return -EINVAL; + } + + cfg->active_channels_mask = ch_mask; + + return 0; +} + +static int q6dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; + + cfg->bit_width = params_width(params); + cfg->sample_rate = params_rate(params); + cfg->num_channels = params_channels(params); + + return 0; +} + +static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + if (!dai_data->is_port_started[dai->id]) + return; + rc = q6apm_graph_stop(dai_data->graph[dai->id]); + if (rc < 0) + dev_err(dai->dev, "fail to close APM port (%d)\n", rc); + + q6apm_graph_close(dai_data->graph[dai->id]); + dai_data->is_port_started[dai->id] = false; +} + +static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; + struct q6apm_graph *graph; + int graph_id = dai->id; + int rc; + + /** + * It is recommend to load DSP with source graph first and then sink + * graph, so sequence for playback and capture will be different + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id); + if (IS_ERR(graph)) { + dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); + rc = PTR_ERR(graph); + return rc; + } + dai_data->graph[graph_id] = graph; + } + + cfg->direction = substream->stream; + rc = q6apm_graph_media_format_pcm(dai_data->graph[dai->id], cfg); + + if (rc) { + dev_err(dai->dev, "Failed to set media format %d\n", rc); + return rc; + } + + rc = q6apm_graph_prepare(dai_data->graph[dai->id]); + if (rc) { + dev_err(dai->dev, "Failed to prepare Graph %d\n", rc); + return rc; + } + + rc = q6apm_graph_start(dai_data->graph[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to start APM port %x\n", dai->id); + return rc; + } + dai_data->is_port_started[dai->id] = true; + + return 0; +} + +static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6apm_graph *graph; + int graph_id = dai->id; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id); + if (IS_ERR(graph)) { + dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); + return PTR_ERR(graph); + } + dai_data->graph[graph_id] = graph; + } + + return 0; +} + +static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; + + cfg->fmt = fmt; + + return 0; +} + +static const struct snd_soc_dai_ops q6dma_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, + .shutdown = q6apm_lpass_dai_shutdown, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, +}; + +static const struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, + .shutdown = q6apm_lpass_dai_shutdown, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, + .set_fmt = q6i2s_set_fmt, +}; + +static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .name = "q6apm-be-dai-component", + .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, + .be_pcm_base = AUDIOREACH_BE_PCM_BASE, + .use_dai_pcm_id = true, +}; + +static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) +{ + struct q6dsp_audio_port_dai_driver_config cfg; + struct q6apm_lpass_dai_data *dai_data; + struct snd_soc_dai_driver *dais; + struct device *dev = &pdev->dev; + int num_dais; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); + + memset(&cfg, 0, sizeof(cfg)); + cfg.q6i2s_ops = &q6i2s_ops; + cfg.q6dma_ops = &q6dma_ops; + dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + + return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); +} + +#ifdef CONFIG_OF +static const struct of_device_id q6apm_lpass_dai_device_id[] = { + { .compatible = "qcom,q6apm-lpass-dais" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6apm_lpass_dai_device_id); +#endif + +static struct platform_driver q6apm_lpass_dai_platform_driver = { + .driver = { + .name = "q6apm-lpass-dais", + .of_match_table = of_match_ptr(q6apm_lpass_dai_device_id), + }, + .probe = q6apm_lpass_dai_dev_probe, +}; +module_platform_driver(q6apm_lpass_dai_platform_driver); + +MODULE_DESCRIPTION("AUDIOREACH APM LPASS dai driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c new file mode 100644 index 000000000000..13598ef5bacb --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -0,0 +1,822 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <dt-bindings/soc/qcom,gpr.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/soc/qcom/apr.h> +#include <linux/wait.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include "audioreach.h" +#include "q6apm.h" + +/* Graph Management */ +struct apm_graph_mgmt_cmd { + struct apm_module_param_data param_data; + uint32_t num_sub_graphs; + uint32_t sub_graph_id_list[]; +} __packed; + +#define APM_GRAPH_MGMT_PSIZE(p, n) ALIGN(struct_size(p, sub_graph_id_list, n), 8) + +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode) +{ + gpr_device_t *gdev = apm->gdev; + + return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock, + NULL, &apm->wait, pkt, rsp_opcode); +} + +static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, uint32_t graph_id) +{ + struct audioreach_graph_info *info; + struct audioreach_graph *graph; + int id; + + mutex_lock(&apm->lock); + graph = idr_find(&apm->graph_idr, graph_id); + mutex_unlock(&apm->lock); + + if (graph) { + kref_get(&graph->refcount); + return graph; + } + + info = idr_find(&apm->graph_info_idr, graph_id); + + if (!info) + return ERR_PTR(-ENODEV); + + graph = kzalloc(sizeof(*graph), GFP_KERNEL); + if (!graph) + return ERR_PTR(-ENOMEM); + + graph->apm = apm; + graph->info = info; + graph->id = graph_id; + + graph->graph = audioreach_alloc_graph_pkt(apm, &info->sg_list, graph_id); + if (IS_ERR(graph->graph)) { + void *err = graph->graph; + + kfree(graph); + return ERR_CAST(err); + } + + mutex_lock(&apm->lock); + id = idr_alloc(&apm->graph_idr, graph, graph_id, graph_id + 1, GFP_KERNEL); + if (id < 0) { + dev_err(apm->dev, "Unable to allocate graph id (%d)\n", graph_id); + kfree(graph); + mutex_unlock(&apm->lock); + return ERR_PTR(id); + } + mutex_unlock(&apm->lock); + + kref_init(&graph->refcount); + + q6apm_send_cmd_sync(apm, graph->graph, 0); + + return graph; +} + +static int audioreach_graph_mgmt_cmd(struct audioreach_graph *graph, uint32_t opcode) +{ + struct audioreach_graph_info *info = graph->info; + int num_sub_graphs = info->num_sub_graphs; + struct apm_module_param_data *param_data; + struct apm_graph_mgmt_cmd *mgmt_cmd; + struct audioreach_sub_graph *sg; + struct q6apm *apm = graph->apm; + int i = 0, rc, payload_size; + struct gpr_pkt *pkt; + + payload_size = APM_GRAPH_MGMT_PSIZE(mgmt_cmd, num_sub_graphs); + + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, opcode, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + mgmt_cmd = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + mgmt_cmd->num_sub_graphs = num_sub_graphs; + + param_data = &mgmt_cmd->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_SUB_GRAPH_LIST; + param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE; + + list_for_each_entry(sg, &info->sg_list, node) + mgmt_cmd->sub_graph_id_list[i++] = sg->sub_graph_id; + + rc = q6apm_send_cmd_sync(apm, pkt, 0); + + kfree(pkt); + + return rc; +} + +static void q6apm_put_audioreach_graph(struct kref *ref) +{ + struct audioreach_graph *graph; + struct q6apm *apm; + + graph = container_of(ref, struct audioreach_graph, refcount); + apm = graph->apm; + + audioreach_graph_mgmt_cmd(graph, APM_CMD_GRAPH_CLOSE); + + mutex_lock(&apm->lock); + graph = idr_remove(&apm->graph_idr, graph->id); + mutex_unlock(&apm->lock); + + kfree(graph->graph); + kfree(graph); +} + +static int q6apm_get_apm_state(struct q6apm *apm) +{ + struct gpr_pkt *pkt; + + pkt = audioreach_alloc_apm_cmd_pkt(0, APM_CMD_GET_SPF_STATE, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + q6apm_send_cmd_sync(apm, pkt, APM_CMD_RSP_GET_SPF_STATE); + + kfree(pkt); + + return apm->state; +} + +static struct audioreach_module *__q6apm_find_module_by_mid(struct q6apm *apm, + struct audioreach_graph_info *info, + uint32_t mid) +{ + struct audioreach_container *container; + struct audioreach_sub_graph *sgs; + struct audioreach_module *module; + + list_for_each_entry(sgs, &info->sg_list, node) { + list_for_each_entry(container, &sgs->container_list, node) { + list_for_each_entry(module, &container->modules_list, node) { + if (mid == module->module_id) + return module; + } + } + } + + return NULL; +} + +static struct audioreach_module *q6apm_graph_get_last_module(struct q6apm *apm, u32 sgid) +{ + struct audioreach_container *container; + struct audioreach_module *module; + struct audioreach_sub_graph *sg; + + mutex_lock(&apm->lock); + sg = idr_find(&apm->sub_graphs_idr, sgid); + mutex_unlock(&apm->lock); + if (!sg) + return NULL; + + container = list_last_entry(&sg->container_list, struct audioreach_container, node); + module = audioreach_get_container_last_module(container); + + return module; +} + +static struct audioreach_module *q6apm_graph_get_first_module(struct q6apm *apm, u32 sgid) +{ + struct audioreach_container *container; + struct audioreach_module *module; + struct audioreach_sub_graph *sg; + + mutex_lock(&apm->lock); + sg = idr_find(&apm->sub_graphs_idr, sgid); + mutex_unlock(&apm->lock); + if (!sg) + return NULL; + + container = list_first_entry(&sg->container_list, struct audioreach_container, node); + module = audioreach_get_container_first_module(container); + + return module; +} + +bool q6apm_is_sub_graphs_connected(struct q6apm *apm, u32 src_sgid, u32 dst_sgid) +{ + struct audioreach_module *module; + u32 iid; + + module = q6apm_graph_get_last_module(apm, src_sgid); + if (!module) + return false; + + iid = module->instance_id; + module = q6apm_graph_get_first_module(apm, dst_sgid); + if (!module) + return false; + + if (module->src_mod_inst_id == iid) + return true; + + return false; +} + +int q6apm_connect_sub_graphs(struct q6apm *apm, u32 src_sgid, u32 dst_sgid, bool connect) +{ + struct audioreach_module *module; + u32 iid; + + if (connect) { + module = q6apm_graph_get_last_module(apm, src_sgid); + if (!module) + return -ENODEV; + + iid = module->instance_id; + } else { + iid = 0; + } + + module = q6apm_graph_get_first_module(apm, dst_sgid); + if (!module) + return -ENODEV; + + /* set src module in dst subgraph first module */ + module->src_mod_inst_id = iid; + + return 0; +} + +int q6apm_graph_media_format_shmem(struct q6apm_graph *graph, + struct audioreach_module_config *cfg) +{ + struct audioreach_module *module; + + if (cfg->direction == SNDRV_PCM_STREAM_CAPTURE) + module = q6apm_find_module_by_mid(graph, MODULE_ID_RD_SHARED_MEM_EP); + else + module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP); + + if (!module) + return -ENODEV; + + audioreach_set_media_format(graph, module, cfg); + + return 0; + +} +EXPORT_SYMBOL_GPL(q6apm_graph_media_format_shmem); + +int q6apm_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, phys_addr_t phys, + size_t period_sz, unsigned int periods) +{ + struct audioreach_graph_data *data; + struct audio_buffer *buf; + int cnt; + int rc; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + data = &graph->rx_data; + else + data = &graph->tx_data; + + mutex_lock(&graph->lock); + + if (data->buf) { + mutex_unlock(&graph->lock); + return 0; + } + + buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_KERNEL); + if (!buf) { + mutex_unlock(&graph->lock); + return -ENOMEM; + } + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + data = &graph->rx_data; + else + data = &graph->tx_data; + + data->buf = buf; + + buf[0].phys = phys; + buf[0].size = period_sz; + + for (cnt = 1; cnt < periods; cnt++) { + if (period_sz > 0) { + buf[cnt].phys = buf[0].phys + (cnt * period_sz); + buf[cnt].size = period_sz; + } + } + data->num_periods = periods; + + mutex_unlock(&graph->lock); + + rc = audioreach_map_memory_regions(graph, dir, period_sz, periods, 1); + if (rc < 0) { + dev_err(graph->dev, "Memory_map_regions failed\n"); + audioreach_graph_free_buf(graph); + } + + return rc; +} +EXPORT_SYMBOL_GPL(q6apm_map_memory_regions); + +int q6apm_unmap_memory_regions(struct q6apm_graph *graph, unsigned int dir) +{ + struct apm_cmd_shared_mem_unmap_regions *cmd; + struct audioreach_graph_data *data; + struct gpr_pkt *pkt; + int rc; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + data = &graph->rx_data; + else + data = &graph->tx_data; + + if (!data->mem_map_handle) + return 0; + + pkt = audioreach_alloc_apm_pkt(sizeof(*cmd), APM_CMD_SHARED_MEM_UNMAP_REGIONS, dir, + graph->port->id); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + cmd = (void *)pkt + GPR_HDR_SIZE; + cmd->mem_map_handle = data->mem_map_handle; + + rc = audioreach_graph_send_cmd_sync(graph, pkt, APM_CMD_SHARED_MEM_UNMAP_REGIONS); + kfree(pkt); + + audioreach_graph_free_buf(graph); + + return rc; +} +EXPORT_SYMBOL_GPL(q6apm_unmap_memory_regions); + +int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, struct audioreach_module_config *cfg) +{ + struct audioreach_graph_info *info = graph->info; + struct audioreach_sub_graph *sgs; + struct audioreach_container *container; + struct audioreach_module *module; + + list_for_each_entry(sgs, &info->sg_list, node) { + list_for_each_entry(container, &sgs->container_list, node) { + list_for_each_entry(module, &container->modules_list, node) { + if ((module->module_id == MODULE_ID_WR_SHARED_MEM_EP) || + (module->module_id == MODULE_ID_RD_SHARED_MEM_EP)) + continue; + + audioreach_set_media_format(graph, module, cfg); + } + } + } + + return 0; + +} +EXPORT_SYMBOL_GPL(q6apm_graph_media_format_pcm); + +static int q6apm_graph_get_tx_shmem_module_iid(struct q6apm_graph *graph) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_RD_SHARED_MEM_EP); + if (!module) + return -ENODEV; + + return module->instance_id; + +} + +int q6apm_graph_get_rx_shmem_module_iid(struct q6apm_graph *graph) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP); + if (!module) + return -ENODEV; + + return module->instance_id; + +} +EXPORT_SYMBOL_GPL(q6apm_graph_get_rx_shmem_module_iid); + +int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t wflags) +{ + struct apm_data_cmd_wr_sh_mem_ep_data_buffer_v2 *write_buffer; + struct audio_buffer *ab; + struct gpr_pkt *pkt; + int rc, iid; + + iid = q6apm_graph_get_rx_shmem_module_iid(graph); + pkt = audioreach_alloc_pkt(sizeof(*write_buffer), DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2, + graph->rx_data.dsp_buf | (len << APM_WRITE_TOKEN_LEN_SHIFT), + graph->port->id, iid); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + write_buffer = (void *)pkt + GPR_HDR_SIZE; + + mutex_lock(&graph->lock); + ab = &graph->rx_data.buf[graph->rx_data.dsp_buf]; + + write_buffer->buf_addr_lsw = lower_32_bits(ab->phys); + write_buffer->buf_addr_msw = upper_32_bits(ab->phys); + write_buffer->buf_size = len; + write_buffer->timestamp_lsw = lsw_ts; + write_buffer->timestamp_msw = msw_ts; + write_buffer->mem_map_handle = graph->rx_data.mem_map_handle; + write_buffer->flags = wflags; + + graph->rx_data.dsp_buf++; + + if (graph->rx_data.dsp_buf >= graph->rx_data.num_periods) + graph->rx_data.dsp_buf = 0; + + mutex_unlock(&graph->lock); + + rc = gpr_send_port_pkt(graph->port, pkt); + + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6apm_write_async); + +int q6apm_read(struct q6apm_graph *graph) +{ + struct data_cmd_rd_sh_mem_ep_data_buffer_v2 *read_buffer; + struct audioreach_graph_data *port; + struct audio_buffer *ab; + struct gpr_pkt *pkt; + int rc, iid; + + iid = q6apm_graph_get_tx_shmem_module_iid(graph); + pkt = audioreach_alloc_pkt(sizeof(*read_buffer), DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2, + graph->tx_data.dsp_buf, graph->port->id, iid); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + read_buffer = (void *)pkt + GPR_HDR_SIZE; + + mutex_lock(&graph->lock); + port = &graph->tx_data; + ab = &port->buf[port->dsp_buf]; + + read_buffer->buf_addr_lsw = lower_32_bits(ab->phys); + read_buffer->buf_addr_msw = upper_32_bits(ab->phys); + read_buffer->mem_map_handle = port->mem_map_handle; + read_buffer->buf_size = ab->size; + + port->dsp_buf++; + + if (port->dsp_buf >= port->num_periods) + port->dsp_buf = 0; + + mutex_unlock(&graph->lock); + + rc = gpr_send_port_pkt(graph->port, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6apm_read); + +static int graph_callback(struct gpr_resp_pkt *data, void *priv, int op) +{ + struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done; + struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done_v2 *done; + struct apm_cmd_rsp_shared_mem_map_regions *rsp; + struct gpr_ibasic_rsp_result_t *result; + struct q6apm_graph *graph = priv; + struct gpr_hdr *hdr = &data->hdr; + struct device *dev = graph->dev; + uint32_t client_event; + phys_addr_t phys; + int token; + + result = data->payload; + + switch (hdr->opcode) { + case DATA_CMD_RSP_WR_SH_MEM_EP_DATA_BUFFER_DONE_V2: + client_event = APM_CLIENT_EVENT_DATA_WRITE_DONE; + mutex_lock(&graph->lock); + token = hdr->token & APM_WRITE_TOKEN_MASK; + + done = data->payload; + phys = graph->rx_data.buf[token].phys; + mutex_unlock(&graph->lock); + + if (lower_32_bits(phys) == done->buf_addr_lsw && + upper_32_bits(phys) == done->buf_addr_msw) { + graph->result.opcode = hdr->opcode; + graph->result.status = done->status; + if (graph->cb) + graph->cb(client_event, hdr->token, data->payload, graph->priv); + } else { + dev_err(dev, "WR BUFF Unexpected addr %08x-%08x\n", done->buf_addr_lsw, + done->buf_addr_msw); + } + + break; + case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS: + graph->result.opcode = hdr->opcode; + graph->result.status = 0; + rsp = data->payload; + + if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK) + graph->rx_data.mem_map_handle = rsp->mem_map_handle; + else + graph->tx_data.mem_map_handle = rsp->mem_map_handle; + + wake_up(&graph->cmd_wait); + break; + case DATA_CMD_RSP_RD_SH_MEM_EP_DATA_BUFFER_V2: + client_event = APM_CLIENT_EVENT_DATA_READ_DONE; + mutex_lock(&graph->lock); + rd_done = data->payload; + phys = graph->tx_data.buf[hdr->token].phys; + mutex_unlock(&graph->lock); + + if (upper_32_bits(phys) == rd_done->buf_addr_msw && + lower_32_bits(phys) == rd_done->buf_addr_lsw) { + graph->result.opcode = hdr->opcode; + graph->result.status = rd_done->status; + if (graph->cb) + graph->cb(client_event, hdr->token, data->payload, graph->priv); + } else { + dev_err(dev, "RD BUFF Unexpected addr %08x-%08x\n", rd_done->buf_addr_lsw, + rd_done->buf_addr_msw); + } + break; + case DATA_CMD_WR_SH_MEM_EP_EOS_RENDERED: + break; + case GPR_BASIC_RSP_RESULT: + switch (result->opcode) { + case APM_CMD_SHARED_MEM_UNMAP_REGIONS: + graph->result.opcode = result->opcode; + graph->result.status = 0; + if (hdr->token == SNDRV_PCM_STREAM_PLAYBACK) + graph->rx_data.mem_map_handle = 0; + else + graph->tx_data.mem_map_handle = 0; + + wake_up(&graph->cmd_wait); + break; + case APM_CMD_SHARED_MEM_MAP_REGIONS: + case DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT: + case APM_CMD_SET_CFG: + graph->result.opcode = result->opcode; + graph->result.status = result->status; + if (result->status) + dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", + result->status, result->opcode); + wake_up(&graph->cmd_wait); + break; + default: + break; + } + break; + default: + break; + } + return 0; +} + +struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, + void *priv, int graph_id) +{ + struct q6apm *apm = dev_get_drvdata(dev->parent); + struct audioreach_graph *ar_graph; + struct q6apm_graph *graph; + int ret; + + ar_graph = q6apm_get_audioreach_graph(apm, graph_id); + if (IS_ERR(ar_graph)) { + dev_err(dev, "No graph found with id %d\n", graph_id); + return ERR_CAST(ar_graph); + } + + graph = kzalloc(sizeof(*graph), GFP_KERNEL); + if (!graph) { + ret = -ENOMEM; + goto err; + } + + graph->apm = apm; + graph->priv = priv; + graph->cb = cb; + graph->info = ar_graph->info; + graph->ar_graph = ar_graph; + graph->id = ar_graph->id; + graph->dev = dev; + + mutex_init(&graph->lock); + init_waitqueue_head(&graph->cmd_wait); + + graph->port = gpr_alloc_port(apm->gdev, dev, graph_callback, graph); + if (!graph->port) { + kfree(graph); + ret = -ENOMEM; + goto err; + } + + return graph; +err: + kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(q6apm_graph_open); + +int q6apm_graph_close(struct q6apm_graph *graph) +{ + struct audioreach_graph *ar_graph = graph->ar_graph; + + gpr_free_port(graph->port); + kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph); + kfree(graph); + + return 0; +} +EXPORT_SYMBOL_GPL(q6apm_graph_close); + +int q6apm_graph_prepare(struct q6apm_graph *graph) +{ + return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_PREPARE); +} +EXPORT_SYMBOL_GPL(q6apm_graph_prepare); + +int q6apm_graph_start(struct q6apm_graph *graph) +{ + struct audioreach_graph *ar_graph = graph->ar_graph; + int ret = 0; + + if (ar_graph->start_count == 0) + ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START); + + ar_graph->start_count++; + + return ret; +} +EXPORT_SYMBOL_GPL(q6apm_graph_start); + +int q6apm_graph_stop(struct q6apm_graph *graph) +{ + struct audioreach_graph *ar_graph = graph->ar_graph; + + if (--ar_graph->start_count > 0) + return 0; + + return audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_STOP); +} +EXPORT_SYMBOL_GPL(q6apm_graph_stop); + +int q6apm_graph_flush(struct q6apm_graph *graph) +{ + return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_FLUSH); +} +EXPORT_SYMBOL_GPL(q6apm_graph_flush); + +static int q6apm_audio_probe(struct snd_soc_component *component) +{ + return audioreach_tplg_init(component); +} + +static void q6apm_audio_remove(struct snd_soc_component *component) +{ + /* remove topology */ + snd_soc_tplg_component_remove(component); +} + +#define APM_AUDIO_DRV_NAME "q6apm-audio" + +static const struct snd_soc_component_driver q6apm_audio_component = { + .name = APM_AUDIO_DRV_NAME, + .probe = q6apm_audio_probe, + .remove = q6apm_audio_remove, +}; + +static int apm_probe(gpr_device_t *gdev) +{ + struct device *dev = &gdev->dev; + struct q6apm *apm; + int ret; + + apm = devm_kzalloc(dev, sizeof(*apm), GFP_KERNEL); + if (!apm) + return -ENOMEM; + + dev_set_drvdata(dev, apm); + + mutex_init(&apm->lock); + apm->dev = dev; + apm->gdev = gdev; + init_waitqueue_head(&apm->wait); + + idr_init(&apm->graph_idr); + idr_init(&apm->graph_info_idr); + idr_init(&apm->sub_graphs_idr); + idr_init(&apm->containers_idr); + + idr_init(&apm->modules_idr); + + q6apm_get_apm_state(apm); + + ret = devm_snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0); + if (ret < 0) { + dev_err(dev, "failed to get register q6apm: %d\n", ret); + return ret; + } + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, uint32_t mid) +{ + struct audioreach_graph_info *info = graph->info; + struct q6apm *apm = graph->apm; + + return __q6apm_find_module_by_mid(apm, info, mid); + +} + +static int apm_callback(struct gpr_resp_pkt *data, void *priv, int op) +{ + gpr_device_t *gdev = priv; + struct q6apm *apm = dev_get_drvdata(&gdev->dev); + struct device *dev = &gdev->dev; + struct gpr_ibasic_rsp_result_t *result; + struct gpr_hdr *hdr = &data->hdr; + + result = data->payload; + + switch (hdr->opcode) { + case APM_CMD_RSP_GET_SPF_STATE: + apm->result.opcode = hdr->opcode; + apm->result.status = 0; + /* First word of result it state */ + apm->state = result->opcode; + wake_up(&apm->wait); + break; + case GPR_BASIC_RSP_RESULT: + switch (result->opcode) { + case APM_CMD_GRAPH_START: + case APM_CMD_GRAPH_OPEN: + case APM_CMD_GRAPH_PREPARE: + case APM_CMD_GRAPH_CLOSE: + case APM_CMD_GRAPH_FLUSH: + case APM_CMD_GRAPH_STOP: + case APM_CMD_SET_CFG: + apm->result.opcode = result->opcode; + apm->result.status = result->status; + if (result->status) + dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status, + result->opcode); + wake_up(&apm->wait); + break; + default: + break; + } + break; + default: + break; + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id apm_device_id[] = { + { .compatible = "qcom,q6apm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apm_device_id); +#endif + +static gpr_driver_t apm_driver = { + .probe = apm_probe, + .gpr_callback = apm_callback, + .driver = { + .name = "qcom-apm", + .of_match_table = of_match_ptr(apm_device_id), + }, +}; + +module_gpr_driver(apm_driver); +MODULE_DESCRIPTION("Audio Process Manager"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h new file mode 100644 index 000000000000..54eadadf712c --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6APM_H__ +#define __Q6APM_H__ +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <sound/soc.h> +#include <linux/of_platform.h> +#include <linux/jiffies.h> +#include <linux/soc/qcom/apr.h> +#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h> +#include "audioreach.h" + +#define APM_PORT_MAX 127 +#define APM_PORT_MAX_AUDIO_CHAN_CNT 8 +#define PCM_CHANNEL_NULL 0 +#define PCM_CHANNEL_FL 1 /* Front left channel. */ +#define PCM_CHANNEL_FR 2 /* Front right channel. */ +#define PCM_CHANNEL_FC 3 /* Front center channel. */ +#define PCM_CHANNEL_LS 4 /* Left surround channel. */ +#define PCM_CHANNEL_RS 5 /* Right surround channel. */ +#define PCM_CHANNEL_LFE 6 /* Low frequency effect channel. */ +#define PCM_CHANNEL_CS 7 /* Center surround channel; Rear center ch */ +#define PCM_CHANNEL_LB 8 /* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_RB 9 /* Right back channel; Rear right channel. */ +#define PCM_CHANNELS 10 /* Top surround channel. */ + +#define APM_TIMESTAMP_FLAG 0x80000000 +#define FORMAT_LINEAR_PCM 0x0000 +/* APM client callback events */ +#define APM_CMD_EOS 0x0003 +#define APM_CLIENT_EVENT_CMD_EOS_DONE 0x1003 +#define APM_CMD_CLOSE 0x0004 +#define APM_CLIENT_EVENT_CMD_CLOSE_DONE 0x1004 +#define APM_CLIENT_EVENT_CMD_RUN_DONE 0x1008 +#define APM_CLIENT_EVENT_DATA_WRITE_DONE 0x1009 +#define APM_CLIENT_EVENT_DATA_READ_DONE 0x100a +#define APM_WRITE_TOKEN_MASK GENMASK(15, 0) +#define APM_WRITE_TOKEN_LEN_MASK GENMASK(31, 16) +#define APM_WRITE_TOKEN_LEN_SHIFT 16 + +#define APM_MAX_SESSIONS 8 + +struct q6apm { + struct device *dev; + gpr_port_t *port; + gpr_device_t *gdev; + /* For Graph OPEN/START/STOP/CLOSE operations */ + wait_queue_head_t wait; + struct gpr_ibasic_rsp_result_t result; + + struct mutex cmd_lock; + struct mutex lock; + uint32_t state; + + struct idr graph_idr; + struct idr graph_info_idr; + struct idr sub_graphs_idr; + struct idr containers_idr; + struct idr modules_idr; +}; + +struct audio_buffer { + phys_addr_t phys; + uint32_t size; /* size of buffer */ +}; + +struct audioreach_graph_data { + struct audio_buffer *buf; + uint32_t num_periods; + uint32_t dsp_buf; + uint32_t mem_map_handle; +}; + +struct audioreach_graph { + struct audioreach_graph_info *info; + uint32_t id; + int state; + int start_count; + /* Cached Graph data */ + void *graph; + struct kref refcount; + struct q6apm *apm; +}; + +typedef void (*q6apm_cb) (uint32_t opcode, uint32_t token, + void *payload, void *priv); +struct q6apm_graph { + void *priv; + q6apm_cb cb; + uint32_t id; + struct device *dev; + struct q6apm *apm; + gpr_port_t *port; + struct audioreach_graph_data rx_data; + struct audioreach_graph_data tx_data; + struct gpr_ibasic_rsp_result_t result; + wait_queue_head_t cmd_wait; + struct mutex lock; + struct audioreach_graph *ar_graph; + struct audioreach_graph_info *info; +}; + +/* Graph Operations */ +struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, + void *priv, int graph_id); +int q6apm_graph_close(struct q6apm_graph *graph); +int q6apm_graph_prepare(struct q6apm_graph *graph); +int q6apm_graph_start(struct q6apm_graph *graph); +int q6apm_graph_stop(struct q6apm_graph *graph); +int q6apm_graph_flush(struct q6apm_graph *graph); + +/* Media Format */ +int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, + struct audioreach_module_config *cfg); + +int q6apm_graph_media_format_shmem(struct q6apm_graph *graph, + struct audioreach_module_config *cfg); + +/* read/write related */ +int q6apm_send_eos_nowait(struct q6apm_graph *graph); +int q6apm_read(struct q6apm_graph *graph); +int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t wflags); + +/* Memory Map related */ +int q6apm_map_memory_regions(struct q6apm_graph *graph, + unsigned int dir, phys_addr_t phys, + size_t period_sz, unsigned int periods); +int q6apm_unmap_memory_regions(struct q6apm_graph *graph, + unsigned int dir); +/* Helpers */ +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, + uint32_t rsp_opcode); + +/* Callback for graph specific */ +struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, + uint32_t mid); + +void q6apm_set_fe_dai_ops(struct snd_soc_dai_driver *dai_drv); +int q6apm_connect_sub_graphs(struct q6apm *apm, u32 src_sgid, u32 dst_sgid, + bool connect); +bool q6apm_is_sub_graphs_connected(struct q6apm *apm, u32 src_sgid, + u32 dst_sgid); +int q6apm_graph_get_rx_shmem_module_iid(struct q6apm_graph *graph); + +#endif /* __APM_GRAPH_ */ diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c new file mode 100644 index 000000000000..4613867d1133 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h> +#include "q6dsp-lpass-clocks.h" + +#define Q6DSP_MAX_CLK_ID 104 +#define Q6DSP_LPASS_CLK_ROOT_DEFAULT 0 + + +struct q6dsp_clk { + struct device *dev; + int q6dsp_clk_id; + int attributes; + int rate; + uint32_t handle; + struct clk_hw hw; +}; + +#define to_q6dsp_clk(_hw) container_of(_hw, struct q6dsp_clk, hw) + +struct q6dsp_cc { + struct device *dev; + struct q6dsp_clk *clks[Q6DSP_MAX_CLK_ID]; + const struct q6dsp_clk_desc *desc; +}; + +static int clk_q6dsp_prepare(struct clk_hw *hw) +{ + struct q6dsp_clk *clk = to_q6dsp_clk(hw); + struct q6dsp_cc *cc = dev_get_drvdata(clk->dev); + + return cc->desc->lpass_set_clk(clk->dev, clk->q6dsp_clk_id, clk->attributes, + Q6DSP_LPASS_CLK_ROOT_DEFAULT, clk->rate); +} + +static void clk_q6dsp_unprepare(struct clk_hw *hw) +{ + struct q6dsp_clk *clk = to_q6dsp_clk(hw); + struct q6dsp_cc *cc = dev_get_drvdata(clk->dev); + + cc->desc->lpass_set_clk(clk->dev, clk->q6dsp_clk_id, clk->attributes, + Q6DSP_LPASS_CLK_ROOT_DEFAULT, 0); +} + +static int clk_q6dsp_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct q6dsp_clk *clk = to_q6dsp_clk(hw); + + clk->rate = rate; + + return 0; +} + +static unsigned long clk_q6dsp_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct q6dsp_clk *clk = to_q6dsp_clk(hw); + + return clk->rate; +} + +static long clk_q6dsp_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return rate; +} + +static const struct clk_ops clk_q6dsp_ops = { + .prepare = clk_q6dsp_prepare, + .unprepare = clk_q6dsp_unprepare, + .set_rate = clk_q6dsp_set_rate, + .round_rate = clk_q6dsp_round_rate, + .recalc_rate = clk_q6dsp_recalc_rate, +}; + +static int clk_vote_q6dsp_block(struct clk_hw *hw) +{ + struct q6dsp_clk *clk = to_q6dsp_clk(hw); + struct q6dsp_cc *cc = dev_get_drvdata(clk->dev); + + return cc->desc->lpass_vote_clk(clk->dev, clk->q6dsp_clk_id, + clk_hw_get_name(&clk->hw), &clk->handle); +} + +static void clk_unvote_q6dsp_block(struct clk_hw *hw) +{ + struct q6dsp_clk *clk = to_q6dsp_clk(hw); + struct q6dsp_cc *cc = dev_get_drvdata(clk->dev); + + cc->desc->lpass_unvote_clk(clk->dev, clk->q6dsp_clk_id, clk->handle); +} + +static const struct clk_ops clk_vote_q6dsp_ops = { + .prepare = clk_vote_q6dsp_block, + .unprepare = clk_unvote_q6dsp_block, +}; + + +static struct clk_hw *q6dsp_of_clk_hw_get(struct of_phandle_args *clkspec, + void *data) +{ + struct q6dsp_cc *cc = data; + unsigned int idx = clkspec->args[0]; + unsigned int attr = clkspec->args[1]; + + if (idx >= Q6DSP_MAX_CLK_ID || attr > LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR) { + dev_err(cc->dev, "Invalid clk specifier (%d, %d)\n", idx, attr); + return ERR_PTR(-EINVAL); + } + + if (cc->clks[idx]) { + cc->clks[idx]->attributes = attr; + return &cc->clks[idx]->hw; + } + + return ERR_PTR(-ENOENT); +} + +int q6dsp_clock_dev_probe(struct platform_device *pdev) +{ + struct q6dsp_cc *cc; + struct device *dev = &pdev->dev; + const struct q6dsp_clk_init *q6dsp_clks; + const struct q6dsp_clk_desc *desc; + int i, ret; + + cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); + if (!cc) + return -ENOMEM; + + desc = of_device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + cc->desc = desc; + cc->dev = dev; + q6dsp_clks = desc->clks; + + for (i = 0; i < desc->num_clks; i++) { + unsigned int id = q6dsp_clks[i].clk_id; + struct clk_init_data init = { + .name = q6dsp_clks[i].name, + }; + struct q6dsp_clk *clk; + + clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL); + if (!clk) + return -ENOMEM; + + clk->dev = dev; + clk->q6dsp_clk_id = q6dsp_clks[i].q6dsp_clk_id; + clk->rate = q6dsp_clks[i].rate; + clk->hw.init = &init; + + if (clk->rate) + init.ops = &clk_q6dsp_ops; + else + init.ops = &clk_vote_q6dsp_ops; + + cc->clks[id] = clk; + + ret = devm_clk_hw_register(dev, &clk->hw); + if (ret) + return ret; + } + + ret = devm_of_clk_add_hw_provider(dev, q6dsp_of_clk_hw_get, cc); + if (ret) + return ret; + + dev_set_drvdata(dev, cc); + + return 0; +} +EXPORT_SYMBOL_GPL(q6dsp_clock_dev_probe); diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.h b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.h new file mode 100644 index 000000000000..3770d81f2bd6 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6DSP_AUDIO_CLOCKS_H__ +#define __Q6DSP_AUDIO_CLOCKS_H__ + +struct q6dsp_clk_init { + int clk_id; + int q6dsp_clk_id; + char *name; + int rate; +}; + +#define Q6DSP_VOTE_CLK(id, blkid, n) { \ + .clk_id = id, \ + .q6dsp_clk_id = blkid, \ + .name = n, \ + } + +struct q6dsp_clk_desc { + const struct q6dsp_clk_init *clks; + size_t num_clks; + int (*lpass_set_clk)(struct device *dev, int clk_id, int attr, + int root_clk, unsigned int freq); + int (*lpass_vote_clk)(struct device *dev, uint32_t hid, const char *n, uint32_t *h); + int (*lpass_unvote_clk)(struct device *dev, uint32_t hid, uint32_t h); +}; + +int q6dsp_clock_dev_probe(struct platform_device *pdev); + +#endif /* __Q6DSP_AUDIO_CLOCKS_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c new file mode 100644 index 000000000000..f67c16fd90b9 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <dt-bindings/sound/qcom,q6afe.h> +#include "q6dsp-lpass-ports.h" + +#define Q6AFE_TDM_PB_DAI(pre, num, did) { \ + .playback = { \ + .stream_name = pre" TDM"#num" Playback", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .id = did, \ + } + +#define Q6AFE_TDM_CAP_DAI(pre, num, did) { \ + .capture = { \ + .stream_name = pre" TDM"#num" Capture", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .id = did, \ + } + +#define Q6AFE_CDC_DMA_RX_DAI(did) { \ + .playback = { \ + .stream_name = #did" Playback", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .id = did, \ + } + +#define Q6AFE_CDC_DMA_TX_DAI(did) { \ + .capture = { \ + .stream_name = #did" Capture", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .id = did, \ + } + + +static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + { + .playback = { + .stream_name = "HDMI Playback", + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 8, + .rate_max = 192000, + .rate_min = 48000, + }, + .id = HDMI_RX, + .name = "HDMI", + }, { + .name = "SLIMBUS_0_RX", + .id = SLIMBUS_0_RX, + .playback = { + .stream_name = "Slimbus Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .name = "SLIMBUS_0_TX", + .id = SLIMBUS_0_TX, + .capture = { + .stream_name = "Slimbus Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus1 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_1_RX", + .id = SLIMBUS_1_RX, + }, { + .name = "SLIMBUS_1_TX", + .id = SLIMBUS_1_TX, + .capture = { + .stream_name = "Slimbus1 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus2 Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_2_RX", + .id = SLIMBUS_2_RX, + + }, { + .name = "SLIMBUS_2_TX", + .id = SLIMBUS_2_TX, + .capture = { + .stream_name = "Slimbus2 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus3 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_3_RX", + .id = SLIMBUS_3_RX, + + }, { + .name = "SLIMBUS_3_TX", + .id = SLIMBUS_3_TX, + .capture = { + .stream_name = "Slimbus3 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus4 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_4_RX", + .id = SLIMBUS_4_RX, + + }, { + .name = "SLIMBUS_4_TX", + .id = SLIMBUS_4_TX, + .capture = { + .stream_name = "Slimbus4 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus5 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_5_RX", + .id = SLIMBUS_5_RX, + + }, { + .name = "SLIMBUS_5_TX", + .id = SLIMBUS_5_TX, + .capture = { + .stream_name = "Slimbus5 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus6 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_6_RX", + .id = SLIMBUS_6_RX, + + }, { + .name = "SLIMBUS_6_TX", + .id = SLIMBUS_6_TX, + .capture = { + .stream_name = "Slimbus6 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Primary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = PRIMARY_MI2S_RX, + .name = "PRI_MI2S_RX", + }, { + .capture = { + .stream_name = "Primary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = PRIMARY_MI2S_TX, + .name = "PRI_MI2S_TX", + }, { + .playback = { + .stream_name = "Secondary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "SEC_MI2S_RX", + .id = SECONDARY_MI2S_RX, + }, { + .capture = { + .stream_name = "Secondary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = SECONDARY_MI2S_TX, + .name = "SEC_MI2S_TX", + }, { + .playback = { + .stream_name = "Tertiary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "TERT_MI2S_RX", + .id = TERTIARY_MI2S_RX, + }, { + .capture = { + .stream_name = "Tertiary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = TERTIARY_MI2S_TX, + .name = "TERT_MI2S_TX", + }, { + .playback = { + .stream_name = "Quaternary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "QUAT_MI2S_RX", + .id = QUATERNARY_MI2S_RX, + }, { + .capture = { + .stream_name = "Quaternary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = QUATERNARY_MI2S_TX, + .name = "QUAT_MI2S_TX", + }, { + .playback = { + .stream_name = "Quinary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .id = QUINARY_MI2S_RX, + .name = "QUIN_MI2S_RX", + }, { + .capture = { + .stream_name = "Quinary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = QUINARY_MI2S_TX, + .name = "QUIN_MI2S_TX", + }, + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Primary", 3, PRIMARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Primary", 4, PRIMARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Primary", 5, PRIMARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Primary", 6, PRIMARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Primary", 7, PRIMARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Primary", 0, PRIMARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Primary", 1, PRIMARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Primary", 2, PRIMARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Primary", 3, PRIMARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Primary", 4, PRIMARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Primary", 5, PRIMARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Primary", 6, PRIMARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Primary", 7, PRIMARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Secondary", 0, SECONDARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Secondary", 1, SECONDARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Secondary", 2, SECONDARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Secondary", 3, SECONDARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Secondary", 4, SECONDARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Secondary", 5, SECONDARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Secondary", 6, SECONDARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Secondary", 7, SECONDARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Secondary", 0, SECONDARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Secondary", 1, SECONDARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Secondary", 2, SECONDARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Secondary", 3, SECONDARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Secondary", 4, SECONDARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Secondary", 5, SECONDARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Secondary", 6, SECONDARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Secondary", 7, SECONDARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Tertiary", 0, TERTIARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Tertiary", 1, TERTIARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Tertiary", 2, TERTIARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Tertiary", 3, TERTIARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Tertiary", 4, TERTIARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Tertiary", 5, TERTIARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Tertiary", 6, TERTIARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Tertiary", 7, TERTIARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Tertiary", 0, TERTIARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Tertiary", 1, TERTIARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Tertiary", 2, TERTIARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Tertiary", 3, TERTIARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Tertiary", 4, TERTIARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Tertiary", 5, TERTIARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Tertiary", 6, TERTIARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Tertiary", 7, TERTIARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Quaternary", 0, QUATERNARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Quaternary", 1, QUATERNARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Quaternary", 2, QUATERNARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Quaternary", 3, QUATERNARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Quaternary", 4, QUATERNARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Quaternary", 5, QUATERNARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Quaternary", 6, QUATERNARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Quaternary", 7, QUATERNARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Quaternary", 0, QUATERNARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Quaternary", 1, QUATERNARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Quaternary", 2, QUATERNARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Quaternary", 3, QUATERNARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Quaternary", 4, QUATERNARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Quaternary", 5, QUATERNARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Quaternary", 6, QUATERNARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Quaternary", 7, QUATERNARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Quinary", 0, QUINARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Quinary", 1, QUINARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Quinary", 2, QUINARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Quinary", 3, QUINARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Quinary", 4, QUINARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Quinary", 5, QUINARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Quinary", 6, QUINARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Quinary", 7, QUINARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Quinary", 0, QUINARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Quinary", 1, QUINARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Quinary", 2, QUINARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Quinary", 3, QUINARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Quinary", 4, QUINARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Quinary", 5, QUINARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Quinary", 6, QUINARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Quinary", 7, QUINARY_TDM_TX_7), + { + .playback = { + .stream_name = "Display Port Playback", + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 8, + .rate_max = 192000, + .rate_min = 48000, + }, + .id = DISPLAY_PORT_RX, + .name = "DISPLAY_PORT", + }, + Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_0), + Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_0), + Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_1), + Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_1), + Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_2), + Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_0), + Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_1), + Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_2), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_0), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_0), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_1), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_1), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_2), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_2), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_3), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_3), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_4), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_4), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_5), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_5), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_6), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_7), +}; + +int q6dsp_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + int id = args->args[0]; + int ret = -EINVAL; + int i; + + for (i = 0; i < ARRAY_SIZE(q6dsp_audio_fe_dais); i++) { + if (q6dsp_audio_fe_dais[i].id == id) { + *dai_name = q6dsp_audio_fe_dais[i].name; + ret = 0; + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(q6dsp_audio_ports_of_xlate_dai_name); + +struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, + struct q6dsp_audio_port_dai_driver_config *cfg, + int *num_dais) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(q6dsp_audio_fe_dais); i++) { + q6dsp_audio_fe_dais[i].probe = cfg->probe; + q6dsp_audio_fe_dais[i].remove = cfg->remove; + + switch (q6dsp_audio_fe_dais[i].id) { + case HDMI_RX: + case DISPLAY_PORT_RX: + q6dsp_audio_fe_dais[i].ops = cfg->q6hdmi_ops; + break; + case SLIMBUS_0_RX ... SLIMBUS_6_TX: + q6dsp_audio_fe_dais[i].ops = cfg->q6slim_ops; + break; + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + q6dsp_audio_fe_dais[i].ops = cfg->q6tdm_ops; + break; + case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7: + q6dsp_audio_fe_dais[i].ops = cfg->q6dma_ops; + break; + default: + break; + } + } + + *num_dais = ARRAY_SIZE(q6dsp_audio_fe_dais); + return q6dsp_audio_fe_dais; +} +EXPORT_SYMBOL_GPL(q6dsp_audio_ports_set_config); diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h new file mode 100644 index 000000000000..7f052c8a1257 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6DSP_AUDIO_PORTS_H__ +#define __Q6DSP_AUDIO_PORTS_H__ + +struct q6dsp_audio_port_dai_driver_config { + int (*probe)(struct snd_soc_dai *dai); + int (*remove)(struct snd_soc_dai *dai); + const struct snd_soc_dai_ops *q6hdmi_ops; + const struct snd_soc_dai_ops *q6slim_ops; + const struct snd_soc_dai_ops *q6i2s_ops; + const struct snd_soc_dai_ops *q6tdm_ops; + const struct snd_soc_dai_ops *q6dma_ops; +}; + +struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, + struct q6dsp_audio_port_dai_driver_config *cfg, + int *num_dais); +int q6dsp_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name); +#endif /* __Q6DSP_AUDIO_PORTS_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c new file mode 100644 index 000000000000..a26cda5140c1 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021, Linaro Limited + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h> +#include "q6dsp-lpass-clocks.h" +#include "q6prm.h" + +#define Q6PRM_CLK(id) { \ + .clk_id = id, \ + .q6dsp_clk_id = Q6PRM_##id, \ + .name = #id, \ + .rate = 19200000, \ + } + +static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_PRI_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_PRI_MI2S_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_SEC_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_SEC_MI2S_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_TER_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_TER_MI2S_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUAD_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUAD_MI2S_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_SPEAKER_I2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_SPEAKER_I2S_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_SPEAKER_I2S_OSR), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_SEN_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_SEN_MI2S_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT0_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT1_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT2_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT3_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT4_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_TX_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_TX_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_2X_MCLK), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, + "LPASS_HW_DCODEC"), +}; + +static const struct q6dsp_clk_desc q6dsp_clk_q6prm __maybe_unused = { + .clks = q6prm_clks, + .num_clks = ARRAY_SIZE(q6prm_clks), + .lpass_set_clk = q6prm_set_lpass_clock, + .lpass_vote_clk = q6prm_vote_lpass_core_hw, + .lpass_unvote_clk = q6prm_unvote_lpass_core_hw, +}; + +#ifdef CONFIG_OF +static const struct of_device_id q6prm_clock_device_id[] = { + { .compatible = "qcom,q6prm-lpass-clocks", .data = &q6dsp_clk_q6prm }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6prm_clock_device_id); +#endif + +static struct platform_driver q6prm_clock_platform_driver = { + .driver = { + .name = "q6prm-lpass-clock", + .of_match_table = of_match_ptr(q6prm_clock_device_id), + }, + .probe = q6dsp_clock_dev_probe, +}; +module_platform_driver(q6prm_clock_platform_driver); + +MODULE_DESCRIPTION("Q6 Proxy Resource Manager LPASS clock driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c new file mode 100644 index 000000000000..82c40f2d4e1d --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6prm.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021, Linaro Limited + +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/of_platform.h> +#include <linux/jiffies.h> +#include <linux/soc/qcom/apr.h> +#include <dt-bindings/soc/qcom,gpr.h> +#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h> +#include "q6prm.h" +#include "audioreach.h" + +struct q6prm { + struct device *dev; + gpr_device_t *gdev; + wait_queue_head_t wait; + struct gpr_ibasic_rsp_result_t result; + struct mutex lock; +}; + +#define PRM_CMD_REQUEST_HW_RSC 0x0100100F +#define PRM_CMD_RSP_REQUEST_HW_RSC 0x02001002 +#define PRM_CMD_RELEASE_HW_RSC 0x01001010 +#define PRM_CMD_RSP_RELEASE_HW_RSC 0x02001003 +#define PARAM_ID_RSC_HW_CORE 0x08001032 +#define PARAM_ID_RSC_LPASS_CORE 0x0800102B +#define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C + +struct prm_cmd_request_hw_core { + struct apm_module_param_data param_data; + uint32_t hw_clk_id; +} __packed; + +struct prm_cmd_request_rsc { + struct apm_module_param_data param_data; + uint32_t num_clk_id; + struct audio_hw_clk_cfg clock_id; +} __packed; + +static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode) +{ + return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock, + NULL, &prm->wait, pkt, rsp_opcode); +} + +static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool enable) +{ + struct q6prm *prm = dev_get_drvdata(dev->parent); + struct apm_module_param_data *param_data; + struct prm_cmd_request_hw_core *req; + gpr_device_t *gdev = prm->gdev; + uint32_t opcode, rsp_opcode; + struct gpr_pkt *pkt; + int rc; + + if (enable) { + opcode = PRM_CMD_REQUEST_HW_RSC; + rsp_opcode = PRM_CMD_RSP_REQUEST_HW_RSC; + } else { + opcode = PRM_CMD_RELEASE_HW_RSC; + rsp_opcode = PRM_CMD_RSP_RELEASE_HW_RSC; + } + + pkt = audioreach_alloc_cmd_pkt(sizeof(*req), opcode, 0, gdev->svc.id, GPR_PRM_MODULE_IID); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + param_data = &req->param_data; + + param_data->module_instance_id = GPR_PRM_MODULE_IID; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_RSC_HW_CORE; + param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; + + req->hw_clk_id = hw_block_id; + + rc = q6prm_send_cmd_sync(prm, pkt, rsp_opcode); + + kfree(pkt); + + return rc; +} + +int q6prm_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, + const char *client_name, uint32_t *client_handle) +{ + return q6prm_set_hw_core_req(dev, hw_block_id, true); + +} +EXPORT_SYMBOL_GPL(q6prm_vote_lpass_core_hw); + +int q6prm_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, uint32_t client_handle) +{ + return q6prm_set_hw_core_req(dev, hw_block_id, false); +} +EXPORT_SYMBOL_GPL(q6prm_unvote_lpass_core_hw); + +int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_root, + unsigned int freq) +{ + struct q6prm *prm = dev_get_drvdata(dev->parent); + struct apm_module_param_data *param_data; + struct prm_cmd_request_rsc *req; + gpr_device_t *gdev = prm->gdev; + struct gpr_pkt *pkt; + int rc; + + pkt = audioreach_alloc_cmd_pkt(sizeof(*req), PRM_CMD_REQUEST_HW_RSC, 0, gdev->svc.id, + GPR_PRM_MODULE_IID); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + req = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + + param_data = &req->param_data; + + param_data->module_instance_id = GPR_PRM_MODULE_IID; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_RSC_AUDIO_HW_CLK; + param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; + + req->num_clk_id = 1; + req->clock_id.clock_id = clk_id; + req->clock_id.clock_freq = freq; + req->clock_id.clock_attri = clk_attr; + req->clock_id.clock_root = clk_root; + + rc = q6prm_send_cmd_sync(prm, pkt, PRM_CMD_RSP_REQUEST_HW_RSC); + + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6prm_set_lpass_clock); + +static int prm_callback(struct gpr_resp_pkt *data, void *priv, int op) +{ + gpr_device_t *gdev = priv; + struct q6prm *prm = dev_get_drvdata(&gdev->dev); + struct gpr_ibasic_rsp_result_t *result; + struct gpr_hdr *hdr = &data->hdr; + + switch (hdr->opcode) { + case PRM_CMD_RSP_REQUEST_HW_RSC: + case PRM_CMD_RSP_RELEASE_HW_RSC: + result = data->payload; + prm->result.opcode = hdr->opcode; + prm->result.status = result->status; + wake_up(&prm->wait); + break; + default: + break; + } + + return 0; +} + +static int prm_probe(gpr_device_t *gdev) +{ + struct device *dev = &gdev->dev; + struct q6prm *cc; + + cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); + if (!cc) + return -ENOMEM; + + cc->dev = dev; + cc->gdev = gdev; + mutex_init(&cc->lock); + init_waitqueue_head(&cc->wait); + dev_set_drvdata(dev, cc); + + return devm_of_platform_populate(dev); +} + +#ifdef CONFIG_OF +static const struct of_device_id prm_device_id[] = { + { .compatible = "qcom,q6prm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, prm_device_id); +#endif + +static gpr_driver_t prm_driver = { + .probe = prm_probe, + .gpr_callback = prm_callback, + .driver = { + .name = "qcom-prm", + .of_match_table = of_match_ptr(prm_device_id), + }, +}; + +module_gpr_driver(prm_driver); +MODULE_DESCRIPTION("Audio Process Manager"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h new file mode 100644 index 000000000000..fea4d1954bc1 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6prm.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6PRM_H__ +#define __Q6PRM_H__ + +/* Clock ID for Primary I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 +/* Clock ID for Primary I2S EBIT */ +#define Q6PRM_LPASS_CLK_ID_PRI_MI2S_EBIT 0x101 +/* Clock ID for Secondary I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_SEC_MI2S_IBIT 0x102 +/* Clock ID for Secondary I2S EBIT */ +#define Q6PRM_LPASS_CLK_ID_SEC_MI2S_EBIT 0x103 +/* Clock ID for Tertiary I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_TER_MI2S_IBIT 0x104 +/* Clock ID for Tertiary I2S EBIT */ +#define Q6PRM_LPASS_CLK_ID_TER_MI2S_EBIT 0x105 +/* Clock ID for Quartnery I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_QUAD_MI2S_IBIT 0x106 +/* Clock ID for Quartnery I2S EBIT */ +#define Q6PRM_LPASS_CLK_ID_QUAD_MI2S_EBIT 0x107 +/* Clock ID for Speaker I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_SPEAKER_I2S_IBIT 0x108 +/* Clock ID for Speaker I2S EBIT */ +#define Q6PRM_LPASS_CLK_ID_SPEAKER_I2S_EBIT 0x109 +/* Clock ID for Speaker I2S OSR */ +#define Q6PRM_LPASS_CLK_ID_SPEAKER_I2S_OSR 0x10A + +/* Clock ID for QUINARY I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_QUI_MI2S_IBIT 0x10B +/* Clock ID for QUINARY I2S EBIT */ +#define Q6PRM_LPASS_CLK_ID_QUI_MI2S_EBIT 0x10C +/* Clock ID for SENARY I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_SEN_MI2S_IBIT 0x10D +/* Clock ID for SENARY I2S EBIT */ +#define Q6PRM_LPASS_CLK_ID_SEN_MI2S_EBIT 0x10E +/* Clock ID for INT0 I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_INT0_MI2S_IBIT 0x10F +/* Clock ID for INT1 I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_INT1_MI2S_IBIT 0x110 +/* Clock ID for INT2 I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_INT2_MI2S_IBIT 0x111 +/* Clock ID for INT3 I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_INT3_MI2S_IBIT 0x112 +/* Clock ID for INT4 I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_INT4_MI2S_IBIT 0x113 +/* Clock ID for INT5 I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_INT5_MI2S_IBIT 0x114 +/* Clock ID for INT6 I2S IBIT */ +#define Q6PRM_LPASS_CLK_ID_INT6_MI2S_IBIT 0x115 + +/* Clock ID for QUINARY MI2S OSR CLK */ +#define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + +#define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305 +#define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306 + +#define Q6PRM_LPASS_CLK_ID_VA_CORE_MCLK 0x307 +#define Q6PRM_LPASS_CLK_ID_VA_CORE_2X_MCLK 0x308 + +#define Q6PRM_LPASS_CLK_ID_TX_CORE_MCLK 0x30c +#define Q6PRM_LPASS_CLK_ID_TX_CORE_NPL_MCLK 0x30d + +#define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK 0x30e +#define Q6PRM_LPASS_CLK_ID_RX_CORE_NPL_MCLK 0x30f + +#define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 +#define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 +#define Q6PRM_HW_CORE_ID_LPASS 1 +#define Q6PRM_HW_CORE_ID_DCODEC 2 + +int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, + int clk_root, unsigned int freq); +int q6prm_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, + const char *client_name, uint32_t *client_handle); +int q6prm_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, + uint32_t client_handle); +#endif /* __Q6PRM_H__ */ diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c new file mode 100644 index 000000000000..bd649c232a06 --- /dev/null +++ b/sound/soc/qcom/qdsp6/topology.c @@ -0,0 +1,1113 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/control.h> +#include <sound/asound.h> +#include <linux/firmware.h> +#include <sound/soc-topology.h> +#include <sound/soc-dpcm.h> +#include <uapi/sound/snd_ar_tokens.h> +#include <linux/kernel.h> +#include <linux/wait.h> +#include "q6apm.h" +#include "audioreach.h" + +struct snd_ar_control { + u32 sgid; /* Sub Graph ID */ + struct snd_soc_component *scomp; +}; + +static struct audioreach_graph_info *audioreach_tplg_alloc_graph_info(struct q6apm *apm, + uint32_t graph_id, + bool *found) +{ + struct audioreach_graph_info *info; + int ret; + + mutex_lock(&apm->lock); + info = idr_find(&apm->graph_info_idr, graph_id); + mutex_unlock(&apm->lock); + + if (info) { + *found = true; + return info; + } + + *found = false; + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&info->sg_list); + + mutex_lock(&apm->lock); + ret = idr_alloc(&apm->graph_info_idr, info, graph_id, graph_id + 1, GFP_KERNEL); + mutex_unlock(&apm->lock); + + if (ret < 0) { + dev_err(apm->dev, "Failed to allocate Graph ID (%x)\n", graph_id); + kfree(info); + return ERR_PTR(ret); + } + + info->id = ret; + + return info; +} + +static void audioreach_tplg_add_sub_graph(struct audioreach_sub_graph *sg, + struct audioreach_graph_info *info) +{ + list_add_tail(&sg->node, &info->sg_list); + sg->info = info; + info->num_sub_graphs++; +} + +static struct audioreach_sub_graph *audioreach_tplg_alloc_sub_graph(struct q6apm *apm, + uint32_t sub_graph_id, + bool *found) +{ + struct audioreach_sub_graph *sg; + int ret; + + if (!sub_graph_id) + return ERR_PTR(-EINVAL); + + /* Find if there is already a matching sub-graph */ + mutex_lock(&apm->lock); + sg = idr_find(&apm->sub_graphs_idr, sub_graph_id); + mutex_unlock(&apm->lock); + + if (sg) { + *found = true; + return sg; + } + + *found = false; + sg = kzalloc(sizeof(*sg), GFP_KERNEL); + if (!sg) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&sg->container_list); + + mutex_lock(&apm->lock); + ret = idr_alloc(&apm->sub_graphs_idr, sg, sub_graph_id, sub_graph_id + 1, GFP_KERNEL); + mutex_unlock(&apm->lock); + + if (ret < 0) { + dev_err(apm->dev, "Failed to allocate Sub-Graph Instance ID (%x)\n", sub_graph_id); + kfree(sg); + return ERR_PTR(ret); + } + + sg->sub_graph_id = ret; + + return sg; +} + +static struct audioreach_container *audioreach_tplg_alloc_container(struct q6apm *apm, + struct audioreach_sub_graph *sg, + uint32_t container_id, + bool *found) +{ + struct audioreach_container *cont; + int ret; + + if (!container_id) + return ERR_PTR(-EINVAL); + + mutex_lock(&apm->lock); + cont = idr_find(&apm->containers_idr, container_id); + mutex_unlock(&apm->lock); + + if (cont) { + *found = true; + return cont; + } + *found = false; + + cont = kzalloc(sizeof(*cont), GFP_KERNEL); + if (!cont) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&cont->modules_list); + + mutex_lock(&apm->lock); + ret = idr_alloc(&apm->containers_idr, cont, container_id, container_id + 1, GFP_KERNEL); + mutex_unlock(&apm->lock); + + if (ret < 0) { + dev_err(apm->dev, "Failed to allocate Container Instance ID (%x)\n", container_id); + kfree(cont); + return ERR_PTR(ret); + } + + cont->container_id = ret; + cont->sub_graph = sg; + /* add to container list */ + list_add_tail(&cont->node, &sg->container_list); + sg->num_containers++; + + return cont; +} + +static struct audioreach_module *audioreach_tplg_alloc_module(struct q6apm *apm, + struct audioreach_container *cont, + struct snd_soc_dapm_widget *w, + uint32_t module_id, bool *found) +{ + struct audioreach_module *mod; + int ret; + + mutex_lock(&apm->lock); + mod = idr_find(&apm->modules_idr, module_id); + mutex_unlock(&apm->lock); + + if (mod) { + *found = true; + return mod; + } + *found = false; + mod = kzalloc(sizeof(*mod), GFP_KERNEL); + if (!mod) + return ERR_PTR(-ENOMEM); + + mutex_lock(&apm->lock); + if (!module_id) { /* alloc module id dynamically */ + ret = idr_alloc_cyclic(&apm->modules_idr, mod, + AR_MODULE_DYNAMIC_INSTANCE_ID_START, + AR_MODULE_DYNAMIC_INSTANCE_ID_END, GFP_KERNEL); + } else { + ret = idr_alloc(&apm->modules_idr, mod, module_id, module_id + 1, GFP_KERNEL); + } + mutex_unlock(&apm->lock); + + if (ret < 0) { + dev_err(apm->dev, "Failed to allocate Module Instance ID (%x)\n", module_id); + kfree(mod); + return ERR_PTR(ret); + } + + mod->instance_id = ret; + /* add to module list */ + list_add_tail(&mod->node, &cont->modules_list); + mod->container = cont; + mod->widget = w; + cont->num_modules++; + + return mod; +} + +static struct snd_soc_tplg_vendor_array *audioreach_get_sg_array( + struct snd_soc_tplg_private *private) +{ + struct snd_soc_tplg_vendor_array *sg_array = NULL; + bool found = false; + int sz; + + for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) { + struct snd_soc_tplg_vendor_value_elem *sg_elem; + int tkn_count = 0; + + sg_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz); + sg_elem = sg_array->value; + sz = sz + le32_to_cpu(sg_array->size); + while (!found && tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) { + switch (le32_to_cpu(sg_elem->token)) { + case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: + found = true; + break; + default: + break; + } + tkn_count++; + sg_elem++; + } + } + + if (found) + return sg_array; + + return NULL; +} + +static struct snd_soc_tplg_vendor_array *audioreach_get_cont_array( + struct snd_soc_tplg_private *private) +{ + struct snd_soc_tplg_vendor_array *cont_array = NULL; + bool found = false; + int sz; + + for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) { + struct snd_soc_tplg_vendor_value_elem *cont_elem; + int tkn_count = 0; + + cont_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz); + cont_elem = cont_array->value; + sz = sz + le32_to_cpu(cont_array->size); + while (!found && tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) { + switch (le32_to_cpu(cont_elem->token)) { + case AR_TKN_U32_CONTAINER_INSTANCE_ID: + found = true; + break; + default: + break; + } + tkn_count++; + cont_elem++; + } + } + + if (found) + return cont_array; + + return NULL; +} + +static struct snd_soc_tplg_vendor_array *audioreach_get_module_array( + struct snd_soc_tplg_private *private) +{ + struct snd_soc_tplg_vendor_array *mod_array = NULL; + bool found = false; + int sz = 0; + + for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) { + struct snd_soc_tplg_vendor_value_elem *mod_elem; + int tkn_count = 0; + + mod_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz); + mod_elem = mod_array->value; + sz = sz + le32_to_cpu(mod_array->size); + while (!found && tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { + switch (le32_to_cpu(mod_elem->token)) { + case AR_TKN_U32_MODULE_INSTANCE_ID: + found = true; + break; + default: + break; + } + tkn_count++; + mod_elem++; + } + } + + if (found) + return mod_array; + + return NULL; +} + +static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm, + struct snd_soc_tplg_private *private) +{ + struct snd_soc_tplg_vendor_value_elem *sg_elem; + struct snd_soc_tplg_vendor_array *sg_array; + struct audioreach_graph_info *info = NULL; + int graph_id, sub_graph_id, tkn_count = 0; + struct audioreach_sub_graph *sg; + bool found; + + sg_array = audioreach_get_sg_array(private); + sg_elem = sg_array->value; + + while (tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) { + switch (le32_to_cpu(sg_elem->token)) { + case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: + sub_graph_id = le32_to_cpu(sg_elem->value); + sg = audioreach_tplg_alloc_sub_graph(apm, sub_graph_id, &found); + if (IS_ERR(sg)) { + return sg; + } else if (found) { + /* Already parsed data for this sub-graph */ + return sg; + } + break; + case AR_TKN_DAI_INDEX: + /* Sub graph is associated with predefined graph */ + graph_id = le32_to_cpu(sg_elem->value); + info = audioreach_tplg_alloc_graph_info(apm, graph_id, &found); + if (IS_ERR(info)) + return ERR_CAST(info); + break; + case AR_TKN_U32_SUB_GRAPH_PERF_MODE: + sg->perf_mode = le32_to_cpu(sg_elem->value); + break; + case AR_TKN_U32_SUB_GRAPH_DIRECTION: + sg->direction = le32_to_cpu(sg_elem->value); + break; + case AR_TKN_U32_SUB_GRAPH_SCENARIO_ID: + sg->scenario_id = le32_to_cpu(sg_elem->value); + break; + default: + dev_err(apm->dev, "Not a valid token %d for graph\n", sg_elem->token); + break; + + } + tkn_count++; + sg_elem++; + } + + /* Sub graph is associated with predefined graph */ + if (info) + audioreach_tplg_add_sub_graph(sg, info); + + return sg; +} + +static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *apm, + struct audioreach_sub_graph *sg, + struct snd_soc_tplg_private *private) +{ + struct snd_soc_tplg_vendor_value_elem *cont_elem; + struct snd_soc_tplg_vendor_array *cont_array; + struct audioreach_container *cont; + int container_id, tkn_count = 0; + bool found = false; + + cont_array = audioreach_get_cont_array(private); + cont_elem = cont_array->value; + + while (tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) { + switch (le32_to_cpu(cont_elem->token)) { + case AR_TKN_U32_CONTAINER_INSTANCE_ID: + container_id = le32_to_cpu(cont_elem->value); + cont = audioreach_tplg_alloc_container(apm, sg, container_id, &found); + if (IS_ERR(cont) || found)/* Error or Already parsed container data */ + return cont; + break; + case AR_TKN_U32_CONTAINER_CAPABILITY_ID: + cont->capability_id = le32_to_cpu(cont_elem->value); + break; + case AR_TKN_U32_CONTAINER_STACK_SIZE: + cont->stack_size = le32_to_cpu(cont_elem->value); + break; + case AR_TKN_U32_CONTAINER_GRAPH_POS: + cont->graph_pos = le32_to_cpu(cont_elem->value); + break; + case AR_TKN_U32_CONTAINER_PROC_DOMAIN: + cont->proc_domain = le32_to_cpu(cont_elem->value); + break; + default: + dev_err(apm->dev, "Not a valid token %d for graph\n", cont_elem->token); + break; + + } + tkn_count++; + cont_elem++; + } + + return cont; +} + +static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *apm, + struct audioreach_container *cont, + struct snd_soc_tplg_private *private, + struct snd_soc_dapm_widget *w) +{ + uint32_t max_ip_port = 0, max_op_port = 0, in_port = 0, out_port = 0; + uint32_t src_mod_inst_id = 0, src_mod_op_port_id = 0; + uint32_t dst_mod_inst_id = 0, dst_mod_ip_port_id = 0; + int module_id = 0, instance_id = 0, tkn_count = 0; + struct snd_soc_tplg_vendor_value_elem *mod_elem; + struct snd_soc_tplg_vendor_array *mod_array; + struct audioreach_module *mod = NULL; + bool found; + + mod_array = audioreach_get_module_array(private); + mod_elem = mod_array->value; + + while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { + switch (le32_to_cpu(mod_elem->token)) { + /* common module info */ + case AR_TKN_U32_MODULE_ID: + module_id = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_INSTANCE_ID: + instance_id = le32_to_cpu(mod_elem->value); + mod = audioreach_tplg_alloc_module(apm, cont, w, + instance_id, &found); + if (IS_ERR(mod)) { + return mod; + } else if (found) { + dev_err(apm->dev, "Duplicate Module Instance ID 0x%08x found\n", + instance_id); + return ERR_PTR(-EINVAL); + } + + break; + case AR_TKN_U32_MODULE_MAX_IP_PORTS: + max_ip_port = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_MAX_OP_PORTS: + max_op_port = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_IN_PORTS: + in_port = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_OUT_PORTS: + out_port = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_SRC_OP_PORT_ID: + src_mod_op_port_id = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_SRC_INSTANCE_ID: + src_mod_inst_id = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_DST_INSTANCE_ID: + dst_mod_inst_id = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_DST_IN_PORT_ID: + dst_mod_ip_port_id = le32_to_cpu(mod_elem->value); + break; + default: + break; + + } + tkn_count++; + mod_elem++; + } + + if (mod) { + mod->module_id = module_id; + mod->max_ip_port = max_ip_port; + mod->max_op_port = max_op_port; + mod->in_port = in_port; + mod->out_port = out_port; + mod->src_mod_inst_id = src_mod_inst_id; + mod->src_mod_op_port_id = src_mod_op_port_id; + mod->dst_mod_inst_id = dst_mod_inst_id; + mod->dst_mod_ip_port_id = dst_mod_ip_port_id; + } + + return mod; +} + +static int audioreach_widget_load_module_common(struct snd_soc_component *component, + int index, struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tplg_w) +{ + struct q6apm *apm = dev_get_drvdata(component->dev); + struct audioreach_container *cont; + struct audioreach_sub_graph *sg; + struct audioreach_module *mod; + struct snd_soc_dobj *dobj; + + sg = audioreach_parse_sg_tokens(apm, &tplg_w->priv); + if (IS_ERR(sg)) + return PTR_ERR(sg); + + cont = audioreach_parse_cont_tokens(apm, sg, &tplg_w->priv); + if (IS_ERR(cont)) + return PTR_ERR(cont); + + mod = audioreach_parse_common_tokens(apm, cont, &tplg_w->priv, w); + if (IS_ERR(mod)) + return PTR_ERR(mod); + + dobj = &w->dobj; + dobj->private = mod; + + return 0; +} + +static int audioreach_widget_load_enc_dec_cnv(struct snd_soc_component *component, + int index, struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tplg_w) +{ + struct snd_soc_tplg_vendor_value_elem *mod_elem; + struct snd_soc_tplg_vendor_array *mod_array; + struct audioreach_module *mod; + struct snd_soc_dobj *dobj; + int tkn_count = 0; + int ret; + + ret = audioreach_widget_load_module_common(component, index, w, tplg_w); + if (ret) + return ret; + + dobj = &w->dobj; + mod = dobj->private; + mod_array = audioreach_get_module_array(&tplg_w->priv); + mod_elem = mod_array->value; + + while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { + switch (le32_to_cpu(mod_elem->token)) { + case AR_TKN_U32_MODULE_FMT_INTERLEAVE: + mod->interleave_type = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_FMT_SAMPLE_RATE: + mod->rate = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_FMT_BIT_DEPTH: + mod->bit_depth = le32_to_cpu(mod_elem->value); + break; + default: + break; + } + tkn_count++; + mod_elem++; + } + + return 0; +} + +static int audioreach_widget_log_module_load(struct audioreach_module *mod, + struct snd_soc_tplg_vendor_array *mod_array) +{ + struct snd_soc_tplg_vendor_value_elem *mod_elem; + int tkn_count = 0; + + mod_elem = mod_array->value; + + while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { + switch (le32_to_cpu(mod_elem->token)) { + + case AR_TKN_U32_MODULE_LOG_CODE: + mod->log_code = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_LOG_TAP_POINT_ID: + mod->log_tap_point_id = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_LOG_MODE: + mod->log_mode = le32_to_cpu(mod_elem->value); + break; + default: + break; + } + tkn_count++; + mod_elem++; + } + + return 0; +} + +static int audioreach_widget_dma_module_load(struct audioreach_module *mod, + struct snd_soc_tplg_vendor_array *mod_array) +{ + struct snd_soc_tplg_vendor_value_elem *mod_elem; + int tkn_count = 0; + + mod_elem = mod_array->value; + + while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { + switch (le32_to_cpu(mod_elem->token)) { + case AR_TKN_U32_MODULE_HW_IF_IDX: + mod->hw_interface_idx = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_FMT_DATA: + mod->data_format = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_HW_IF_TYPE: + mod->hw_interface_type = le32_to_cpu(mod_elem->value); + break; + default: + break; + } + tkn_count++; + mod_elem++; + } + + return 0; +} + +static int audioreach_widget_i2s_module_load(struct audioreach_module *mod, + struct snd_soc_tplg_vendor_array *mod_array) +{ + struct snd_soc_tplg_vendor_value_elem *mod_elem; + int tkn_count = 0; + + mod_elem = mod_array->value; + + while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { + switch (le32_to_cpu(mod_elem->token)) { + case AR_TKN_U32_MODULE_HW_IF_IDX: + mod->hw_interface_idx = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_FMT_DATA: + mod->data_format = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_HW_IF_TYPE: + mod->hw_interface_type = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_SD_LINE_IDX: + mod->sd_line_idx = le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_WS_SRC: + mod->ws_src = le32_to_cpu(mod_elem->value); + break; + default: + break; + } + tkn_count++; + mod_elem++; + } + + return 0; +} + +static int audioreach_widget_load_buffer(struct snd_soc_component *component, + int index, struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tplg_w) +{ + struct snd_soc_tplg_vendor_array *mod_array; + struct audioreach_module *mod; + struct snd_soc_dobj *dobj; + int ret; + + ret = audioreach_widget_load_module_common(component, index, w, tplg_w); + if (ret) + return ret; + + dobj = &w->dobj; + mod = dobj->private; + + mod_array = audioreach_get_module_array(&tplg_w->priv); + + switch (mod->module_id) { + case MODULE_ID_CODEC_DMA_SINK: + case MODULE_ID_CODEC_DMA_SOURCE: + audioreach_widget_dma_module_load(mod, mod_array); + break; + case MODULE_ID_DATA_LOGGING: + audioreach_widget_log_module_load(mod, mod_array); + break; + case MODULE_ID_I2S_SINK: + case MODULE_ID_I2S_SOURCE: + audioreach_widget_i2s_module_load(mod, mod_array); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int audioreach_widget_load_mixer(struct snd_soc_component *component, + int index, struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tplg_w) +{ + struct snd_soc_tplg_vendor_value_elem *w_elem; + struct snd_soc_tplg_vendor_array *w_array; + struct snd_ar_control *scontrol; + struct snd_soc_dobj *dobj; + int tkn_count = 0; + + w_array = &tplg_w->priv.array[0]; + + scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL); + if (!scontrol) + return -ENOMEM; + + scontrol->scomp = component; + dobj = &w->dobj; + dobj->private = scontrol; + + w_elem = w_array->value; + while (tkn_count <= (le32_to_cpu(w_array->num_elems) - 1)) { + switch (le32_to_cpu(w_elem->token)) { + case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: + scontrol->sgid = le32_to_cpu(w_elem->value); + break; + default: /* ignore other tokens */ + break; + } + tkn_count++; + w_elem++; + } + + return 0; +} + +static int audioreach_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) + +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct audioreach_module *mod = w->dobj.private; + struct q6apm *apm = dev_get_drvdata(c->dev); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* apply gain after power up of widget */ + audioreach_gain_set_vol_ctrl(apm, mod, mod->gain); + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_tplg_widget_events audioreach_widget_ops[] = { + { AR_PGA_DAPM_EVENT, audioreach_pga_event }, +}; + +static int audioreach_widget_load_pga(struct snd_soc_component *component, + int index, struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tplg_w) +{ + struct audioreach_module *mod; + struct snd_soc_dobj *dobj; + int ret; + + ret = audioreach_widget_load_module_common(component, index, w, tplg_w); + if (ret) + return ret; + + dobj = &w->dobj; + mod = dobj->private; + mod->gain = VOL_CTRL_DEFAULT_GAIN; + + ret = snd_soc_tplg_widget_bind_event(w, audioreach_widget_ops, + ARRAY_SIZE(audioreach_widget_ops), + le16_to_cpu(tplg_w->event_type)); + if (ret) { + dev_err(component->dev, "matching event handlers NOT found for %d\n", + le16_to_cpu(tplg_w->event_type)); + return -EINVAL; + } + + return 0; +} + +static int audioreach_widget_ready(struct snd_soc_component *component, + int index, struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tplg_w) +{ + switch (w->id) { + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + audioreach_widget_load_buffer(component, index, w, tplg_w); + break; + case snd_soc_dapm_decoder: + case snd_soc_dapm_encoder: + case snd_soc_dapm_src: + audioreach_widget_load_enc_dec_cnv(component, index, w, tplg_w); + break; + case snd_soc_dapm_buffer: + audioreach_widget_load_buffer(component, index, w, tplg_w); + break; + case snd_soc_dapm_mixer: + return audioreach_widget_load_mixer(component, index, w, tplg_w); + case snd_soc_dapm_pga: + return audioreach_widget_load_pga(component, index, w, tplg_w); + case snd_soc_dapm_dai_link: + case snd_soc_dapm_scheduler: + case snd_soc_dapm_out_drv: + default: + dev_err(component->dev, "Widget type (0x%x) not yet supported\n", w->id); + break; + } + + return 0; +} + +static int audioreach_widget_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_soc_dapm_widget *w = container_of(dobj, struct snd_soc_dapm_widget, dobj); + struct q6apm *apm = dev_get_drvdata(scomp->dev); + struct audioreach_container *cont; + struct audioreach_module *mod; + + mod = dobj->private; + cont = mod->container; + + if (w->id == snd_soc_dapm_mixer) { + /* virtual widget */ + kfree(dobj->private); + return 0; + } + + mutex_lock(&apm->lock); + idr_remove(&apm->modules_idr, mod->instance_id); + cont->num_modules--; + + list_del(&mod->node); + kfree(mod); + /* Graph Info has N sub-graphs, sub-graph has N containers, Container has N Modules */ + if (list_empty(&cont->modules_list)) { /* if no modules in the container then remove it */ + struct audioreach_sub_graph *sg = cont->sub_graph; + + idr_remove(&apm->containers_idr, cont->container_id); + list_del(&cont->node); + sg->num_containers--; + kfree(cont); + /* check if there are no more containers in the sub graph and remove it */ + if (list_empty(&sg->container_list)) { + struct audioreach_graph_info *info = sg->info; + + idr_remove(&apm->sub_graphs_idr, sg->sub_graph_id); + list_del(&sg->node); + info->num_sub_graphs--; + kfree(sg); + /* Check if there are no more sub-graphs left then remove graph info */ + if (list_empty(&info->sg_list)) { + idr_remove(&apm->graph_info_idr, info->id); + kfree(info); + } + } + } + + mutex_unlock(&apm->lock); + + return 0; +} + +static struct audioreach_module *audioreach_find_widget(struct snd_soc_component *comp, + const char *name) +{ + struct q6apm *apm = dev_get_drvdata(comp->dev); + struct audioreach_module *module; + int id; + + idr_for_each_entry(&apm->modules_idr, module, id) { + if (!strcmp(name, module->widget->name)) + return module; + } + + return NULL; +} + +static int audioreach_route_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_route *route) +{ + struct audioreach_module *src, *sink; + + src = audioreach_find_widget(scomp, route->source); + sink = audioreach_find_widget(scomp, route->sink); + + if (src && sink) { + src->dst_mod_inst_id = sink->instance_id; + sink->src_mod_inst_id = src->instance_id; + } + + return 0; +} + +static int audioreach_route_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + return 0; +} + +static int audioreach_tplg_complete(struct snd_soc_component *component) +{ + /* TBD */ + return 0; +} + +/* DAI link - used for any driver specific init */ +static int audioreach_link_load(struct snd_soc_component *component, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + link->nonatomic = true; + link->dynamic = true; + link->platforms->name = NULL; + link->platforms->of_node = of_get_compatible_child(component->dev->of_node, + "qcom,q6apm-dais"); + return 0; +} + +static int audioreach_get_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct snd_ar_control *dapm_scontrol = dw->dobj.private; + struct snd_ar_control *scontrol = mc->dobj.private; + struct q6apm *data = dev_get_drvdata(c->dev); + bool connected; + + connected = q6apm_is_sub_graphs_connected(data, scontrol->sgid, dapm_scontrol->sgid); + if (connected) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int audioreach_put_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct snd_ar_control *dapm_scontrol = dw->dobj.private; + struct snd_ar_control *scontrol = mc->dobj.private; + struct q6apm *data = dev_get_drvdata(c->dev); + + if (ucontrol->value.integer.value[0]) { + q6apm_connect_sub_graphs(data, scontrol->sgid, dapm_scontrol->sgid, true); + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 1, NULL); + } else { + q6apm_connect_sub_graphs(data, scontrol->sgid, dapm_scontrol->sgid, false); + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 0, NULL); + } + return 0; +} + +static int audioreach_get_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol); + struct audioreach_module *mod = dw->dobj.private; + + ucontrol->value.integer.value[0] = mod->gain; + + return 0; +} + +static int audioreach_put_vol_ctrl_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *dw = snd_soc_dapm_kcontrol_widget(kcontrol); + struct audioreach_module *mod = dw->dobj.private; + + mod->gain = ucontrol->value.integer.value[0]; + + return 1; +} + +static int audioreach_control_load_mix(struct snd_soc_component *scomp, + struct snd_ar_control *scontrol, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_soc_tplg_vendor_value_elem *c_elem; + struct snd_soc_tplg_vendor_array *c_array; + struct snd_soc_tplg_mixer_control *mc; + int tkn_count = 0; + + mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); + c_array = (struct snd_soc_tplg_vendor_array *)mc->priv.data; + + c_elem = c_array->value; + + while (tkn_count <= (le32_to_cpu(c_array->num_elems) - 1)) { + switch (le32_to_cpu(c_elem->token)) { + case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: + scontrol->sgid = le32_to_cpu(c_elem->value); + break; + default: + /* Ignore other tokens */ + break; + } + c_elem++; + tkn_count++; + } + + return 0; +} + +static int audioreach_control_load(struct snd_soc_component *scomp, int index, + struct snd_kcontrol_new *kc, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct snd_ar_control *scontrol; + struct soc_mixer_control *sm; + struct snd_soc_dobj *dobj; + int ret = 0; + + scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL); + if (!scontrol) + return -ENOMEM; + + scontrol->scomp = scomp; + + switch (le32_to_cpu(hdr->ops.get)) { + case SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX: + sm = (struct soc_mixer_control *)kc->private_value; + dobj = &sm->dobj; + ret = audioreach_control_load_mix(scomp, scontrol, kc, hdr); + break; + case SND_SOC_AR_TPLG_VOL_CTL: + sm = (struct soc_mixer_control *)kc->private_value; + dobj = &sm->dobj; + break; + default: + dev_warn(scomp->dev, "control type not supported %d:%d:%d\n", + hdr->ops.get, hdr->ops.put, hdr->ops.info); + kfree(scontrol); + return -EINVAL; + } + + dobj->private = scontrol; + return ret; +} + +static int audioreach_control_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_ar_control *scontrol = dobj->private; + + kfree(scontrol); + + return 0; +} + +static const struct snd_soc_tplg_kcontrol_ops audioreach_io_ops[] = { + {SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX, audioreach_get_audio_mixer, + audioreach_put_audio_mixer, snd_soc_info_volsw}, + {SND_SOC_AR_TPLG_VOL_CTL, audioreach_get_vol_ctrl_audio_mixer, + audioreach_put_vol_ctrl_audio_mixer, snd_soc_info_volsw}, +}; + +static struct snd_soc_tplg_ops audioreach_tplg_ops = { + .io_ops = audioreach_io_ops, + .io_ops_count = ARRAY_SIZE(audioreach_io_ops), + + .control_load = audioreach_control_load, + .control_unload = audioreach_control_unload, + + .widget_ready = audioreach_widget_ready, + .widget_unload = audioreach_widget_unload, + + .complete = audioreach_tplg_complete, + .link_load = audioreach_link_load, + + .dapm_route_load = audioreach_route_load, + .dapm_route_unload = audioreach_route_unload, +}; + +int audioreach_tplg_init(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct device *dev = component->dev; + const struct firmware *fw; + char *tplg_fw_name; + int ret; + + /* Inline with Qualcomm UCM configs and linux-firmware path */ + tplg_fw_name = kasprintf(GFP_KERNEL, "qcom/%s/%s-tplg.bin", card->driver_name, card->name); + if (!tplg_fw_name) + return -ENOMEM; + + ret = request_firmware(&fw, tplg_fw_name, dev); + if (ret < 0) { + dev_err(dev, "tplg firmware loading %s failed %d \n", tplg_fw_name, ret); + goto err; + } + + ret = snd_soc_tplg_component_load(component, &audioreach_tplg_ops, fw); + if (ret < 0) { + dev_err(dev, "tplg component load failed%d\n", ret); + ret = -EINVAL; + } + + release_firmware(fw); +err: + kfree(tplg_fw_name); + + return ret; +} +EXPORT_SYMBOL_GPL(audioreach_tplg_init); diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index fe8fd7367e21..b2ca2579810b 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -8,6 +8,8 @@ #include <sound/soc-dapm.h> #include <sound/pcm.h> #include <linux/soundwire/sdw.h> +#include <sound/jack.h> +#include <linux/input-event-codes.h> #include "qdsp6/q6afe.h" #include "common.h" @@ -18,8 +20,66 @@ struct sm8250_snd_data { bool stream_prepared[AFE_PORT_MAX]; struct snd_soc_card *card; struct sdw_stream_runtime *sruntime[AFE_PORT_MAX]; + struct snd_soc_jack jack; + bool jack_setup; }; +static int sm8250_snd_init(struct snd_soc_pcm_runtime *rtd) +{ + 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 snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + int rval, i; + + if (!data->jack_setup) { + struct snd_jack *jack; + + rval = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_MECHANICAL | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | + SND_JACK_BTN_4 | SND_JACK_BTN_5, + &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_MEDIA); + 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; + } + + switch (cpu_dai->id) { + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_3: + for_each_rtd_codec_dais(rtd, i, codec_dai) { + 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; + } + } + + break; + default: + break; + } + + + return 0; +} + static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -69,6 +129,12 @@ static int sm8250_snd_hw_params(struct snd_pcm_substream *substream, switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: + case RX_CODEC_DMA_RX_0: + case RX_CODEC_DMA_RX_1: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_3: for_each_rtd_codec_dais(rtd, i, codec_dai) { sruntime = snd_soc_dai_get_sdw_stream(codec_dai, substream->stream); @@ -129,6 +195,12 @@ static int sm8250_snd_prepare(struct snd_pcm_substream *substream) switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: + case RX_CODEC_DMA_RX_0: + case RX_CODEC_DMA_RX_1: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_3: return sm8250_snd_wsa_dma_prepare(substream); default: break; @@ -147,6 +219,12 @@ static int sm8250_snd_hw_free(struct snd_pcm_substream *substream) switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: + case RX_CODEC_DMA_RX_0: + case RX_CODEC_DMA_RX_1: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_3: if (sruntime && data->stream_prepared[cpu_dai->id]) { sdw_disable_stream(sruntime); sdw_deprepare_stream(sruntime); @@ -174,6 +252,7 @@ static void sm8250_add_be_ops(struct snd_soc_card *card) for_each_card_prelinks(card, i, link) { if (link->no_pcm == 1) { + link->init = sm8250_snd_init; link->be_hw_params_fixup = sm8250_be_hw_params_fixup; link->ops = &sm8250_be_ops; } diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig index 053097b73e28..42f76bc0fb02 100644 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -16,6 +16,17 @@ config SND_SOC_ROCKCHIP_I2S Rockchip I2S device. The device supports upto maximum of 8 channels each for play and record. +config SND_SOC_ROCKCHIP_I2S_TDM + tristate "Rockchip I2S/TDM Device Driver" + depends on HAVE_CLK && SND_SOC_ROCKCHIP + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for the I2S/TDM driver for + Rockchip I2S/TDM devices, found in Rockchip SoCs. These devices + interface between the AHB bus and the I2S bus, and support up to a + maximum of 8 channels each for playback and recording. + + config SND_SOC_ROCKCHIP_PDM tristate "Rockchip PDM Controller Driver" depends on HAVE_CLK && SND_SOC_ROCKCHIP diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile index 65e814d46006..30c57c0d7660 100644 --- a/sound/soc/rockchip/Makefile +++ b/sound/soc/rockchip/Makefile @@ -1,13 +1,14 @@ # SPDX-License-Identifier: GPL-2.0 # ROCKCHIP Platform Support snd-soc-rockchip-i2s-objs := rockchip_i2s.o -snd-soc-rockchip-pcm-objs := rockchip_pcm.o +snd-soc-rockchip-i2s-tdm-objs := rockchip_i2s_tdm.o snd-soc-rockchip-pdm-objs := rockchip_pdm.o snd-soc-rockchip-spdif-objs := rockchip_spdif.o -obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S) += snd-soc-rockchip-i2s.o snd-soc-rockchip-pcm.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S) += snd-soc-rockchip-i2s.o obj-$(CONFIG_SND_SOC_ROCKCHIP_PDM) += snd-soc-rockchip-pdm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_SPDIF) += snd-soc-rockchip-spdif.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S_TDM) += snd-soc-rockchip-i2s-tdm.o snd-soc-rockchip-max98090-objs := rockchip_max98090.o snd-soc-rockchip-rt5645-objs := rockchip_rt5645.o diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 7e89f5b0c237..a6d7656c206e 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -20,7 +20,6 @@ #include <sound/dmaengine_pcm.h> #include "rockchip_i2s.h" -#include "rockchip_pcm.h" #define DRV_NAME "rockchip-i2s" @@ -756,7 +755,7 @@ static int rockchip_i2s_probe(struct platform_device *pdev) goto err_suspend; } - ret = rockchip_pcm_platform_register(&pdev->dev); + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) { dev_err(&pdev->dev, "Could not register PCM\n"); goto err_suspend; diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c new file mode 100644 index 000000000000..17b9b287853a --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -0,0 +1,1762 @@ +// SPDX-License-Identifier: GPL-2.0-only +// ALSA SoC Audio Layer - Rockchip I2S/TDM Controller driver + +// Copyright (c) 2018 Rockchip Electronics Co. Ltd. +// Author: Sugar Zhang <sugar.zhang@rock-chips.com> +// Author: Nicolas Frattaroli <frattaroli.nicolas@gmail.com> + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/spinlock.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "rockchip_i2s_tdm.h" + +#define DRV_NAME "rockchip-i2s-tdm" + +#define DEFAULT_MCLK_FS 256 +#define CH_GRP_MAX 4 /* The max channel 8 / 2 */ +#define MULTIPLEX_CH_MAX 10 +#define CLK_PPM_MIN -1000 +#define CLK_PPM_MAX 1000 + +#define TRCM_TXRX 0 +#define TRCM_TX 1 +#define TRCM_RX 2 + +struct txrx_config { + u32 addr; + u32 reg; + u32 txonly; + u32 rxonly; +}; + +struct rk_i2s_soc_data { + u32 softrst_offset; + u32 grf_reg_offset; + u32 grf_shift; + int config_count; + const struct txrx_config *configs; + int (*init)(struct device *dev, u32 addr); +}; + +struct rk_i2s_tdm_dev { + struct device *dev; + struct clk *hclk; + struct clk *mclk_tx; + struct clk *mclk_rx; + /* The mclk_tx_src is parent of mclk_tx */ + struct clk *mclk_tx_src; + /* The mclk_rx_src is parent of mclk_rx */ + struct clk *mclk_rx_src; + /* + * The mclk_root0 and mclk_root1 are root parent and supplies for + * the different FS. + * + * e.g: + * mclk_root0 is VPLL0, used for FS=48000Hz + * mclk_root1 is VPLL1, used for FS=44100Hz + */ + struct clk *mclk_root0; + struct clk *mclk_root1; + struct regmap *regmap; + struct regmap *grf; + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct reset_control *tx_reset; + struct reset_control *rx_reset; + struct rk_i2s_soc_data *soc_data; + bool is_master_mode; + bool io_multiplex; + bool mclk_calibrate; + bool tdm_mode; + unsigned int mclk_rx_freq; + unsigned int mclk_tx_freq; + unsigned int mclk_root0_freq; + unsigned int mclk_root1_freq; + unsigned int mclk_root0_initial_freq; + unsigned int mclk_root1_initial_freq; + unsigned int frame_width; + unsigned int clk_trcm; + unsigned int i2s_sdis[CH_GRP_MAX]; + unsigned int i2s_sdos[CH_GRP_MAX]; + int clk_ppm; + int refcount; + spinlock_t lock; /* xfer lock */ + bool has_playback; + bool has_capture; +}; + +static int to_ch_num(unsigned int val) +{ + switch (val) { + case I2S_CHN_4: + return 4; + case I2S_CHN_6: + return 6; + case I2S_CHN_8: + return 8; + default: + return 2; + } +} + +static void i2s_tdm_disable_unprepare_mclk(struct rk_i2s_tdm_dev *i2s_tdm) +{ + clk_disable_unprepare(i2s_tdm->mclk_tx); + clk_disable_unprepare(i2s_tdm->mclk_rx); + if (i2s_tdm->mclk_calibrate) { + clk_disable_unprepare(i2s_tdm->mclk_tx_src); + clk_disable_unprepare(i2s_tdm->mclk_rx_src); + clk_disable_unprepare(i2s_tdm->mclk_root0); + clk_disable_unprepare(i2s_tdm->mclk_root1); + } +} + +/** + * i2s_tdm_prepare_enable_mclk - prepare to enable all mclks, disable them on + * failure. + * @i2s_tdm: rk_i2s_tdm_dev struct + * + * This function attempts to enable all mclk clocks, but cleans up after + * itself on failure. Guarantees to balance its calls. + * + * Returns success (0) or negative errno. + */ +static int i2s_tdm_prepare_enable_mclk(struct rk_i2s_tdm_dev *i2s_tdm) +{ + int ret = 0; + + ret = clk_prepare_enable(i2s_tdm->mclk_tx); + if (ret) + goto err_mclk_tx; + ret = clk_prepare_enable(i2s_tdm->mclk_rx); + if (ret) + goto err_mclk_rx; + if (i2s_tdm->mclk_calibrate) { + ret = clk_prepare_enable(i2s_tdm->mclk_tx_src); + if (ret) + goto err_mclk_rx; + ret = clk_prepare_enable(i2s_tdm->mclk_rx_src); + if (ret) + goto err_mclk_rx_src; + ret = clk_prepare_enable(i2s_tdm->mclk_root0); + if (ret) + goto err_mclk_root0; + ret = clk_prepare_enable(i2s_tdm->mclk_root1); + if (ret) + goto err_mclk_root1; + } + + return 0; + +err_mclk_root1: + clk_disable_unprepare(i2s_tdm->mclk_root0); +err_mclk_root0: + clk_disable_unprepare(i2s_tdm->mclk_rx_src); +err_mclk_rx_src: + clk_disable_unprepare(i2s_tdm->mclk_tx_src); +err_mclk_rx: + clk_disable_unprepare(i2s_tdm->mclk_tx); +err_mclk_tx: + return ret; +} + +static int __maybe_unused i2s_tdm_runtime_suspend(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + + regcache_cache_only(i2s_tdm->regmap, true); + i2s_tdm_disable_unprepare_mclk(i2s_tdm); + + clk_disable_unprepare(i2s_tdm->hclk); + + return 0; +} + +static int __maybe_unused i2s_tdm_runtime_resume(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s_tdm->hclk); + if (ret) + goto err_hclk; + + ret = i2s_tdm_prepare_enable_mclk(i2s_tdm); + if (ret) + goto err_mclk; + + regcache_cache_only(i2s_tdm->regmap, false); + regcache_mark_dirty(i2s_tdm->regmap); + + ret = regcache_sync(i2s_tdm->regmap); + if (ret) + goto err_regcache; + + return 0; + +err_regcache: + i2s_tdm_disable_unprepare_mclk(i2s_tdm); +err_mclk: + clk_disable_unprepare(i2s_tdm->hclk); +err_hclk: + return ret; +} + +static inline struct rk_i2s_tdm_dev *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +/* + * Makes sure that both tx and rx are reset at the same time to sync lrck + * when clk_trcm > 0. + */ +static void rockchip_snd_xfer_sync_reset(struct rk_i2s_tdm_dev *i2s_tdm) +{ + /* This is technically race-y. + * + * In an ideal world, we could atomically assert both resets at the + * same time, through an atomic bulk reset API. This API however does + * not exist, so what the downstream vendor code used to do was + * implement half a reset controller here and require the CRU to be + * passed to the driver as a device tree node. Violating abstractions + * like that is bad, especially when it influences something like the + * bindings which are supposed to describe the hardware, not whatever + * workarounds the driver needs, so it was dropped. + * + * In practice, asserting the resets one by one appears to work just + * fine for playback. During duplex (playback + capture) operation, + * this might become an issue, but that should be solved by the + * implementation of the aforementioned API, not by shoving a reset + * controller into an audio driver. + */ + + reset_control_assert(i2s_tdm->tx_reset); + reset_control_assert(i2s_tdm->rx_reset); + udelay(10); + reset_control_deassert(i2s_tdm->tx_reset); + reset_control_deassert(i2s_tdm->rx_reset); + udelay(10); +} + +static void rockchip_snd_reset(struct reset_control *rc) +{ + reset_control_assert(rc); + udelay(10); + reset_control_deassert(rc); + udelay(10); +} + +static void rockchip_snd_xfer_clear(struct rk_i2s_tdm_dev *i2s_tdm, + unsigned int clr) +{ + unsigned int xfer_mask = 0; + unsigned int xfer_val = 0; + unsigned int val; + int retry = 10; + bool tx = clr & I2S_CLR_TXC; + bool rx = clr & I2S_CLR_RXC; + + if (!(rx || tx)) + return; + + if (tx) { + xfer_mask = I2S_XFER_TXS_START; + xfer_val = I2S_XFER_TXS_STOP; + } + if (rx) { + xfer_mask |= I2S_XFER_RXS_START; + xfer_val |= I2S_XFER_RXS_STOP; + } + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, xfer_mask, xfer_val); + udelay(150); + regmap_update_bits(i2s_tdm->regmap, I2S_CLR, clr, clr); + + regmap_read(i2s_tdm->regmap, I2S_CLR, &val); + /* Wait on the clear operation to finish */ + while (val) { + udelay(15); + regmap_read(i2s_tdm->regmap, I2S_CLR, &val); + retry--; + if (!retry) { + dev_warn(i2s_tdm->dev, "clear failed, reset %s%s\n", + tx ? "tx" : "", rx ? "rx" : ""); + if (rx && tx) + rockchip_snd_xfer_sync_reset(i2s_tdm); + else if (tx) + rockchip_snd_reset(i2s_tdm->tx_reset); + else if (rx) + rockchip_snd_reset(i2s_tdm->rx_reset); + break; + } + } +} + +static inline void rockchip_enable_tde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_TDE_ENABLE, + I2S_DMACR_TDE_ENABLE); +} + +static inline void rockchip_disable_tde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_TDE_ENABLE, + I2S_DMACR_TDE_DISABLE); +} + +static inline void rockchip_enable_rde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_RDE_ENABLE, + I2S_DMACR_RDE_ENABLE); +} + +static inline void rockchip_disable_rde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_RDE_ENABLE, + I2S_DMACR_RDE_DISABLE); +} + +/* only used when clk_trcm > 0 */ +static void rockchip_snd_txrxctrl(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int on) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + unsigned long flags; + + spin_lock_irqsave(&i2s_tdm->lock, flags); + if (on) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_enable_tde(i2s_tdm->regmap); + else + rockchip_enable_rde(i2s_tdm->regmap); + + if (++i2s_tdm->refcount == 1) { + rockchip_snd_xfer_sync_reset(i2s_tdm); + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START); + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_disable_tde(i2s_tdm->regmap); + else + rockchip_disable_rde(i2s_tdm->regmap); + + if (--i2s_tdm->refcount == 0) { + rockchip_snd_xfer_clear(i2s_tdm, + I2S_CLR_TXC | I2S_CLR_RXC); + } + } + spin_unlock_irqrestore(&i2s_tdm->lock, flags); +} + +static void rockchip_snd_txctrl(struct rk_i2s_tdm_dev *i2s_tdm, int on) +{ + if (on) { + rockchip_enable_tde(i2s_tdm->regmap); + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_START, + I2S_XFER_TXS_START); + } else { + rockchip_disable_tde(i2s_tdm->regmap); + + rockchip_snd_xfer_clear(i2s_tdm, I2S_CLR_TXC); + } +} + +static void rockchip_snd_rxctrl(struct rk_i2s_tdm_dev *i2s_tdm, int on) +{ + if (on) { + rockchip_enable_rde(i2s_tdm->regmap); + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_RXS_START, + I2S_XFER_RXS_START); + } else { + rockchip_disable_rde(i2s_tdm->regmap); + + rockchip_snd_xfer_clear(i2s_tdm, I2S_CLR_RXC); + } +} + +static int rockchip_i2s_tdm_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(cpu_dai); + unsigned int mask, val, tdm_val, txcr_val, rxcr_val; + int ret; + bool is_tdm = i2s_tdm->tdm_mode; + + ret = pm_runtime_get_sync(cpu_dai->dev); + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(cpu_dai->dev); + return ret; + } + + mask = I2S_CKR_MSS_MASK; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + val = I2S_CKR_MSS_MASTER; + i2s_tdm->is_master_mode = true; + break; + case SND_SOC_DAIFMT_CBP_CFP: + val = I2S_CKR_MSS_SLAVE; + i2s_tdm->is_master_mode = false; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, mask, val); + + mask = I2S_CKR_CKP_MASK | I2S_CKR_TLP_MASK | I2S_CKR_RLP_MASK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = I2S_CKR_CKP_NORMAL | + I2S_CKR_TLP_NORMAL | + I2S_CKR_RLP_NORMAL; + break; + case SND_SOC_DAIFMT_NB_IF: + val = I2S_CKR_CKP_NORMAL | + I2S_CKR_TLP_INVERTED | + I2S_CKR_RLP_INVERTED; + break; + case SND_SOC_DAIFMT_IB_NF: + val = I2S_CKR_CKP_INVERTED | + I2S_CKR_TLP_NORMAL | + I2S_CKR_RLP_NORMAL; + break; + case SND_SOC_DAIFMT_IB_IF: + val = I2S_CKR_CKP_INVERTED | + I2S_CKR_TLP_INVERTED | + I2S_CKR_RLP_INVERTED; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, mask, val); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + txcr_val = I2S_TXCR_IBM_RSJM; + rxcr_val = I2S_RXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + txcr_val = I2S_TXCR_IBM_LSJM; + rxcr_val = I2S_RXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + txcr_val = I2S_TXCR_IBM_NORMAL; + rxcr_val = I2S_RXCR_IBM_NORMAL; + break; + case SND_SOC_DAIFMT_DSP_A: /* PCM no delay mode */ + txcr_val = I2S_TXCR_TFS_PCM; + rxcr_val = I2S_RXCR_TFS_PCM; + break; + case SND_SOC_DAIFMT_DSP_B: /* PCM delay 1 mode */ + txcr_val = I2S_TXCR_TFS_PCM | I2S_TXCR_PBM_MODE(1); + rxcr_val = I2S_RXCR_TFS_PCM | I2S_RXCR_PBM_MODE(1); + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + mask = I2S_TXCR_IBM_MASK | I2S_TXCR_TFS_MASK | I2S_TXCR_PBM_MASK; + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, mask, txcr_val); + + mask = I2S_RXCR_IBM_MASK | I2S_RXCR_TFS_MASK | I2S_RXCR_PBM_MASK; + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, mask, rxcr_val); + + if (is_tdm) { + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_TXCR_TFS_TDM_I2S; + tdm_val = TDM_SHIFT_CTRL(2); + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_TXCR_TFS_TDM_I2S; + tdm_val = TDM_SHIFT_CTRL(1); + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_TXCR_TFS_TDM_I2S; + tdm_val = TDM_SHIFT_CTRL(0); + break; + case SND_SOC_DAIFMT_DSP_A: + val = I2S_TXCR_TFS_TDM_PCM; + tdm_val = TDM_SHIFT_CTRL(0); + break; + case SND_SOC_DAIFMT_DSP_B: + val = I2S_TXCR_TFS_TDM_PCM; + tdm_val = TDM_SHIFT_CTRL(2); + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + tdm_val |= TDM_FSYNC_WIDTH_SEL1(1); + tdm_val |= TDM_FSYNC_WIDTH_HALF_FRAME; + + mask = I2S_TXCR_TFS_MASK; + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, mask, val); + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, mask, val); + + mask = TDM_FSYNC_WIDTH_SEL1_MSK | TDM_FSYNC_WIDTH_SEL0_MSK | + TDM_SHIFT_CTRL_MSK; + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_TXCR, + mask, tdm_val); + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_RXCR, + mask, tdm_val); + } + +err_pm_put: + pm_runtime_put(cpu_dai->dev); + + return ret; +} + +static void rockchip_i2s_tdm_xfer_pause(struct snd_pcm_substream *substream, + struct rk_i2s_tdm_dev *i2s_tdm) +{ + int stream; + + stream = SNDRV_PCM_STREAM_LAST - substream->stream; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_disable_tde(i2s_tdm->regmap); + else + rockchip_disable_rde(i2s_tdm->regmap); + + rockchip_snd_xfer_clear(i2s_tdm, I2S_CLR_TXC | I2S_CLR_RXC); +} + +static void rockchip_i2s_tdm_xfer_resume(struct snd_pcm_substream *substream, + struct rk_i2s_tdm_dev *i2s_tdm) +{ + int stream; + + stream = SNDRV_PCM_STREAM_LAST - substream->stream; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_enable_tde(i2s_tdm->regmap); + else + rockchip_enable_rde(i2s_tdm->regmap); + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START); +} + +static int rockchip_i2s_tdm_clk_set_rate(struct rk_i2s_tdm_dev *i2s_tdm, + struct clk *clk, unsigned long rate, + int ppm) +{ + unsigned long rate_target; + int delta, ret; + + if (ppm == i2s_tdm->clk_ppm) + return 0; + + if (ppm < 0) + delta = -1; + else + delta = 1; + + delta *= (int)div64_u64((u64)rate * (u64)abs(ppm) + 500000, + 1000000); + + rate_target = rate + delta; + + if (!rate_target) + return -EINVAL; + + ret = clk_set_rate(clk, rate_target); + if (ret) + return ret; + + i2s_tdm->clk_ppm = ppm; + + return 0; +} + +static int rockchip_i2s_tdm_calibrate_mclk(struct rk_i2s_tdm_dev *i2s_tdm, + struct snd_pcm_substream *substream, + unsigned int lrck_freq) +{ + struct clk *mclk_root; + struct clk *mclk_parent; + unsigned int mclk_root_freq; + unsigned int mclk_root_initial_freq; + unsigned int mclk_parent_freq; + unsigned int div, delta; + u64 ppm; + int ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mclk_parent = i2s_tdm->mclk_tx_src; + else + mclk_parent = i2s_tdm->mclk_rx_src; + + switch (lrck_freq) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + case 192000: + mclk_root = i2s_tdm->mclk_root0; + mclk_root_freq = i2s_tdm->mclk_root0_freq; + mclk_root_initial_freq = i2s_tdm->mclk_root0_initial_freq; + mclk_parent_freq = DEFAULT_MCLK_FS * 192000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + mclk_root = i2s_tdm->mclk_root1; + mclk_root_freq = i2s_tdm->mclk_root1_freq; + mclk_root_initial_freq = i2s_tdm->mclk_root1_initial_freq; + mclk_parent_freq = DEFAULT_MCLK_FS * 176400; + break; + default: + dev_err(i2s_tdm->dev, "Invalid LRCK frequency: %u Hz\n", + lrck_freq); + return -EINVAL; + } + + ret = clk_set_parent(mclk_parent, mclk_root); + if (ret) + return ret; + + ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, mclk_root, + mclk_root_freq, 0); + if (ret) + return ret; + + delta = abs(mclk_root_freq % mclk_parent_freq - mclk_parent_freq); + ppm = div64_u64((uint64_t)delta * 1000000, (uint64_t)mclk_root_freq); + + if (ppm) { + div = DIV_ROUND_CLOSEST(mclk_root_initial_freq, mclk_parent_freq); + if (!div) + return -EINVAL; + + mclk_root_freq = mclk_parent_freq * round_up(div, 2); + + ret = clk_set_rate(mclk_root, mclk_root_freq); + if (ret) + return ret; + + i2s_tdm->mclk_root0_freq = clk_get_rate(i2s_tdm->mclk_root0); + i2s_tdm->mclk_root1_freq = clk_get_rate(i2s_tdm->mclk_root1); + } + + return clk_set_rate(mclk_parent, mclk_parent_freq); +} + +static int rockchip_i2s_tdm_set_mclk(struct rk_i2s_tdm_dev *i2s_tdm, + struct snd_pcm_substream *substream, + struct clk **mclk) +{ + unsigned int mclk_freq; + int ret; + + if (i2s_tdm->clk_trcm) { + if (i2s_tdm->mclk_tx_freq != i2s_tdm->mclk_rx_freq) { + dev_err(i2s_tdm->dev, + "clk_trcm, tx: %d and rx: %d should be the same\n", + i2s_tdm->mclk_tx_freq, + i2s_tdm->mclk_rx_freq); + return -EINVAL; + } + + ret = clk_set_rate(i2s_tdm->mclk_tx, i2s_tdm->mclk_tx_freq); + if (ret) + return ret; + + ret = clk_set_rate(i2s_tdm->mclk_rx, i2s_tdm->mclk_rx_freq); + if (ret) + return ret; + + /* mclk_rx is also ok. */ + *mclk = i2s_tdm->mclk_tx; + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + *mclk = i2s_tdm->mclk_tx; + mclk_freq = i2s_tdm->mclk_tx_freq; + } else { + *mclk = i2s_tdm->mclk_rx; + mclk_freq = i2s_tdm->mclk_rx_freq; + } + + ret = clk_set_rate(*mclk, mclk_freq); + if (ret) + return ret; + } + + return 0; +} + +static int rockchip_i2s_ch_to_io(unsigned int ch, bool substream_capture) +{ + if (substream_capture) { + switch (ch) { + case I2S_CHN_4: + return I2S_IO_6CH_OUT_4CH_IN; + case I2S_CHN_6: + return I2S_IO_4CH_OUT_6CH_IN; + case I2S_CHN_8: + return I2S_IO_2CH_OUT_8CH_IN; + default: + return I2S_IO_8CH_OUT_2CH_IN; + } + } else { + switch (ch) { + case I2S_CHN_4: + return I2S_IO_4CH_OUT_6CH_IN; + case I2S_CHN_6: + return I2S_IO_6CH_OUT_4CH_IN; + case I2S_CHN_8: + return I2S_IO_8CH_OUT_2CH_IN; + default: + return I2S_IO_2CH_OUT_8CH_IN; + } + } +} + +static int rockchip_i2s_io_multiplex(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + int usable_chs = MULTIPLEX_CH_MAX; + unsigned int val = 0; + + if (!i2s_tdm->io_multiplex) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + struct snd_pcm_str *playback_str = + &substream->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; + + if (playback_str->substream_opened) { + regmap_read(i2s_tdm->regmap, I2S_TXCR, &val); + val &= I2S_TXCR_CSR_MASK; + usable_chs = MULTIPLEX_CH_MAX - to_ch_num(val); + } + + regmap_read(i2s_tdm->regmap, I2S_RXCR, &val); + val &= I2S_RXCR_CSR_MASK; + + if (to_ch_num(val) > usable_chs) { + dev_err(i2s_tdm->dev, + "Capture channels (%d) > usable channels (%d)\n", + to_ch_num(val), usable_chs); + return -EINVAL; + } + + rockchip_i2s_ch_to_io(val, true); + } else { + struct snd_pcm_str *capture_str = + &substream->pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (capture_str->substream_opened) { + regmap_read(i2s_tdm->regmap, I2S_RXCR, &val); + val &= I2S_RXCR_CSR_MASK; + usable_chs = MULTIPLEX_CH_MAX - to_ch_num(val); + } + + regmap_read(i2s_tdm->regmap, I2S_TXCR, &val); + val &= I2S_TXCR_CSR_MASK; + + if (to_ch_num(val) > usable_chs) { + dev_err(i2s_tdm->dev, + "Playback channels (%d) > usable channels (%d)\n", + to_ch_num(val), usable_chs); + return -EINVAL; + } + } + + val <<= i2s_tdm->soc_data->grf_shift; + val |= (I2S_IO_DIRECTION_MASK << i2s_tdm->soc_data->grf_shift) << 16; + regmap_write(i2s_tdm->grf, i2s_tdm->soc_data->grf_reg_offset, val); + + return 0; +} + +static int rockchip_i2s_trcm_mode(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + unsigned int div_bclk, + unsigned int div_lrck, + unsigned int fmt) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + unsigned long flags; + + if (!i2s_tdm->clk_trcm) + return 0; + + spin_lock_irqsave(&i2s_tdm->lock, flags); + if (i2s_tdm->refcount) + rockchip_i2s_tdm_xfer_pause(substream, i2s_tdm); + + regmap_update_bits(i2s_tdm->regmap, I2S_CLKDIV, + I2S_CLKDIV_TXM_MASK | I2S_CLKDIV_RXM_MASK, + I2S_CLKDIV_TXM(div_bclk) | I2S_CLKDIV_RXM(div_bclk)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, + I2S_CKR_TSD_MASK | I2S_CKR_RSD_MASK, + I2S_CKR_TSD(div_lrck) | I2S_CKR_RSD(div_lrck)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, + I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, + fmt); + else + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, + I2S_RXCR_VDW_MASK | I2S_RXCR_CSR_MASK, + fmt); + + if (i2s_tdm->refcount) + rockchip_i2s_tdm_xfer_resume(substream, i2s_tdm); + spin_unlock_irqrestore(&i2s_tdm->lock, flags); + + return 0; +} + +static int rockchip_i2s_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + struct clk *mclk; + int ret = 0; + unsigned int val = 0; + unsigned int mclk_rate, bclk_rate, div_bclk = 4, div_lrck = 64; + + if (i2s_tdm->is_master_mode) { + if (i2s_tdm->mclk_calibrate) + rockchip_i2s_tdm_calibrate_mclk(i2s_tdm, substream, + params_rate(params)); + + ret = rockchip_i2s_tdm_set_mclk(i2s_tdm, substream, &mclk); + if (ret) + return ret; + + mclk_rate = clk_get_rate(mclk); + bclk_rate = i2s_tdm->frame_width * params_rate(params); + if (!bclk_rate) + return -EINVAL; + + div_bclk = DIV_ROUND_CLOSEST(mclk_rate, bclk_rate); + div_lrck = bclk_rate / params_rate(params); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + val |= I2S_TXCR_VDW(8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + val |= I2S_TXCR_VDW(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= I2S_TXCR_VDW(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + val |= I2S_TXCR_VDW(24); + break; + case SNDRV_PCM_FORMAT_S32_LE: + val |= I2S_TXCR_VDW(32); + break; + default: + return -EINVAL; + } + + switch (params_channels(params)) { + case 8: + val |= I2S_CHN_8; + break; + case 6: + val |= I2S_CHN_6; + break; + case 4: + val |= I2S_CHN_4; + break; + case 2: + val |= I2S_CHN_2; + break; + default: + return -EINVAL; + } + + if (i2s_tdm->clk_trcm) { + rockchip_i2s_trcm_mode(substream, dai, div_bclk, div_lrck, val); + } else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(i2s_tdm->regmap, I2S_CLKDIV, + I2S_CLKDIV_TXM_MASK, + I2S_CLKDIV_TXM(div_bclk)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, + I2S_CKR_TSD_MASK, + I2S_CKR_TSD(div_lrck)); + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, + I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, + val); + } else { + regmap_update_bits(i2s_tdm->regmap, I2S_CLKDIV, + I2S_CLKDIV_RXM_MASK, + I2S_CLKDIV_RXM(div_bclk)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, + I2S_CKR_RSD_MASK, + I2S_CKR_RSD(div_lrck)); + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, + I2S_RXCR_VDW_MASK | I2S_RXCR_CSR_MASK, + val); + } + + return rockchip_i2s_io_multiplex(substream, dai); +} + +static int rockchip_i2s_tdm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (i2s_tdm->clk_trcm) + rockchip_snd_txrxctrl(substream, dai, 1); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s_tdm, 1); + else + rockchip_snd_txctrl(i2s_tdm, 1); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (i2s_tdm->clk_trcm) + rockchip_snd_txrxctrl(substream, dai, 0); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s_tdm, 0); + else + rockchip_snd_txctrl(i2s_tdm, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rockchip_i2s_tdm_set_sysclk(struct snd_soc_dai *cpu_dai, int stream, + unsigned int freq, int dir) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(cpu_dai); + + /* Put set mclk rate into rockchip_i2s_tdm_set_mclk() */ + if (i2s_tdm->clk_trcm) { + i2s_tdm->mclk_tx_freq = freq; + i2s_tdm->mclk_rx_freq = freq; + } else { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_tdm->mclk_tx_freq = freq; + else + i2s_tdm->mclk_rx_freq = freq; + } + + dev_dbg(i2s_tdm->dev, "The target mclk_%s freq is: %d\n", + stream ? "rx" : "tx", freq); + + return 0; +} + +static int rockchip_i2s_tdm_clk_compensation_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = CLK_PPM_MIN; + uinfo->value.integer.max = CLK_PPM_MAX; + uinfo->value.integer.step = 1; + + return 0; +} + +static int rockchip_i2s_tdm_clk_compensation_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.integer.value[0] = i2s_tdm->clk_ppm; + + return 0; +} + +static int rockchip_i2s_tdm_clk_compensation_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + int ret = 0, ppm = 0; + int changed = 0; + unsigned long old_rate; + + if (ucontrol->value.integer.value[0] < CLK_PPM_MIN || + ucontrol->value.integer.value[0] > CLK_PPM_MAX) + return -EINVAL; + + ppm = ucontrol->value.integer.value[0]; + + old_rate = clk_get_rate(i2s_tdm->mclk_root0); + ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, i2s_tdm->mclk_root0, + i2s_tdm->mclk_root0_freq, ppm); + if (ret) + return ret; + if (old_rate != clk_get_rate(i2s_tdm->mclk_root0)) + changed = 1; + + if (clk_is_match(i2s_tdm->mclk_root0, i2s_tdm->mclk_root1)) + return changed; + + old_rate = clk_get_rate(i2s_tdm->mclk_root1); + ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, i2s_tdm->mclk_root1, + i2s_tdm->mclk_root1_freq, ppm); + if (ret) + return ret; + if (old_rate != clk_get_rate(i2s_tdm->mclk_root1)) + changed = 1; + + return changed; +} + +static struct snd_kcontrol_new rockchip_i2s_tdm_compensation_control = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Clock Compensation in PPM", + .info = rockchip_i2s_tdm_clk_compensation_info, + .get = rockchip_i2s_tdm_clk_compensation_get, + .put = rockchip_i2s_tdm_clk_compensation_put, +}; + +static int rockchip_i2s_tdm_dai_probe(struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + + if (i2s_tdm->has_capture) + dai->capture_dma_data = &i2s_tdm->capture_dma_data; + if (i2s_tdm->has_playback) + dai->playback_dma_data = &i2s_tdm->playback_dma_data; + + if (i2s_tdm->mclk_calibrate) + snd_soc_add_dai_controls(dai, &rockchip_i2s_tdm_compensation_control, 1); + + return 0; +} + +static int rockchip_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + + i2s_tdm->tdm_mode = true; + i2s_tdm->frame_width = slots * slot_width; + mask = TDM_SLOT_BIT_WIDTH_MSK | TDM_FRAME_WIDTH_MSK; + val = TDM_SLOT_BIT_WIDTH(slot_width) | + TDM_FRAME_WIDTH(slots * slot_width); + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_TXCR, + mask, val); + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_RXCR, + mask, val); + + return 0; +} + +static int rockchip_i2s_tdm_set_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + + if (ratio < 32 || ratio > 512 || ratio % 2 == 1) + return -EINVAL; + + i2s_tdm->frame_width = ratio; + + return 0; +} + +static const struct snd_soc_dai_ops rockchip_i2s_tdm_dai_ops = { + .hw_params = rockchip_i2s_tdm_hw_params, + .set_bclk_ratio = rockchip_i2s_tdm_set_bclk_ratio, + .set_sysclk = rockchip_i2s_tdm_set_sysclk, + .set_fmt = rockchip_i2s_tdm_set_fmt, + .set_tdm_slot = rockchip_dai_tdm_slot, + .trigger = rockchip_i2s_tdm_trigger, +}; + +static const struct snd_soc_component_driver rockchip_i2s_tdm_component = { + .name = DRV_NAME, +}; + +static bool rockchip_i2s_tdm_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + case I2S_TDM_TXCR: + case I2S_TDM_RXCR: + case I2S_CLKDIV: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_tdm_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + case I2S_RXDR: + case I2S_TXFIFOLR: + case I2S_INTSR: + case I2S_RXFIFOLR: + case I2S_TDM_TXCR: + case I2S_TDM_RXCR: + case I2S_CLKDIV: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_tdm_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXFIFOLR: + case I2S_INTSR: + case I2S_CLR: + case I2S_TXDR: + case I2S_RXDR: + case I2S_RXFIFOLR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_tdm_precious_reg(struct device *dev, unsigned int reg) +{ + if (reg == I2S_RXDR) + return true; + return false; +} + +static const struct reg_default rockchip_i2s_tdm_reg_defaults[] = { + {0x00, 0x7200000f}, + {0x04, 0x01c8000f}, + {0x08, 0x00001f1f}, + {0x10, 0x001f0000}, + {0x14, 0x01f00000}, + {0x30, 0x00003eff}, + {0x34, 0x00003eff}, + {0x38, 0x00000707}, +}; + +static const struct regmap_config rockchip_i2s_tdm_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = I2S_CLKDIV, + .reg_defaults = rockchip_i2s_tdm_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rockchip_i2s_tdm_reg_defaults), + .writeable_reg = rockchip_i2s_tdm_wr_reg, + .readable_reg = rockchip_i2s_tdm_rd_reg, + .volatile_reg = rockchip_i2s_tdm_volatile_reg, + .precious_reg = rockchip_i2s_tdm_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int common_soc_init(struct device *dev, u32 addr) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + const struct txrx_config *configs = i2s_tdm->soc_data->configs; + u32 reg = 0, val = 0, trcm = i2s_tdm->clk_trcm; + int i; + + if (trcm == TRCM_TXRX) + return 0; + + for (i = 0; i < i2s_tdm->soc_data->config_count; i++) { + if (addr != configs[i].addr) + continue; + reg = configs[i].reg; + if (trcm == TRCM_TX) + val = configs[i].txonly; + else + val = configs[i].rxonly; + + if (reg) + regmap_write(i2s_tdm->grf, reg, val); + } + + return 0; +} + +static const struct txrx_config px30_txrx_config[] = { + { 0xff060000, 0x184, PX30_I2S0_CLK_TXONLY, PX30_I2S0_CLK_RXONLY }, +}; + +static const struct txrx_config rk1808_txrx_config[] = { + { 0xff7e0000, 0x190, RK1808_I2S0_CLK_TXONLY, RK1808_I2S0_CLK_RXONLY }, +}; + +static const struct txrx_config rk3308_txrx_config[] = { + { 0xff300000, 0x308, RK3308_I2S0_CLK_TXONLY, RK3308_I2S0_CLK_RXONLY }, + { 0xff310000, 0x308, RK3308_I2S1_CLK_TXONLY, RK3308_I2S1_CLK_RXONLY }, +}; + +static const struct txrx_config rk3568_txrx_config[] = { + { 0xfe410000, 0x504, RK3568_I2S1_CLK_TXONLY, RK3568_I2S1_CLK_RXONLY }, + { 0xfe410000, 0x508, RK3568_I2S1_MCLK_TX_OE, RK3568_I2S1_MCLK_RX_OE }, + { 0xfe420000, 0x508, RK3568_I2S2_MCLK_OE, RK3568_I2S2_MCLK_OE }, + { 0xfe430000, 0x504, RK3568_I2S3_CLK_TXONLY, RK3568_I2S3_CLK_RXONLY }, + { 0xfe430000, 0x508, RK3568_I2S3_MCLK_TXONLY, RK3568_I2S3_MCLK_RXONLY }, + { 0xfe430000, 0x508, RK3568_I2S3_MCLK_OE, RK3568_I2S3_MCLK_OE }, +}; + +static const struct txrx_config rv1126_txrx_config[] = { + { 0xff800000, 0x10260, RV1126_I2S0_CLK_TXONLY, RV1126_I2S0_CLK_RXONLY }, +}; + +static struct rk_i2s_soc_data px30_i2s_soc_data = { + .softrst_offset = 0x0300, + .configs = px30_txrx_config, + .config_count = ARRAY_SIZE(px30_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rk1808_i2s_soc_data = { + .softrst_offset = 0x0300, + .configs = rk1808_txrx_config, + .config_count = ARRAY_SIZE(rk1808_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rk3308_i2s_soc_data = { + .softrst_offset = 0x0400, + .grf_reg_offset = 0x0308, + .grf_shift = 5, + .configs = rk3308_txrx_config, + .config_count = ARRAY_SIZE(rk3308_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rk3568_i2s_soc_data = { + .softrst_offset = 0x0400, + .configs = rk3568_txrx_config, + .config_count = ARRAY_SIZE(rk3568_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rv1126_i2s_soc_data = { + .softrst_offset = 0x0300, + .configs = rv1126_txrx_config, + .config_count = ARRAY_SIZE(rv1126_txrx_config), + .init = common_soc_init, +}; + +static const struct of_device_id rockchip_i2s_tdm_match[] = { + { .compatible = "rockchip,px30-i2s-tdm", .data = &px30_i2s_soc_data }, + { .compatible = "rockchip,rk1808-i2s-tdm", .data = &rk1808_i2s_soc_data }, + { .compatible = "rockchip,rk3308-i2s-tdm", .data = &rk3308_i2s_soc_data }, + { .compatible = "rockchip,rk3568-i2s-tdm", .data = &rk3568_i2s_soc_data }, + { .compatible = "rockchip,rv1126-i2s-tdm", .data = &rv1126_i2s_soc_data }, + {}, +}; + +static struct snd_soc_dai_driver i2s_tdm_dai = { + .probe = rockchip_i2s_tdm_dai_probe, + .playback = { + .stream_name = "Playback", + }, + .capture = { + .stream_name = "Capture", + }, + .ops = &rockchip_i2s_tdm_dai_ops, +}; + +static void rockchip_i2s_tdm_init_dai(struct rk_i2s_tdm_dev *i2s_tdm) +{ + struct property *dma_names; + const char *dma_name; + u64 formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE); + struct device_node *node = i2s_tdm->dev->of_node; + + of_property_for_each_string(node, "dma-names", dma_names, dma_name) { + if (!strcmp(dma_name, "tx")) + i2s_tdm->has_playback = true; + if (!strcmp(dma_name, "rx")) + i2s_tdm->has_capture = true; + } + + if (i2s_tdm->has_playback) { + i2s_tdm_dai.playback.channels_min = 2; + i2s_tdm_dai.playback.channels_max = 8; + i2s_tdm_dai.playback.rates = SNDRV_PCM_RATE_8000_192000; + i2s_tdm_dai.playback.formats = formats; + } + + if (i2s_tdm->has_capture) { + i2s_tdm_dai.capture.channels_min = 2; + i2s_tdm_dai.capture.channels_max = 8; + i2s_tdm_dai.capture.rates = SNDRV_PCM_RATE_8000_192000; + i2s_tdm_dai.capture.formats = formats; + } +} + +static int rockchip_i2s_tdm_path_check(struct rk_i2s_tdm_dev *i2s_tdm, + int num, + bool is_rx_path) +{ + unsigned int *i2s_data; + int i, j; + + if (is_rx_path) + i2s_data = i2s_tdm->i2s_sdis; + else + i2s_data = i2s_tdm->i2s_sdos; + + for (i = 0; i < num; i++) { + if (i2s_data[i] > CH_GRP_MAX - 1) { + dev_err(i2s_tdm->dev, + "%s path i2s_data[%d]: %d is too high, max is: %d\n", + is_rx_path ? "RX" : "TX", + i, i2s_data[i], CH_GRP_MAX); + return -EINVAL; + } + + for (j = 0; j < num; j++) { + if (i == j) + continue; + + if (i2s_data[i] == i2s_data[j]) { + dev_err(i2s_tdm->dev, + "%s path invalid routed i2s_data: [%d]%d == [%d]%d\n", + is_rx_path ? "RX" : "TX", + i, i2s_data[i], + j, i2s_data[j]); + return -EINVAL; + } + } + } + + return 0; +} + +static void rockchip_i2s_tdm_tx_path_config(struct rk_i2s_tdm_dev *i2s_tdm, + int num) +{ + int idx; + + for (idx = 0; idx < num; idx++) { + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, + I2S_TXCR_PATH_MASK(idx), + I2S_TXCR_PATH(idx, i2s_tdm->i2s_sdos[idx])); + } +} + +static void rockchip_i2s_tdm_rx_path_config(struct rk_i2s_tdm_dev *i2s_tdm, + int num) +{ + int idx; + + for (idx = 0; idx < num; idx++) { + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, + I2S_RXCR_PATH_MASK(idx), + I2S_RXCR_PATH(idx, i2s_tdm->i2s_sdis[idx])); + } +} + +static void rockchip_i2s_tdm_path_config(struct rk_i2s_tdm_dev *i2s_tdm, + int num, bool is_rx_path) +{ + if (is_rx_path) + rockchip_i2s_tdm_rx_path_config(i2s_tdm, num); + else + rockchip_i2s_tdm_tx_path_config(i2s_tdm, num); +} + +static int rockchip_i2s_tdm_get_calibrate_mclks(struct rk_i2s_tdm_dev *i2s_tdm) +{ + int num_mclks = 0; + + i2s_tdm->mclk_tx_src = devm_clk_get(i2s_tdm->dev, "mclk_tx_src"); + if (!IS_ERR(i2s_tdm->mclk_tx_src)) + num_mclks++; + + i2s_tdm->mclk_rx_src = devm_clk_get(i2s_tdm->dev, "mclk_rx_src"); + if (!IS_ERR(i2s_tdm->mclk_rx_src)) + num_mclks++; + + i2s_tdm->mclk_root0 = devm_clk_get(i2s_tdm->dev, "mclk_root0"); + if (!IS_ERR(i2s_tdm->mclk_root0)) + num_mclks++; + + i2s_tdm->mclk_root1 = devm_clk_get(i2s_tdm->dev, "mclk_root1"); + if (!IS_ERR(i2s_tdm->mclk_root1)) + num_mclks++; + + if (num_mclks < 4 && num_mclks != 0) + return -ENOENT; + + if (num_mclks == 4) + i2s_tdm->mclk_calibrate = 1; + + return 0; +} + +static int rockchip_i2s_tdm_path_prepare(struct rk_i2s_tdm_dev *i2s_tdm, + struct device_node *np, + bool is_rx_path) +{ + char *i2s_tx_path_prop = "rockchip,i2s-tx-route"; + char *i2s_rx_path_prop = "rockchip,i2s-rx-route"; + char *i2s_path_prop; + unsigned int *i2s_data; + int num, ret = 0; + + if (is_rx_path) { + i2s_path_prop = i2s_rx_path_prop; + i2s_data = i2s_tdm->i2s_sdis; + } else { + i2s_path_prop = i2s_tx_path_prop; + i2s_data = i2s_tdm->i2s_sdos; + } + + num = of_count_phandle_with_args(np, i2s_path_prop, NULL); + if (num < 0) { + if (num != -ENOENT) { + dev_err(i2s_tdm->dev, + "Failed to read '%s' num: %d\n", + i2s_path_prop, num); + ret = num; + } + return ret; + } else if (num != CH_GRP_MAX) { + dev_err(i2s_tdm->dev, + "The num: %d should be: %d\n", num, CH_GRP_MAX); + return -EINVAL; + } + + ret = of_property_read_u32_array(np, i2s_path_prop, + i2s_data, num); + if (ret < 0) { + dev_err(i2s_tdm->dev, + "Failed to read '%s': %d\n", + i2s_path_prop, ret); + return ret; + } + + ret = rockchip_i2s_tdm_path_check(i2s_tdm, num, is_rx_path); + if (ret < 0) { + dev_err(i2s_tdm->dev, + "Failed to check i2s data bus: %d\n", ret); + return ret; + } + + rockchip_i2s_tdm_path_config(i2s_tdm, num, is_rx_path); + + return 0; +} + +static int rockchip_i2s_tdm_tx_path_prepare(struct rk_i2s_tdm_dev *i2s_tdm, + struct device_node *np) +{ + return rockchip_i2s_tdm_path_prepare(i2s_tdm, np, 0); +} + +static int rockchip_i2s_tdm_rx_path_prepare(struct rk_i2s_tdm_dev *i2s_tdm, + struct device_node *np) +{ + return rockchip_i2s_tdm_path_prepare(i2s_tdm, np, 1); +} + +static int rockchip_i2s_tdm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + const struct of_device_id *of_id; + struct rk_i2s_tdm_dev *i2s_tdm; + struct resource *res; + void __iomem *regs; + int ret; + + i2s_tdm = devm_kzalloc(&pdev->dev, sizeof(*i2s_tdm), GFP_KERNEL); + if (!i2s_tdm) + return -ENOMEM; + + i2s_tdm->dev = &pdev->dev; + + of_id = of_match_device(rockchip_i2s_tdm_match, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + spin_lock_init(&i2s_tdm->lock); + i2s_tdm->soc_data = (struct rk_i2s_soc_data *)of_id->data; + + rockchip_i2s_tdm_init_dai(i2s_tdm); + + i2s_tdm->frame_width = 64; + + i2s_tdm->clk_trcm = TRCM_TXRX; + if (of_property_read_bool(node, "rockchip,trcm-sync-tx-only")) + i2s_tdm->clk_trcm = TRCM_TX; + if (of_property_read_bool(node, "rockchip,trcm-sync-rx-only")) { + if (i2s_tdm->clk_trcm) { + dev_err(i2s_tdm->dev, "invalid trcm-sync configuration\n"); + return -EINVAL; + } + i2s_tdm->clk_trcm = TRCM_RX; + } + if (i2s_tdm->clk_trcm != TRCM_TXRX) + i2s_tdm_dai.symmetric_rate = 1; + + i2s_tdm->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf"); + if (IS_ERR(i2s_tdm->grf)) + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->grf), + "Error in rockchip,grf\n"); + + i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "tx-m"); + if (IS_ERR(i2s_tdm->tx_reset)) { + ret = PTR_ERR(i2s_tdm->tx_reset); + return dev_err_probe(i2s_tdm->dev, ret, + "Error in tx-m reset control\n"); + } + + i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "rx-m"); + if (IS_ERR(i2s_tdm->rx_reset)) { + ret = PTR_ERR(i2s_tdm->rx_reset); + return dev_err_probe(i2s_tdm->dev, ret, + "Error in rx-m reset control\n"); + } + + i2s_tdm->hclk = devm_clk_get(&pdev->dev, "hclk"); + if (IS_ERR(i2s_tdm->hclk)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->hclk), + "Failed to get clock hclk\n"); + } + + i2s_tdm->mclk_tx = devm_clk_get(&pdev->dev, "mclk_tx"); + if (IS_ERR(i2s_tdm->mclk_tx)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->mclk_tx), + "Failed to get clock mclk_tx\n"); + } + + i2s_tdm->mclk_rx = devm_clk_get(&pdev->dev, "mclk_rx"); + if (IS_ERR(i2s_tdm->mclk_rx)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->mclk_rx), + "Failed to get clock mclk_rx\n"); + } + + i2s_tdm->io_multiplex = + of_property_read_bool(node, "rockchip,io-multiplex"); + + ret = rockchip_i2s_tdm_get_calibrate_mclks(i2s_tdm); + if (ret) + return dev_err_probe(i2s_tdm->dev, ret, + "mclk-calibrate clocks missing"); + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(regs), + "Failed to get resource IORESOURCE_MEM\n"); + } + + i2s_tdm->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &rockchip_i2s_tdm_regmap_config); + if (IS_ERR(i2s_tdm->regmap)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->regmap), + "Failed to initialise regmap\n"); + } + + if (i2s_tdm->has_playback) { + i2s_tdm->playback_dma_data.addr = res->start + I2S_TXDR; + i2s_tdm->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s_tdm->playback_dma_data.maxburst = 8; + } + + if (i2s_tdm->has_capture) { + i2s_tdm->capture_dma_data.addr = res->start + I2S_RXDR; + i2s_tdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s_tdm->capture_dma_data.maxburst = 8; + } + + ret = rockchip_i2s_tdm_tx_path_prepare(i2s_tdm, node); + if (ret < 0) { + dev_err(&pdev->dev, "I2S TX path prepare failed: %d\n", ret); + return ret; + } + + ret = rockchip_i2s_tdm_rx_path_prepare(i2s_tdm, node); + if (ret < 0) { + dev_err(&pdev->dev, "I2S RX path prepare failed: %d\n", ret); + return ret; + } + + dev_set_drvdata(&pdev->dev, i2s_tdm); + + ret = clk_prepare_enable(i2s_tdm->hclk); + if (ret) { + return dev_err_probe(i2s_tdm->dev, ret, + "Failed to enable clock hclk\n"); + } + + ret = i2s_tdm_prepare_enable_mclk(i2s_tdm); + if (ret) { + ret = dev_err_probe(i2s_tdm->dev, ret, + "Failed to enable one or more mclks\n"); + goto err_disable_hclk; + } + + if (i2s_tdm->mclk_calibrate) { + i2s_tdm->mclk_root0_initial_freq = clk_get_rate(i2s_tdm->mclk_root0); + i2s_tdm->mclk_root1_initial_freq = clk_get_rate(i2s_tdm->mclk_root1); + i2s_tdm->mclk_root0_freq = i2s_tdm->mclk_root0_initial_freq; + i2s_tdm->mclk_root1_freq = i2s_tdm->mclk_root1_initial_freq; + } + + pm_runtime_enable(&pdev->dev); + + regmap_update_bits(i2s_tdm->regmap, I2S_DMACR, I2S_DMACR_TDL_MASK, + I2S_DMACR_TDL(16)); + regmap_update_bits(i2s_tdm->regmap, I2S_DMACR, I2S_DMACR_RDL_MASK, + I2S_DMACR_RDL(16)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, I2S_CKR_TRCM_MASK, + i2s_tdm->clk_trcm << I2S_CKR_TRCM_SHIFT); + + if (i2s_tdm->soc_data && i2s_tdm->soc_data->init) + i2s_tdm->soc_data->init(&pdev->dev, res->start); + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_tdm_component, + &i2s_tdm_dai, 1); + + if (ret) { + dev_err(&pdev->dev, "Could not register DAI\n"); + goto err_suspend; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM\n"); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_tdm_runtime_suspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + +err_disable_hclk: + clk_disable_unprepare(i2s_tdm->hclk); + + return ret; +} + +static int rockchip_i2s_tdm_remove(struct platform_device *pdev) +{ + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_tdm_runtime_suspend(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused rockchip_i2s_tdm_suspend(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + + regcache_mark_dirty(i2s_tdm->regmap); + + return 0; +} + +static int __maybe_unused rockchip_i2s_tdm_resume(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + return ret; + ret = regcache_sync(i2s_tdm->regmap); + pm_runtime_put(dev); + + return ret; +} + +static const struct dev_pm_ops rockchip_i2s_tdm_pm_ops = { + SET_RUNTIME_PM_OPS(i2s_tdm_runtime_suspend, i2s_tdm_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(rockchip_i2s_tdm_suspend, + rockchip_i2s_tdm_resume) +}; + +static struct platform_driver rockchip_i2s_tdm_driver = { + .probe = rockchip_i2s_tdm_probe, + .remove = rockchip_i2s_tdm_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(rockchip_i2s_tdm_match), + .pm = &rockchip_i2s_tdm_pm_ops, + }, +}; +module_platform_driver(rockchip_i2s_tdm_driver); + +MODULE_DESCRIPTION("ROCKCHIP I2S/TDM ASoC Interface"); +MODULE_AUTHOR("Sugar Zhang <sugar.zhang@rock-chips.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, rockchip_i2s_tdm_match); diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.h b/sound/soc/rockchip/rockchip_i2s_tdm.h new file mode 100644 index 000000000000..0aa1c6da1e2c --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s_tdm.h @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ALSA SoC Audio Layer - Rockchip I2S/TDM Controller driver + * + * Copyright (c) 2018 Rockchip Electronics Co. Ltd. + * Author: Sugar Zhang <sugar.zhang@rock-chips.com> + * + */ + +#ifndef _ROCKCHIP_I2S_TDM_H +#define _ROCKCHIP_I2S_TDM_H + +/* + * TXCR + * transmit operation control register + */ +#define I2S_TXCR_PATH_SHIFT(x) (23 + (x) * 2) +#define I2S_TXCR_PATH_MASK(x) (0x3 << I2S_TXCR_PATH_SHIFT(x)) +#define I2S_TXCR_PATH(x, v) ((v) << I2S_TXCR_PATH_SHIFT(x)) +#define I2S_TXCR_RCNT_SHIFT 17 +#define I2S_TXCR_RCNT_MASK (0x3f << I2S_TXCR_RCNT_SHIFT) +#define I2S_TXCR_CSR_SHIFT 15 +#define I2S_TXCR_CSR(x) ((x) << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_CSR_MASK (3 << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_HWT BIT(14) +#define I2S_TXCR_SJM_SHIFT 12 +#define I2S_TXCR_SJM_R (0 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_SJM_L (1 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_FBM_SHIFT 11 +#define I2S_TXCR_FBM_MSB (0 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_FBM_LSB (1 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_IBM_SHIFT 9 +#define I2S_TXCR_IBM_NORMAL (0 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_LSJM (1 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_RSJM (2 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_MASK (3 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_PBM_SHIFT 7 +#define I2S_TXCR_PBM_MODE(x) ((x) << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_PBM_MASK (3 << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_TFS_SHIFT 5 +#define I2S_TXCR_TFS_I2S (0 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_PCM (1 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_TDM_PCM (2 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_TDM_I2S (3 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_MASK (3 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_VDW_SHIFT 0 +#define I2S_TXCR_VDW(x) (((x) - 1) << I2S_TXCR_VDW_SHIFT) +#define I2S_TXCR_VDW_MASK (0x1f << I2S_TXCR_VDW_SHIFT) + +/* + * RXCR + * receive operation control register + */ +#define I2S_RXCR_PATH_SHIFT(x) (17 + (x) * 2) +#define I2S_RXCR_PATH_MASK(x) (0x3 << I2S_RXCR_PATH_SHIFT(x)) +#define I2S_RXCR_PATH(x, v) ((v) << I2S_RXCR_PATH_SHIFT(x)) +#define I2S_RXCR_CSR_SHIFT 15 +#define I2S_RXCR_CSR(x) ((x) << I2S_RXCR_CSR_SHIFT) +#define I2S_RXCR_CSR_MASK (3 << I2S_RXCR_CSR_SHIFT) +#define I2S_RXCR_HWT BIT(14) +#define I2S_RXCR_SJM_SHIFT 12 +#define I2S_RXCR_SJM_R (0 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_SJM_L (1 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_FBM_SHIFT 11 +#define I2S_RXCR_FBM_MSB (0 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_FBM_LSB (1 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_IBM_SHIFT 9 +#define I2S_RXCR_IBM_NORMAL (0 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_LSJM (1 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_RSJM (2 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_MASK (3 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_PBM_SHIFT 7 +#define I2S_RXCR_PBM_MODE(x) ((x) << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_PBM_MASK (3 << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_TFS_SHIFT 5 +#define I2S_RXCR_TFS_I2S (0 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_PCM (1 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_TDM_PCM (2 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_TDM_I2S (3 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_MASK (3 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_VDW_SHIFT 0 +#define I2S_RXCR_VDW(x) (((x) - 1) << I2S_RXCR_VDW_SHIFT) +#define I2S_RXCR_VDW_MASK (0x1f << I2S_RXCR_VDW_SHIFT) + +/* + * CKR + * clock generation register + */ +#define I2S_CKR_TRCM_SHIFT 28 +#define I2S_CKR_TRCM(x) ((x) << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXRX (0 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXONLY (1 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_RXONLY (2 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_MASK (3 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_MSS_SHIFT 27 +#define I2S_CKR_MSS_MASTER (0 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_SLAVE (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_MASK (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_CKP_SHIFT 26 +#define I2S_CKR_CKP_NORMAL (0 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_CKP_INVERTED (1 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_CKP_MASK (1 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_RLP_SHIFT 25 +#define I2S_CKR_RLP_NORMAL (0 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_RLP_INVERTED (1 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_RLP_MASK (1 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_TLP_SHIFT 24 +#define I2S_CKR_TLP_NORMAL (0 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_TLP_INVERTED (1 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_TLP_MASK (1 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_MDIV_SHIFT 16 +#define I2S_CKR_MDIV(x) (((x) - 1) << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_MDIV_MASK (0xff << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_RSD_SHIFT 8 +#define I2S_CKR_RSD(x) (((x) - 1) << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_RSD_MASK (0xff << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_TSD_SHIFT 0 +#define I2S_CKR_TSD(x) (((x) - 1) << I2S_CKR_TSD_SHIFT) +#define I2S_CKR_TSD_MASK (0xff << I2S_CKR_TSD_SHIFT) + +/* + * FIFOLR + * FIFO level register + */ +#define I2S_FIFOLR_RFL_SHIFT 24 +#define I2S_FIFOLR_RFL_MASK (0x3f << I2S_FIFOLR_RFL_SHIFT) +#define I2S_FIFOLR_TFL3_SHIFT 18 +#define I2S_FIFOLR_TFL3_MASK (0x3f << I2S_FIFOLR_TFL3_SHIFT) +#define I2S_FIFOLR_TFL2_SHIFT 12 +#define I2S_FIFOLR_TFL2_MASK (0x3f << I2S_FIFOLR_TFL2_SHIFT) +#define I2S_FIFOLR_TFL1_SHIFT 6 +#define I2S_FIFOLR_TFL1_MASK (0x3f << I2S_FIFOLR_TFL1_SHIFT) +#define I2S_FIFOLR_TFL0_SHIFT 0 +#define I2S_FIFOLR_TFL0_MASK (0x3f << I2S_FIFOLR_TFL0_SHIFT) + +/* + * DMACR + * DMA control register + */ +#define I2S_DMACR_RDE_SHIFT 24 +#define I2S_DMACR_RDE_DISABLE (0 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDE_ENABLE (1 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDL_SHIFT 16 +#define I2S_DMACR_RDL(x) (((x) - 1) << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_RDL_MASK (0x1f << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_TDE_SHIFT 8 +#define I2S_DMACR_TDE_DISABLE (0 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDE_ENABLE (1 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDL_SHIFT 0 +#define I2S_DMACR_TDL(x) ((x) << I2S_DMACR_TDL_SHIFT) +#define I2S_DMACR_TDL_MASK (0x1f << I2S_DMACR_TDL_SHIFT) + +/* + * INTCR + * interrupt control register + */ +#define I2S_INTCR_RFT_SHIFT 20 +#define I2S_INTCR_RFT(x) (((x) - 1) << I2S_INTCR_RFT_SHIFT) +#define I2S_INTCR_RXOIC BIT(18) +#define I2S_INTCR_RXOIE_SHIFT 17 +#define I2S_INTCR_RXOIE_DISABLE (0 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXOIE_ENABLE (1 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXFIE_SHIFT 16 +#define I2S_INTCR_RXFIE_DISABLE (0 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_RXFIE_ENABLE (1 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_TFT_SHIFT 4 +#define I2S_INTCR_TFT(x) (((x) - 1) << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TFT_MASK (0x1f << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TXUIC BIT(2) +#define I2S_INTCR_TXUIE_SHIFT 1 +#define I2S_INTCR_TXUIE_DISABLE (0 << I2S_INTCR_TXUIE_SHIFT) +#define I2S_INTCR_TXUIE_ENABLE (1 << I2S_INTCR_TXUIE_SHIFT) + +/* + * INTSR + * interrupt status register + */ +#define I2S_INTSR_TXEIE_SHIFT 0 +#define I2S_INTSR_TXEIE_DISABLE (0 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_TXEIE_ENABLE (1 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_RXOI_SHIFT 17 +#define I2S_INTSR_RXOI_INA (0 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXOI_ACT (1 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXFI_SHIFT 16 +#define I2S_INTSR_RXFI_INA (0 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_RXFI_ACT (1 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_TXUI_SHIFT 1 +#define I2S_INTSR_TXUI_INA (0 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXUI_ACT (1 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXEI_SHIFT 0 +#define I2S_INTSR_TXEI_INA (0 << I2S_INTSR_TXEI_SHIFT) +#define I2S_INTSR_TXEI_ACT (1 << I2S_INTSR_TXEI_SHIFT) + +/* + * XFER + * Transfer start register + */ +#define I2S_XFER_RXS_SHIFT 1 +#define I2S_XFER_RXS_STOP (0 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_RXS_START (1 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_TXS_SHIFT 0 +#define I2S_XFER_TXS_STOP (0 << I2S_XFER_TXS_SHIFT) +#define I2S_XFER_TXS_START (1 << I2S_XFER_TXS_SHIFT) + +/* + * CLR + * clear SCLK domain logic register + */ +#define I2S_CLR_RXC BIT(1) +#define I2S_CLR_TXC BIT(0) + +/* + * TXDR + * Transimt FIFO data register, write only. + */ +#define I2S_TXDR_MASK (0xff) + +/* + * RXDR + * Receive FIFO data register, write only. + */ +#define I2S_RXDR_MASK (0xff) + +/* + * TDM_CTRL + * TDM ctrl register + */ +#define TDM_FSYNC_WIDTH_SEL1_MSK GENMASK(20, 18) +#define TDM_FSYNC_WIDTH_SEL1(x) (((x) - 1) << 18) +#define TDM_FSYNC_WIDTH_SEL0_MSK BIT(17) +#define TDM_FSYNC_WIDTH_HALF_FRAME 0 +#define TDM_FSYNC_WIDTH_ONE_FRAME BIT(17) +#define TDM_SHIFT_CTRL_MSK GENMASK(16, 14) +#define TDM_SHIFT_CTRL(x) ((x) << 14) +#define TDM_SLOT_BIT_WIDTH_MSK GENMASK(13, 9) +#define TDM_SLOT_BIT_WIDTH(x) (((x) - 1) << 9) +#define TDM_FRAME_WIDTH_MSK GENMASK(8, 0) +#define TDM_FRAME_WIDTH(x) (((x) - 1) << 0) + +/* + * CLKDIV + * Mclk div register + */ +#define I2S_CLKDIV_TXM_SHIFT 0 +#define I2S_CLKDIV_TXM(x) (((x) - 1) << I2S_CLKDIV_TXM_SHIFT) +#define I2S_CLKDIV_TXM_MASK (0xff << I2S_CLKDIV_TXM_SHIFT) +#define I2S_CLKDIV_RXM_SHIFT 8 +#define I2S_CLKDIV_RXM(x) (((x) - 1) << I2S_CLKDIV_RXM_SHIFT) +#define I2S_CLKDIV_RXM_MASK (0xff << I2S_CLKDIV_RXM_SHIFT) + +/* Clock divider id */ +enum { + ROCKCHIP_DIV_MCLK = 0, + ROCKCHIP_DIV_BCLK, +}; + +/* channel select */ +#define I2S_CSR_SHIFT 15 +#define I2S_CHN_2 (0 << I2S_CSR_SHIFT) +#define I2S_CHN_4 (1 << I2S_CSR_SHIFT) +#define I2S_CHN_6 (2 << I2S_CSR_SHIFT) +#define I2S_CHN_8 (3 << I2S_CSR_SHIFT) + +/* io direction cfg register */ +#define I2S_IO_DIRECTION_MASK (7) +#define I2S_IO_8CH_OUT_2CH_IN (7) +#define I2S_IO_6CH_OUT_4CH_IN (3) +#define I2S_IO_4CH_OUT_6CH_IN (1) +#define I2S_IO_2CH_OUT_8CH_IN (0) + +/* I2S REGS */ +#define I2S_TXCR (0x0000) +#define I2S_RXCR (0x0004) +#define I2S_CKR (0x0008) +#define I2S_TXFIFOLR (0x000c) +#define I2S_DMACR (0x0010) +#define I2S_INTCR (0x0014) +#define I2S_INTSR (0x0018) +#define I2S_XFER (0x001c) +#define I2S_CLR (0x0020) +#define I2S_TXDR (0x0024) +#define I2S_RXDR (0x0028) +#define I2S_RXFIFOLR (0x002c) +#define I2S_TDM_TXCR (0x0030) +#define I2S_TDM_RXCR (0x0034) +#define I2S_CLKDIV (0x0038) + +#define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16)) + +/* PX30 GRF CONFIGS */ +#define PX30_I2S0_CLK_IN_SRC_FROM_TX HIWORD_UPDATE(1, 13, 12) +#define PX30_I2S0_CLK_IN_SRC_FROM_RX HIWORD_UPDATE(2, 13, 12) +#define PX30_I2S0_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(1, 5, 5) +#define PX30_I2S0_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(0, 5, 5) + +#define PX30_I2S0_CLK_TXONLY \ + (PX30_I2S0_MCLK_OUT_SRC_FROM_TX | PX30_I2S0_CLK_IN_SRC_FROM_TX) + +#define PX30_I2S0_CLK_RXONLY \ + (PX30_I2S0_MCLK_OUT_SRC_FROM_RX | PX30_I2S0_CLK_IN_SRC_FROM_RX) + +/* RK1808 GRF CONFIGS */ +#define RK1808_I2S0_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 2, 2) +#define RK1808_I2S0_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 2, 2) +#define RK1808_I2S0_CLK_IN_SRC_FROM_TX HIWORD_UPDATE(1, 1, 0) +#define RK1808_I2S0_CLK_IN_SRC_FROM_RX HIWORD_UPDATE(2, 1, 0) + +#define RK1808_I2S0_CLK_TXONLY \ + (RK1808_I2S0_MCLK_OUT_SRC_FROM_TX | RK1808_I2S0_CLK_IN_SRC_FROM_TX) + +#define RK1808_I2S0_CLK_RXONLY \ + (RK1808_I2S0_MCLK_OUT_SRC_FROM_RX | RK1808_I2S0_CLK_IN_SRC_FROM_RX) + +/* RK3308 GRF CONFIGS */ +#define RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 10, 10) +#define RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 10, 10) +#define RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_TX HIWORD_UPDATE(1, 9, 9) +#define RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_RX HIWORD_UPDATE(0, 9, 9) +#define RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_RX HIWORD_UPDATE(1, 8, 8) +#define RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_TX HIWORD_UPDATE(0, 8, 8) +#define RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 2, 2) +#define RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 2, 2) +#define RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_TX HIWORD_UPDATE(1, 1, 1) +#define RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_RX HIWORD_UPDATE(0, 1, 1) +#define RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_RX HIWORD_UPDATE(1, 0, 0) +#define RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_TX HIWORD_UPDATE(0, 0, 0) + +#define RK3308_I2S0_CLK_TXONLY \ + (RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_TX | \ + RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_TX | \ + RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_TX) + +#define RK3308_I2S0_CLK_RXONLY \ + (RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_RX | \ + RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_RX | \ + RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_RX) + +#define RK3308_I2S1_CLK_TXONLY \ + (RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_TX | \ + RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_TX | \ + RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_TX) + +#define RK3308_I2S1_CLK_RXONLY \ + (RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_RX | \ + RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_RX | \ + RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_RX) + +/* RK3568 GRF CONFIGS */ +#define RK3568_I2S1_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(1, 5, 5) +#define RK3568_I2S1_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(0, 5, 5) + +#define RK3568_I2S1_CLK_TXONLY \ + RK3568_I2S1_MCLK_OUT_SRC_FROM_TX + +#define RK3568_I2S1_CLK_RXONLY \ + RK3568_I2S1_MCLK_OUT_SRC_FROM_RX + +#define RK3568_I2S3_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(1, 15, 15) +#define RK3568_I2S3_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(0, 15, 15) +#define RK3568_I2S3_SCLK_SRC_FROM_TX HIWORD_UPDATE(1, 7, 7) +#define RK3568_I2S3_SCLK_SRC_FROM_RX HIWORD_UPDATE(0, 7, 7) +#define RK3568_I2S3_LRCK_SRC_FROM_TX HIWORD_UPDATE(1, 6, 6) +#define RK3568_I2S3_LRCK_SRC_FROM_RX HIWORD_UPDATE(0, 6, 6) + +#define RK3568_I2S3_MCLK_TXONLY \ + RK3568_I2S3_MCLK_OUT_SRC_FROM_TX + +#define RK3568_I2S3_CLK_TXONLY \ + (RK3568_I2S3_SCLK_SRC_FROM_TX | \ + RK3568_I2S3_LRCK_SRC_FROM_TX) + +#define RK3568_I2S3_MCLK_RXONLY \ + RK3568_I2S3_MCLK_OUT_SRC_FROM_RX + +#define RK3568_I2S3_CLK_RXONLY \ + (RK3568_I2S3_SCLK_SRC_FROM_RX | \ + RK3568_I2S3_LRCK_SRC_FROM_RX) + +#define RK3568_I2S3_MCLK_IE HIWORD_UPDATE(0, 3, 3) +#define RK3568_I2S3_MCLK_OE HIWORD_UPDATE(1, 3, 3) +#define RK3568_I2S2_MCLK_IE HIWORD_UPDATE(0, 2, 2) +#define RK3568_I2S2_MCLK_OE HIWORD_UPDATE(1, 2, 2) +#define RK3568_I2S1_MCLK_TX_IE HIWORD_UPDATE(0, 1, 1) +#define RK3568_I2S1_MCLK_TX_OE HIWORD_UPDATE(1, 1, 1) +#define RK3568_I2S1_MCLK_RX_IE HIWORD_UPDATE(0, 0, 0) +#define RK3568_I2S1_MCLK_RX_OE HIWORD_UPDATE(1, 0, 0) + +/* RV1126 GRF CONFIGS */ +#define RV1126_I2S0_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 9, 9) +#define RV1126_I2S0_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 9, 9) + +#define RV1126_I2S0_CLK_TXONLY \ + RV1126_I2S0_MCLK_OUT_SRC_FROM_TX + +#define RV1126_I2S0_CLK_RXONLY \ + RV1126_I2S0_MCLK_OUT_SRC_FROM_RX + +#endif /* _ROCKCHIP_I2S_TDM_H */ diff --git a/sound/soc/rockchip/rockchip_pcm.c b/sound/soc/rockchip/rockchip_pcm.c deleted file mode 100644 index 02254e42135e..000000000000 --- a/sound/soc/rockchip/rockchip_pcm.c +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2018 Rockchip Electronics Co. Ltd. - */ - -#include <linux/device.h> -#include <linux/init.h> -#include <linux/module.h> - -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> -#include <sound/dmaengine_pcm.h> - -#include "rockchip_pcm.h" - -static const struct snd_pcm_hardware snd_rockchip_hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME | - SNDRV_PCM_INFO_INTERLEAVED, - .period_bytes_min = 32, - .period_bytes_max = 8192, - .periods_min = 1, - .periods_max = 52, - .buffer_bytes_max = 64 * 1024, - .fifo_size = 32, -}; - -static const struct snd_dmaengine_pcm_config rk_dmaengine_pcm_config = { - .pcm_hardware = &snd_rockchip_hardware, - .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, - .prealloc_buffer_size = 32 * 1024, -}; - -int rockchip_pcm_platform_register(struct device *dev) -{ - return devm_snd_dmaengine_pcm_register(dev, &rk_dmaengine_pcm_config, - SND_DMAENGINE_PCM_FLAG_COMPAT); -} -EXPORT_SYMBOL_GPL(rockchip_pcm_platform_register); - -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/rockchip/rockchip_pcm.h b/sound/soc/rockchip/rockchip_pcm.h deleted file mode 100644 index 7f00e2ce3603..000000000000 --- a/sound/soc/rockchip/rockchip_pcm.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (c) 2018 Rockchip Electronics Co. Ltd. - */ - -#ifndef _ROCKCHIP_PCM_H -#define _ROCKCHIP_PCM_H - -int rockchip_pcm_platform_register(struct device *dev); - -#endif diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c index 38bd603eeb45..64d9891b6434 100644 --- a/sound/soc/rockchip/rockchip_pdm.c +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -20,10 +20,12 @@ #define PDM_DMA_BURST_SIZE (8) /* size * width: 8*4 = 32 bytes */ #define PDM_SIGNOFF_CLK_RATE (100000000) +#define PDM_PATH_MAX (4) enum rk_pdm_version { RK_PDM_RK3229, RK_PDM_RK3308, + RK_PDM_RV1126, }; struct rk_pdm_dev { @@ -121,6 +123,55 @@ static unsigned int get_pdm_ds_ratio(unsigned int sr) return ratio; } +static unsigned int get_pdm_cic_ratio(unsigned int clk) +{ + switch (clk) { + case 4096000: + case 5644800: + case 6144000: + return 0; + case 2048000: + case 2822400: + case 3072000: + return 1; + case 1024000: + case 1411200: + case 1536000: + return 2; + default: + return 1; + } +} + +static unsigned int samplerate_to_bit(unsigned int samplerate) +{ + switch (samplerate) { + case 8000: + case 11025: + case 12000: + return 0; + case 16000: + case 22050: + case 24000: + return 1; + case 32000: + return 2; + case 44100: + case 48000: + return 3; + case 64000: + case 88200: + case 96000: + return 4; + case 128000: + case 176400: + case 192000: + return 5; + default: + return 1; + } +} + static inline struct rk_pdm_dev *to_info(struct snd_soc_dai *dai) { return snd_soc_dai_get_drvdata(dai); @@ -166,7 +217,8 @@ static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream, if (ret) return -EINVAL; - if (pdm->version == RK_PDM_RK3308) { + if (pdm->version == RK_PDM_RK3308 || + pdm->version == RK_PDM_RV1126) { rational_best_approximation(clk_out, clk_src, GENMASK(16 - 1, 0), GENMASK(16 - 1, 0), @@ -194,8 +246,18 @@ static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream, PDM_CLK_FD_RATIO_MSK, val); } - val = get_pdm_ds_ratio(samplerate); - regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_DS_RATIO_MSK, val); + + if (pdm->version == RK_PDM_RV1126) { + val = get_pdm_cic_ratio(clk_out); + regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_CIC_RATIO_MSK, val); + val = samplerate_to_bit(samplerate); + regmap_update_bits(pdm->regmap, PDM_CTRL0, + PDM_SAMPLERATE_MSK, PDM_SAMPLERATE(val)); + } else { + val = get_pdm_ds_ratio(samplerate); + regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_DS_RATIO_MSK, val); + } + regmap_update_bits(pdm->regmap, PDM_HPF_CTRL, PDM_HPF_CF_MSK, PDM_HPF_60HZ); regmap_update_bits(pdm->regmap, PDM_HPF_CTRL, @@ -441,9 +503,10 @@ static bool rockchip_pdm_precious_reg(struct device *dev, unsigned int reg) } static const struct reg_default rockchip_pdm_reg_defaults[] = { - {0x04, 0x78000017}, - {0x08, 0x0bb8ea60}, - {0x18, 0x0000001f}, + { PDM_CTRL0, 0x78000017 }, + { PDM_CTRL1, 0x0bb8ea60 }, + { PDM_CLK_CTRL, 0x0000e401 }, + { PDM_DMA_CTRL, 0x0000001f }, }; static const struct regmap_config rockchip_pdm_regmap_config = { @@ -469,12 +532,44 @@ static const struct of_device_id rockchip_pdm_match[] __maybe_unused = { .data = (void *)RK_PDM_RK3308 }, { .compatible = "rockchip,rk3308-pdm", .data = (void *)RK_PDM_RK3308 }, + { .compatible = "rockchip,rk3568-pdm", + .data = (void *)RK_PDM_RV1126 }, + { .compatible = "rockchip,rv1126-pdm", + .data = (void *)RK_PDM_RV1126 }, {}, }; MODULE_DEVICE_TABLE(of, rockchip_pdm_match); +static int rockchip_pdm_path_parse(struct rk_pdm_dev *pdm, struct device_node *node) +{ + unsigned int path[PDM_PATH_MAX]; + int cnt = 0, ret = 0, i = 0, val = 0, msk = 0; + + cnt = of_count_phandle_with_args(node, "rockchip,path-map", + NULL); + if (cnt != PDM_PATH_MAX) + return cnt; + + ret = of_property_read_u32_array(node, "rockchip,path-map", + path, cnt); + if (ret) + return ret; + + for (i = 0; i < cnt; i++) { + if (path[i] >= PDM_PATH_MAX) + return -EINVAL; + msk |= PDM_PATH_MASK(i); + val |= PDM_PATH(i, path[i]); + } + + regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, msk, val); + + return 0; +} + static int rockchip_pdm_probe(struct platform_device *pdev) { + struct device_node *node = pdev->dev.of_node; const struct of_device_id *match; struct rk_pdm_dev *pdm; struct resource *res; @@ -540,6 +635,11 @@ static int rockchip_pdm_probe(struct platform_device *pdev) } rockchip_pdm_rxctrl(pdm, 0); + + ret = rockchip_pdm_path_parse(pdm, node); + if (ret != 0 && ret != -ENOENT) + goto err_suspend; + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) { dev_err(&pdev->dev, "could not register pcm: %d\n", ret); diff --git a/sound/soc/rockchip/rockchip_pdm.h b/sound/soc/rockchip/rockchip_pdm.h index 8e5bbafef7bb..cab977272ee6 100644 --- a/sound/soc/rockchip/rockchip_pdm.h +++ b/sound/soc/rockchip/rockchip_pdm.h @@ -41,6 +41,8 @@ #define PDM_PATH1_EN BIT(28) #define PDM_PATH0_EN BIT(27) #define PDM_HWT_EN BIT(26) +#define PDM_SAMPLERATE_MSK GENMASK(7, 5) +#define PDM_SAMPLERATE(x) ((x) << 5) #define PDM_VDW_MSK (0x1f << 0) #define PDM_VDW(X) ((X - 1) << 0) @@ -51,6 +53,9 @@ #define PDM_FD_DENOMINATOR_MSK GENMASK(15, 0) /* PDM CLK CTRL */ +#define PDM_PATH_SHIFT(x) (8 + (x) * 2) +#define PDM_PATH_MASK(x) (0x3 << PDM_PATH_SHIFT(x)) +#define PDM_PATH(x, v) ((v) << PDM_PATH_SHIFT(x)) #define PDM_CLK_FD_RATIO_MSK BIT(6) #define PDM_CLK_FD_RATIO_40 (0X0 << 6) #define PDM_CLK_FD_RATIO_35 BIT(6) @@ -66,6 +71,7 @@ #define PDM_CLK_1280FS (0x2 << 0) #define PDM_CLK_2560FS (0x3 << 0) #define PDM_CLK_5120FS (0x4 << 0) +#define PDM_CIC_RATIO_MSK (0x3 << 0) /* PDM HPF CTRL */ #define PDM_HPF_LE BIT(3) diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index e9481187a08c..de66cc422e6e 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -397,6 +397,8 @@ static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, /* clear again, just in case */ writel(0x0, i2s->regs + S3C2412_IISFIC); + fallthrough; + case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (!i2s->master) { diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 978bd0406729..6a8fe0da7670 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -1225,6 +1225,7 @@ int rsnd_node_count(struct rsnd_priv *priv, struct device_node *node, char *name if (i < 0) { dev_err(dev, "strange node numbering (%s)", of_node_full_name(node)); + of_node_put(np); return 0; } i++; diff --git a/sound/soc/soc-acpi.c b/sound/soc/soc-acpi.c index 395229bf5c51..2ae99b49d3f5 100644 --- a/sound/soc/soc-acpi.c +++ b/sound/soc/soc-acpi.c @@ -8,14 +8,34 @@ #include <linux/module.h> #include <sound/soc-acpi.h> +static bool snd_soc_acpi_id_present(struct snd_soc_acpi_mach *machine) +{ + const struct snd_soc_acpi_codecs *comp_ids = machine->comp_ids; + int i; + + if (machine->id[0]) { + if (acpi_dev_present(machine->id, NULL, -1)) + return true; + } + + if (comp_ids) { + for (i = 0; i < comp_ids->num_codecs; i++) { + if (acpi_dev_present(comp_ids->codecs[i], NULL, -1)) + return true; + } + } + + return false; +} + struct snd_soc_acpi_mach * snd_soc_acpi_find_machine(struct snd_soc_acpi_mach *machines) { struct snd_soc_acpi_mach *mach; struct snd_soc_acpi_mach *mach_alt; - for (mach = machines; mach->id[0]; mach++) { - if (acpi_dev_present(mach->id, NULL, -1)) { + for (mach = machines; mach->id[0] || mach->comp_ids; mach++) { + if (snd_soc_acpi_id_present(mach)) { if (mach->machine_quirk) { mach_alt = mach->machine_quirk(mach); if (!mach_alt) diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 8e8d917d22f8..c76ff9c59dfb 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -13,9 +13,10 @@ #include <sound/soc.h> #include <linux/bitops.h> -#define soc_component_ret(dai, ret) _soc_component_ret(dai, __func__, ret) +#define soc_component_ret(dai, ret) _soc_component_ret(dai, __func__, ret, -1) +#define soc_component_ret_reg_rw(dai, ret, reg) _soc_component_ret(dai, __func__, ret, reg) static inline int _soc_component_ret(struct snd_soc_component *component, - const char *func, int ret) + const char *func, int ret, int reg) { /* Positive/Zero values are not errors */ if (ret >= 0) @@ -27,9 +28,14 @@ static inline int _soc_component_ret(struct snd_soc_component *component, case -ENOTSUPP: break; default: - dev_err(component->dev, - "ASoC: error at %s on %s: %d\n", - func, component->name, ret); + if (reg == -1) + dev_err(component->dev, + "ASoC: error at %s on %s: %d\n", + func, component->name, ret); + else + dev_err(component->dev, + "ASoC: error at %s on %s for register: [0x%08x] %d\n", + func, component->name, reg, ret); } return ret; @@ -251,8 +257,7 @@ int snd_soc_component_set_jack(struct snd_soc_component *component, EXPORT_SYMBOL_GPL(snd_soc_component_set_jack); int snd_soc_component_module_get(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - int upon_open) + void *mark, int upon_open) { int ret = 0; @@ -260,25 +265,24 @@ int snd_soc_component_module_get(struct snd_soc_component *component, !try_module_get(component->dev->driver->owner)) ret = -ENODEV; - /* mark substream if succeeded */ + /* mark module if succeeded */ if (ret == 0) - soc_component_mark_push(component, substream, module); + soc_component_mark_push(component, mark, module); return soc_component_ret(component, ret); } void snd_soc_component_module_put(struct snd_soc_component *component, - struct snd_pcm_substream *substream, - int upon_open, int rollback) + void *mark, int upon_open, int rollback) { - if (rollback && !soc_component_mark_match(component, substream, module)) + if (rollback && !soc_component_mark_match(component, mark, module)) return; if (component->driver->module_get_upon_open == !!upon_open) module_put(component->dev->driver->owner); - /* remove marked substream */ - soc_component_mark_pop(component, substream, module); + /* remove the mark from module */ + soc_component_mark_pop(component, mark, module); } int snd_soc_component_open(struct snd_soc_component *component, @@ -425,43 +429,36 @@ EXPORT_SYMBOL_GPL(snd_soc_component_exit_regmap); #endif -int snd_soc_component_compr_open(struct snd_compr_stream *cstream) +int snd_soc_component_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) { - struct snd_soc_pcm_runtime *rtd = cstream->private_data; - struct snd_soc_component *component; - int i, ret; + int ret = 0; - for_each_rtd_components(rtd, i, component) { - if (component->driver->compress_ops && - component->driver->compress_ops->open) { - ret = component->driver->compress_ops->open(component, cstream); - if (ret < 0) - return soc_component_ret(component, ret); - } + if (component->driver->compress_ops && + component->driver->compress_ops->open) + ret = component->driver->compress_ops->open(component, cstream); + + /* mark substream if succeeded */ + if (ret == 0) soc_component_mark_push(component, cstream, compr_open); - } - return 0; + return soc_component_ret(component, ret); } EXPORT_SYMBOL_GPL(snd_soc_component_compr_open); -void snd_soc_component_compr_free(struct snd_compr_stream *cstream, +void snd_soc_component_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int rollback) { - struct snd_soc_pcm_runtime *rtd = cstream->private_data; - struct snd_soc_component *component; - int i; - - for_each_rtd_components(rtd, i, component) { - if (rollback && !soc_component_mark_match(component, cstream, compr_open)) - continue; + if (rollback && !soc_component_mark_match(component, cstream, compr_open)) + return; - if (component->driver->compress_ops && - component->driver->compress_ops->free) - component->driver->compress_ops->free(component, cstream); + if (component->driver->compress_ops && + component->driver->compress_ops->free) + component->driver->compress_ops->free(component, cstream); - soc_component_mark_pop(component, cstream, compr_open); - } + /* remove marked substream */ + soc_component_mark_pop(component, cstream, compr_open); } EXPORT_SYMBOL_GPL(snd_soc_component_compr_free); @@ -696,7 +693,7 @@ static unsigned int soc_component_read_no_lock( ret = -EIO; if (ret < 0) - return soc_component_ret(component, ret); + return soc_component_ret_reg_rw(component, ret, reg); return val; } @@ -732,7 +729,7 @@ static int soc_component_write_no_lock( else if (component->driver->write) ret = component->driver->write(component, reg, val); - return soc_component_ret(component, ret); + return soc_component_ret_reg_rw(component, ret, reg); } /** @@ -774,7 +771,7 @@ static int snd_soc_component_update_bits_legacy( mutex_unlock(&component->io_mutex); - return soc_component_ret(component, ret); + return soc_component_ret_reg_rw(component, ret, reg); } /** @@ -802,7 +799,7 @@ int snd_soc_component_update_bits(struct snd_soc_component *component, mask, val, &change); if (ret < 0) - return soc_component_ret(component, ret); + return soc_component_ret_reg_rw(component, ret, reg); return change; } EXPORT_SYMBOL_GPL(snd_soc_component_update_bits); @@ -838,7 +835,7 @@ int snd_soc_component_update_bits_async(struct snd_soc_component *component, mask, val, &change); if (ret < 0) - return soc_component_ret(component, ret); + return soc_component_ret_reg_rw(component, ret, reg); return change; } EXPORT_SYMBOL_GPL(snd_soc_component_update_bits_async); diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 36060800e9bd..8e2494a9f3a7 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -22,6 +22,39 @@ #include <sound/soc-link.h> #include <linux/pm_runtime.h> +static int snd_soc_compr_components_open(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + int ret = 0; + int i; + + for_each_rtd_components(rtd, i, component) { + ret = snd_soc_component_module_get_when_open(component, cstream); + if (ret < 0) + break; + + ret = snd_soc_component_compr_open(component, cstream); + if (ret < 0) + break; + } + + return ret; +} + +static void snd_soc_compr_components_free(struct snd_compr_stream *cstream, + int rollback) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + int i; + + for_each_rtd_components(rtd, i, component) { + snd_soc_component_compr_free(component, cstream, rollback); + snd_soc_component_module_put_when_close(component, cstream, rollback); + } +} + static int soc_compr_clean(struct snd_compr_stream *cstream, int rollback) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; @@ -44,7 +77,7 @@ static int soc_compr_clean(struct snd_compr_stream *cstream, int rollback) snd_soc_link_compr_shutdown(cstream, rollback); - snd_soc_component_compr_free(cstream, rollback); + snd_soc_compr_components_free(cstream, rollback); snd_soc_dai_compr_shutdown(cpu_dai, cstream, rollback); @@ -80,7 +113,7 @@ static int soc_compr_open(struct snd_compr_stream *cstream) if (ret < 0) goto err; - ret = snd_soc_component_compr_open(cstream); + ret = snd_soc_compr_components_open(cstream); if (ret < 0) goto err; @@ -137,7 +170,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) if (ret < 0) goto out; - ret = snd_soc_component_compr_open(cstream); + ret = snd_soc_compr_components_open(cstream); if (ret < 0) goto open_err; @@ -160,7 +193,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) return 0; machine_err: - snd_soc_component_compr_free(cstream, 1); + snd_soc_compr_components_free(cstream, 1); open_err: snd_soc_dai_compr_shutdown(cpu_dai, cstream, 1); out: @@ -205,7 +238,7 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) snd_soc_link_compr_shutdown(cstream, 0); - snd_soc_component_compr_free(cstream, 0); + snd_soc_compr_components_free(cstream, 0); snd_soc_dai_compr_shutdown(cpu_dai, cstream, 0); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 80ca260595fd..dcf6be4c4aaa 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -229,31 +229,12 @@ static void snd_soc_debugfs_exit(void) #else -static inline void soc_init_component_debugfs( - struct snd_soc_component *component) -{ -} - -static inline void soc_cleanup_component_debugfs( - struct snd_soc_component *component) -{ -} - -static inline void soc_init_card_debugfs(struct snd_soc_card *card) -{ -} - -static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) -{ -} - -static inline void snd_soc_debugfs_init(void) -{ -} - -static inline void snd_soc_debugfs_exit(void) -{ -} +static inline void soc_init_component_debugfs(struct snd_soc_component *component) { } +static inline void soc_cleanup_component_debugfs(struct snd_soc_component *component) { } +static inline void soc_init_card_debugfs(struct snd_soc_card *card) { } +static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) { } +static inline void snd_soc_debugfs_init(void) { } +static inline void snd_soc_debugfs_exit(void) { } #endif @@ -739,9 +720,7 @@ static void soc_resume_init(struct snd_soc_card *card) #else #define snd_soc_suspend NULL #define snd_soc_resume NULL -static inline void soc_resume_init(struct snd_soc_card *card) -{ -} +static inline void soc_resume_init(struct snd_soc_card *card) { } #endif static struct device_node @@ -1239,6 +1218,9 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, unsigned int i; int ret; + if (!dai_fmt) + return 0; + for_each_rtd_codec_dais(rtd, i, codec_dai) { ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); if (ret != 0 && ret != -ENOTSUPP) @@ -1247,14 +1229,13 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, /* * Flip the polarity for the "CPU" end of a CODEC<->CODEC link - * the component which has non_legacy_dai_naming is Codec */ inv_dai_fmt = snd_soc_daifmt_clock_provider_fliped(dai_fmt); for_each_rtd_cpu_dais(rtd, i, cpu_dai) { unsigned int fmt = dai_fmt; - if (cpu_dai->component->driver->non_legacy_dai_naming) + if (snd_soc_component_is_codec(cpu_dai->component)) fmt = inv_dai_fmt; ret = snd_soc_dai_set_fmt(cpu_dai, fmt); @@ -1283,11 +1264,9 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card, return ret; snd_soc_runtime_get_dai_fmt(rtd); - if (dai_link->dai_fmt) { - ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt); - if (ret) - return ret; - } + ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt); + if (ret) + return ret; /* add DPCM sysfs entries */ soc_dpcm_debugfs_add(rtd); @@ -1363,9 +1342,6 @@ static void soc_remove_component(struct snd_soc_component *component, if (probed) snd_soc_component_remove(component); - /* For framework level robustness */ - snd_soc_component_set_jack(component, NULL, NULL); - list_del_init(&component->card_list); snd_soc_dapm_free(snd_soc_component_get_dapm(component)); soc_cleanup_component_debugfs(component); @@ -2521,7 +2497,7 @@ static int snd_soc_register_dais(struct snd_soc_component *component, for (i = 0; i < count; i++) { dai = snd_soc_register_dai(component, dai_drv + i, count == 1 && - !component->driver->non_legacy_dai_naming); + !snd_soc_component_is_codec(component)); if (dai == NULL) { ret = -ENOMEM; goto err; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 59d07648a7e7..2892b0aba151 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1331,11 +1331,13 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, return paths; } +EXPORT_SYMBOL_GPL(snd_soc_dapm_dai_get_connected_widgets); void snd_soc_dapm_dai_free_widgets(struct snd_soc_dapm_widget_list **list) { dapm_widget_list_free(list); } +EXPORT_SYMBOL_GPL(snd_soc_dapm_dai_free_widgets); /* * Handler for regulator supply widget. diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 4aa48c74f21a..c54c8ca8d715 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -15,6 +15,10 @@ #include <sound/dmaengine_pcm.h> +static unsigned int prealloc_buffer_size_kbytes = 512; +module_param(prealloc_buffer_size_kbytes, uint, 0444); +MODULE_PARM_DESC(prealloc_buffer_size_kbytes, "Preallocate DMA buffer size (KB)."); + /* * The platforms dmaengine driver does not support reporting the amount of * bytes that are still left to transfer. @@ -237,7 +241,7 @@ static int dmaengine_pcm_new(struct snd_soc_component *component, prealloc_buffer_size = config->prealloc_buffer_size; max_buffer_size = config->pcm_hardware->buffer_bytes_max; } else { - prealloc_buffer_size = 512 * 1024; + prealloc_buffer_size = prealloc_buffer_size_kbytes * 1024; max_buffer_size = SIZE_MAX; } diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 48f71bb81a2f..4d41ad302802 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -879,12 +879,10 @@ static int soc_pcm_hw_clean(struct snd_pcm_substream *substream, int rollback) /* clear the corresponding DAIs parameters when going to be inactive */ for_each_rtd_dais(rtd, i, dai) { - int active = snd_soc_dai_stream_active(dai, substream->stream); - if (snd_soc_dai_active(dai) == 1) soc_pcm_set_dai_params(dai, NULL); - if (active == 1) + if (snd_soc_dai_stream_active(dai, substream->stream) == 1) snd_soc_dai_digital_mute(dai, 1, substream->stream); } @@ -898,12 +896,9 @@ static int soc_pcm_hw_clean(struct snd_pcm_substream *substream, int rollback) snd_soc_pcm_component_hw_free(substream, rollback); /* now free hw params for the DAIs */ - for_each_rtd_dais(rtd, i, dai) { - if (!snd_soc_dai_stream_valid(dai, substream->stream)) - continue; - - snd_soc_dai_hw_free(dai, substream, rollback); - } + for_each_rtd_dais(rtd, i, dai) + if (snd_soc_dai_stream_valid(dai, substream->stream)) + snd_soc_dai_hw_free(dai, substream, rollback); mutex_unlock(&rtd->card->pcm_mutex); return 0; @@ -1262,8 +1257,7 @@ static int widget_in_list(struct snd_soc_dapm_widget_list *list, return 0; } -static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, - enum snd_soc_dapm_direction dir) +bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, enum snd_soc_dapm_direction dir) { struct snd_soc_card *card = widget->dapm->card; struct snd_soc_pcm_runtime *rtd; @@ -1281,6 +1275,7 @@ static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, return false; } +EXPORT_SYMBOL_GPL(dpcm_end_walk_at_be); int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int stream, struct snd_soc_dapm_widget_list **list) @@ -1395,6 +1390,16 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, if (!fe->dpcm[stream].runtime && !fe->fe_compr) continue; + /* + * Filter for systems with 'component_chaining' enabled. + * This helps to avoid unnecessary re-configuration of an + * already active BE on such systems. + */ + if (fe->card->component_chaining && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_NEW) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_CLOSE)) + continue; + /* newly connected FE and BE */ err = dpcm_be_connect(fe, be, stream); if (err < 0) { diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index f6e5ac3e0314..557e22c5254c 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -78,7 +78,7 @@ struct soc_tplg { }; static int soc_tplg_process_headers(struct soc_tplg *tplg); -static void soc_tplg_complete(struct soc_tplg *tplg); +static int soc_tplg_complete(struct soc_tplg *tplg); /* check we dont overflow the data for this control chunk */ static int soc_tplg_check_elem_count(struct soc_tplg *tplg, size_t elem_size, @@ -312,10 +312,12 @@ static int soc_tplg_dai_link_load(struct soc_tplg *tplg, } /* tell the component driver that all firmware has been loaded in this request */ -static void soc_tplg_complete(struct soc_tplg *tplg) +static int soc_tplg_complete(struct soc_tplg *tplg) { if (tplg->ops && tplg->ops->complete) - tplg->ops->complete(tplg->comp); + return tplg->ops->complete(tplg->comp); + + return 0; } /* add a dynamic kcontrol */ @@ -349,7 +351,7 @@ static int soc_tplg_add_kcontrol(struct soc_tplg *tplg, struct snd_soc_component *comp = tplg->comp; return soc_tplg_add_dcontrol(comp->card->snd_card, - comp->dev, k, comp->name_prefix, comp, kcontrol); + tplg->dev, k, comp->name_prefix, comp, kcontrol); } /* remove a mixer kcontrol */ @@ -1473,10 +1475,6 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, goto widget; } - control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; - dev_dbg(tplg->dev, "ASoC: template %s has %d controls of type %x\n", - w->name, w->num_kcontrols, control_hdr->type); - template.num_kcontrols = le32_to_cpu(w->num_kcontrols); kc = devm_kcalloc(tplg->dev, le32_to_cpu(w->num_kcontrols), sizeof(*kc), GFP_KERNEL); if (!kc) @@ -1487,7 +1485,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, if (!kcontrol_type) goto err; - for (i = 0; i < w->num_kcontrols; i++) { + for (i = 0; i < le32_to_cpu(w->num_kcontrols); i++) { control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; switch (le32_to_cpu(control_hdr->ops.info)) { case SND_SOC_TPLG_CTL_VOLSW: @@ -1536,6 +1534,8 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, } template.kcontrol_news = kc; + dev_dbg(tplg->dev, "ASoC: template %s with %d/%d/%d (mixer/enum/bytes) control\n", + w->name, mixer_count, enum_count, bytes_count); widget: ret = soc_tplg_widget_load(tplg, &template, w); @@ -1591,11 +1591,28 @@ static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, struct snd_soc_tplg_dapm_widget *widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; int ret; + /* + * check if widget itself fits within topology file + * use sizeof instead of widget->size, as we can't be sure + * it is set properly yet (file may end before it is present) + */ + if (soc_tplg_get_offset(tplg) + sizeof(*widget) >= tplg->fw->size) { + dev_err(tplg->dev, "ASoC: invalid widget data size\n"); + return -EINVAL; + } + + /* check if widget has proper size */ if (le32_to_cpu(widget->size) != sizeof(*widget)) { dev_err(tplg->dev, "ASoC: invalid widget size\n"); return -EINVAL; } + /* check if widget private data fits within topology file */ + if (soc_tplg_get_offset(tplg) + le32_to_cpu(widget->priv.size) >= tplg->fw->size) { + dev_err(tplg->dev, "ASoC: invalid widget private data size\n"); + return -EINVAL; + } + ret = soc_tplg_dapm_widget_create(tplg, widget); if (ret < 0) { dev_err(tplg->dev, "ASoC: failed to load widget %s\n", @@ -2438,6 +2455,7 @@ static int soc_tplg_manifest_load(struct soc_tplg *tplg, _manifest = manifest; } else { abi_match = false; + ret = manifest_new_ver(tplg, manifest, &_manifest); if (ret < 0) return ret; @@ -2468,6 +2486,14 @@ static int soc_valid_header(struct soc_tplg *tplg, return -EINVAL; } + if (soc_tplg_get_hdr_offset(tplg) + hdr->payload_size >= tplg->fw->size) { + dev_err(tplg->dev, + "ASoC: invalid header of type %d at offset %ld payload_size %d\n", + le32_to_cpu(hdr->type), soc_tplg_get_hdr_offset(tplg), + hdr->payload_size); + return -EINVAL; + } + /* big endian firmware objects not supported atm */ if (le32_to_cpu(hdr->magic) == SOC_TPLG_MAGIC_BIG_ENDIAN) { dev_err(tplg->dev, @@ -2627,7 +2653,7 @@ static int soc_tplg_load(struct soc_tplg *tplg) ret = soc_tplg_process_headers(tplg); if (ret == 0) - soc_tplg_complete(tplg); + return soc_tplg_complete(tplg); return ret; } @@ -2642,17 +2668,17 @@ int snd_soc_tplg_component_load(struct snd_soc_component *comp, /* * check if we have sane parameters: * comp - needs to exist to keep and reference data while parsing - * comp->dev - used for resource management and prints * comp->card - used for setting card related parameters + * comp->card->dev - used for resource management and prints * fw - we need it, as it is the very thing we parse */ - if (!comp || !comp->dev || !comp->card || !fw) + if (!comp || !comp->card || !comp->card->dev || !fw) return -EINVAL; /* setup parsing context */ memset(&tplg, 0, sizeof(tplg)); tplg.fw = fw; - tplg.dev = comp->dev; + tplg.dev = comp->card->dev; tplg.comp = comp; if (ops) { tplg.ops = ops; diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c index 299b5d6ebfd1..a4efe7e52a8b 100644 --- a/sound/soc/soc-utils.c +++ b/sound/soc/soc-utils.c @@ -63,10 +63,23 @@ static const struct snd_pcm_hardware dummy_dma_hardware = { .periods_max = 128, }; + +static const struct snd_soc_component_driver dummy_platform; + static int dummy_dma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + int i; + + /* + * If there are other components associated with rtd, we shouldn't + * override their hwparams + */ + for_each_rtd_components(rtd, i, component) { + if (component->driver == &dummy_platform) + return 0; + } /* BE's dont need dummy params */ if (!rtd->dai_link->no_pcm) diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index cd659493b5df..6bb4db87af03 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -config SND_SOC_SOF_TOPLEVEL +menuconfig SND_SOC_SOF_TOPLEVEL bool "Sound Open Firmware Support" help This adds support for Sound Open Firmware (SOF). SOF is free and @@ -46,6 +46,10 @@ config SND_SOC_SOF_OF required to enable i.MX8 devices. Say Y if you need this option. If unsure select "N". +config SND_SOC_SOF_COMPRESS + tristate + select SND_SOC_COMPRESS + config SND_SOC_SOF_DEBUG_PROBES bool "SOF enable data probing" select SND_SOC_COMPRESS diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 606d8137cd98..06e5f49f7ee8 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -1,8 +1,10 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ - control.o trace.o utils.o sof-audio.o -snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += probe.o compress.o + control.o trace.o utils.o sof-audio.o stream-ipc.o + +snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += sof-probes.o +snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o snd-sof-pci-objs := sof-pci-dev.o snd-sof-acpi-objs := sof-acpi-dev.o diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 57d5bf0a171e..01ca85f0b87f 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -1,147 +1,51 @@ // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) // -// This file is provided under a dual BSD/GPLv2 license. When using or -// redistributing this file, you may do so under either license. -// -// Copyright(c) 2019-2020 Intel Corporation. All rights reserved. -// -// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// Copyright 2021 NXP // +// Author: Daniel Baluta <daniel.baluta@nxp.com> #include <sound/soc.h> -#include "compress.h" -#include "ops.h" -#include "probe.h" - -const struct snd_compress_ops sof_probe_compressed_ops = { - .copy = sof_probe_compr_copy, -}; -EXPORT_SYMBOL(sof_probe_compressed_ops); - -int sof_probe_compr_open(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); - int ret; - - ret = snd_sof_probe_compr_assign(sdev, cstream, dai); - if (ret < 0) { - dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); - return ret; - } +#include <sound/sof.h> +#include <sound/compress_driver.h> +#include "sof-audio.h" +#include "sof-priv.h" - sdev->extractor_stream_tag = ret; - return 0; -} -EXPORT_SYMBOL(sof_probe_compr_open); - -int sof_probe_compr_free(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) +static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work) { - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); - struct sof_probe_point_desc *desc; - size_t num_desc; - int i, ret; - - /* disconnect all probe points */ - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); - if (ret < 0) { - dev_err(dai->dev, "Failed to get probe points: %d\n", ret); - goto exit; - } - - for (i = 0; i < num_desc; i++) - sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1); - kfree(desc); - -exit: - ret = sof_ipc_probe_deinit(sdev); - if (ret < 0) - dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); - - sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; - snd_compr_free_pages(cstream); + struct snd_sof_pcm_stream *sps = + container_of(work, struct snd_sof_pcm_stream, + period_elapsed_work); - return snd_sof_probe_compr_free(sdev, cstream, dai); + snd_compr_fragment_elapsed(sps->cstream); } -EXPORT_SYMBOL(sof_probe_compr_free); -int sof_probe_compr_set_params(struct snd_compr_stream *cstream, - struct snd_compr_params *params, struct snd_soc_dai *dai) +void snd_sof_compr_init_elapsed_work(struct work_struct *work) { - struct snd_compr_runtime *rtd = cstream->runtime; - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); - int ret; - - cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; - cstream->dma_buffer.dev.dev = sdev->dev; - ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); - if (ret < 0) - return ret; - - ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai); - if (ret < 0) - return ret; - - ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag, - rtd->dma_bytes); - if (ret < 0) { - dev_err(dai->dev, "Failed to init probe: %d\n", ret); - return ret; - } - - return 0; -} -EXPORT_SYMBOL(sof_probe_compr_set_params); - -int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); - - return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai); -} -EXPORT_SYMBOL(sof_probe_compr_trigger); - -int sof_probe_compr_pointer(struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); - - return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai); + INIT_WORK(work, snd_sof_compr_fragment_elapsed_work); } -EXPORT_SYMBOL(sof_probe_compr_pointer); -int sof_probe_compr_copy(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - char __user *buf, size_t count) +/* + * sof compr fragment elapse, this could be called in irq thread context + */ +void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) { - struct snd_compr_runtime *rtd = cstream->runtime; - unsigned int offset, n; - void *ptr; - int ret; + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + struct snd_sof_pcm *spcm; - if (count > rtd->buffer_size) - count = rtd->buffer_size; + if (!cstream) + return; - div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); - ptr = rtd->dma_area + offset; - n = rtd->buffer_size - offset; + rtd = cstream->private_data; + component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); - if (count < n) { - ret = copy_to_user(buf, ptr, count); - } else { - ret = copy_to_user(buf, ptr, n); - ret += copy_to_user(buf + n, rtd->dma_area, count - n); + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(component->dev, + "fragment elapsed called for unknown stream!\n"); + return; } - if (ret) - return count - ret; - return count; + /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */ + schedule_work(&spcm->stream[cstream->direction].period_elapsed_work); } -EXPORT_SYMBOL(sof_probe_compr_copy); diff --git a/sound/soc/sof/compress.h b/sound/soc/sof/compress.h deleted file mode 100644 index 4448c799e14b..000000000000 --- a/sound/soc/sof/compress.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ -/* - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * Copyright(c) 2019-2020 Intel Corporation. All rights reserved. - * - * Author: Cezary Rojewski <cezary.rojewski@intel.com> - */ - -#ifndef __SOF_COMPRESS_H -#define __SOF_COMPRESS_H - -#include <sound/compress_driver.h> - -extern const struct snd_compress_ops sof_probe_compressed_ops; - -int sof_probe_compr_open(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int sof_probe_compr_free(struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int sof_probe_compr_set_params(struct snd_compr_stream *cstream, - struct snd_compr_params *params, struct snd_soc_dai *dai); -int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai); -int sof_probe_compr_pointer(struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai); -int sof_probe_compr_copy(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - char __user *buf, size_t count); - -#endif diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c index a5dd728c580a..58bb89af4de1 100644 --- a/sound/soc/sof/control.c +++ b/sound/soc/sof/control.c @@ -65,6 +65,40 @@ static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) return i - 1; } +static void snd_sof_refresh_control(struct snd_sof_control *scontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_soc_component *scomp = scontrol->scomp; + enum sof_ipc_ctrl_type ctrl_type; + int ret; + + if (!scontrol->comp_data_dirty) + return; + + if (!pm_runtime_active(scomp->dev)) + return; + + if (scontrol->cmd == SOF_CTRL_CMD_BINARY) + ctrl_type = SOF_IPC_COMP_GET_DATA; + else + ctrl_type = SOF_IPC_COMP_GET_VALUE; + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + + /* refresh the component data from DSP */ + scontrol->comp_data_dirty = false; + ret = snd_sof_ipc_set_get_comp_data(scontrol, ctrl_type, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + scontrol->cmd, false); + if (ret < 0) { + dev_err(scomp->dev, "error: failed to get control data: %d\n", ret); + /* Set the flag to re-try next time to get the data */ + scontrol->comp_data_dirty = true; + } +} + int snd_sof_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -74,6 +108,8 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol, struct sof_ipc_ctrl_data *cdata = scontrol->control_data; unsigned int i, channels = scontrol->num_channels; + snd_sof_refresh_control(scontrol); + /* read back each channel */ for (i = 0; i < channels; i++) ucontrol->value.integer.value[i] = @@ -108,7 +144,7 @@ int snd_sof_volume_put(struct snd_kcontrol *kcontrol, if (pm_runtime_active(scomp->dev)) snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_VALUE, - SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_TYPE_VALUE_CHAN_SET, SOF_CTRL_CMD_VOLUME, true); return change; @@ -145,6 +181,8 @@ int snd_sof_switch_get(struct snd_kcontrol *kcontrol, struct sof_ipc_ctrl_data *cdata = scontrol->control_data; unsigned int i, channels = scontrol->num_channels; + snd_sof_refresh_control(scontrol); + /* read back each channel */ for (i = 0; i < channels; i++) ucontrol->value.integer.value[i] = cdata->chanv[i].value; @@ -179,7 +217,7 @@ int snd_sof_switch_put(struct snd_kcontrol *kcontrol, if (pm_runtime_active(scomp->dev)) snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_VALUE, - SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_TYPE_VALUE_CHAN_SET, SOF_CTRL_CMD_SWITCH, true); @@ -195,6 +233,8 @@ int snd_sof_enum_get(struct snd_kcontrol *kcontrol, struct sof_ipc_ctrl_data *cdata = scontrol->control_data; unsigned int i, channels = scontrol->num_channels; + snd_sof_refresh_control(scontrol); + /* read back each channel */ for (i = 0; i < channels; i++) ucontrol->value.enumerated.item[i] = cdata->chanv[i].value; @@ -226,7 +266,7 @@ int snd_sof_enum_put(struct snd_kcontrol *kcontrol, if (pm_runtime_active(scomp->dev)) snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_SET_VALUE, - SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_TYPE_VALUE_CHAN_SET, SOF_CTRL_CMD_ENUM, true); @@ -244,6 +284,8 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, struct sof_abi_hdr *data = cdata->data; size_t size; + snd_sof_refresh_control(scontrol); + if (be->max > sizeof(ucontrol->value.bytes.data)) { dev_err_ratelimited(scomp->dev, "error: data max %d exceeds ucontrol data array size\n", @@ -475,6 +517,8 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, (struct snd_ctl_tlv __user *)binary_data; size_t data_size; + snd_sof_refresh_control(scontrol); + /* * Decrement the limit by ext bytes header size to * ensure the user space buffer is not exceeded. @@ -511,3 +555,145 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, return 0; } + +static void snd_sof_update_control(struct snd_sof_control *scontrol, + struct sof_ipc_ctrl_data *cdata) +{ + struct snd_soc_component *scomp = scontrol->scomp; + struct sof_ipc_ctrl_data *local_cdata; + int i; + + local_cdata = scontrol->control_data; + + if (cdata->cmd == SOF_CTRL_CMD_BINARY) { + if (cdata->num_elems != local_cdata->data->size) { + dev_err(scomp->dev, + "error: cdata binary size mismatch %u - %u\n", + cdata->num_elems, local_cdata->data->size); + return; + } + + /* copy the new binary data */ + memcpy(local_cdata->data, cdata->data, cdata->num_elems); + } else if (cdata->num_elems != scontrol->num_channels) { + dev_err(scomp->dev, + "error: cdata channel count mismatch %u - %d\n", + cdata->num_elems, scontrol->num_channels); + } else { + /* copy the new values */ + for (i = 0; i < cdata->num_elems; i++) + local_cdata->chanv[i].value = cdata->chanv[i].value; + } +} + +void snd_sof_control_notify(struct snd_sof_dev *sdev, + struct sof_ipc_ctrl_data *cdata) +{ + struct snd_soc_dapm_widget *widget; + struct snd_sof_control *scontrol; + struct snd_sof_widget *swidget; + struct snd_kcontrol *kc = NULL; + struct soc_mixer_control *sm; + struct soc_bytes_ext *be; + size_t expected_size; + struct soc_enum *se; + bool found = false; + int i, type; + + /* Find the swidget first */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == cdata->comp_id) { + found = true; + break; + } + } + + if (!found) + return; + + /* Translate SOF cmd to TPLG type */ + switch (cdata->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_SWITCH: + type = SND_SOC_TPLG_TYPE_MIXER; + break; + case SOF_CTRL_CMD_BINARY: + type = SND_SOC_TPLG_TYPE_BYTES; + break; + case SOF_CTRL_CMD_ENUM: + type = SND_SOC_TPLG_TYPE_ENUM; + break; + default: + dev_err(sdev->dev, "error: unknown cmd %u\n", cdata->cmd); + return; + } + + widget = swidget->widget; + for (i = 0; i < widget->num_kcontrols; i++) { + /* skip non matching types or non matching indexes within type */ + if (widget->dobj.widget.kcontrol_type[i] == type && + widget->kcontrol_news[i].index == cdata->index) { + kc = widget->kcontrols[i]; + break; + } + } + + if (!kc) + return; + + switch (cdata->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_SWITCH: + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + break; + case SOF_CTRL_CMD_BINARY: + be = (struct soc_bytes_ext *)kc->private_value; + scontrol = be->dobj.private; + break; + case SOF_CTRL_CMD_ENUM: + se = (struct soc_enum *)kc->private_value; + scontrol = se->dobj.private; + break; + default: + return; + } + + expected_size = sizeof(struct sof_ipc_ctrl_data); + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + expected_size += cdata->num_elems * + sizeof(struct sof_ipc_ctrl_value_chan); + break; + case SOF_CTRL_TYPE_VALUE_COMP_GET: + case SOF_CTRL_TYPE_VALUE_COMP_SET: + expected_size += cdata->num_elems * + sizeof(struct sof_ipc_ctrl_value_comp); + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr); + break; + default: + return; + } + + if (cdata->rhdr.hdr.size != expected_size) { + dev_err(sdev->dev, "error: component notification size mismatch\n"); + return; + } + + if (cdata->num_elems) + /* + * The message includes the updated value/data, update the + * control's local cache using the received notification + */ + snd_sof_update_control(scontrol, cdata); + else + /* Mark the scontrol that the value/data is changed in SOF */ + scontrol->comp_data_dirty = true; + + snd_ctl_notify_one(swidget->scomp->card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, kc, 0); +} diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 59d0d7b2b55c..2c3de295f11f 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -15,11 +15,11 @@ #include "sof-priv.h" #include "ops.h" #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "probe.h" +#include "sof-probes.h" #endif /* see SOF_DBG_ flags */ -int sof_core_debug; +int sof_core_debug = IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE); module_param_named(sof_debug, sof_core_debug, int, 0444); MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)"); @@ -67,7 +67,7 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, /* is firmware dead ? */ if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { - dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n", + dev_err(sdev->dev, "unexpected fault %#010x trace %#010x\n", panic_code, tracep_code); return; /* no fault ? */ } @@ -76,20 +76,20 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { if (panic_msg[i].id == code) { - dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg); - dev_err(sdev->dev, "error: trace point %8.8x\n", - tracep_code); + dev_err(sdev->dev, "reason: %s (%#x)\n", panic_msg[i].msg, + code & SOF_IPC_PANIC_CODE_MASK); + dev_err(sdev->dev, "trace point: %#010x\n", tracep_code); goto out; } } /* unknown error */ - dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code); - dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); + dev_err(sdev->dev, "unknown panic code: %#x\n", code & SOF_IPC_PANIC_CODE_MASK); + dev_err(sdev->dev, "trace point: %#010x\n", tracep_code); out: - dev_err(sdev->dev, "error: panic at %s:%d\n", - panic_info->filename, panic_info->linenum); + dev_err(sdev->dev, "panic at %s:%d\n", panic_info->filename, + panic_info->linenum); sof_oops(sdev, oops); sof_stack(sdev, oops, stack, stack_words); } @@ -147,7 +147,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) return ret; } - sdev->fw_state = SOF_FW_BOOT_PREPARE; + sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); /* check machine info */ ret = sof_machine_check(sdev); @@ -189,7 +189,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_load_err; } - sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; + sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); /* * Boot the firmware. The FW boot status will be modified @@ -202,8 +202,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_run_err; } - if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || - (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { + if (sof_core_debug & SOF_DBG_ENABLE_TRACE) { sdev->dtrace_is_supported = true; /* init DMA trace */ @@ -266,7 +265,7 @@ dsp_err: snd_sof_remove(sdev); /* all resources freed, update state to match */ - sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); sdev->first_boot = true; return ret; @@ -301,7 +300,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) sdev->pdata = plat_data; sdev->first_boot = true; - sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; #endif @@ -326,9 +325,6 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) spin_lock_init(&sdev->hw_lock); mutex_init(&sdev->power_state_access); - if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) - INIT_WORK(&sdev->probe_work, sof_probe_work); - /* set default timeouts if none provided */ if (plat_data->desc->ipc_timeout == 0) sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC_MS; @@ -340,6 +336,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) sdev->boot_timeout = plat_data->desc->boot_timeout; if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { + INIT_WORK(&sdev->probe_work, sof_probe_work); schedule_work(&sdev->probe_work); return 0; } diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index a51a928ea40a..dc1df5fb7b4c 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -20,7 +20,7 @@ #include "ops.h" #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "probe.h" +#include "sof-probes.h" /** * strsplit_u32 - Split string into sequence of u32 tokens @@ -546,10 +546,10 @@ static const struct file_operations sof_dfs_fops = { }; /* create FS entry for debug files that can expose DSP memories, registers */ -int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, - void __iomem *base, size_t size, - const char *name, - enum sof_debugfs_access_type access_type) +static int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, + void __iomem *base, size_t size, + const char *name, + enum sof_debugfs_access_type access_type) { struct snd_sof_dfsentry *dfse; @@ -586,7 +586,21 @@ int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, return 0; } -EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item); + +int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev, + enum snd_sof_fw_blk_type blk_type, u32 offset, + size_t size, const char *name, + enum sof_debugfs_access_type access_type) +{ + int bar = snd_sof_dsp_get_bar_index(sdev, blk_type); + + if (bar < 0) + return bar; + + return snd_sof_debugfs_io_item(sdev, sdev->bar[bar] + offset, size, name, + access_type); +} +EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem); /* create FS entry for debug files to expose kernel memory */ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, @@ -808,18 +822,75 @@ void snd_sof_free_debug(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_GPL(snd_sof_free_debug); +static const struct soc_fw_state_info { + enum snd_sof_fw_state state; + const char *name; +} fw_state_dbg[] = { + {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"}, + {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"}, + {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"}, + {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"}, + {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"}, + {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"}, +}; + +static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) { + if (sdev->fw_state == fw_state_dbg[i].state) { + dev_err(sdev->dev, "fw_state: %s (%d)\n", fw_state_dbg[i].name, i); + return; + } + } + + dev_err(sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state); +} + +void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags) +{ + bool print_all = !!(sof_core_debug & SOF_DBG_PRINT_ALL_DUMPS); + + if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all) + return; + + if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) { + dev_err(sdev->dev, "------------[ DSP dump start ]------------\n"); + snd_sof_dbg_print_fw_state(sdev); + sof_ops(sdev)->dbg_dump(sdev, flags); + dev_err(sdev->dev, "------------[ DSP dump end ]------------\n"); + if (!print_all) + sdev->dbg_dump_printed = true; + } +} +EXPORT_SYMBOL(snd_sof_dsp_dbg_dump); + +static void snd_sof_ipc_dump(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->ipc_dump && !sdev->ipc_dump_printed) { + dev_err(sdev->dev, "------------[ IPC dump start ]------------\n"); + sof_ops(sdev)->ipc_dump(sdev); + dev_err(sdev->dev, "------------[ IPC dump end ]------------\n"); + if (!(sof_core_debug & SOF_DBG_PRINT_ALL_DUMPS)) + sdev->ipc_dump_printed = true; + } +} + void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) { if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) || (sof_core_debug & SOF_DBG_RETAIN_CTX)) { /* should we prevent DSP entering D3 ? */ - dev_info(sdev->dev, "info: preventing DSP entering D3 state to preserve context\n"); + if (!sdev->ipc_dump_printed) + dev_info(sdev->dev, + "preventing DSP entering D3 state to preserve context\n"); pm_runtime_get_noresume(sdev->dev); } /* dump vital information to the logs */ - snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); snd_sof_ipc_dump(sdev); + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); snd_sof_trace_notify_for_error(sdev); } EXPORT_SYMBOL(snd_sof_handle_fw_exception); diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig index 49d605cb09a5..34cf228c188f 100644 --- a/sound/soc/sof/imx/Kconfig +++ b/sound/soc/sof/imx/Kconfig @@ -38,6 +38,7 @@ config SND_SOC_SOF_IMX8 tristate select SND_SOC_SOF_IMX_COMMON select SND_SOC_SOF_XTENSA + select SND_SOC_SOF_COMPRESS help This option is not user-selectable but automagically handled by 'select' statements at a higher level. @@ -54,6 +55,7 @@ config SND_SOC_SOF_IMX8M tristate select SND_SOC_SOF_IMX_COMMON select SND_SOC_SOF_XTENSA + select SND_SOC_SOF_COMPRESS help This option is not user-selectable but automagically handled by 'select' statements at a higher level. diff --git a/sound/soc/sof/imx/imx-ops.h b/sound/soc/sof/imx/imx-ops.h new file mode 100644 index 000000000000..24235ef8c8fa --- /dev/null +++ b/sound/soc/sof/imx/imx-ops.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +#ifndef __IMX_OPS_H__ +#define __IMX_OPS_H__ + +extern struct snd_sof_dsp_ops sof_imx8_ops; +extern struct snd_sof_dsp_ops sof_imx8x_ops; +extern struct snd_sof_dsp_ops sof_imx8m_ops; + +#endif diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 7e9723a10d02..dd59a74480d6 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -22,6 +22,7 @@ #include <dt-bindings/firmware/imx/rsrc.h> #include "../ops.h" #include "imx-common.h" +#include "imx-ops.h" /* DSP memories */ #define IRAM_OFFSET 0x10000 @@ -375,20 +376,6 @@ static int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type) } } -static void imx8_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz) -{ - sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); -} - -static int imx8_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) -{ - return 0; -} - static struct snd_soc_dai_driver imx8_dai[] = { { .name = "esai0", @@ -426,8 +413,9 @@ struct snd_sof_dsp_ops sof_imx8_ops = { .block_read = sof_block_read, .block_write = sof_block_write, - /* Module IO */ - .read64 = sof_io_read64, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, /* ipc */ .send_msg = imx8_send_msg, @@ -435,8 +423,8 @@ struct snd_sof_dsp_ops sof_imx8_ops = { .get_mailbox_offset = imx8_get_mailbox_offset, .get_window_offset = imx8_get_window_offset, - .ipc_msg_data = imx8_ipc_msg_data, - .ipc_pcm_params = imx8_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -446,9 +434,14 @@ struct snd_sof_dsp_ops sof_imx8_ops = { /* Debug information */ .dbg_dump = imx8_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, + + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, /* Firmware ops */ - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, /* DAI drivers */ .drv = imx8_dai, @@ -475,8 +468,9 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { .block_read = sof_block_read, .block_write = sof_block_write, - /* Module IO */ - .read64 = sof_io_read64, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, /* ipc */ .send_msg = imx8_send_msg, @@ -484,8 +478,8 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { .get_mailbox_offset = imx8_get_mailbox_offset, .get_window_offset = imx8_get_window_offset, - .ipc_msg_data = imx8_ipc_msg_data, - .ipc_pcm_params = imx8_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -495,9 +489,14 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { /* Debug information */ .dbg_dump = imx8_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, + + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, /* Firmware ops */ - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, /* DAI drivers */ .drv = imx8_dai, diff --git a/sound/soc/sof/imx/imx8m.c b/sound/soc/sof/imx/imx8m.c index 892e1482f97f..e4618980cf8b 100644 --- a/sound/soc/sof/imx/imx8m.c +++ b/sound/soc/sof/imx/imx8m.c @@ -18,6 +18,7 @@ #include "../ops.h" #include "imx-common.h" +#include "imx-ops.h" #define MBOX_OFFSET 0x800000 #define MBOX_SIZE 0x1000 @@ -238,22 +239,19 @@ static int imx8m_get_bar_index(struct snd_sof_dev *sdev, u32 type) } } -static void imx8m_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz) -{ - sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); -} - -static int imx8m_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) -{ - return 0; -} - static struct snd_soc_dai_driver imx8m_dai[] = { { + .name = "sai1", + .playback = { + .channels_min = 1, + .channels_max = 32, + }, + .capture = { + .channels_min = 1, + .channels_max = 32, + }, +}, +{ .name = "sai3", .playback = { .channels_min = 1, @@ -278,8 +276,9 @@ struct snd_sof_dsp_ops sof_imx8m_ops = { .block_read = sof_block_read, .block_write = sof_block_write, - /* Module IO */ - .read64 = sof_io_read64, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, /* ipc */ .send_msg = imx8m_send_msg, @@ -287,8 +286,8 @@ struct snd_sof_dsp_ops sof_imx8m_ops = { .get_mailbox_offset = imx8m_get_mailbox_offset, .get_window_offset = imx8m_get_window_offset, - .ipc_msg_data = imx8m_ipc_msg_data, - .ipc_pcm_params = imx8m_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -298,9 +297,13 @@ struct snd_sof_dsp_ops sof_imx8m_ops = { /* Debug information */ .dbg_dump = imx8_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, /* Firmware ops */ - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, /* DAI drivers */ .drv = imx8m_dai, diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile index feae487f0227..1f473d4d8416 100644 --- a/sound/soc/sof/intel/Makefile +++ b/sound/soc/sof/intel/Makefile @@ -3,13 +3,11 @@ snd-sof-acpi-intel-byt-objs := byt.o snd-sof-acpi-intel-bdw-objs := bdw.o -snd-sof-intel-ipc-objs := intel-ipc.o - snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \ hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \ hda-dai.o hda-bus.o \ apl.o cnl.o tgl.o icl.o -snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-compress.o +snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o snd-sof-intel-hda-objs := hda-codec.o @@ -18,7 +16,6 @@ snd-sof-intel-atom-objs := atom.o obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-atom.o obj-$(CONFIG_SND_SOC_SOF_BAYTRAIL) += snd-sof-acpi-intel-byt.o obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-acpi-intel-bdw.o -obj-$(CONFIG_SND_SOC_SOF_INTEL_HIFI_EP_IPC) += snd-sof-intel-ipc.o obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index c7ed2b3d6abc..917f78cf6daf 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -42,6 +42,10 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* doorbell */ .irq_thread = hda_dsp_ipc_irq_thread, @@ -65,6 +69,7 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .debug_map_count = ARRAY_SIZE(apl_dsp_debugfs), .dbg_dump = hda_dsp_dump, .ipc_dump = hda_ipc_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ .pcm_open = hda_dsp_pcm_open, @@ -125,7 +130,7 @@ const struct snd_sof_dsp_ops sof_apl_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/atom.c b/sound/soc/sof/intel/atom.c index d8804efede5e..74c630bb9847 100644 --- a/sound/soc/sof/intel/atom.c +++ b/sound/soc/sof/intel/atom.c @@ -283,11 +283,8 @@ int atom_run(struct snd_sof_dev *sdev) break; msleep(100); } - if (tries < 0) { - dev_err(sdev->dev, "error: unable to run DSP firmware\n"); - atom_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); + if (tries < 0) return -ENODEV; - } /* return init core mask */ return 1; diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c index 89a6c1f04a55..2c09a523288e 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -535,8 +535,8 @@ static int bdw_probe(struct snd_sof_dev *sdev) return ret; } - /* set default mailbox */ - snd_sof_dsp_mailbox_init(sdev, MBOX_OFFSET, MBOX_SIZE, 0, 0); + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; return ret; } @@ -616,14 +616,18 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* ipc */ .send_msg = bdw_send_msg, .fw_ready = sof_fw_ready, .get_mailbox_offset = bdw_get_mailbox_offset, .get_window_offset = bdw_get_window_offset, - .ipc_msg_data = intel_ipc_msg_data, - .ipc_pcm_params = intel_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* machine driver */ .machine_select = bdw_machine_select, @@ -635,10 +639,11 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { .debug_map = bdw_debugfs, .debug_map_count = ARRAY_SIZE(bdw_debugfs), .dbg_dump = bdw_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ - .pcm_open = intel_pcm_open, - .pcm_close = intel_pcm_close, + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, /* Module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -657,7 +662,7 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_BATCH, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; static const struct sof_intel_dsp_desc bdw_chip_info = { diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index 8edaf6fdd218..e2fa08f1ae74 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -226,6 +226,10 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* doorbell */ .irq_handler = atom_irq_handler, .irq_thread = atom_irq_thread, @@ -236,8 +240,8 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, - .ipc_msg_data = intel_ipc_msg_data, - .ipc_pcm_params = intel_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* machine driver */ .machine_select = atom_machine_select, @@ -249,10 +253,11 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { .debug_map = byt_debugfs, .debug_map_count = ARRAY_SIZE(byt_debugfs), .dbg_dump = atom_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ - .pcm_open = intel_pcm_open, - .pcm_close = intel_pcm_close, + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -275,7 +280,7 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_BATCH, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; static const struct sof_intel_dsp_desc byt_chip_info = { @@ -303,6 +308,10 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* doorbell */ .irq_handler = atom_irq_handler, .irq_thread = atom_irq_thread, @@ -313,8 +322,8 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, - .ipc_msg_data = intel_ipc_msg_data, - .ipc_pcm_params = intel_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* machine driver */ .machine_select = atom_machine_select, @@ -326,10 +335,11 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { .debug_map = cht_debugfs, .debug_map_count = ARRAY_SIZE(cht_debugfs), .dbg_dump = atom_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ - .pcm_open = intel_pcm_open, - .pcm_close = intel_pcm_close, + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -353,7 +363,7 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_BATCH, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; static const struct sof_intel_dsp_desc cht_chip_info = { diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index e115e12a856f..3957e2b3db32 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -247,6 +247,10 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* doorbell */ .irq_thread = cnl_ipc_irq_thread, @@ -270,6 +274,7 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs), .dbg_dump = hda_dsp_dump, .ipc_dump = cnl_ipc_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ .pcm_open = hda_dsp_pcm_open, @@ -330,7 +335,7 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index c1f9f0f58464..76579383d290 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -16,6 +16,10 @@ #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) +#include "../sof-probes.h" +#endif + struct hda_pipe_params { u8 host_dma_id; u8 link_dma_id; @@ -68,6 +72,7 @@ static struct hdac_ext_stream * return NULL; } + spin_lock_irq(&bus->reg_lock); list_for_each_entry(stream, &bus->stream_list, list) { struct hdac_ext_stream *hstream = stream_to_hdac_ext_stream(stream); @@ -107,12 +112,12 @@ static struct hdac_ext_stream * * is updated in snd_hdac_ext_stream_decouple(). */ if (!res->decoupled) - snd_hdac_ext_stream_decouple(bus, res, true); - spin_lock_irq(&bus->reg_lock); + snd_hdac_ext_stream_decouple_locked(bus, res, true); + res->link_locked = 1; res->link_substream = substream; - spin_unlock_irq(&bus->reg_lock); } + spin_unlock_irq(&bus->reg_lock); return res; } @@ -151,49 +156,68 @@ static int hda_link_dma_params(struct hdac_ext_stream *stream, return 0; } -/* Send DAI_CONFIG IPC to the DAI that matches the dai_name and direction */ -static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream, - const char *dai_name, int channel, int dir) +/* Update config for the DAI widget */ +static struct sof_ipc_dai_config *hda_dai_update_config(struct snd_soc_dapm_widget *w, + int channel) { + struct snd_sof_widget *swidget = w->dobj.private; struct sof_ipc_dai_config *config; struct snd_sof_dai *sof_dai; - struct sof_ipc_reply reply; - int ret = 0; - list_for_each_entry(sof_dai, &hda_stream->sdev->dai_list, list) { - if (!sof_dai->cpu_dai_name) - continue; + if (!swidget) + return NULL; - if (!strcmp(dai_name, sof_dai->cpu_dai_name) && - dir == sof_dai->comp_dai.direction) { - config = sof_dai->dai_config; + sof_dai = swidget->private; - if (!config) { - dev_err(hda_stream->sdev->dev, - "error: no config for DAI %s\n", - sof_dai->name); - return -EINVAL; - } + if (!sof_dai || !sof_dai->dai_config) { + dev_err(swidget->scomp->dev, "error: No config for DAI %s\n", w->name); + return NULL; + } - /* update config with stream tag */ - config->hda.link_dma_ch = channel; + config = &sof_dai->dai_config[sof_dai->current_config]; - /* send IPC */ - ret = sof_ipc_tx_message(hda_stream->sdev->ipc, - config->hdr.cmd, - config, - config->hdr.size, - &reply, sizeof(reply)); + /* update config with stream tag */ + config->hda.link_dma_ch = channel; - if (ret < 0) - dev_err(hda_stream->sdev->dev, - "error: failed to set dai config for %s\n", - sof_dai->name); - return ret; - } + return config; +} + +static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream, + struct snd_soc_dapm_widget *w, int channel) +{ + struct snd_sof_dev *sdev = hda_stream->sdev; + struct sof_ipc_dai_config *config; + struct sof_ipc_reply reply; + + config = hda_dai_update_config(w, channel); + if (!config) { + dev_err(sdev->dev, "error: no config for DAI %s\n", w->name); + return -ENOENT; } - return -EINVAL; + /* send DAI_CONFIG IPC */ + return sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, + &reply, sizeof(reply)); +} + +static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream, + struct snd_soc_dapm_widget *w, + int channel, bool widget_setup) +{ + struct snd_sof_dev *sdev = hda_stream->sdev; + struct sof_ipc_dai_config *config; + + config = hda_dai_update_config(w, channel); + if (!config) { + dev_err(sdev->dev, "error: no config for DAI %s\n", w->name); + return -ENOENT; + } + + /* set up/free DAI widget and send DAI_CONFIG IPC */ + if (widget_setup) + return hda_ctrl_dai_widget_setup(w); + + return hda_ctrl_dai_widget_free(w); } static int hda_link_hw_params(struct snd_pcm_substream *substream, @@ -207,6 +231,7 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct sof_intel_hda_stream *hda_stream; struct hda_pipe_params p_params = {0}; + struct snd_soc_dapm_widget *w; struct hdac_ext_link *link; int stream_tag; int ret; @@ -225,9 +250,13 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream, hda_stream = hstream_to_sof_hda_stream(link_dev); - /* update the DSP with the new tag */ - ret = hda_link_config_ipc(hda_stream, dai->name, stream_tag - 1, - substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = dai->playback_widget; + else + w = dai->capture_widget; + + /* set up the DAI widget and send the DAI_CONFIG with the new tag */ + ret = hda_link_dai_widget_update(hda_stream, w, stream_tag - 1, true); if (ret < 0) return ret; @@ -283,6 +312,7 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, snd_soc_dai_get_dma_data(dai, substream); struct sof_intel_hda_stream *hda_stream; struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dapm_widget *w; struct hdac_ext_link *link; struct hdac_stream *hstream; struct hdac_bus *bus; @@ -317,12 +347,16 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = dai->playback_widget; + else + w = dai->capture_widget; + /* * clear link DMA channel. It will be assigned when * hw_params is set up again after resume. */ - ret = hda_link_config_ipc(hda_stream, dai->name, - DMA_CHAN_INVALID, substream->stream); + ret = hda_link_config_ipc(hda_stream, w, DMA_CHAN_INVALID); if (ret < 0) return ret; @@ -353,6 +387,7 @@ static int hda_link_hw_free(struct snd_pcm_substream *substream, struct hdac_stream *hstream; struct snd_soc_pcm_runtime *rtd; struct hdac_ext_stream *link_dev; + struct snd_soc_dapm_widget *w; int ret; hstream = substream->runtime->private_data; @@ -368,9 +403,13 @@ static int hda_link_hw_free(struct snd_pcm_substream *substream, hda_stream = hstream_to_sof_hda_stream(link_dev); - /* free the link DMA channel in the FW */ - ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID, - substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = dai->playback_widget; + else + w = dai->capture_widget; + + /* free the link DMA channel in the FW and the DAI widget */ + ret = hda_link_dai_widget_update(hda_stream, w, DMA_CHAN_INVALID, false); if (ret < 0) return ret; @@ -400,61 +439,131 @@ static const struct snd_soc_dai_ops hda_link_dai_ops = { .prepare = hda_link_pcm_prepare, }; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -#include "../compress.h" - -static struct snd_soc_cdai_ops sof_probe_compr_ops = { - .startup = sof_probe_compr_open, - .shutdown = sof_probe_compr_free, - .set_params = sof_probe_compr_set_params, - .trigger = sof_probe_compr_trigger, - .pointer = sof_probe_compr_pointer, +#endif + +/* only one flag used so far to harden hw_params/hw_free/trigger/prepare */ +struct ssp_dai_dma_data { + bool setup; }; -#endif -#endif +static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, + bool setup) +{ + struct snd_soc_component *component; + struct snd_sof_widget *swidget; + struct snd_soc_dapm_widget *w; + struct sof_ipc_fw_version *v; + struct snd_sof_dev *sdev; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = dai->playback_widget; + else + w = dai->capture_widget; + + swidget = w->dobj.private; + component = swidget->scomp; + sdev = snd_soc_component_get_drvdata(component); + v = &sdev->fw_ready.version; + + /* DAI_CONFIG IPC during hw_params is not supported in older firmware */ + if (v->abi_version < SOF_ABI_VER(3, 18, 0)) + return 0; + + if (setup) + return hda_ctrl_dai_widget_setup(w); + + return hda_ctrl_dai_widget_free(w); +} + +static int ssp_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ssp_dai_dma_data *dma_data; + + dma_data = kzalloc(sizeof(*dma_data), GFP_KERNEL); + if (!dma_data) + return -ENOMEM; + + snd_soc_dai_set_dma_data(dai, substream, dma_data); + + return 0; +} + +static int ssp_dai_setup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + bool setup) +{ + struct ssp_dai_dma_data *dma_data; + int ret = 0; + + dma_data = snd_soc_dai_get_dma_data(dai, substream); + if (!dma_data) { + dev_err(dai->dev, "%s: failed to get dma_data\n", __func__); + return -EIO; + } + + if (dma_data->setup != setup) { + ret = ssp_dai_setup_or_free(substream, dai, setup); + if (!ret) + dma_data->setup = setup; + } + return ret; +} static int ssp_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct sof_ipc_fw_version *v = &sdev->fw_ready.version; - struct sof_ipc_dai_config *config; - struct snd_sof_dai *sof_dai; - struct sof_ipc_reply reply; - int ret; + /* params are ignored for now */ + return ssp_dai_setup(substream, dai, true); +} - /* DAI_CONFIG IPC during hw_params is not supported in older firmware */ - if (v->abi_version < SOF_ABI_VER(3, 18, 0)) +static int ssp_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + /* + * the SSP will only be reconfigured during resume operations and + * not in case of xruns + */ + return ssp_dai_setup(substream, dai, true); +} + +static int ssp_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + if (cmd != SNDRV_PCM_TRIGGER_SUSPEND) return 0; - list_for_each_entry(sof_dai, &sdev->dai_list, list) { - if (!sof_dai->cpu_dai_name || !sof_dai->dai_config) - continue; + return ssp_dai_setup(substream, dai, false); +} - if (!strcmp(dai->name, sof_dai->cpu_dai_name) && - substream->stream == sof_dai->comp_dai.direction) { - config = &sof_dai->dai_config[sof_dai->current_config]; +static int ssp_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return ssp_dai_setup(substream, dai, false); +} - /* send IPC */ - ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, - config->hdr.size, &reply, sizeof(reply)); +static void ssp_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ssp_dai_dma_data *dma_data; - if (ret < 0) - dev_err(sdev->dev, "error: failed to set DAI config for %s\n", - sof_dai->name); - return ret; - } + dma_data = snd_soc_dai_get_dma_data(dai, substream); + if (!dma_data) { + dev_err(dai->dev, "%s: failed to get dma_data\n", __func__); + return; } - - return 0; + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(dma_data); } static const struct snd_soc_dai_ops ssp_dai_ops = { + .startup = ssp_dai_startup, .hw_params = ssp_dai_hw_params, + .prepare = ssp_dai_prepare, + .trigger = ssp_dai_trigger, + .hw_free = ssp_dai_hw_free, + .shutdown = ssp_dai_shutdown, }; /* diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 623cf291e207..058baca2cd0e 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -34,7 +34,7 @@ MODULE_PARM_DESC(enable_trace_D0I3_S0, * DSP Core control. */ -int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask) +static int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask) { u32 adspcs; u32 reset; @@ -73,7 +73,7 @@ int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask) return ret; } -int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask) +static int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask) { unsigned int crst; u32 adspcs; @@ -113,7 +113,7 @@ int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask) return ret; } -int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask) +static int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask) { /* stall core */ snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, @@ -125,6 +125,31 @@ int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask) return hda_dsp_core_reset_enter(sdev, core_mask); } +static bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + int val; + bool is_enable; + + val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS); + +#define MASK_IS_EQUAL(v, m, field) ({ \ + u32 _m = field(m); \ + ((v) & _m) == _m; \ +}) + + is_enable = MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_CPA_MASK) && + MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_SPA_MASK) && + !(val & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) && + !(val & HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)); + +#undef MASK_IS_EQUAL + + dev_dbg(sdev->dev, "DSP core(s) enabled? %d : core_mask %x\n", + is_enable, core_mask); + + return is_enable; +} + int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask) { int ret; @@ -156,7 +181,7 @@ int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask) * Power Management. */ -int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) +static int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) { unsigned int cpa; u32 adspcs; @@ -195,7 +220,7 @@ int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) return ret; } -int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask) +static int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask) { u32 adspcs; int ret; @@ -218,32 +243,6 @@ int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask) return ret; } -bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, - unsigned int core_mask) -{ - int val; - bool is_enable; - - val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS); - -#define MASK_IS_EQUAL(v, m, field) ({ \ - u32 _m = field(m); \ - ((v) & _m) == _m; \ -}) - - is_enable = MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_CPA_MASK) && - MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_SPA_MASK) && - !(val & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) && - !(val & HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)); - -#undef MASK_IS_EQUAL - - dev_dbg(sdev->dev, "DSP core(s) enabled? %d : core_mask %x\n", - is_enable, core_mask); - - return is_enable; -} - int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index acfeca42604c..11f20a5a62df 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -253,9 +253,9 @@ int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) return SRAM_WINDOW_OFFSET(id); } -void hda_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz) +int hda_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) { if (!substream || !sdev->stream_box.size) { sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); @@ -268,10 +268,13 @@ void hda_ipc_msg_data(struct snd_sof_dev *sdev, hda_stream.hstream); /* The stream might already be closed */ - if (hstream) - sof_mailbox_read(sdev, hda_stream->stream.posn_offset, - p, sz); + if (!hstream) + return -ESTRPIPE; + + sof_mailbox_read(sdev, hda_stream->stream.posn_offset, p, sz); } + + return 0; } int hda_ipc_pcm_params(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index 6f4771bf9de3..abad6d0ceb83 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -177,13 +177,13 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) __func__); err: - flags = SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX; + flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_OPTIONAL; - /* force error log level after max boot attempts */ + /* after max boot attempts make sure that the dump is printed */ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) - flags |= SOF_DBG_DUMP_FORCE_ERR_LEVEL; + flags &= ~SOF_DBG_DUMP_OPTIONAL; - hda_dsp_dump(sdev, flags); + snd_sof_dsp_dbg_dump(sdev, flags); snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask); return ret; @@ -414,8 +414,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) if (!ret) { dev_dbg(sdev->dev, "Firmware download successful, booting...\n"); } else { - hda_dsp_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX | - SOF_DBG_DUMP_FORCE_ERR_LEVEL); + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX); dev_err(sdev->dev, "error: load fw failed ret: %d\n", ret); } diff --git a/sound/soc/sof/intel/hda-compress.c b/sound/soc/sof/intel/hda-probes.c index fe2f3f7d236b..fe2f3f7d236b 100644 --- a/sound/soc/sof/intel/hda-compress.c +++ b/sound/soc/sof/intel/hda-probes.c diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 63c367478f1c..1d845c2cbc33 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -25,6 +25,33 @@ #define HDA_LTRP_GB_VALUE_US 95 +static inline const char *hda_hstream_direction_str(struct hdac_stream *hstream) +{ + if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + +static char *hda_hstream_dbg_get_stream_info_str(struct hdac_stream *hstream) +{ + struct snd_soc_pcm_runtime *rtd; + + if (hstream->substream) + rtd = asoc_substream_to_rtd(hstream->substream); + else if (hstream->cstream) + rtd = hstream->cstream->private_data; + else + /* Non audio DMA user, like dma-trace */ + return kasprintf(GFP_KERNEL, "-- (%s, stream_tag: %u)", + hda_hstream_direction_str(hstream), + hstream->stream_tag); + + return kasprintf(GFP_KERNEL, "dai_link \"%s\" (%s, stream_tag: %u)", + rtd->dai_link->name, hda_hstream_direction_str(hstream), + hstream->stream_tag); +} + /* * set up one of BDL entries for a stream */ @@ -89,13 +116,13 @@ int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, int remain, ioc; period_bytes = stream->period_bytes; - dev_dbg(sdev->dev, "period_bytes:0x%x\n", period_bytes); + dev_dbg(sdev->dev, "%s: period_bytes:0x%x\n", __func__, period_bytes); if (!period_bytes) period_bytes = stream->bufsize; periods = stream->bufsize / period_bytes; - dev_dbg(sdev->dev, "periods:%d\n", periods); + dev_dbg(sdev->dev, "%s: periods:%d\n", __func__, periods); remain = stream->bufsize % period_bytes; if (remain) @@ -244,7 +271,8 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN); if (!found) { - dev_dbg(sdev->dev, "stream_tag %d not opened!\n", stream_tag); + dev_dbg(sdev->dev, "%s: stream_tag %d not opened!\n", + __func__, stream_tag); return -ENODEV; } @@ -257,7 +285,7 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, struct hdac_stream *hstream = &stream->hstream; int sd_offset = SOF_STREAM_SD_OFFSET(hstream); u32 dma_start = SOF_HDA_SD_CTL_DMA_START; - int ret; + int ret = 0; u32 run; /* cmd must be for audio stream */ @@ -283,14 +311,9 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_STREAM_RUN_TIMEOUT); - if (ret < 0) { - dev_err(sdev->dev, - "error: %s: cmd %d: timeout on STREAM_SD_OFFSET read\n", - __func__, cmd); - return ret; - } + if (ret >= 0) + hstream->running = true; - hstream->running = true; break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: @@ -306,27 +329,32 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_STREAM_RUN_TIMEOUT); - if (ret < 0) { - dev_err(sdev->dev, - "error: %s: cmd %d: timeout on STREAM_SD_OFFSET read\n", - __func__, cmd); - return ret; - } + if (ret >= 0) { + snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK); - snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset + - SOF_HDA_ADSP_REG_CL_SD_STS, - SOF_HDA_CL_DMA_SD_INT_MASK); - - hstream->running = false; - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, - 1 << hstream->index, 0x0); + hstream->running = false; + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + SOF_HDA_INTCTL, + 1 << hstream->index, 0x0); + } break; default: dev_err(sdev->dev, "error: unknown command: %d\n", cmd); return -EINVAL; } - return 0; + if (ret < 0) { + char *stream_name = hda_hstream_dbg_get_stream_info_str(hstream); + + dev_err(sdev->dev, + "%s: cmd %d on %s: timeout on STREAM_SD_OFFSET read\n", + __func__, cmd, stream_name ? stream_name : "unknown stream"); + kfree(stream_name); + } + + return ret; } /* minimal recommended programming for ICCMAX stream */ @@ -440,9 +468,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, HDA_DSP_STREAM_RUN_TIMEOUT); if (ret < 0) { + char *stream_name = hda_hstream_dbg_get_stream_info_str(hstream); + dev_err(sdev->dev, - "error: %s: timeout on STREAM_SD_OFFSET read1\n", - __func__); + "%s: on %s: timeout on STREAM_SD_OFFSET read1\n", + __func__, stream_name ? stream_name : "unknown stream"); + kfree(stream_name); return ret; } @@ -506,9 +537,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, HDA_DSP_STREAM_RUN_TIMEOUT); if (ret < 0) { + char *stream_name = hda_hstream_dbg_get_stream_info_str(hstream); + dev_err(sdev->dev, - "error: %s: timeout on STREAM_SD_OFFSET read2\n", - __func__); + "%s: on %s: timeout on STREAM_SD_OFFSET read1\n", + __func__, stream_name ? stream_name : "unknown stream"); + kfree(stream_name); return ret; } diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index f60e2c57d3d0..883d78dd01b5 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -41,6 +41,92 @@ #define EXCEPT_MAX_HDR_SIZE 0x400 #define HDA_EXT_ROM_STATUS_SIZE 8 +int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w) +{ + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_soc_component *component = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct sof_ipc_dai_config *config; + struct snd_sof_dai *sof_dai; + struct sof_ipc_reply reply; + int ret; + + sof_dai = swidget->private; + + if (!sof_dai || !sof_dai->dai_config) { + dev_err(sdev->dev, "No config for DAI %s\n", w->name); + return -EINVAL; + } + + config = &sof_dai->dai_config[sof_dai->current_config]; + + /* + * For static pipelines, the DAI widget would already be set up and calling + * sof_widget_setup() simply returns without doing anything. + * For dynamic pipelines, the DAI widget will be set up now. + */ + ret = sof_widget_setup(sdev, swidget); + if (ret < 0) { + dev_err(sdev->dev, "error: failed setting up DAI widget %s\n", w->name); + return ret; + } + + /* set HW_PARAMS flag */ + config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_HW_PARAMS); + + /* send DAI_CONFIG IPC */ + ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed setting DAI config for %s\n", w->name); + return ret; + } + + sof_dai->configured = true; + + return 0; +} + +int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w) +{ + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_soc_component *component = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct sof_ipc_dai_config *config; + struct snd_sof_dai *sof_dai; + struct sof_ipc_reply reply; + int ret; + + sof_dai = swidget->private; + + if (!sof_dai || !sof_dai->dai_config) { + dev_err(sdev->dev, "error: No config to free DAI %s\n", w->name); + return -EINVAL; + } + + /* nothing to do if hw_free() is called without restarting the stream after resume. */ + if (!sof_dai->configured) + return 0; + + config = &sof_dai->dai_config[sof_dai->current_config]; + + /* set HW_FREE flag */ + config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_HW_FREE); + + ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: failed resetting DAI config for %s\n", w->name); + + /* + * Reset the configured_flag and free the widget even if the IPC fails to keep + * the widget use_count balanced + */ + sof_dai->configured = false; + + return sof_widget_free(sdev, swidget); +} + static const struct sof_intel_dsp_desc *get_chip_info(struct snd_sof_pdata *pdata) { @@ -64,67 +150,70 @@ static int sdw_clock_stop_quirks = SDW_INTEL_CLK_STOP_BUS_RESET; module_param(sdw_clock_stop_quirks, int, 0444); MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks"); +static int sdw_dai_config_ipc(struct snd_sof_dev *sdev, + struct snd_soc_dapm_widget *w, + int link_id, int alh_stream_id, int dai_id, bool setup) +{ + struct snd_sof_widget *swidget = w->dobj.private; + struct sof_ipc_dai_config *config; + struct snd_sof_dai *sof_dai; + + if (!swidget) { + dev_err(sdev->dev, "error: No private data for widget %s\n", w->name); + return -EINVAL; + } + + sof_dai = swidget->private; + + if (!sof_dai || !sof_dai->dai_config) { + dev_err(sdev->dev, "error: No config for DAI %s\n", w->name); + return -EINVAL; + } + + config = &sof_dai->dai_config[sof_dai->current_config]; + + /* update config with link and stream ID */ + config->dai_index = (link_id << 8) | dai_id; + config->alh.stream_id = alh_stream_id; + + if (setup) + return hda_ctrl_dai_widget_setup(w); + + return hda_ctrl_dai_widget_free(w); +} + static int sdw_params_stream(struct device *dev, struct sdw_intel_stream_params_data *params_data) { + struct snd_pcm_substream *substream = params_data->substream; struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct snd_soc_dai *d = params_data->dai; - struct sof_ipc_dai_config config; - struct sof_ipc_reply reply; - int link_id = params_data->link_id; - int alh_stream_id = params_data->alh_stream_id; - int ret; - u32 size = sizeof(config); - - memset(&config, 0, size); - config.hdr.size = size; - config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; - config.type = SOF_DAI_INTEL_ALH; - config.dai_index = (link_id << 8) | (d->id); - config.alh.stream_id = alh_stream_id; - - /* send message to DSP */ - ret = sof_ipc_tx_message(sdev->ipc, - config.hdr.cmd, &config, size, &reply, - sizeof(reply)); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to set DAI hw_params for link %d dai->id %d ALH %d\n", - link_id, d->id, alh_stream_id); - } + struct snd_soc_dapm_widget *w; - return ret; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = d->playback_widget; + else + w = d->capture_widget; + + return sdw_dai_config_ipc(sdev, w, params_data->link_id, params_data->alh_stream_id, + d->id, true); } static int sdw_free_stream(struct device *dev, struct sdw_intel_stream_free_data *free_data) { + struct snd_pcm_substream *substream = free_data->substream; struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct snd_soc_dai *d = free_data->dai; - struct sof_ipc_dai_config config; - struct sof_ipc_reply reply; - int link_id = free_data->link_id; - int ret; - u32 size = sizeof(config); - - memset(&config, 0, size); - config.hdr.size = size; - config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; - config.type = SOF_DAI_INTEL_ALH; - config.dai_index = (link_id << 8) | d->id; - config.alh.stream_id = 0xFFFF; /* invalid value on purpose */ - - /* send message to DSP */ - ret = sof_ipc_tx_message(sdev->ipc, - config.hdr.cmd, &config, size, &reply, - sizeof(reply)); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to free stream for link %d dai->id %d\n", - link_id, d->id); - } + struct snd_soc_dapm_widget *w; - return ret; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = d->playback_widget; + else + w = d->capture_widget; + + /* send invalid stream_id */ + return sdw_dai_config_ipc(sdev, w, free_data->link_id, 0xFFFF, d->id, false); } static const struct sdw_intel_ops sdw_callback = { @@ -294,7 +383,38 @@ void hda_sdw_process_wakeen(struct snd_sof_dev *sdev) sdw_intel_process_wakeen_event(hdev->sdw); } -#endif +#else /* IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) */ +static inline int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_probe(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_exit(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev) +{ + return false; +} + +static inline irqreturn_t hda_dsp_sdw_thread(int irq, void *context) +{ + return IRQ_HANDLED; +} + +static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev) +{ + return false; +} + +#endif /* IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) */ /* * Debug @@ -412,8 +532,7 @@ static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, u32 flags) len += snprintf(msg + len, sizeof(msg) - len, " 0x%x", value); } - sof_dev_dbg_or_err(sdev->dev, flags & SOF_DBG_DUMP_FORCE_ERR_LEVEL, - "extended rom status: %s", msg); + dev_err(sdev->dev, "extended rom status: %s", msg); } @@ -426,8 +545,7 @@ void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags) /* print ROM/FW status */ hda_dsp_get_status(sdev); - /* print panic info if FW boot is complete. Otherwise, print the extended ROM status */ - if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) { + if (flags & SOF_DBG_DUMP_REGS) { u32 status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_STATUS); u32 panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_TRACEP); @@ -456,12 +574,9 @@ void hda_ipc_irq_dump(struct snd_sof_dev *sdev) ppsts = snd_sof_dsp_read(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPSTS); rirbsts = snd_hdac_chip_readb(bus, RIRBSTS); - dev_err(sdev->dev, - "error: hda irq intsts 0x%8.8x intlctl 0x%8.8x rirb %2.2x\n", + dev_err(sdev->dev, "hda irq intsts 0x%8.8x intlctl 0x%8.8x rirb %2.2x\n", intsts, intctl, rirbsts); - dev_err(sdev->dev, - "error: dsp irq ppsts 0x%8.8x adspis 0x%8.8x\n", - ppsts, adspis); + dev_err(sdev->dev, "dsp irq ppsts 0x%8.8x adspis 0x%8.8x\n", ppsts, adspis); } void hda_ipc_dump(struct snd_sof_dev *sdev) @@ -479,8 +594,7 @@ void hda_ipc_dump(struct snd_sof_dev *sdev) /* dump the IPC regs */ /* TODO: parse the raw msg */ - dev_err(sdev->dev, - "error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n", + dev_err(sdev->dev, "host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n", hipcie, hipct, hipcctl); } diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 4fdfb108645c..1195018a1f4f 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -492,17 +492,8 @@ struct sof_intel_hda_stream { */ int hda_dsp_probe(struct snd_sof_dev *sdev); int hda_dsp_remove(struct snd_sof_dev *sdev); -int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, - unsigned int core_mask); -int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, - unsigned int core_mask); -int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask); int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask); -int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask); int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask); -int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask); -bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, - unsigned int core_mask); int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, unsigned int core_mask); void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev); @@ -572,9 +563,9 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream, int enable, u32 size); -void hda_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz); +int hda_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); int hda_ipc_pcm_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, const struct sof_ipc_pcm_params_reply *reply); @@ -619,8 +610,6 @@ int hda_dsp_ipc_cmd_done(struct snd_sof_dev *sdev, int dir); */ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev); int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev); -int hda_dsp_cl_boot_firmware_iccmax_icl(struct snd_sof_dev *sdev); -int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev); /* pre and post fw run ops */ int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev); @@ -695,45 +684,15 @@ bool hda_common_check_sdw_irq(struct snd_sof_dev *sdev); #else -static inline int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) -{ - return 0; -} - -static inline int hda_sdw_probe(struct snd_sof_dev *sdev) -{ - return 0; -} - static inline int hda_sdw_startup(struct snd_sof_dev *sdev) { return 0; } -static inline int hda_sdw_exit(struct snd_sof_dev *sdev) -{ - return 0; -} - static inline void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable) { } -static inline bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev) -{ - return false; -} - -static inline irqreturn_t hda_dsp_sdw_thread(int irq, void *context) -{ - return IRQ_HANDLED; -} - -static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev) -{ - return false; -} - static inline void hda_sdw_process_wakeen(struct snd_sof_dev *sdev) { } @@ -774,4 +733,9 @@ void hda_set_mach_params(const struct snd_soc_acpi_mach *mach, /* PCI driver selection and probe */ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id); +struct snd_sof_dai; +struct sof_ipc_dai_config; +int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w); +int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w); + #endif diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index ee095b8f2d01..0b2cc331d55b 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -41,6 +41,10 @@ const struct snd_sof_dsp_ops sof_icl_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* doorbell */ .irq_thread = cnl_ipc_irq_thread, @@ -64,6 +68,7 @@ const struct snd_sof_dsp_ops sof_icl_ops = { .debug_map_count = ARRAY_SIZE(icl_dsp_debugfs), .dbg_dump = hda_dsp_dump, .ipc_dump = cnl_ipc_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ .pcm_open = hda_dsp_pcm_open, @@ -125,7 +130,7 @@ const struct snd_sof_dsp_ops sof_icl_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; EXPORT_SYMBOL_NS(sof_icl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/pci-apl.c b/sound/soc/sof/intel/pci-apl.c index f89e746c2570..a023b3cc0af4 100644 --- a/sound/soc/sof/intel/pci-apl.c +++ b/sound/soc/sof/intel/pci-apl.c @@ -26,7 +26,6 @@ static const struct sof_dev_desc bxt_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &apl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -42,7 +41,6 @@ static const struct sof_dev_desc glk_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &apl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", diff --git a/sound/soc/sof/intel/pci-cnl.c b/sound/soc/sof/intel/pci-cnl.c index f23257adf2ab..40cf1cd00042 100644 --- a/sound/soc/sof/intel/pci-cnl.c +++ b/sound/soc/sof/intel/pci-cnl.c @@ -27,7 +27,6 @@ static const struct sof_dev_desc cnl_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -44,7 +43,6 @@ static const struct sof_dev_desc cfl_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -61,7 +59,6 @@ static const struct sof_dev_desc cml_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &cnl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", diff --git a/sound/soc/sof/intel/pci-icl.c b/sound/soc/sof/intel/pci-icl.c index 2f60c28ae81f..39c84121b313 100644 --- a/sound/soc/sof/intel/pci-icl.c +++ b/sound/soc/sof/intel/pci-icl.c @@ -27,7 +27,6 @@ static const struct sof_dev_desc icl_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &icl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -43,7 +42,6 @@ static const struct sof_dev_desc jsl_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &jsl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", diff --git a/sound/soc/sof/intel/pci-tgl.c b/sound/soc/sof/intel/pci-tgl.c index d04ce84fe7cc..f2ea34df9741 100644 --- a/sound/soc/sof/intel/pci-tgl.c +++ b/sound/soc/sof/intel/pci-tgl.c @@ -27,7 +27,6 @@ static const struct sof_dev_desc tgl_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &tgl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -44,7 +43,6 @@ static const struct sof_dev_desc tglh_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &tglh_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -60,7 +58,6 @@ static const struct sof_dev_desc ehl_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &ehl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -77,7 +74,6 @@ static const struct sof_dev_desc adls_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &adls_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", @@ -94,7 +90,6 @@ static const struct sof_dev_desc adl_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &tgl_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", diff --git a/sound/soc/sof/intel/pci-tng.c b/sound/soc/sof/intel/pci-tng.c index 4bded668b672..18eb41b8a8f4 100644 --- a/sound/soc/sof/intel/pci-tng.c +++ b/sound/soc/sof/intel/pci-tng.c @@ -142,6 +142,10 @@ const struct snd_sof_dsp_ops sof_tng_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* doorbell */ .irq_handler = atom_irq_handler, .irq_thread = atom_irq_thread, @@ -152,8 +156,8 @@ const struct snd_sof_dsp_ops sof_tng_ops = { .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, - .ipc_msg_data = intel_ipc_msg_data, - .ipc_pcm_params = intel_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* machine driver */ .machine_select = atom_machine_select, @@ -165,10 +169,11 @@ const struct snd_sof_dsp_ops sof_tng_ops = { .debug_map = tng_debugfs, .debug_map_count = ARRAY_SIZE(tng_debugfs), .dbg_dump = atom_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ - .pcm_open = intel_pcm_open, - .pcm_close = intel_pcm_close, + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -187,7 +192,7 @@ const struct snd_sof_dsp_ops sof_tng_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_BATCH, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; const struct sof_intel_dsp_desc tng_chip_info = { @@ -201,7 +206,6 @@ static const struct sof_dev_desc tng_desc = { .resindex_pcicfg_base = -1, .resindex_imr_base = 0, .irqindex_host_ipc = -1, - .resindex_dma_base = -1, .chip_info = &tng_chip_info, .default_fw_path = "intel/sof", .default_tplg_path = "intel/sof-tplg", diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 199d41a7dc9b..48da8e7a67bc 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -37,6 +37,10 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { .block_read = sof_block_read, .block_write = sof_block_write, + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + /* doorbell */ .irq_thread = cnl_ipc_irq_thread, @@ -60,6 +64,7 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { .debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs), .dbg_dump = hda_dsp_dump, .ipc_dump = cnl_ipc_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* stream callbacks */ .pcm_open = hda_dsp_pcm_open, @@ -120,7 +125,7 @@ const struct snd_sof_dsp_ops sof_tgl_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - .arch_ops = &sof_xtensa_arch_ops, + .dsp_arch_ops = &sof_xtensa_arch_ops, }; EXPORT_SYMBOL_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index c2d07b783f60..e6c53c6c470e 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -18,7 +18,7 @@ #include "sof-audio.h" #include "ops.h" -static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id); +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type); static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); /* @@ -192,6 +192,29 @@ static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) str2 = "unknown type"; break; } break; + case SOF_IPC_GLB_PROBE: + str = "GLB_PROBE"; + switch (type) { + case SOF_IPC_PROBE_INIT: + str2 = "INIT"; break; + case SOF_IPC_PROBE_DEINIT: + str2 = "DEINIT"; break; + case SOF_IPC_PROBE_DMA_ADD: + str2 = "DMA_ADD"; break; + case SOF_IPC_PROBE_DMA_INFO: + str2 = "DMA_INFO"; break; + case SOF_IPC_PROBE_DMA_REMOVE: + str2 = "DMA_REMOVE"; break; + case SOF_IPC_PROBE_POINT_ADD: + str2 = "POINT_ADD"; break; + case SOF_IPC_PROBE_POINT_INFO: + str2 = "POINT_INFO"; break; + case SOF_IPC_PROBE_POINT_REMOVE: + str2 = "POINT_REMOVE"; break; + default: + str2 = "unknown type"; break; + } + break; default: str = "unknown GLB command"; break; } @@ -226,15 +249,17 @@ static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, msecs_to_jiffies(sdev->ipc_timeout)); if (ret == 0) { - dev_err(sdev->dev, "error: ipc timed out for 0x%x size %d\n", - hdr->cmd, hdr->size); + dev_err(sdev->dev, + "ipc tx timed out for %#x (msg/reply size: %d/%zu)\n", + hdr->cmd, hdr->size, msg->reply_size); snd_sof_handle_fw_exception(ipc->sdev); ret = -ETIMEDOUT; } else { ret = msg->reply_error; if (ret < 0) { - dev_err(sdev->dev, "error: ipc error for 0x%x size %zu\n", - hdr->cmd, msg->reply_size); + dev_err(sdev->dev, + "ipc tx error for %#x (msg/reply size: %d/%zu): %d\n", + hdr->cmd, hdr->size, msg->reply_size, ret); } else { ipc_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd); if (msg->reply_size) @@ -242,6 +267,12 @@ static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, memcpy(reply_data, msg->reply_data, msg->reply_size); } + + /* re-enable dumps after successful IPC tx */ + if (sdev->ipc_dump_printed) { + sdev->dbg_dump_printed = false; + sdev->ipc_dump_printed = false; + } } return ret; @@ -286,7 +317,7 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, spin_unlock_irq(&sdev->ipc_lock); - if (ret < 0) { + if (ret) { dev_err_ratelimited(sdev->dev, "error: ipc tx failed with error %d\n", ret); @@ -296,10 +327,7 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, ipc_log_header(sdev->dev, "ipc tx", msg->header); /* now wait for completion */ - if (!ret) - ret = tx_wait_done(ipc, msg, reply_data); - - return ret; + return tx_wait_done(ipc, msg, reply_data); } /* send IPC message from host to DSP */ @@ -369,15 +397,52 @@ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) } EXPORT_SYMBOL(snd_sof_ipc_reply); +static void ipc_comp_notification(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr) +{ + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + struct sof_ipc_ctrl_data *cdata; + int ret; + + switch (msg_type) { + case SOF_IPC_COMP_GET_VALUE: + case SOF_IPC_COMP_GET_DATA: + cdata = kmalloc(hdr->size, GFP_KERNEL); + if (!cdata) + return; + + /* read back full message */ + ret = snd_sof_ipc_msg_data(sdev, NULL, cdata, hdr->size); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to read component event: %d\n", ret); + goto err; + } + break; + default: + dev_err(sdev->dev, "error: unhandled component message %#x\n", msg_type); + return; + } + + snd_sof_control_notify(sdev, cdata); + +err: + kfree(cdata); +} + /* DSP firmware has sent host a message */ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) { struct sof_ipc_cmd_hdr hdr; u32 cmd, type; - int err = 0; + int err; /* read back header */ - snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); + err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); + if (err < 0) { + dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); + return; + } ipc_log_header(sdev->dev, "ipc rx", hdr.cmd); cmd = hdr.cmd & SOF_GLB_TYPE_MASK; @@ -393,9 +458,9 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { err = sof_ops(sdev)->fw_ready(sdev, cmd); if (err < 0) - sdev->fw_state = SOF_FW_BOOT_READY_FAILED; + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); else - sdev->fw_state = SOF_FW_BOOT_COMPLETE; + sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE); /* wake up firmware loader */ wake_up(&sdev->boot_wait); @@ -404,7 +469,9 @@ void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) case SOF_IPC_GLB_COMPOUND: case SOF_IPC_GLB_TPLG_MSG: case SOF_IPC_GLB_PM_MSG: + break; case SOF_IPC_GLB_COMP_MSG: + ipc_comp_notification(sdev, &hdr); break; case SOF_IPC_GLB_STREAM_MSG: /* need to pass msg id into the function */ @@ -426,19 +493,22 @@ EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); * IPC trace mechanism. */ -static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id) +static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type) { struct sof_ipc_dma_trace_posn posn; + int ret; - switch (msg_id) { + switch (msg_type) { case SOF_IPC_TRACE_DMA_POSITION: /* read back full message */ - snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn)); - snd_sof_trace_update_pos(sdev, &posn); + ret = snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn)); + if (ret < 0) + dev_warn(sdev->dev, "failed to read trace position: %d\n", ret); + else + snd_sof_trace_update_pos(sdev, &posn); break; default: - dev_err(sdev->dev, "error: unhandled trace message %x\n", - msg_id); + dev_err(sdev->dev, "error: unhandled trace message %#x\n", msg_type); break; } } @@ -453,7 +523,7 @@ static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) struct snd_sof_pcm_stream *stream; struct sof_ipc_stream_posn posn; struct snd_sof_pcm *spcm; - int direction; + int direction, ret; spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); if (!spcm) { @@ -464,15 +534,21 @@ static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) } stream = &spcm->stream[direction]; - snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); + return; + } dev_vdbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", posn.host_posn, posn.dai_posn, posn.wallclock); memcpy(&stream->posn, &posn, sizeof(posn)); - /* only inform ALSA for period_wakeup mode */ - if (!stream->substream->runtime->no_period_wakeup) + if (spcm->pcm.compress) + snd_sof_compr_fragment_elapsed(stream->cstream); + else if (!stream->substream->runtime->no_period_wakeup) + /* only inform ALSA for period_wakeup mode */ snd_sof_pcm_period_elapsed(stream->substream); } @@ -483,7 +559,7 @@ static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) struct snd_sof_pcm_stream *stream; struct sof_ipc_stream_posn posn; struct snd_sof_pcm *spcm; - int direction; + int direction, ret; spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); if (!spcm) { @@ -493,7 +569,11 @@ static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) } stream = &spcm->stream[direction]; - snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read overrun position: %d\n", ret); + return; + } dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", posn.host_posn, posn.xrun_comp_id, posn.xrun_size); @@ -520,7 +600,7 @@ static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) ipc_xrun(sdev, msg_id); break; default: - dev_err(sdev->dev, "error: unhandled stream message %x\n", + dev_err(sdev->dev, "error: unhandled stream message %#x\n", msg_id); break; } @@ -672,24 +752,50 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, struct sof_ipc_fw_ready *ready = &sdev->fw_ready; struct sof_ipc_fw_version *v = &ready->version; struct sof_ipc_ctrl_data_params sparams; + struct snd_sof_widget *swidget; + bool widget_found = false; size_t send_bytes; int err; + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == scontrol->comp_id) { + widget_found = true; + break; + } + } + + if (!widget_found) { + dev_err(sdev->dev, "error: can't find widget with id %d\n", scontrol->comp_id); + return -EINVAL; + } + + /* + * Volatile controls should always be part of static pipelines and the widget use_count + * would always be > 0 in this case. For the others, just return the cached value if the + * widget is not set up. + */ + if (!swidget->use_count) + return 0; + /* read or write firmware volume */ if (scontrol->readback_offset != 0) { /* write/read value header via mmaped region */ send_bytes = sizeof(struct sof_ipc_ctrl_value_chan) * cdata->num_elems; if (send) - snd_sof_dsp_block_write(sdev, sdev->mmio_bar, - scontrol->readback_offset, - cdata->chanv, send_bytes); + err = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_IRAM, + scontrol->readback_offset, + cdata->chanv, send_bytes); else - snd_sof_dsp_block_read(sdev, sdev->mmio_bar, - scontrol->readback_offset, - cdata->chanv, send_bytes); - return 0; + err = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_IRAM, + scontrol->readback_offset, + cdata->chanv, send_bytes); + + if (err) + dev_err_once(sdev->dev, "error: %s TYPE_IRAM failed\n", + send ? "write to" : "read from"); + return err; } cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; @@ -762,22 +868,6 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, } EXPORT_SYMBOL(snd_sof_ipc_set_get_comp_data); -/* - * IPC layer enumeration. - */ - -int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, - size_t dspbox_size, u32 hostbox, - size_t hostbox_size) -{ - sdev->dsp_box.offset = dspbox; - sdev->dsp_box.size = dspbox_size; - sdev->host_box.offset = hostbox; - sdev->host_box.size = hostbox_size; - return 0; -} -EXPORT_SYMBOL(snd_sof_dsp_mailbox_init); - int snd_sof_ipc_valid(struct snd_sof_dev *sdev) { struct sof_ipc_fw_ready *ready = &sdev->fw_ready; @@ -829,6 +919,22 @@ int snd_sof_ipc_valid(struct snd_sof_dev *sdev) } EXPORT_SYMBOL(snd_sof_ipc_valid); +int sof_ipc_init_msg_memory(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg; + + msg = &sdev->ipc->msg; + msg->msg_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!msg->msg_data) + return -ENOMEM; + + msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!msg->reply_data) + return -ENOMEM; + + return 0; +} + struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) { struct snd_sof_ipc *ipc; @@ -845,17 +951,6 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) /* indicate that we aren't sending a message ATM */ msg->ipc_complete = true; - /* pre-allocate message data */ - msg->msg_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, - GFP_KERNEL); - if (!msg->msg_data) - return NULL; - - msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, - GFP_KERNEL); - if (!msg->reply_data) - return NULL; - init_waitqueue_head(&msg->waitq); return ipc; diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index bb79c77775b3..c04646647637 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -13,6 +13,7 @@ #include <linux/firmware.h> #include <sound/sof.h> #include <sound/sof/ext_manifest.h> +#include "sof-priv.h" #include "ops.h" static int get_ext_windows(struct snd_sof_dev *sdev, @@ -86,7 +87,7 @@ static int get_cc_info(struct snd_sof_dev *sdev, } /* parse the extended FW boot data structures from FW boot message */ -int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) +static int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset) { struct sof_ipc_ext_data_hdr *ext_hdr; void *ext_data; @@ -97,15 +98,16 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) return -ENOMEM; /* get first header */ - snd_sof_dsp_block_read(sdev, bar, offset, ext_data, + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, sizeof(*ext_hdr)); ext_hdr = ext_data; while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { /* read in ext structure */ - snd_sof_dsp_block_read(sdev, bar, offset + sizeof(*ext_hdr), - (void *)((u8 *)ext_data + sizeof(*ext_hdr)), - ext_hdr->hdr.size - sizeof(*ext_hdr)); + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, + offset + sizeof(*ext_hdr), + (void *)((u8 *)ext_data + sizeof(*ext_hdr)), + ext_hdr->hdr.size - sizeof(*ext_hdr)); dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", ext_hdr->type, ext_hdr->hdr.size); @@ -138,7 +140,7 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) /* move to next header */ offset += ext_hdr->hdr.size; - snd_sof_dsp_block_read(sdev, bar, offset, ext_data, + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, sizeof(*ext_hdr)); ext_hdr = ext_data; } @@ -146,7 +148,6 @@ int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset) kfree(ext_data); return ret; } -EXPORT_SYMBOL(snd_sof_fw_parse_ext_data); static int ext_man_get_fw_version(struct snd_sof_dev *sdev, const struct sof_ext_man_elem_header *hdr) @@ -372,7 +373,6 @@ static void sof_get_windows(struct snd_sof_dev *sdev) u32 debug_size = 0; u32 debug_offset = 0; int window_offset; - int bar; int i; if (!sdev->info_window) { @@ -380,12 +380,6 @@ static void sof_get_windows(struct snd_sof_dev *sdev) return; } - bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM); - if (bar < 0) { - dev_err(sdev->dev, "error: have no bar mapping\n"); - return; - } - for (i = 0; i < sdev->info_window->num_windows; i++) { elem = &sdev->info_window->window[i]; @@ -400,64 +394,53 @@ static void sof_get_windows(struct snd_sof_dev *sdev) case SOF_IPC_REGION_UPBOX: inbox_offset = window_offset + elem->offset; inbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[bar] + - inbox_offset, - elem->size, "inbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + inbox_offset, + elem->size, "inbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); break; case SOF_IPC_REGION_DOWNBOX: outbox_offset = window_offset + elem->offset; outbox_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[bar] + - outbox_offset, - elem->size, "outbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + outbox_offset, + elem->size, "outbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); break; case SOF_IPC_REGION_TRACE: - snd_sof_debugfs_io_item(sdev, - sdev->bar[bar] + - window_offset + - elem->offset, - elem->size, "etrace", - SOF_DEBUGFS_ACCESS_D0_ONLY); + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "etrace", + SOF_DEBUGFS_ACCESS_D0_ONLY); break; case SOF_IPC_REGION_DEBUG: debug_offset = window_offset + elem->offset; debug_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[bar] + - window_offset + - elem->offset, - elem->size, "debug", - SOF_DEBUGFS_ACCESS_D0_ONLY); + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "debug", + SOF_DEBUGFS_ACCESS_D0_ONLY); break; case SOF_IPC_REGION_STREAM: stream_offset = window_offset + elem->offset; stream_size = elem->size; - snd_sof_debugfs_io_item(sdev, - sdev->bar[bar] + - stream_offset, - elem->size, "stream", - SOF_DEBUGFS_ACCESS_D0_ONLY); + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + stream_offset, + elem->size, "stream", + SOF_DEBUGFS_ACCESS_D0_ONLY); break; case SOF_IPC_REGION_REGS: - snd_sof_debugfs_io_item(sdev, - sdev->bar[bar] + - window_offset + - elem->offset, - elem->size, "regs", - SOF_DEBUGFS_ACCESS_D0_ONLY); + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "regs", + SOF_DEBUGFS_ACCESS_D0_ONLY); break; case SOF_IPC_REGION_EXCEPTION: sdev->dsp_oops_offset = window_offset + elem->offset; - snd_sof_debugfs_io_item(sdev, - sdev->bar[bar] + - window_offset + - elem->offset, - elem->size, "exception", - SOF_DEBUGFS_ACCESS_D0_ONLY); + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "exception", + SOF_DEBUGFS_ACCESS_D0_ONLY); break; default: dev_err(sdev->dev, "error: get illegal window info\n"); @@ -470,8 +453,12 @@ static void sof_get_windows(struct snd_sof_dev *sdev) return; } - snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size, - outbox_offset, outbox_size); + sdev->dsp_box.offset = inbox_offset; + sdev->dsp_box.size = inbox_size; + + sdev->host_box.offset = outbox_offset; + sdev->host_box.size = outbox_size; + sdev->stream_box.offset = stream_offset; sdev->stream_box.size = stream_size; @@ -493,7 +480,6 @@ int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) { struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; int offset; - int bar; int ret; /* mailbox must be on 4k boundary */ @@ -503,12 +489,6 @@ int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) return offset; } - bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM); - if (bar < 0) { - dev_err(sdev->dev, "error: have no bar mapping\n"); - return -EINVAL; - } - dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", msg_id, offset); @@ -516,8 +496,17 @@ int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) if (!sdev->first_boot) return 0; - /* copy data from the DSP FW ready offset */ - snd_sof_dsp_block_read(sdev, bar, offset, fw_ready, sizeof(*fw_ready)); + /* + * copy data from the DSP FW ready offset + * Subsequent error handling is not needed for BLK_TYPE_SRAM + */ + ret = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, fw_ready, + sizeof(*fw_ready)); + if (ret) { + dev_err(sdev->dev, + "error: unable to read fw_ready, read from TYPE_SRAM failed\n"); + return ret; + } /* make sure ABI version is compatible */ ret = snd_sof_ipc_valid(sdev); @@ -525,12 +514,11 @@ int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) return ret; /* now check for extended data */ - snd_sof_fw_parse_ext_data(sdev, bar, offset + - sizeof(struct sof_ipc_fw_ready)); + snd_sof_fw_parse_ext_data(sdev, offset + sizeof(struct sof_ipc_fw_ready)); sof_get_windows(sdev); - return 0; + return sof_ipc_init_msg_memory(sdev); } EXPORT_SYMBOL(sof_fw_ready); @@ -539,7 +527,7 @@ int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, struct snd_sof_mod_hdr *module) { struct snd_sof_blk_hdr *block; - int count, bar; + int count, ret; u32 offset; size_t remaining; @@ -576,13 +564,6 @@ int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, case SOF_FW_BLK_TYPE_DRAM: case SOF_FW_BLK_TYPE_SRAM: offset = block->offset; - bar = snd_sof_dsp_get_bar_index(sdev, block->type); - if (bar < 0) { - dev_err(sdev->dev, - "error: no BAR mapping for block type 0x%x\n", - block->type); - return bar; - } break; default: dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n", @@ -600,8 +581,13 @@ int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, block->size); return -EINVAL; } - snd_sof_dsp_block_write(sdev, bar, offset, - block + 1, block->size); + ret = snd_sof_dsp_block_write(sdev, block->type, offset, + block + 1, block->size); + if (ret < 0) { + dev_err(sdev->dev, "error: write to block type 0x%x failed\n", + block->type); + return ret; + } if (remaining < block->size) { dev_err(sdev->dev, "error: not enough data remaining\n"); @@ -800,22 +786,16 @@ error: } EXPORT_SYMBOL(snd_sof_load_firmware_memcpy); -int snd_sof_load_firmware(struct snd_sof_dev *sdev) -{ - dev_dbg(sdev->dev, "loading firmware\n"); - - if (sof_ops(sdev)->load_firmware) - return sof_ops(sdev)->load_firmware(sdev); - return 0; -} -EXPORT_SYMBOL(snd_sof_load_firmware); - int snd_sof_run_firmware(struct snd_sof_dev *sdev) { int ret; init_waitqueue_head(&sdev->boot_wait); + /* (re-)enable dsp dump */ + sdev->dbg_dump_printed = false; + sdev->ipc_dump_printed = false; + /* create read-only fw_version debugfs to store boot version info */ if (sdev->first_boot) { ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version, @@ -840,7 +820,8 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) /* boot the firmware on the DSP */ ret = snd_sof_dsp_run(sdev); if (ret < 0) { - dev_err(sdev->dev, "error: failed to reset DSP\n"); + dev_err(sdev->dev, "error: failed to start DSP\n"); + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI); return ret; } @@ -856,8 +837,8 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) if (ret == 0) { dev_err(sdev->dev, "error: firmware boot failure\n"); snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX | - SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_FORCE_ERR_LEVEL); - sdev->fw_state = SOF_FW_BOOT_FAILED; + SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI); + sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); return -EIO; } diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c index 11ecebd07907..160b88a2d59f 100644 --- a/sound/soc/sof/ops.c +++ b/sound/soc/sof/ops.c @@ -157,6 +157,9 @@ void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset) dev_dbg(sdev->dev, "panic: dsp_oops_offset %zu offset %d\n", sdev->dsp_oops_offset, offset); + /* We want to see the DSP panic! */ + sdev->dbg_dump_printed = false; + snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); snd_sof_trace_notify_for_error(sdev); } diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 4a5d6e497f05..09bf38fdfb8a 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -241,16 +241,17 @@ snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev, } /* debug */ -static inline void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags) -{ - if (sof_ops(sdev)->dbg_dump) - sof_ops(sdev)->dbg_dump(sdev, flags); -} +void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags); -static inline void snd_sof_ipc_dump(struct snd_sof_dev *sdev) +static inline int snd_sof_debugfs_add_region_item(struct snd_sof_dev *sdev, + enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size, + const char *name, enum sof_debugfs_access_type access_type) { - if (sof_ops(sdev)->ipc_dump) - sof_ops(sdev)->ipc_dump(sdev); + if (sof_ops(sdev) && sof_ops(sdev)->debugfs_add_region_item) + return sof_ops(sdev)->debugfs_add_region_item(sdev, blk_type, offset, + size, name, access_type); + + return 0; } /* register IO */ @@ -297,16 +298,33 @@ static inline u64 snd_sof_dsp_read64(struct snd_sof_dev *sdev, u32 bar, } /* block IO */ -static inline void snd_sof_dsp_block_read(struct snd_sof_dev *sdev, u32 bar, - u32 offset, void *dest, size_t bytes) +static inline int snd_sof_dsp_block_read(struct snd_sof_dev *sdev, + enum snd_sof_fw_blk_type blk_type, + u32 offset, void *dest, size_t bytes) +{ + return sof_ops(sdev)->block_read(sdev, blk_type, offset, dest, bytes); +} + +static inline int snd_sof_dsp_block_write(struct snd_sof_dev *sdev, + enum snd_sof_fw_blk_type blk_type, + u32 offset, void *src, size_t bytes) +{ + return sof_ops(sdev)->block_write(sdev, blk_type, offset, src, bytes); +} + +/* mailbox IO */ +static inline void snd_sof_dsp_mailbox_read(struct snd_sof_dev *sdev, + u32 offset, void *dest, size_t bytes) { - sof_ops(sdev)->block_read(sdev, bar, offset, dest, bytes); + if (sof_ops(sdev)->mailbox_read) + sof_ops(sdev)->mailbox_read(sdev, offset, dest, bytes); } -static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, u32 bar, - u32 offset, void *src, size_t bytes) +static inline void snd_sof_dsp_mailbox_write(struct snd_sof_dev *sdev, + u32 offset, void *src, size_t bytes) { - sof_ops(sdev)->block_write(sdev, bar, offset, src, bytes); + if (sof_ops(sdev)->mailbox_write) + sof_ops(sdev)->mailbox_write(sdev, offset, src, bytes); } /* ipc */ @@ -400,12 +418,20 @@ snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, return 0; } +/* Firmware loading */ +static inline int snd_sof_load_firmware(struct snd_sof_dev *sdev) +{ + dev_dbg(sdev->dev, "loading firmware\n"); + + return sof_ops(sdev)->load_firmware(sdev); +} + /* host DSP message data */ -static inline void snd_sof_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz) +static inline int snd_sof_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) { - sof_ops(sdev)->ipc_msg_data(sdev, substream, p, sz); + return sof_ops(sdev)->ipc_msg_data(sdev, substream, p, sz); } /* host configure DSP HW parameters */ @@ -503,21 +529,6 @@ snd_sof_set_mach_params(const struct snd_soc_acpi_mach *mach, sof_ops(sdev)->set_mach_params(mach, sdev); } -static inline const struct snd_sof_dsp_ops -*sof_get_ops(const struct sof_dev_desc *d, - const struct sof_ops_table mach_ops[], int asize) -{ - int i; - - for (i = 0; i < asize; i++) { - if (d == mach_ops[i].desc) - return mach_ops[i].ops; - } - - /* not found */ - return NULL; -} - /** * snd_sof_dsp_register_poll_timeout - Periodically poll an address * until a condition is met or a timeout occurs diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 9893b182da43..fa0bfcd2474e 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -17,7 +17,7 @@ #include "sof-audio.h" #include "ops.h" #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "compress.h" +#include "sof-probes.h" #endif /* Create DMA buffer page table for DSP */ @@ -57,7 +57,7 @@ static int sof_pcm_dsp_params(struct snd_sof_pcm *spcm, struct snd_pcm_substream /* * sof pcm period elapse work */ -void snd_sof_pcm_period_elapsed_work(struct work_struct *work) +static void snd_sof_pcm_period_elapsed_work(struct work_struct *work) { struct snd_sof_pcm_stream *sps = container_of(work, struct snd_sof_pcm_stream, @@ -66,6 +66,11 @@ void snd_sof_pcm_period_elapsed_work(struct work_struct *work) snd_pcm_period_elapsed(sps->substream); } +void snd_sof_pcm_init_elapsed_work(struct work_struct *work) +{ + INIT_WORK(work, snd_sof_pcm_period_elapsed_work); +} + /* * sof pcm period elapse, this could be called at irq thread context. */ @@ -116,6 +121,40 @@ static int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, return ret; } +static int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, int dir) +{ + struct snd_soc_dai *dai; + int ret, j; + + /* query DAPM for list of connected widgets and set them up */ + for_each_rtd_cpu_dais(rtd, j, dai) { + struct snd_soc_dapm_widget_list *list; + + ret = snd_soc_dapm_dai_get_connected_widgets(dai, dir, &list, + dpcm_end_walk_at_be); + if (ret < 0) { + dev_err(sdev->dev, "error: dai %s has no valid %s path\n", dai->name, + dir == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture"); + return ret; + } + + spcm->stream[dir].list = list; + + ret = sof_widget_list_setup(sdev, spcm, dir); + if (ret < 0) { + dev_err(sdev->dev, "error: failed widget list set up for pcm %d dir %d\n", + spcm->pcm.pcm_id, dir); + spcm->stream[dir].list = NULL; + snd_soc_dapm_dai_free_widgets(&list); + return ret; + } + } + + return 0; +} + static int sof_pcm_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) @@ -213,7 +252,14 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag); - /* send IPC to the DSP */ + /* if this is a repeated hw_params without hw_free, skip setting up widgets */ + if (!spcm->stream[substream->stream].list) { + ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, substream->stream); + if (ret < 0) + return ret; + } + + /* send hw_params IPC to the DSP */ ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), &ipc_params_reply, sizeof(ipc_params_reply)); if (ret < 0) { @@ -259,6 +305,10 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, err = ret; } + ret = sof_widget_list_free(sdev, spcm, substream->stream); + if (ret < 0) + err = ret; + cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); ret = snd_sof_pcm_platform_hw_free(sdev, substream); @@ -316,6 +366,7 @@ static int sof_pcm_trigger(struct snd_soc_component *component, struct sof_ipc_stream stream; struct sof_ipc_reply reply; bool reset_hw_params = false; + bool free_widget_list = false; bool ipc_first = false; int ret; @@ -386,6 +437,7 @@ static int sof_pcm_trigger(struct snd_soc_component *component, spcm->stream[substream->stream].suspend_ignored = true; return 0; } + free_widget_list = true; fallthrough; case SNDRV_PCM_TRIGGER_STOP: stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; @@ -414,8 +466,15 @@ static int sof_pcm_trigger(struct snd_soc_component *component, snd_sof_pcm_platform_trigger(sdev, substream, cmd); /* free PCM if reset_hw_params is set and the STOP IPC is successful */ - if (!ret && reset_hw_params) + if (!ret && reset_hw_params) { ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); + if (ret < 0) + return ret; + + /* free widget list only for SUSPEND trigger */ + if (free_widget_list) + ret = sof_widget_list_free(sdev, spcm, substream->stream); + } return ret; } @@ -829,11 +888,7 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->trigger = sof_pcm_trigger; pd->pointer = sof_pcm_pointer; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) - pd->compress_ops = &sof_compressed_ops; -#endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - /* override cops when probe support is enabled */ pd->compress_ops = &sof_probe_compressed_ops; #endif pd->pcm_construct = sof_pcm_new; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index c83fb6255961..ac8ae6e422a7 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -122,7 +122,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) old_state == SOF_DSP_PM_D0) return 0; - sdev->fw_state = SOF_FW_BOOT_PREPARE; + sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); /* load the firmware */ ret = snd_sof_load_firmware(sdev); @@ -133,7 +133,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } - sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; + sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); /* * Boot the firmware. The FW boot status will be modified @@ -157,7 +157,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) } /* restore pipelines */ - ret = sof_restore_pipelines(sdev->dev); + ret = sof_set_up_pipelines(sdev, false); if (ret < 0) { dev_err(sdev->dev, "error: failed to restore pipeline after resume %d\n", @@ -191,7 +191,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) goto suspend; - /* set restore_stream for all streams during system suspend */ + /* prepare for streams to be resumed properly upon resume */ if (!runtime_suspend) { ret = sof_set_hw_params_upon_resume(sdev->dev); if (ret < 0) { @@ -208,6 +208,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) if (target_state == SOF_DSP_PM_D0) goto suspend; + sof_tear_down_pipelines(sdev, false); + /* release trace */ snd_sof_release_trace(sdev); @@ -255,7 +257,7 @@ suspend: return ret; /* reset FW state */ - sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); sdev->enabled_cores_mask = 0; return ret; diff --git a/sound/soc/sof/probe.h b/sound/soc/sof/probe.h deleted file mode 100644 index 5e159ab239fa..000000000000 --- a/sound/soc/sof/probe.h +++ /dev/null @@ -1,85 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ -/* - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * Copyright(c) 2019-2020 Intel Corporation. All rights reserved. - * - * Author: Cezary Rojewski <cezary.rojewski@intel.com> - */ - -#ifndef __SOF_PROBE_H -#define __SOF_PROBE_H - -#include <sound/sof/header.h> - -struct snd_sof_dev; - -#define SOF_PROBE_INVALID_NODE_ID UINT_MAX - -struct sof_probe_dma { - unsigned int stream_tag; - unsigned int dma_buffer_size; -} __packed; - -enum sof_connection_purpose { - SOF_CONNECTION_PURPOSE_EXTRACT = 1, - SOF_CONNECTION_PURPOSE_INJECT, -}; - -struct sof_probe_point_desc { - unsigned int buffer_id; - unsigned int purpose; - unsigned int stream_tag; -} __packed; - -struct sof_ipc_probe_dma_add_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - struct sof_probe_dma dma[]; -} __packed; - -struct sof_ipc_probe_info_params { - struct sof_ipc_reply rhdr; - unsigned int num_elems; - union { - struct sof_probe_dma dma[0]; - struct sof_probe_point_desc desc[0]; - }; -} __packed; - -struct sof_ipc_probe_dma_remove_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - unsigned int stream_tag[]; -} __packed; - -struct sof_ipc_probe_point_add_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - struct sof_probe_point_desc desc[]; -} __packed; - -struct sof_ipc_probe_point_remove_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - unsigned int buffer_id[]; -} __packed; - -int sof_ipc_probe_init(struct snd_sof_dev *sdev, - u32 stream_tag, size_t buffer_size); -int sof_ipc_probe_deinit(struct snd_sof_dev *sdev); -int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev, - struct sof_probe_dma **dma, size_t *num_dma); -int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev, - struct sof_probe_dma *dma, size_t num_dma); -int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev, - unsigned int *stream_tag, size_t num_stream_tag); -int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, size_t *num_desc); -int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, size_t num_desc); -int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id); - -#endif diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 989912f2b739..7cbe757c1fe2 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -8,9 +8,493 @@ // Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> // +#include <linux/bitfield.h> #include "sof-audio.h" #include "ops.h" +static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + int ipc_cmd, ctrl_type; + int ret; + + /* reset readback offset for scontrol */ + scontrol->readback_offset = 0; + + /* notify DSP of kcontrol values */ + switch (scontrol->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_ENUM: + case SOF_CTRL_CMD_SWITCH: + ipc_cmd = SOF_IPC_COMP_SET_VALUE; + ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + break; + case SOF_CTRL_CMD_BINARY: + ipc_cmd = SOF_IPC_COMP_SET_DATA; + ctrl_type = SOF_CTRL_TYPE_DATA_SET; + break; + default: + return 0; + } + + ret = snd_sof_ipc_set_get_comp_data(scontrol, ipc_cmd, ctrl_type, scontrol->cmd, true); + if (ret < 0) + dev_err(sdev->dev, "error: failed kcontrol value set for widget: %d\n", + scontrol->comp_id); + + return ret; +} + +static int sof_dai_config_setup(struct snd_sof_dev *sdev, struct snd_sof_dai *dai) +{ + struct sof_ipc_dai_config *config; + struct sof_ipc_reply reply; + int ret; + + config = &dai->dai_config[dai->current_config]; + if (!config) { + dev_err(sdev->dev, "error: no config for DAI %s\n", dai->name); + return -EINVAL; + } + + /* set NONE flag to clear all previous settings */ + config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_NONE); + + ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, + &reply, sizeof(reply)); + + if (ret < 0) + dev_err(sdev->dev, "error: failed to set dai config for %s\n", dai->name); + + return ret; +} + +static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct snd_sof_control *scontrol; + int ret; + + /* set up all controls for the widget */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) + if (scontrol->comp_id == swidget->comp_id) { + ret = sof_kcontrol_setup(sdev, scontrol); + if (ret < 0) { + dev_err(sdev->dev, "error: fail to set up kcontrols for widget %s\n", + swidget->widget->name); + return ret; + } + } + + return 0; +} + +static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget) +{ + struct snd_sof_route *sroute; + + list_for_each_entry(sroute, &sdev->route_list, list) + if (sroute->src_widget == widget || sroute->sink_widget == widget) + sroute->setup = false; +} + +int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc_free ipc_free = { + .hdr = { + .size = sizeof(ipc_free), + .cmd = SOF_IPC_GLB_TPLG_MSG, + }, + .id = swidget->comp_id, + }; + struct sof_ipc_reply reply; + int ret; + + if (!swidget->private) + return 0; + + /* only free when use_count is 0 */ + if (--swidget->use_count) + return 0; + + switch (swidget->id) { + case snd_soc_dapm_scheduler: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE; + break; + case snd_soc_dapm_buffer: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE; + break; + default: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE; + break; + } + + ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free), + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to free widget %s\n", swidget->widget->name); + swidget->use_count++; + return ret; + } + + /* reset route setup status for all routes that contain this widget */ + sof_reset_route_setup_status(sdev, swidget); + swidget->complete = 0; + dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name); + + return 0; +} +EXPORT_SYMBOL(sof_widget_free); + +int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc_pipe_new *pipeline; + struct sof_ipc_comp_reply r; + struct sof_ipc_cmd_hdr *hdr; + struct sof_ipc_comp *comp; + struct snd_sof_dai *dai; + size_t ipc_size; + int ret; + + /* skip if there is no private data */ + if (!swidget->private) + return 0; + + /* widget already set up */ + if (++swidget->use_count > 1) + return 0; + + ret = sof_pipeline_core_enable(sdev, swidget); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to enable target core: %d for widget %s\n", + ret, swidget->widget->name); + goto use_count_dec; + } + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + ipc_size = sizeof(struct sof_ipc_comp_dai) + sizeof(struct sof_ipc_comp_ext); + comp = kzalloc(ipc_size, GFP_KERNEL); + if (!comp) + return -ENOMEM; + + dai = swidget->private; + dai->configured = false; + memcpy(comp, &dai->comp_dai, sizeof(struct sof_ipc_comp_dai)); + + /* append extended data to the end of the component */ + memcpy((u8 *)comp + sizeof(struct sof_ipc_comp_dai), &swidget->comp_ext, + sizeof(swidget->comp_ext)); + + ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, comp, ipc_size, &r, sizeof(r)); + kfree(comp); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load widget %s\n", + swidget->widget->name); + goto use_count_dec; + } + + ret = sof_dai_config_setup(sdev, dai); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load dai config for DAI %s\n", + swidget->widget->name); + sof_widget_free(sdev, swidget); + return ret; + } + break; + case snd_soc_dapm_scheduler: + pipeline = swidget->private; + ret = sof_load_pipeline_ipc(sdev, pipeline, &r); + break; + default: + hdr = swidget->private; + ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, swidget->private, hdr->size, + &r, sizeof(r)); + break; + } + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load widget %s\n", swidget->widget->name); + goto use_count_dec; + } + + /* restore kcontrols for widget */ + ret = sof_widget_kcontrol_setup(sdev, swidget); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n", + swidget->widget->name); + sof_widget_free(sdev, swidget); + return ret; + } + + dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name); + + return 0; + +use_count_dec: + swidget->use_count--; + return ret; +} +EXPORT_SYMBOL(sof_widget_setup); + +static int sof_route_setup_ipc(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) +{ + struct sof_ipc_pipe_comp_connect *connect; + struct sof_ipc_reply reply; + int ret; + + /* skip if there's no private data */ + if (!sroute->private) + return 0; + + /* nothing to do if route is already set up */ + if (sroute->setup) + return 0; + + connect = sroute->private; + + dev_dbg(sdev->dev, "setting up route %s -> %s\n", + sroute->src_widget->widget->name, + sroute->sink_widget->widget->name); + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, + connect->hdr.cmd, + connect, sizeof(*connect), + &reply, sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, "%s: route setup failed %d\n", __func__, ret); + return ret; + } + + sroute->setup = true; + + return 0; +} + +static int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, + struct snd_soc_dapm_widget *wsink) +{ + struct snd_sof_widget *src_widget = wsource->dobj.private; + struct snd_sof_widget *sink_widget = wsink->dobj.private; + struct snd_sof_route *sroute; + bool route_found = false; + + /* ignore routes involving virtual widgets in topology */ + switch (src_widget->id) { + case snd_soc_dapm_out_drv: + case snd_soc_dapm_output: + case snd_soc_dapm_input: + return 0; + default: + break; + } + + switch (sink_widget->id) { + case snd_soc_dapm_out_drv: + case snd_soc_dapm_output: + case snd_soc_dapm_input: + return 0; + default: + break; + } + + /* find route matching source and sink widgets */ + list_for_each_entry(sroute, &sdev->route_list, list) + if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) { + route_found = true; + break; + } + + if (!route_found) { + dev_err(sdev->dev, "error: cannot find SOF route for source %s -> %s sink\n", + wsource->name, wsink->name); + return -EINVAL; + } + + return sof_route_setup_ipc(sdev, sroute); +} + +static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, + struct snd_soc_dapm_widget_list *list, int dir) +{ + struct snd_soc_dapm_widget *widget; + struct snd_soc_dapm_path *p; + int ret; + int i; + + /* + * Set up connections between widgets in the sink/source paths based on direction. + * Some non-SOF widgets exist in topology either for compatibility or for the + * purpose of connecting a pipeline from a host to a DAI in order to receive the DAPM + * events. But they are not handled by the firmware. So ignore them. + */ + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + for_each_dapm_widgets(list, i, widget) { + if (!widget->dobj.private) + continue; + + snd_soc_dapm_widget_for_each_sink_path(widget, p) + if (p->sink->dobj.private) { + ret = sof_route_setup(sdev, widget, p->sink); + if (ret < 0) + return ret; + } + } + } else { + for_each_dapm_widgets(list, i, widget) { + if (!widget->dobj.private) + continue; + + snd_soc_dapm_widget_for_each_source_path(widget, p) + if (p->source->dobj.private) { + ret = sof_route_setup(sdev, p->source, widget); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) +{ + struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; + struct snd_soc_dapm_widget *widget; + int i, ret, num_widgets; + + /* nothing to set up */ + if (!list) + return 0; + + /* set up widgets in the list */ + for_each_dapm_widgets(list, num_widgets, widget) { + struct snd_sof_widget *swidget = widget->dobj.private; + struct snd_sof_widget *pipe_widget; + + if (!swidget) + continue; + + /* + * The scheduler widget for a pipeline is not part of the connected DAPM + * widget list and it needs to be set up before the widgets in the pipeline + * are set up. The use_count for the scheduler widget is incremented for every + * widget in a given pipeline to ensure that it is freed only after the last + * widget in the pipeline is freed. + */ + pipe_widget = swidget->pipe_widget; + if (!pipe_widget) { + dev_err(sdev->dev, "error: no pipeline widget found for %s\n", + swidget->widget->name); + ret = -EINVAL; + goto widget_free; + } + + ret = sof_widget_setup(sdev, pipe_widget); + if (ret < 0) + goto widget_free; + + /* set up the widget */ + ret = sof_widget_setup(sdev, swidget); + if (ret < 0) { + sof_widget_free(sdev, pipe_widget); + goto widget_free; + } + } + + /* + * error in setting pipeline connections will result in route status being reset for + * routes that were successfully set up when the widgets are freed. + */ + ret = sof_setup_pipeline_connections(sdev, list, dir); + if (ret < 0) + goto widget_free; + + /* complete pipelines */ + for_each_dapm_widgets(list, i, widget) { + struct snd_sof_widget *swidget = widget->dobj.private; + struct snd_sof_widget *pipe_widget; + + if (!swidget) + continue; + + pipe_widget = swidget->pipe_widget; + if (!pipe_widget) { + dev_err(sdev->dev, "error: no pipeline widget found for %s\n", + swidget->widget->name); + ret = -EINVAL; + goto widget_free; + } + + if (pipe_widget->complete) + continue; + + pipe_widget->complete = snd_sof_complete_pipeline(sdev, pipe_widget); + if (pipe_widget->complete < 0) { + ret = pipe_widget->complete; + goto widget_free; + } + } + + return 0; + +widget_free: + /* free all widgets that have been set up successfully */ + for_each_dapm_widgets(list, i, widget) { + struct snd_sof_widget *swidget = widget->dobj.private; + + if (!swidget) + continue; + + if (!num_widgets--) + break; + + sof_widget_free(sdev, swidget); + sof_widget_free(sdev, swidget->pipe_widget); + } + + return ret; +} + +int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) +{ + struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; + struct snd_soc_dapm_widget *widget; + int i, ret; + int ret1 = 0; + + /* nothing to free */ + if (!list) + return 0; + + /* + * Free widgets in the list. This can fail but continue freeing other widgets to keep + * use_counts balanced. + */ + for_each_dapm_widgets(list, i, widget) { + struct snd_sof_widget *swidget = widget->dobj.private; + + if (!swidget) + continue; + + /* + * free widget and its pipe_widget. Either of these can fail, but free as many as + * possible before freeing the list and returning the error. + */ + ret = sof_widget_free(sdev, swidget); + if (ret < 0) + ret1 = ret; + + ret = sof_widget_free(sdev, swidget->pipe_widget); + if (ret < 0) + ret1 = ret; + } + + snd_soc_dapm_dai_free_widgets(&list); + spcm->stream[dir].list = NULL; + + return ret1; +} + /* * helper to determine if there are only D0i3 compatible * streams active @@ -93,55 +577,6 @@ int sof_set_hw_params_upon_resume(struct device *dev) return snd_sof_dsp_hw_params_upon_resume(sdev); } -static int sof_restore_kcontrols(struct device *dev) -{ - struct snd_sof_dev *sdev = dev_get_drvdata(dev); - struct snd_sof_control *scontrol; - int ipc_cmd, ctrl_type; - int ret = 0; - - /* restore kcontrol values */ - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { - /* reset readback offset for scontrol after resuming */ - scontrol->readback_offset = 0; - - /* notify DSP of kcontrol values */ - switch (scontrol->cmd) { - case SOF_CTRL_CMD_VOLUME: - case SOF_CTRL_CMD_ENUM: - case SOF_CTRL_CMD_SWITCH: - ipc_cmd = SOF_IPC_COMP_SET_VALUE; - ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; - ret = snd_sof_ipc_set_get_comp_data(scontrol, - ipc_cmd, ctrl_type, - scontrol->cmd, - true); - break; - case SOF_CTRL_CMD_BINARY: - ipc_cmd = SOF_IPC_COMP_SET_DATA; - ctrl_type = SOF_CTRL_TYPE_DATA_SET; - ret = snd_sof_ipc_set_get_comp_data(scontrol, - ipc_cmd, ctrl_type, - scontrol->cmd, - true); - break; - - default: - break; - } - - if (ret < 0) { - dev_err(dev, - "error: failed kcontrol value set for widget: %d\n", - scontrol->comp_id); - - return ret; - } - } - - return 0; -} - const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev, int pipeline_id) { @@ -158,142 +593,53 @@ const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev, return NULL; } -int sof_restore_pipelines(struct device *dev) +int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify) { - struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct snd_sof_widget *swidget; struct snd_sof_route *sroute; - struct sof_ipc_pipe_new *pipeline; - struct snd_sof_dai *dai; - struct sof_ipc_cmd_hdr *hdr; - struct sof_ipc_comp *comp; - size_t ipc_size; int ret; /* restore pipeline components */ list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { - struct sof_ipc_comp_reply r; - - /* skip if there is no private data */ - if (!swidget->private) + /* only set up the widgets belonging to static pipelines */ + if (!verify && swidget->dynamic_pipeline_widget) continue; - ret = sof_pipeline_core_enable(sdev, swidget); - if (ret < 0) { - dev_err(dev, - "error: failed to enable target core: %d\n", - ret); - - return ret; - } + /* update DAI config. The IPC will be sent in sof_widget_setup() */ + if (WIDGET_IS_DAI(swidget->id)) { + struct snd_sof_dai *dai = swidget->private; + struct sof_ipc_dai_config *config; - switch (swidget->id) { - case snd_soc_dapm_dai_in: - case snd_soc_dapm_dai_out: - ipc_size = sizeof(struct sof_ipc_comp_dai) + - sizeof(struct sof_ipc_comp_ext); - comp = kzalloc(ipc_size, GFP_KERNEL); - if (!comp) - return -ENOMEM; - - dai = swidget->private; - memcpy(comp, &dai->comp_dai, - sizeof(struct sof_ipc_comp_dai)); - - /* append extended data to the end of the component */ - memcpy((u8 *)comp + sizeof(struct sof_ipc_comp_dai), - &swidget->comp_ext, sizeof(swidget->comp_ext)); - - ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, - comp, ipc_size, - &r, sizeof(r)); - kfree(comp); - break; - case snd_soc_dapm_scheduler: + if (!dai || !dai->dai_config) + continue; + config = dai->dai_config; /* - * During suspend, all DSP cores are powered off. - * Therefore upon resume, create the pipeline comp - * and power up the core that the pipeline is - * scheduled on. + * The link DMA channel would be invalidated for running + * streams but not for streams that were in the PAUSED + * state during suspend. So invalidate it here before setting + * the dai config in the DSP. */ - pipeline = swidget->private; - ret = sof_load_pipeline_ipc(dev, pipeline, &r); - break; - default: - hdr = swidget->private; - ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, - swidget->private, hdr->size, - &r, sizeof(r)); - break; + if (config->type == SOF_DAI_INTEL_HDA) + config->hda.link_dma_ch = DMA_CHAN_INVALID; } - if (ret < 0) { - dev_err(dev, - "error: failed to load widget type %d with ID: %d\n", - swidget->widget->id, swidget->comp_id); + ret = sof_widget_setup(sdev, swidget); + if (ret < 0) return ret; - } } /* restore pipeline connections */ - list_for_each_entry_reverse(sroute, &sdev->route_list, list) { - struct sof_ipc_pipe_comp_connect *connect; - struct sof_ipc_reply reply; + list_for_each_entry(sroute, &sdev->route_list, list) { - /* skip if there's no private data */ - if (!sroute->private) + /* only set up routes belonging to static pipelines */ + if (!verify && (sroute->src_widget->dynamic_pipeline_widget || + sroute->sink_widget->dynamic_pipeline_widget)) continue; - connect = sroute->private; - - /* send ipc */ - ret = sof_ipc_tx_message(sdev->ipc, - connect->hdr.cmd, - connect, sizeof(*connect), - &reply, sizeof(reply)); + ret = sof_route_setup_ipc(sdev, sroute); if (ret < 0) { - dev_err(dev, - "error: failed to load route sink %s control %s source %s\n", - sroute->route->sink, - sroute->route->control ? sroute->route->control - : "none", - sroute->route->source); - - return ret; - } - } - - /* restore dai links */ - list_for_each_entry_reverse(dai, &sdev->dai_list, list) { - struct sof_ipc_reply reply; - struct sof_ipc_dai_config *config = &dai->dai_config[dai->current_config]; - - if (!config) { - dev_err(dev, "error: no config for DAI %s\n", - dai->name); - continue; - } - - /* - * The link DMA channel would be invalidated for running - * streams but not for streams that were in the PAUSED - * state during suspend. So invalidate it here before setting - * the dai config in the DSP. - */ - if (config->type == SOF_DAI_INTEL_HDA) - config->hda.link_dma_ch = DMA_CHAN_INVALID; - - ret = sof_ipc_tx_message(sdev->ipc, - config->hdr.cmd, config, - config->hdr.size, - &reply, sizeof(reply)); - - if (ret < 0) { - dev_err(dev, - "error: failed to set dai config for %s\n", - dai->name); - + dev_err(sdev->dev, "%s: restore pipeline connections failed\n", __func__); return ret; } } @@ -302,21 +648,52 @@ int sof_restore_pipelines(struct device *dev) list_for_each_entry(swidget, &sdev->widget_list, list) { switch (swidget->id) { case snd_soc_dapm_scheduler: + /* only complete static pipelines */ + if (!verify && swidget->dynamic_pipeline_widget) + continue; + swidget->complete = - snd_sof_complete_pipeline(dev, swidget); + snd_sof_complete_pipeline(sdev, swidget); break; default: break; } } - /* restore pipeline kcontrols */ - ret = sof_restore_kcontrols(dev); - if (ret < 0) - dev_err(dev, - "error: restoring kcontrols after resume\n"); + return 0; +} - return ret; +/* + * This function doesn't free widgets during suspend. It only resets the set up status for all + * routes and use_count for all widgets. + */ +int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify) +{ + struct snd_sof_widget *swidget; + struct snd_sof_route *sroute; + int ret; + + /* + * This function is called during suspend and for one-time topology verification during + * first boot. In both cases, there is no need to protect swidget->use_count and + * sroute->setup because during suspend all streams are suspended and during topology + * loading the sound card unavailable to open PCMs. + */ + list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { + if (!verify) { + swidget->use_count = 0; + continue; + } + + ret = sof_widget_free(sdev, swidget); + if (ret < 0) + return ret; + } + + list_for_each_entry(sroute, &sdev->route_list, list) + sroute->setup = false; + + return 0; } /* diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index dc274e63ed9a..05e98e231b85 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -28,13 +28,17 @@ #define DMA_CHAN_INVALID 0xFFFFFFFF +#define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out) + /* PCM stream, mapped to FW component */ struct snd_sof_pcm_stream { u32 comp_id; struct snd_dma_buffer page_table; struct sof_ipc_stream_posn posn; struct snd_pcm_substream *substream; + struct snd_compr_stream *cstream; struct work_struct period_elapsed_work; + struct snd_soc_dapm_widget_list *list; /* list of connected DAPM widgets */ bool d0i3_compatible; /* DSP can be in D0I3 when this pcm is opened */ /* * flag to indicate that the DSP pipelines should be kept @@ -66,6 +70,7 @@ struct snd_sof_control { int min_volume_step; /* min volume step for volume_table */ int max_volume_step; /* max volume step for volume_table */ int num_channels; + unsigned int access; u32 readback_offset; /* offset to mmapped data if used */ struct sof_ipc_ctrl_data *control_data; u32 size; /* cdata size */ @@ -75,19 +80,36 @@ struct snd_sof_control { struct list_head list; /* list in sdev control list */ struct snd_sof_led_control led_ctl; + + /* if true, the control's data needs to be updated from Firmware */ + bool comp_data_dirty; }; +struct snd_sof_widget; + /* ASoC SOF DAPM widget */ struct snd_sof_widget { struct snd_soc_component *scomp; int comp_id; int pipeline_id; int complete; + int use_count; /* use_count will be protected by the PCM mutex held by the core */ int core; int id; + /* + * Flag indicating if the widget should be set up dynamically when a PCM is opened. + * This flag is only set for the scheduler type widget in topology. During topology + * loading, this flag is propagated to all the widgets belonging to the same pipeline. + * When this flag is not set, a widget is set up at the time of topology loading + * and retained until the DSP enters D3. It will need to be set up again when resuming + * from D3. + */ + bool dynamic_pipeline_widget; + struct snd_soc_dapm_widget *widget; struct list_head list; /* list in sdev widget list */ + struct snd_sof_widget *pipe_widget; /* extended data for UUID components */ struct sof_ipc_comp_ext comp_ext; @@ -101,6 +123,9 @@ struct snd_sof_route { struct snd_soc_dapm_route *route; struct list_head list; /* list in sdev route list */ + struct snd_sof_widget *src_widget; + struct snd_sof_widget *sink_widget; + bool setup; void *private; }; @@ -109,11 +134,11 @@ struct snd_sof_route { struct snd_sof_dai { struct snd_soc_component *scomp; const char *name; - const char *cpu_dai_name; struct sof_ipc_comp_dai comp_dai; int number_configs; int current_config; + bool configured; /* DAI configured during BE hw_params */ struct sof_ipc_dai_config *dai_config; struct list_head list; /* list in sdev dai list */ }; @@ -148,6 +173,8 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, unsigned int size); int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int __user *binary_data, unsigned int size); +void snd_sof_control_notify(struct snd_sof_dev *sdev, + struct sof_ipc_ctrl_data *cdata); /* * Topology. @@ -155,10 +182,10 @@ int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int _ * be freed by snd_soc_unregister_component, */ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file); -int snd_sof_complete_pipeline(struct device *dev, +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); -int sof_load_pipeline_ipc(struct device *dev, +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, struct sof_ipc_pipe_new *pipeline, struct sof_ipc_comp_reply *r); int sof_pipeline_core_enable(struct snd_sof_dev *sdev, @@ -205,7 +232,15 @@ struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev, int pipeline_id); void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); -void snd_sof_pcm_period_elapsed_work(struct work_struct *work); +void snd_sof_pcm_init_elapsed_work(struct work_struct *work); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream); +void snd_sof_compr_init_elapsed_work(struct work_struct *work); +#else +static inline void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) { } +static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { } +#endif /* * Mixer IPC @@ -220,7 +255,8 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); /* PM */ -int sof_restore_pipelines(struct device *dev); +int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify); +int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify); int sof_set_hw_params_upon_resume(struct device *dev); bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev); bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev); @@ -229,4 +265,10 @@ bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev); int sof_machine_register(struct snd_sof_dev *sdev, void *pdata); void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata); +int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); +int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); + +/* PCM */ +int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir); +int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir); #endif diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c index d1a21edfa05d..885430a42226 100644 --- a/sound/soc/sof/sof-of-dev.c +++ b/sound/soc/sof/sof-of-dev.c @@ -7,14 +7,20 @@ #include <linux/firmware.h> #include <linux/module.h> +#include <linux/moduleparam.h> #include <linux/pm_runtime.h> #include <sound/sof.h> #include "ops.h" +#include "imx/imx-ops.h" -extern struct snd_sof_dsp_ops sof_imx8_ops; -extern struct snd_sof_dsp_ops sof_imx8x_ops; -extern struct snd_sof_dsp_ops sof_imx8m_ops; +static char *fw_path; +module_param(fw_path, charp, 0444); +MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware."); + +static char *tplg_path; +module_param(tplg_path, charp, 0444); +MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); /* platform specific devices */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) @@ -90,9 +96,15 @@ static int sof_of_probe(struct platform_device *pdev) sof_pdata->dev = &pdev->dev; sof_pdata->fw_filename = desc->default_fw_filename; - /* TODO: read alternate fw and tplg filenames from DT */ - sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; - sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path; + if (fw_path) + sof_pdata->fw_filename_prefix = fw_path; + else + sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; + + if (tplg_path) + sof_pdata->tplg_filename_prefix = tplg_path; + else + sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path; /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_of_probe_complete; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index fd8423172d8f..ba341b1bda0c 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -23,13 +23,23 @@ /* debug flags */ #define SOF_DBG_ENABLE_TRACE BIT(0) #define SOF_DBG_RETAIN_CTX BIT(1) /* prevent DSP D3 on FW exception */ +#define SOF_DBG_VERIFY_TPLG BIT(2) /* verify topology during load */ +#define SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE BIT(3) /* 0: use topology token + * 1: override topology + */ +#define SOF_DBG_DYNAMIC_PIPELINES_ENABLE BIT(4) /* 0: use static pipelines + * 1: use dynamic pipelines + */ +#define SOF_DBG_DISABLE_MULTICORE BIT(5) /* schedule all pipelines/widgets + * on primary core + */ +#define SOF_DBG_PRINT_ALL_DUMPS BIT(6) /* Print all ipc and dsp dumps */ #define SOF_DBG_DUMP_REGS BIT(0) #define SOF_DBG_DUMP_MBOX BIT(1) #define SOF_DBG_DUMP_TEXT BIT(2) #define SOF_DBG_DUMP_PCI BIT(3) -#define SOF_DBG_DUMP_FORCE_ERR_LEVEL BIT(4) /* used to dump dsp status with error log level */ - +#define SOF_DBG_DUMP_OPTIONAL BIT(4) /* only dump if SOF_DBG_PRINT_ALL_DUMPS is set */ /* global debug state set by SOF_DBG_ flags */ extern int sof_core_debug; @@ -83,6 +93,16 @@ enum sof_system_suspend_state { SOF_SUSPEND_S3, }; +enum sof_dfsentry_type { + SOF_DFSENTRY_TYPE_IOMEM = 0, + SOF_DFSENTRY_TYPE_BUF, +}; + +enum sof_debugfs_access_type { + SOF_DEBUGFS_ACCESS_ALWAYS = 0, + SOF_DEBUGFS_ACCESS_D0_ONLY, +}; + struct snd_sof_dev; struct snd_sof_ipc_msg; struct snd_sof_ipc; @@ -127,12 +147,20 @@ struct snd_sof_dsp_ops { void __iomem *addr); /* optional */ /* memcpy IO */ - void (*block_read)(struct snd_sof_dev *sof_dev, u32 bar, - u32 offset, void *dest, - size_t size); /* mandatory */ - void (*block_write)(struct snd_sof_dev *sof_dev, u32 bar, - u32 offset, void *src, - size_t size); /* mandatory */ + int (*block_read)(struct snd_sof_dev *sof_dev, + enum snd_sof_fw_blk_type type, u32 offset, + void *dest, size_t size); /* mandatory */ + int (*block_write)(struct snd_sof_dev *sof_dev, + enum snd_sof_fw_blk_type type, u32 offset, + void *src, size_t size); /* mandatory */ + + /* Mailbox IO */ + void (*mailbox_read)(struct snd_sof_dev *sof_dev, + u32 offset, void *dest, + size_t size); /* optional */ + void (*mailbox_write)(struct snd_sof_dev *sof_dev, + u32 offset, void *src, + size_t size); /* optional */ /* doorbell */ irqreturn_t (*irq_handler)(int irq, void *context); /* optional */ @@ -200,9 +228,9 @@ struct snd_sof_dsp_ops { #endif /* host read DSP stream data */ - void (*ipc_msg_data)(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz); /* mandatory */ + int (*ipc_msg_data)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); /* mandatory */ /* host configure DSP HW parameters */ int (*ipc_pcm_params)(struct snd_sof_dev *sdev, @@ -237,6 +265,10 @@ struct snd_sof_dsp_ops { void (*dbg_dump)(struct snd_sof_dev *sof_dev, u32 flags); /* optional */ void (*ipc_dump)(struct snd_sof_dev *sof_dev); /* optional */ + int (*debugfs_add_region_item)(struct snd_sof_dev *sdev, + enum snd_sof_fw_blk_type blk_type, u32 offset, + size_t size, const char *name, + enum sof_debugfs_access_type access_type); /* optional */ /* host DMA trace initialization */ int (*trace_init)(struct snd_sof_dev *sdev, @@ -268,33 +300,17 @@ struct snd_sof_dsp_ops { /* ALSA HW info flags, will be stored in snd_pcm_runtime.hw.info */ u32 hw_info; - const struct sof_arch_ops *arch_ops; + const struct dsp_arch_ops *dsp_arch_ops; }; /* DSP architecture specific callbacks for oops and stack dumps */ -struct sof_arch_ops { +struct dsp_arch_ops { void (*dsp_oops)(struct snd_sof_dev *sdev, void *oops); void (*dsp_stack)(struct snd_sof_dev *sdev, void *oops, u32 *stack, u32 stack_words); }; -#define sof_arch_ops(sdev) ((sdev)->pdata->desc->ops->arch_ops) - -/* DSP device HW descriptor mapping between bus ID and ops */ -struct sof_ops_table { - const struct sof_dev_desc *desc; - const struct snd_sof_dsp_ops *ops; -}; - -enum sof_dfsentry_type { - SOF_DFSENTRY_TYPE_IOMEM = 0, - SOF_DFSENTRY_TYPE_BUF, -}; - -enum sof_debugfs_access_type { - SOF_DEBUGFS_ACCESS_ALWAYS = 0, - SOF_DEBUGFS_ACCESS_D0_ONLY, -}; +#define sof_dsp_arch_ops(sdev) ((sdev)->pdata->desc->ops->dsp_arch_ops) /* FS entry for debug files that can expose DSP memories, registers */ struct snd_sof_dfsentry { @@ -413,6 +429,8 @@ struct snd_sof_dev { /* debug */ struct dentry *debugfs_root; struct list_head dfsentry_list; + bool dbg_dump_printed; + bool ipc_dump_printed; /* firmware loader */ struct snd_dma_buffer dmab; @@ -485,14 +503,12 @@ int snd_sof_create_page_table(struct device *dev, /* * Firmware loading. */ -int snd_sof_load_firmware(struct snd_sof_dev *sdev); int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev); int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev); int snd_sof_run_firmware(struct snd_sof_dev *sdev); int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, struct snd_sof_mod_hdr *module); void snd_sof_fw_unload(struct snd_sof_dev *sdev); -int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset); /* * IPC low level APIs. @@ -503,9 +519,6 @@ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id); void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev); int snd_sof_ipc_stream_pcm_params(struct snd_sof_dev *sdev, struct sof_ipc_pcm_params *params); -int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox, - size_t dspbox_size, u32 hostbox, - size_t hostbox_size); int snd_sof_ipc_valid(struct snd_sof_dev *sdev); int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, void *msg_data, size_t msg_bytes, void *reply_data, @@ -513,6 +526,7 @@ int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes); +int sof_ipc_init_msg_memory(struct snd_sof_dev *sdev); /* * Trace/debug @@ -522,10 +536,6 @@ void snd_sof_release_trace(struct snd_sof_dev *sdev); void snd_sof_free_trace(struct snd_sof_dev *sdev); int snd_sof_dbg_init(struct snd_sof_dev *sdev); void snd_sof_free_debug(struct snd_sof_dev *sdev); -int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev, - void __iomem *base, size_t size, - const char *name, - enum sof_debugfs_access_type access_type); int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, void *base, size_t size, const char *name, mode_t mode); @@ -539,11 +549,9 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev); int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev); - -/* - * Platform specific ops. - */ -extern struct snd_compress_ops sof_compressed_ops; +int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev, + enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size, + const char *name, enum sof_debugfs_access_type access_type); /* * DSP Architectures. @@ -551,16 +559,29 @@ extern struct snd_compress_ops sof_compressed_ops; static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, u32 stack_words) { - sof_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words); + sof_dsp_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words); } static inline void sof_oops(struct snd_sof_dev *sdev, void *oops) { - if (sof_arch_ops(sdev)->dsp_oops) - sof_arch_ops(sdev)->dsp_oops(sdev, oops); + if (sof_dsp_arch_ops(sdev)->dsp_oops) + sof_dsp_arch_ops(sdev)->dsp_oops(sdev, oops); } -extern const struct sof_arch_ops sof_xtensa_arch_ops; +extern const struct dsp_arch_ops sof_xtensa_arch_ops; + +/* + * Firmware state tracking + */ +static inline void sof_set_fw_state(struct snd_sof_dev *sdev, + enum snd_sof_fw_state new_state) +{ + if (sdev->fw_state == new_state) + return; + + dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state); + sdev->fw_state = new_state; +} /* * Utilities @@ -573,33 +594,24 @@ void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes); void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes); -void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, - size_t size); -void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, - size_t size); +int sof_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *src, size_t size); +int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *dest, size_t size); int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id); -void intel_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz); -int intel_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply); +int sof_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz); +int sof_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); -int intel_pcm_open(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream); -int intel_pcm_close(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream); +int sof_stream_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int sof_stream_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); int sof_machine_check(struct snd_sof_dev *sdev); - -#define sof_dev_dbg_or_err(dev, is_err, fmt, ...) \ - do { \ - if (is_err) \ - dev_err(dev, "error: " fmt, __VA_ARGS__); \ - else \ - dev_dbg(dev, fmt, __VA_ARGS__); \ - } while (0) - #endif diff --git a/sound/soc/sof/probe.c b/sound/soc/sof/sof-probes.c index 14509f4d3f86..5586af9f1a25 100644 --- a/sound/soc/sof/probe.c +++ b/sound/soc/sof/sof-probes.c @@ -3,13 +3,46 @@ // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // -// Copyright(c) 2019-2020 Intel Corporation. All rights reserved. -// +// Copyright(c) 2019-2021 Intel Corporation. All rights reserved. // Author: Cezary Rojewski <cezary.rojewski@intel.com> // +#include <sound/soc.h> +#include "ops.h" #include "sof-priv.h" -#include "probe.h" +#include "sof-probes.h" + +struct sof_probe_dma { + unsigned int stream_tag; + unsigned int dma_buffer_size; +} __packed; + +struct sof_ipc_probe_dma_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_dma dma[]; +} __packed; + +struct sof_ipc_probe_info_params { + struct sof_ipc_reply rhdr; + unsigned int num_elems; + union { + struct sof_probe_dma dma[0]; + struct sof_probe_point_desc desc[0]; + }; +} __packed; + +struct sof_ipc_probe_point_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_point_desc desc[]; +} __packed; + +struct sof_ipc_probe_point_remove_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + unsigned int buffer_id[]; +} __packed; /** * sof_ipc_probe_init - initialize data probing @@ -25,8 +58,8 @@ * Probing is initialized only once and each INIT request must be * matched by DEINIT call. */ -int sof_ipc_probe_init(struct snd_sof_dev *sdev, - u32 stream_tag, size_t buffer_size) +static int sof_ipc_probe_init(struct snd_sof_dev *sdev, u32 stream_tag, + size_t buffer_size) { struct sof_ipc_probe_dma_add_params *msg; struct sof_ipc_reply reply; @@ -47,7 +80,6 @@ int sof_ipc_probe_init(struct snd_sof_dev *sdev, kfree(msg); return ret; } -EXPORT_SYMBOL(sof_ipc_probe_init); /** * sof_ipc_probe_deinit - cleanup after data probing @@ -57,7 +89,7 @@ EXPORT_SYMBOL(sof_ipc_probe_init); * on DSP side once it is no longer needed. DEINIT only when there * are no probes connected and with all injectors detached. */ -int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) +static int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) { struct sof_ipc_cmd_hdr msg; struct sof_ipc_reply reply; @@ -68,10 +100,9 @@ int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) return sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, &reply, sizeof(reply)); } -EXPORT_SYMBOL(sof_ipc_probe_deinit); static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, - void **params, size_t *num_params) + void **params, size_t *num_params) { struct sof_ipc_probe_info_params msg = {{{0}}}; struct sof_ipc_probe_info_params *reply; @@ -113,97 +144,6 @@ exit: } /** - * sof_ipc_probe_dma_info - retrieve list of active injection dmas - * @sdev: SOF sound device - * @dma: Returned list of active dmas - * @num_dma: Returned count of active dmas - * - * Host sends DMA_INFO request to obtain list of injection dmas it - * can use to transfer data over with. - * - * Note that list contains only injection dmas as there is only one - * extractor (dma) and it is always assigned on probing init. - * DSP knows exactly where data from extraction probes is going to, - * which is not the case for injection where multiple streams - * could be engaged. - */ -int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev, - struct sof_probe_dma **dma, size_t *num_dma) -{ - return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_DMA_INFO, - (void **)dma, num_dma); -} -EXPORT_SYMBOL(sof_ipc_probe_dma_info); - -/** - * sof_ipc_probe_dma_add - attach to specified dmas - * @sdev: SOF sound device - * @dma: List of streams (dmas) to attach to - * @num_dma: Number of elements in @dma - * - * Contrary to extraction, injection streams are never assigned - * on init. Before attempting any data injection, host is responsible - * for specifying streams which will be later used to transfer data - * to connected probe points. - */ -int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev, - struct sof_probe_dma *dma, size_t num_dma) -{ - struct sof_ipc_probe_dma_add_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, dma, num_dma); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->num_elems = num_dma; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_ADD; - memcpy(&msg->dma[0], dma, size - sizeof(*msg)); - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} -EXPORT_SYMBOL(sof_ipc_probe_dma_add); - -/** - * sof_ipc_probe_dma_remove - detach from specified dmas - * @sdev: SOF sound device - * @stream_tag: List of stream tags to detach from - * @num_stream_tag: Number of elements in @stream_tag - * - * Host sends DMA_REMOVE request to free previously attached stream - * from being occupied for injection. Each detach operation should - * match equivalent DMA_ADD. Detach only when all probes tied to - * given stream have been disconnected. - */ -int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev, - unsigned int *stream_tag, size_t num_stream_tag) -{ - struct sof_ipc_probe_dma_remove_params *msg; - struct sof_ipc_reply reply; - size_t size = struct_size(msg, stream_tag, num_stream_tag); - int ret; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - msg->hdr.size = size; - msg->num_elems = num_stream_tag; - msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_REMOVE; - memcpy(&msg->stream_tag[0], stream_tag, size - sizeof(*msg)); - - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - kfree(msg); - return ret; -} -EXPORT_SYMBOL(sof_ipc_probe_dma_remove); - -/** * sof_ipc_probe_points_info - retrieve list of active probe points * @sdev: SOF sound device * @desc: Returned list of active probes @@ -214,7 +154,8 @@ EXPORT_SYMBOL(sof_ipc_probe_dma_remove); * required. */ int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, size_t *num_desc) + struct sof_probe_point_desc **desc, + size_t *num_desc) { return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_POINT_INFO, (void **)desc, num_desc); @@ -235,7 +176,7 @@ EXPORT_SYMBOL(sof_ipc_probe_points_info); * request when no longer needed. */ int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, size_t num_desc) + struct sof_probe_point_desc *desc, size_t num_desc) { struct sof_ipc_probe_point_add_params *msg; struct sof_ipc_reply reply; @@ -267,7 +208,7 @@ EXPORT_SYMBOL(sof_ipc_probe_points_add); * points and frees all resources on DSP side. */ int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id) + unsigned int *buffer_id, size_t num_buffer_id) { struct sof_ipc_probe_point_remove_params *msg; struct sof_ipc_reply reply; @@ -288,3 +229,136 @@ int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, return ret; } EXPORT_SYMBOL(sof_ipc_probe_points_remove); + +static int sof_probe_compr_startup(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + int ret; + + ret = snd_sof_probe_compr_assign(sdev, cstream, dai); + if (ret < 0) { + dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); + return ret; + } + + sdev->extractor_stream_tag = ret; + return 0; +} + +static int sof_probe_compr_shutdown(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + struct sof_probe_point_desc *desc; + size_t num_desc; + int i, ret; + + /* disconnect all probe points */ + ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); + if (ret < 0) { + dev_err(dai->dev, "Failed to get probe points: %d\n", ret); + goto exit; + } + + for (i = 0; i < num_desc; i++) + sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1); + kfree(desc); + +exit: + ret = sof_ipc_probe_deinit(sdev); + if (ret < 0) + dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); + + sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; + snd_compr_free_pages(cstream); + + return snd_sof_probe_compr_free(sdev, cstream, dai); +} + +static int sof_probe_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + struct snd_compr_runtime *rtd = cstream->runtime; + int ret; + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = sdev->dev; + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; + + ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai); + if (ret < 0) + return ret; + + ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag, + rtd->dma_bytes); + if (ret < 0) { + dev_err(dai->dev, "Failed to init probe: %d\n", ret); + return ret; + } + + return 0; +} + +static int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + + return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai); +} + +static int sof_probe_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + + return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai); +} + +struct snd_soc_cdai_ops sof_probe_compr_ops = { + .startup = sof_probe_compr_startup, + .shutdown = sof_probe_compr_shutdown, + .set_params = sof_probe_compr_set_params, + .trigger = sof_probe_compr_trigger, + .pointer = sof_probe_compr_pointer, +}; +EXPORT_SYMBOL(sof_probe_compr_ops); + +static int sof_probe_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + unsigned int offset, n; + void *ptr; + int ret; + + if (count > rtd->buffer_size) + count = rtd->buffer_size; + + div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); + ptr = rtd->dma_area + offset; + n = rtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, rtd->dma_area, count - n); + } + + if (ret) + return count - ret; + return count; +} + +const struct snd_compress_ops sof_probe_compressed_ops = { + .copy = sof_probe_compr_copy, +}; +EXPORT_SYMBOL(sof_probe_compressed_ops); diff --git a/sound/soc/sof/sof-probes.h b/sound/soc/sof/sof-probes.h new file mode 100644 index 000000000000..35e1dd8d9e03 --- /dev/null +++ b/sound/soc/sof/sof-probes.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2019-2021 Intel Corporation. All rights reserved. + * Author: Cezary Rojewski <cezary.rojewski@intel.com> + */ + +#ifndef __SOF_PROBES_H +#define __SOF_PROBES_H + +#include <sound/compress_driver.h> +#include <sound/sof/header.h> + +struct snd_sof_dev; + +#define SOF_PROBE_INVALID_NODE_ID UINT_MAX + +struct sof_probe_point_desc { + unsigned int buffer_id; + unsigned int purpose; + unsigned int stream_tag; +} __packed; + +int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, + struct sof_probe_point_desc **desc, + size_t *num_desc); +int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, + struct sof_probe_point_desc *desc, + size_t num_desc); +int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, + unsigned int *buffer_id, size_t num_buffer_id); + +extern struct snd_soc_cdai_ops sof_probe_compr_ops; +extern const struct snd_compress_ops sof_probe_compressed_ops; + +#endif diff --git a/sound/soc/sof/intel/intel-ipc.c b/sound/soc/sof/stream-ipc.c index de66f8a82a07..15a55851faeb 100644 --- a/sound/soc/sof/intel/intel-ipc.c +++ b/sound/soc/sof/stream-ipc.c @@ -7,7 +7,7 @@ // // Authors: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com> -/* Intel-specific SOF IPC code */ +/* Generic SOF IPC code */ #include <linux/device.h> #include <linux/export.h> @@ -17,35 +17,39 @@ #include <sound/pcm.h> #include <sound/sof/stream.h> -#include "../ops.h" -#include "../sof-priv.h" +#include "ops.h" +#include "sof-priv.h" -struct intel_stream { +struct sof_stream { size_t posn_offset; }; -/* Mailbox-based Intel IPC implementation */ -void intel_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz) +/* Mailbox-based Generic IPC implementation */ +int sof_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) { if (!substream || !sdev->stream_box.size) { - sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); } else { - struct intel_stream *stream = substream->runtime->private_data; + struct sof_stream *stream = substream->runtime->private_data; /* The stream might already be closed */ - if (stream) - sof_mailbox_read(sdev, stream->posn_offset, p, sz); + if (!stream) + return -ESTRPIPE; + + snd_sof_dsp_mailbox_read(sdev, stream->posn_offset, p, sz); } + + return 0; } -EXPORT_SYMBOL_NS(intel_ipc_msg_data, SND_SOC_SOF_INTEL_HIFI_EP_IPC); +EXPORT_SYMBOL(sof_ipc_msg_data); -int intel_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) +int sof_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) { - struct intel_stream *stream = substream->runtime->private_data; + struct sof_stream *stream = substream->runtime->private_data; size_t posn_offset = reply->posn_offset; /* check if offset is overflow or it is not aligned */ @@ -60,12 +64,12 @@ int intel_ipc_pcm_params(struct snd_sof_dev *sdev, return 0; } -EXPORT_SYMBOL_NS(intel_ipc_pcm_params, SND_SOC_SOF_INTEL_HIFI_EP_IPC); +EXPORT_SYMBOL(sof_ipc_pcm_params); -int intel_pcm_open(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream) +int sof_stream_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) { - struct intel_stream *stream = kmalloc(sizeof(*stream), GFP_KERNEL); + struct sof_stream *stream = kmalloc(sizeof(*stream), GFP_KERNEL); if (!stream) return -ENOMEM; @@ -82,18 +86,18 @@ int intel_pcm_open(struct snd_sof_dev *sdev, return 0; } -EXPORT_SYMBOL_NS(intel_pcm_open, SND_SOC_SOF_INTEL_HIFI_EP_IPC); +EXPORT_SYMBOL(sof_stream_pcm_open); -int intel_pcm_close(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream) +int sof_stream_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) { - struct intel_stream *stream = substream->runtime->private_data; + struct sof_stream *stream = substream->runtime->private_data; substream->runtime->private_data = NULL; kfree(stream); return 0; } -EXPORT_SYMBOL_NS(intel_pcm_close, SND_SOC_SOF_INTEL_HIFI_EP_IPC); +EXPORT_SYMBOL(sof_stream_pcm_close); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index cc9585bfa4e9..bb9e62bbe5db 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -572,6 +572,12 @@ static const struct sof_topology_token sched_tokens[] = { offsetof(struct sof_ipc_pipe_new, time_domain), 0}, }; +static const struct sof_topology_token pipeline_tokens[] = { + {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct snd_sof_widget, dynamic_pipeline_widget), 0}, + +}; + /* volume */ static const struct sof_topology_token volume_tokens[] = { {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, @@ -1250,6 +1256,7 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, return -ENOMEM; scontrol->scomp = scomp; + scontrol->access = kc->access; switch (le32_to_cpu(hdr->ops.info)) { case SND_SOC_TPLG_CTL_VOLSW: @@ -1512,10 +1519,8 @@ static struct sof_ipc_comp *sof_comp_alloc(struct snd_sof_widget *swidget, static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r, struct snd_sof_dai *dai) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_dai *comp_dai; size_t ipc_size = sizeof(*comp_dai); @@ -1552,10 +1557,7 @@ static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, swidget->widget->name, comp_dai->type, comp_dai->dai_index); sof_dbg_comp_config(scomp, &comp_dai->config); - ret = sof_ipc_tx_message(sdev->ipc, comp_dai->comp.hdr.cmd, - comp_dai, ipc_size, r, sizeof(*r)); - - if (ret == 0 && dai) { + if (dai) { dai->scomp = scomp; /* @@ -1577,10 +1579,8 @@ finish: static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_buffer *buffer; int ret; @@ -1612,15 +1612,7 @@ static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, swidget->private = buffer; - ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer, - sizeof(*buffer), r, sizeof(*r)); - if (ret < 0) { - dev_err(scomp->dev, "error: buffer %s load failed\n", - swidget->widget->name); - kfree(buffer); - } - - return ret; + return 0; } /* bind PCM ID to host component ID */ @@ -1649,10 +1641,8 @@ static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm, static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, enum sof_ipc_stream_direction dir, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_host *host; size_t ipc_size = sizeof(*host); @@ -1691,10 +1681,7 @@ static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, swidget->private = host; - ret = sof_ipc_tx_message(sdev->ipc, host->comp.hdr.cmd, host, - ipc_size, r, sizeof(*r)); - if (ret >= 0) - return ret; + return 0; err: kfree(host); return ret; @@ -1703,11 +1690,10 @@ err: /* * Pipeline Topology */ -int sof_load_pipeline_ipc(struct device *dev, +int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, struct sof_ipc_pipe_new *pipeline, struct sof_ipc_comp_reply *r) { - struct snd_sof_dev *sdev = dev_get_drvdata(dev); int ret = sof_core_enable(sdev, pipeline->core); if (ret < 0) @@ -1716,15 +1702,14 @@ int sof_load_pipeline_ipc(struct device *dev, ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline, sizeof(*pipeline), r, sizeof(*r)); if (ret < 0) - dev_err(dev, "error: load pipeline ipc failure\n"); + dev_err(sdev->dev, "error: load pipeline ipc failure\n"); return ret; } static int sof_widget_load_pipeline(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_pipe_new *pipeline; @@ -1764,16 +1749,30 @@ static int sof_widget_load_pipeline(struct snd_soc_component *scomp, int index, goto err; } - dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d\n", + ret = sof_parse_tokens(scomp, swidget, pipeline_tokens, + ARRAY_SIZE(pipeline_tokens), private->array, + le32_to_cpu(private->size)); + if (ret != 0) { + dev_err(scomp->dev, "error: parse dynamic pipeline token failed %d\n", + private->size); + goto err; + } + + if (sof_core_debug & SOF_DBG_DISABLE_MULTICORE) + pipeline->core = SOF_DSP_PRIMARY_CORE; + + if (sof_core_debug & SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE) + swidget->dynamic_pipeline_widget = sof_core_debug & + SOF_DBG_DYNAMIC_PIPELINES_ENABLE; + + dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d dynamic %d\n", swidget->widget->name, pipeline->period, pipeline->priority, - pipeline->period_mips, pipeline->core, pipeline->frames_per_sched); + pipeline->period_mips, pipeline->core, pipeline->frames_per_sched, + swidget->dynamic_pipeline_widget); swidget->private = pipeline; - /* send ipc's to create pipeline comp and power up schedule core */ - ret = sof_load_pipeline_ipc(scomp->dev, pipeline, r); - if (ret >= 0) - return ret; + return 0; err: kfree(pipeline); return ret; @@ -1785,10 +1784,8 @@ err: static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_mixer *mixer; size_t ipc_size = sizeof(*mixer); @@ -1817,12 +1814,7 @@ static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index, swidget->private = mixer; - ret = sof_ipc_tx_message(sdev->ipc, mixer->comp.hdr.cmd, mixer, - ipc_size, r, sizeof(*r)); - if (ret < 0) - kfree(mixer); - - return ret; + return 0; } /* @@ -1830,10 +1822,8 @@ static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index, */ static int sof_widget_load_mux(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_mux *mux; size_t ipc_size = sizeof(*mux); @@ -1862,12 +1852,7 @@ static int sof_widget_load_mux(struct snd_soc_component *scomp, int index, swidget->private = mux; - ret = sof_ipc_tx_message(sdev->ipc, mux->comp.hdr.cmd, mux, - ipc_size, r, sizeof(*r)); - if (ret < 0) - kfree(mux); - - return ret; + return 0; } /* @@ -1876,8 +1861,7 @@ static int sof_widget_load_mux(struct snd_soc_component *scomp, int index, static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; @@ -1937,10 +1921,7 @@ static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, } } - ret = sof_ipc_tx_message(sdev->ipc, volume->comp.hdr.cmd, volume, - ipc_size, r, sizeof(*r)); - if (ret >= 0) - return ret; + return 0; err: kfree(volume); return ret; @@ -1952,10 +1933,8 @@ err: static int sof_widget_load_src(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_src *src; size_t ipc_size = sizeof(*src); @@ -1994,10 +1973,7 @@ static int sof_widget_load_src(struct snd_soc_component *scomp, int index, swidget->private = src; - ret = sof_ipc_tx_message(sdev->ipc, src->comp.hdr.cmd, src, - ipc_size, r, sizeof(*r)); - if (ret >= 0) - return ret; + return 0; err: kfree(src); return ret; @@ -2009,10 +1985,8 @@ err: static int sof_widget_load_asrc(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_asrc *asrc; size_t ipc_size = sizeof(*asrc); @@ -2053,10 +2027,7 @@ static int sof_widget_load_asrc(struct snd_soc_component *scomp, int index, swidget->private = asrc; - ret = sof_ipc_tx_message(sdev->ipc, asrc->comp.hdr.cmd, asrc, - ipc_size, r, sizeof(*r)); - if (ret >= 0) - return ret; + return 0; err: kfree(asrc); return ret; @@ -2068,10 +2039,8 @@ err: static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_tone *tone; size_t ipc_size = sizeof(*tone); @@ -2110,10 +2079,7 @@ static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, swidget->private = tone; - ret = sof_ipc_tx_message(sdev->ipc, tone->comp.hdr.cmd, tone, - ipc_size, r, sizeof(*r)); - if (ret >= 0) - return ret; + return 0; err: kfree(tone); return ret; @@ -2195,10 +2161,8 @@ static int sof_get_control_data(struct snd_soc_component *scomp, static int sof_process_load(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r, int type) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_dapm_widget *widget = swidget->widget; struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_process *process; @@ -2272,33 +2236,6 @@ static int sof_process_load(struct snd_soc_component *scomp, int index, process->size = ipc_data_size; swidget->private = process; - - ret = sof_ipc_tx_message(sdev->ipc, process->comp.hdr.cmd, process, - ipc_size, r, sizeof(*r)); - - if (ret < 0) { - dev_err(scomp->dev, "error: create process failed\n"); - goto err; - } - - /* we sent the data in single message so return */ - if (ipc_data_size) - goto out; - - /* send control data with large message supported method */ - for (i = 0; i < widget->num_kcontrols; i++) { - wdata[i].control->readback_offset = 0; - ret = snd_sof_ipc_set_get_comp_data(wdata[i].control, - wdata[i].ipc_cmd, - wdata[i].ctrl_type, - wdata[i].control->cmd, - true); - if (ret != 0) { - dev_err(scomp->dev, "error: send control failed\n"); - break; - } - } - err: if (ret < 0) kfree(process); @@ -2314,8 +2251,7 @@ out: static int sof_widget_load_process(struct snd_soc_component *scomp, int index, struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - struct sof_ipc_comp_reply *r) + struct snd_soc_tplg_dapm_widget *tw) { struct snd_soc_tplg_private *private = &tw->priv; struct sof_ipc_comp_process config; @@ -2341,8 +2277,7 @@ static int sof_widget_load_process(struct snd_soc_component *scomp, int index, } /* now load process specific data and send IPC */ - ret = sof_process_load(scomp, index, swidget, tw, r, - find_process_comp_type(config.type)); + ret = sof_process_load(scomp, index, swidget, tw, find_process_comp_type(config.type)); if (ret < 0) { dev_err(scomp->dev, "error: process loading failed\n"); return ret; @@ -2391,8 +2326,6 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_widget *swidget; struct snd_sof_dai *dai; - struct sof_ipc_comp_reply reply; - struct snd_sof_control *scontrol; struct sof_ipc_comp comp = { .core = SOF_DSP_PRIMARY_CORE, }; @@ -2409,7 +2342,6 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, swidget->id = w->id; swidget->pipeline_id = index; swidget->private = NULL; - memset(&reply, 0, sizeof(reply)); dev_dbg(scomp->dev, "tplg: ready widget id %d pipe %d type %d name : %s stream %s\n", swidget->comp_id, index, swidget->id, tw->name, @@ -2426,15 +2358,10 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, return ret; } - swidget->core = comp.core; + if (sof_core_debug & SOF_DBG_DISABLE_MULTICORE) + comp.core = SOF_DSP_PRIMARY_CORE; - /* default is primary core, safe to call for already enabled cores */ - ret = sof_core_enable(sdev, comp.core); - if (ret < 0) { - dev_err(scomp->dev, "error: enable core: %d\n", ret); - kfree(swidget); - return ret; - } + swidget->core = comp.core; ret = sof_parse_tokens(scomp, &swidget->comp_ext, comp_ext_tokens, ARRAY_SIZE(comp_ext_tokens), tw->priv.array, @@ -2456,57 +2383,51 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, return -ENOMEM; } - ret = sof_widget_load_dai(scomp, index, swidget, tw, &reply, dai); - if (ret == 0) { - sof_connect_dai_widget(scomp, w, tw, dai); - list_add(&dai->list, &sdev->dai_list); - swidget->private = dai; - } else { + ret = sof_widget_load_dai(scomp, index, swidget, tw, dai); + if (!ret) + ret = sof_connect_dai_widget(scomp, w, tw, dai); + if (ret < 0) { kfree(dai); + break; } + list_add(&dai->list, &sdev->dai_list); + swidget->private = dai; break; case snd_soc_dapm_mixer: - ret = sof_widget_load_mixer(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_mixer(scomp, index, swidget, tw); break; case snd_soc_dapm_pga: - ret = sof_widget_load_pga(scomp, index, swidget, tw, &reply); - /* Find scontrol for this pga and set readback offset*/ - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { - if (scontrol->comp_id == swidget->comp_id) { - scontrol->readback_offset = reply.offset; - break; - } - } + ret = sof_widget_load_pga(scomp, index, swidget, tw); break; case snd_soc_dapm_buffer: - ret = sof_widget_load_buffer(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_buffer(scomp, index, swidget, tw); break; case snd_soc_dapm_scheduler: - ret = sof_widget_load_pipeline(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_pipeline(scomp, index, swidget, tw); break; case snd_soc_dapm_aif_out: ret = sof_widget_load_pcm(scomp, index, swidget, - SOF_IPC_STREAM_CAPTURE, tw, &reply); + SOF_IPC_STREAM_CAPTURE, tw); break; case snd_soc_dapm_aif_in: ret = sof_widget_load_pcm(scomp, index, swidget, - SOF_IPC_STREAM_PLAYBACK, tw, &reply); + SOF_IPC_STREAM_PLAYBACK, tw); break; case snd_soc_dapm_src: - ret = sof_widget_load_src(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_src(scomp, index, swidget, tw); break; case snd_soc_dapm_asrc: - ret = sof_widget_load_asrc(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_asrc(scomp, index, swidget, tw); break; case snd_soc_dapm_siggen: - ret = sof_widget_load_siggen(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_siggen(scomp, index, swidget, tw); break; case snd_soc_dapm_effect: - ret = sof_widget_load_process(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_process(scomp, index, swidget, tw); break; case snd_soc_dapm_mux: case snd_soc_dapm_demux: - ret = sof_widget_load_mux(scomp, index, swidget, tw, &reply); + ret = sof_widget_load_mux(scomp, index, swidget, tw); break; case snd_soc_dapm_switch: case snd_soc_dapm_dai_link: @@ -2517,12 +2438,12 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, } /* check IPC reply */ - if (ret < 0 || reply.rhdr.error < 0) { + if (ret < 0) { dev_err(scomp->dev, - "error: DSP failed to add widget id %d type %d name : %s stream %s reply %d\n", + "error: failed to add widget id %d type %d name : %s stream %s\n", tw->shift, swidget->id, tw->name, strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 - ? tw->sname : "none", reply.rhdr.error); + ? tw->sname : "none"); kfree(swidget); return ret; } @@ -2598,6 +2519,15 @@ static int sof_widget_unload(struct snd_soc_component *scomp, /* power down the pipeline schedule core */ pipeline = swidget->private; + + /* + * Runtime PM should still function normally if topology loading fails and + * it's components are unloaded. Do not power down the primary core so that the + * CTX_SAVE IPC can succeed during runtime suspend. + */ + if (pipeline->core == SOF_DSP_PRIMARY_CORE) + break; + ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core); if (ret < 0) dev_err(scomp->dev, "error: powering down pipeline schedule core %d\n", @@ -2671,8 +2601,10 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, for_each_pcm_streams(stream) { spcm->stream[stream].comp_id = COMP_ID_UNASSIGNED; - INIT_WORK(&spcm->stream[stream].period_elapsed_work, - snd_sof_pcm_period_elapsed_work); + if (pcm->compress) + snd_sof_compr_init_elapsed_work(&spcm->stream[stream].period_elapsed_work); + else + snd_sof_pcm_init_elapsed_work(&spcm->stream[stream].period_elapsed_work); } spcm->pcm = *pcm; @@ -2828,9 +2760,6 @@ static int sof_set_dai_config_multi(struct snd_sof_dev *sdev, u32 size, continue; if (strcmp(link->name, dai->name) == 0) { - struct sof_ipc_reply reply; - int ret; - /* * the same dai config will be applied to all DAIs in * the same dai link. We have to ensure that the ipc @@ -2842,18 +2771,6 @@ static int sof_set_dai_config_multi(struct snd_sof_dev *sdev, u32 size, dev_dbg(sdev->dev, "set DAI config for %s index %d\n", dai->name, config[curr_conf].dai_index); - /* send message to DSP */ - ret = sof_ipc_tx_message(sdev->ipc, - config[curr_conf].hdr.cmd, - &config[curr_conf], size, - &reply, sizeof(reply)); - - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to set DAI config for %s index %d\n", - dai->name, config[curr_conf].dai_index); - return ret; - } dai->number_configs = num_conf; dai->current_config = curr_conf; @@ -2861,9 +2778,6 @@ static int sof_set_dai_config_multi(struct snd_sof_dev *sdev, u32 size, if (!dai->dai_config) return -ENOMEM; - /* set cpu_dai_name */ - dai->cpu_dai_name = link->cpus->dai_name; - found = 1; } } @@ -2933,12 +2847,12 @@ static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, config[i].ssp.rx_slots = le32_to_cpu(hw_config[i].rx_slots); config[i].ssp.tx_slots = le32_to_cpu(hw_config[i].tx_slots); - dev_dbg(scomp->dev, "tplg: config SSP%d fmt 0x%x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d\n", + dev_dbg(scomp->dev, "tplg: config SSP%d fmt %#x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d clks_control %#x\n", config[i].dai_index, config[i].format, config[i].ssp.mclk_rate, config[i].ssp.bclk_rate, config[i].ssp.fsync_rate, config[i].ssp.sample_valid_bits, config[i].ssp.tdm_slot_width, config[i].ssp.tdm_slots, - config[i].ssp.mclk_id, config[i].ssp.quirks); + config[i].ssp.mclk_id, config[i].ssp.quirks, config[i].ssp.clks_control); /* validate SSP fsync rate and channel count */ if (config[i].ssp.fsync_rate < 8000 || config[i].ssp.fsync_rate > 192000) { @@ -3383,7 +3297,6 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, struct snd_sof_widget *source_swidget, *sink_swidget; struct snd_soc_dobj *dobj = &route->dobj; struct snd_sof_route *sroute; - struct sof_ipc_reply reply; int ret = 0; /* allocate memory for sroute and connect */ @@ -3458,33 +3371,11 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, route->source, route->sink); goto err; } else { - ret = sof_ipc_tx_message(sdev->ipc, - connect->hdr.cmd, - connect, sizeof(*connect), - &reply, sizeof(reply)); - - /* check IPC return value */ - if (ret < 0) { - dev_err(scomp->dev, "error: failed to add route sink %s control %s source %s\n", - route->sink, - route->control ? route->control : "none", - route->source); - goto err; - } - - /* check IPC reply */ - if (reply.error < 0) { - dev_err(scomp->dev, "error: DSP failed to add route sink %s control %s source %s result %d\n", - route->sink, - route->control ? route->control : "none", - route->source, reply.error); - ret = reply.error; - goto err; - } - sroute->route = route; dobj->private = sroute; sroute->private = connect; + sroute->src_widget = source_swidget; + sroute->sink_widget = sink_swidget; /* add route to route list */ list_add(&sroute->list, &sdev->route_list); @@ -3498,59 +3389,14 @@ err: return ret; } -/* Function to set the initial value of SOF kcontrols. - * The value will be stored in scontrol->control_data - */ -static int snd_sof_cache_kcontrol_val(struct snd_soc_component *scomp) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_sof_control *scontrol = NULL; - int ipc_cmd, ctrl_type; - int ret = 0; - - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { - - /* notify DSP of kcontrol values */ - switch (scontrol->cmd) { - case SOF_CTRL_CMD_VOLUME: - case SOF_CTRL_CMD_ENUM: - case SOF_CTRL_CMD_SWITCH: - ipc_cmd = SOF_IPC_COMP_GET_VALUE; - ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_GET; - break; - case SOF_CTRL_CMD_BINARY: - ipc_cmd = SOF_IPC_COMP_GET_DATA; - ctrl_type = SOF_CTRL_TYPE_DATA_GET; - break; - default: - dev_err(scomp->dev, - "error: Invalid scontrol->cmd: %d\n", - scontrol->cmd); - return -EINVAL; - } - ret = snd_sof_ipc_set_get_comp_data(scontrol, - ipc_cmd, ctrl_type, - scontrol->cmd, - false); - if (ret < 0) { - dev_warn(scomp->dev, - "error: kcontrol value get for widget: %d\n", - scontrol->comp_id); - } - } - - return ret; -} - -int snd_sof_complete_pipeline(struct device *dev, +int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { - struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct sof_ipc_pipe_ready ready; struct sof_ipc_reply reply; int ret; - dev_dbg(dev, "tplg: complete pipeline %s id %d\n", + dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n", swidget->widget->name, swidget->comp_id); memset(&ready, 0, sizeof(ready)); @@ -3566,31 +3412,84 @@ int snd_sof_complete_pipeline(struct device *dev, return 1; } +/** + * sof_set_pipe_widget - Set pipe_widget for a component + * @sdev: pointer to struct snd_sof_dev + * @pipe_widget: pointer to struct snd_sof_widget of type snd_soc_dapm_scheduler + * @swidget: pointer to struct snd_sof_widget that has the same pipeline ID as @pipe_widget + * + * Return: 0 if successful, -EINVAL on error. + * The function checks if @swidget is associated with any volatile controls. If so, setting + * the dynamic_pipeline_widget is disallowed. + */ +static int sof_set_pipe_widget(struct snd_sof_dev *sdev, struct snd_sof_widget *pipe_widget, + struct snd_sof_widget *swidget) +{ + struct snd_sof_control *scontrol; + + if (pipe_widget->dynamic_pipeline_widget) { + /* dynamic widgets cannot have volatile kcontrols */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) + if (scontrol->comp_id == swidget->comp_id && + (scontrol->access & SNDRV_CTL_ELEM_ACCESS_VOLATILE)) { + dev_err(sdev->dev, + "error: volatile control found for dynamic widget %s\n", + swidget->widget->name); + return -EINVAL; + } + } + + /* set the pipe_widget and apply the dynamic_pipeline_widget_flag */ + swidget->pipe_widget = pipe_widget; + swidget->dynamic_pipeline_widget = pipe_widget->dynamic_pipeline_widget; + + return 0; +} + /* completion - called at completion of firmware loading */ -static void sof_complete(struct snd_soc_component *scomp) +static int sof_complete(struct snd_soc_component *scomp) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_sof_widget *swidget; + struct snd_sof_widget *swidget, *comp_swidget; + int ret; - /* some widget types require completion notificattion */ + /* set the pipe_widget and apply the dynamic_pipeline_widget_flag */ list_for_each_entry(swidget, &sdev->widget_list, list) { - if (swidget->complete) - continue; - switch (swidget->id) { case snd_soc_dapm_scheduler: - swidget->complete = - snd_sof_complete_pipeline(scomp->dev, swidget); + /* + * Apply the dynamic_pipeline_widget flag and set the pipe_widget field + * for all widgets that have the same pipeline ID as the scheduler widget + */ + list_for_each_entry_reverse(comp_swidget, &sdev->widget_list, list) + if (comp_swidget->pipeline_id == swidget->pipeline_id) { + ret = sof_set_pipe_widget(sdev, swidget, comp_swidget); + if (ret < 0) + return ret; + } break; default: break; } } - /* - * cache initial values of SOF kcontrols by reading DSP value over - * IPC. It may be overwritten by alsa-mixer after booting up - */ - snd_sof_cache_kcontrol_val(scomp); + + /* verify topology components loading including dynamic pipelines */ + if (sof_core_debug & SOF_DBG_VERIFY_TPLG) { + ret = sof_set_up_pipelines(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, "error: topology verification failed %d\n", ret); + return ret; + } + + ret = sof_tear_down_pipelines(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, "error: topology tear down pipelines failed %d\n", ret); + return ret; + } + } + + /* set up static pipelines */ + return sof_set_up_pipelines(sdev, false); } /* manifest - optional to inform component of manifest */ diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index 58f6ca5cf491..e3afc3dac7d1 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -417,7 +417,7 @@ int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) "error: fail in snd_sof_dma_trace_init %d\n", ret); return ret; } - dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag); + dev_dbg(sdev->dev, "%s: stream_tag: %d\n", __func__, params.stream_tag); /* send IPC to the DSP */ ret = sof_ipc_tx_message(sdev->ipc, @@ -480,7 +480,8 @@ int snd_sof_init_trace(struct snd_sof_dev *sdev) goto table_err; sdev->dma_trace_pages = ret; - dev_dbg(sdev->dev, "dma_trace_pages: %d\n", sdev->dma_trace_pages); + dev_dbg(sdev->dev, "%s: dma_trace_pages: %d\n", + __func__, sdev->dma_trace_pages); if (sdev->first_boot) { ret = trace_debugfs_create(sdev); diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c index 5539d3afbe8f..66fa6602fb67 100644 --- a/sound/soc/sof/utils.c +++ b/sound/soc/sof/utils.c @@ -14,6 +14,7 @@ #include <sound/soc.h> #include <sound/sof.h> #include "sof-priv.h" +#include "ops.h" /* * Register IO @@ -72,15 +73,21 @@ EXPORT_SYMBOL(sof_mailbox_read); * Memory copy. */ -void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, - size_t size) +int sof_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *src, size_t size) { - void __iomem *dest = sdev->bar[bar] + offset; + int bar = snd_sof_dsp_get_bar_index(sdev, blk_type); const u8 *src_byte = src; + void __iomem *dest; u32 affected_mask; u32 tmp; int m, n; + if (bar < 0) + return bar; + + dest = sdev->bar[bar] + offset; + m = size / 4; n = size % 4; @@ -100,15 +107,22 @@ void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src, tmp |= *(u32 *)(src_byte + m * 4) & affected_mask; iowrite32(tmp, dest + m * 4); } + + return 0; } EXPORT_SYMBOL(sof_block_write); -void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, - size_t size) +int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *dest, size_t size) { - void __iomem *src = sdev->bar[bar] + offset; + int bar = snd_sof_dsp_get_bar_index(sdev, blk_type); + + if (bar < 0) + return bar; + + memcpy_fromio(dest, sdev->bar[bar] + offset, size); - memcpy_fromio(dest, src, size); + return 0; } EXPORT_SYMBOL(sof_block_read); diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c index f6e3411b33cf..bd09c3825caf 100644 --- a/sound/soc/sof/xtensa/core.c +++ b/sound/soc/sof/xtensa/core.c @@ -128,7 +128,7 @@ static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, } } -const struct sof_arch_ops sof_xtensa_arch_ops = { +const struct dsp_arch_ops sof_xtensa_arch_ops = { .dsp_oops = xtensa_dsp_oops, .dsp_stack = xtensa_stack, }; diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index 83c87f35a7d3..cd454871d654 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -108,6 +108,54 @@ config SND_SOC_TEGRA210_ADMAIF channel. Buffer size is configurable for each ADMAIIF channel. Say Y or M if you want to add support for Tegra210 ADMAIF module. +config SND_SOC_TEGRA210_MVC + tristate "Tegra210 MVC module" + help + Config to enable the digital Master Volume Controller (MVC) which + provides gain or attenuation to a digital signal path. It can be + used in input or output signal path. It can be used either for + per-stream volume control or for master volume control. + Say Y or M if you want to add support for Tegra210 MVC module. + +config SND_SOC_TEGRA210_SFC + tristate "Tegra210 SFC module" + help + Config to enable the Sampling Frequency Converter (SFC) which + converts the sampling frequency of input signal to another + frequency. It supports sampling frequency conversion of streams + upto 2 channels (stereo). + Say Y or M if you want to add support for Tegra210 SFC module. + +config SND_SOC_TEGRA210_AMX + tristate "Tegra210 AMX module" + help + Config to enable the Audio Multiplexer (AMX) which can multiplex + four input streams (each of up to 16 channels) and generate + output stream (of up to 16 channels). A byte RAM helps to form an + output frame by any combination of bytes from the input frames. + Say Y or M if you want to add support for Tegra210 AMX module. + +config SND_SOC_TEGRA210_ADX + tristate "Tegra210 ADX module" + help + Config to enable the Audio Demultiplexer (ADX) which takes an + input stream (up to 16 channels) and demultiplexes it into four + output streams (each of up to 16 channels). A byte RAM helps to + form output frames by any combination of bytes from the input + frame. Its design is identical to that of byte RAM in the AMX + except that the data flow direction is reversed. + Say Y or M if you want to add support for Tegra210 ADX module. + +config SND_SOC_TEGRA210_MIXER + tristate "Tegra210 Mixer module" + help + Config to enable the Mixer module which can help to mix multiple + audio streams. It supports mixing of upto 10 input streams, + where each stream can contain maximum of 8 channels. It supports + 5 output each of which can be a mix of any combination of 10 + input streams. + Say Y or M if you want to add support for Tegra210 Mixer module. + config SND_SOC_TEGRA_AUDIO_GRAPH_CARD tristate "Audio Graph Card based Tegra driver" depends on SND_AUDIO_GRAPH_CARD diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index e2cec9ae31c9..f19d56690a0d 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -13,6 +13,11 @@ snd-soc-tegra210-dmic-objs := tegra210_dmic.o snd-soc-tegra210-i2s-objs := tegra210_i2s.o snd-soc-tegra186-dspk-objs := tegra186_dspk.o snd-soc-tegra210-admaif-objs := tegra210_admaif.o +snd-soc-tegra210-mvc-objs := tegra210_mvc.o +snd-soc-tegra210-sfc-objs := tegra210_sfc.o +snd-soc-tegra210-amx-objs := tegra210_amx.o +snd-soc-tegra210-adx-objs := tegra210_adx.o +snd-soc-tegra210-mixer-objs := tegra210_mixer.o obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o @@ -26,6 +31,11 @@ obj-$(CONFIG_SND_SOC_TEGRA210_AHUB) += snd-soc-tegra210-ahub.o obj-$(CONFIG_SND_SOC_TEGRA210_I2S) += snd-soc-tegra210-i2s.o obj-$(CONFIG_SND_SOC_TEGRA186_DSPK) += snd-soc-tegra186-dspk.o obj-$(CONFIG_SND_SOC_TEGRA210_ADMAIF) += snd-soc-tegra210-admaif.o +obj-$(CONFIG_SND_SOC_TEGRA210_MVC) += snd-soc-tegra210-mvc.o +obj-$(CONFIG_SND_SOC_TEGRA210_SFC) += snd-soc-tegra210-sfc.o +obj-$(CONFIG_SND_SOC_TEGRA210_AMX) += snd-soc-tegra210-amx.o +obj-$(CONFIG_SND_SOC_TEGRA210_ADX) += snd-soc-tegra210-adx.o +obj-$(CONFIG_SND_SOC_TEGRA210_MIXER) += snd-soc-tegra210-mixer.o # Tegra machine Support snd-soc-tegra-wm8903-objs := tegra_wm8903.o diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c new file mode 100644 index 000000000000..d7c7849c2f92 --- /dev/null +++ b/sound/soc/tegra/tegra210_adx.c @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_adx.c - Tegra210 ADX driver +// +// Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra210_adx.h" +#include "tegra_cif.h" + +static const struct reg_default tegra210_adx_reg_defaults[] = { + { TEGRA210_ADX_RX_INT_MASK, 0x00000001}, + { TEGRA210_ADX_RX_CIF_CTRL, 0x00007000}, + { TEGRA210_ADX_TX_INT_MASK, 0x0000000f }, + { TEGRA210_ADX_TX1_CIF_CTRL, 0x00007000}, + { TEGRA210_ADX_TX2_CIF_CTRL, 0x00007000}, + { TEGRA210_ADX_TX3_CIF_CTRL, 0x00007000}, + { TEGRA210_ADX_TX4_CIF_CTRL, 0x00007000}, + { TEGRA210_ADX_CG, 0x1}, + { TEGRA210_ADX_CFG_RAM_CTRL, 0x00004000}, +}; + +static void tegra210_adx_write_map_ram(struct tegra210_adx *adx) +{ + int i; + + regmap_write(adx->regmap, TEGRA210_ADX_CFG_RAM_CTRL, + TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN | + TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN | + TEGRA210_ADX_CFG_RAM_CTRL_RW_WRITE); + + for (i = 0; i < TEGRA210_ADX_RAM_DEPTH; i++) + regmap_write(adx->regmap, TEGRA210_ADX_CFG_RAM_DATA, + adx->map[i]); + + regmap_write(adx->regmap, TEGRA210_ADX_IN_BYTE_EN0, adx->byte_mask[0]); + regmap_write(adx->regmap, TEGRA210_ADX_IN_BYTE_EN1, adx->byte_mask[1]); +} + +static int tegra210_adx_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai); + unsigned int val; + int err; + + /* Ensure if ADX status is disabled */ + err = regmap_read_poll_timeout_atomic(adx->regmap, TEGRA210_ADX_STATUS, + val, !(val & 0x1), 10, 10000); + if (err < 0) { + dev_err(dai->dev, "failed to stop ADX, err = %d\n", err); + return err; + } + + /* + * Soft Reset: Below performs module soft reset which clears + * all FSM logic, flushes flow control of FIFO and resets the + * state register. It also brings module back to disabled + * state (without flushing the data in the pipe). + */ + regmap_update_bits(adx->regmap, TEGRA210_ADX_SOFT_RESET, + TEGRA210_ADX_SOFT_RESET_SOFT_RESET_MASK, + TEGRA210_ADX_SOFT_RESET_SOFT_EN); + + err = regmap_read_poll_timeout(adx->regmap, TEGRA210_ADX_SOFT_RESET, + val, !(val & 0x1), 10, 10000); + if (err < 0) { + dev_err(dai->dev, "failed to reset ADX, err = %d\n", err); + return err; + } + + return 0; +} + +static int __maybe_unused tegra210_adx_runtime_suspend(struct device *dev) +{ + struct tegra210_adx *adx = dev_get_drvdata(dev); + + regcache_cache_only(adx->regmap, true); + regcache_mark_dirty(adx->regmap); + + return 0; +} + +static int __maybe_unused tegra210_adx_runtime_resume(struct device *dev) +{ + struct tegra210_adx *adx = dev_get_drvdata(dev); + + regcache_cache_only(adx->regmap, false); + regcache_sync(adx->regmap); + + tegra210_adx_write_map_ram(adx); + + return 0; +} + +static int tegra210_adx_set_audio_cif(struct snd_soc_dai *dai, + unsigned int channels, + unsigned int format, + unsigned int reg) +{ + struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai); + struct tegra_cif_conf cif_conf; + int audio_bits; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + if (channels < 1 || channels > 16) + return -EINVAL; + + switch (format) { + case SNDRV_PCM_FORMAT_S8: + audio_bits = TEGRA_ACIF_BITS_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + return -EINVAL; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = audio_bits; + + tegra_set_cif(adx->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_adx_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return tegra210_adx_set_audio_cif(dai, params_channels(params), + params_format(params), + TEGRA210_ADX_TX1_CIF_CTRL + ((dai->id - 1) * TEGRA210_ADX_AUDIOCIF_CH_STRIDE)); +} + +static int tegra210_adx_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return tegra210_adx_set_audio_cif(dai, params_channels(params), + params_format(params), + TEGRA210_ADX_RX_CIF_CTRL); +} + +static int tegra210_adx_get_byte_map(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_adx *adx = snd_soc_component_get_drvdata(cmpnt); + struct soc_mixer_control *mc; + unsigned char *bytes_map = (unsigned char *)&adx->map; + int enabled; + + mc = (struct soc_mixer_control *)kcontrol->private_value; + enabled = adx->byte_mask[mc->reg / 32] & (1 << (mc->reg % 32)); + + if (enabled) + ucontrol->value.integer.value[0] = bytes_map[mc->reg]; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int tegra210_adx_put_byte_map(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_adx *adx = snd_soc_component_get_drvdata(cmpnt); + unsigned char *bytes_map = (unsigned char *)&adx->map; + int value = ucontrol->value.integer.value[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value;; + + if (value >= 0 && value <= 255) { + /* update byte map and enable slot */ + bytes_map[mc->reg] = value; + adx->byte_mask[mc->reg / 32] |= (1 << (mc->reg % 32)); + } else { + /* reset byte map and disable slot */ + bytes_map[mc->reg] = 0; + adx->byte_mask[mc->reg / 32] &= ~(1 << (mc->reg % 32)); + } + + return 1; +} + +static const struct snd_soc_dai_ops tegra210_adx_in_dai_ops = { + .hw_params = tegra210_adx_in_hw_params, + .startup = tegra210_adx_startup, +}; + +static const struct snd_soc_dai_ops tegra210_adx_out_dai_ops = { + .hw_params = tegra210_adx_out_hw_params, +}; + +#define IN_DAI \ + { \ + .name = "ADX-RX-CIF", \ + .playback = { \ + .stream_name = "RX-CIF-Playback", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "RX-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra210_adx_in_dai_ops, \ + } + +#define OUT_DAI(id) \ + { \ + .name = "ADX-TX" #id "-CIF", \ + .playback = { \ + .stream_name = "TX" #id "-CIF-Playback",\ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "TX" #id "-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra210_adx_out_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_adx_dais[] = { + IN_DAI, + OUT_DAI(1), + OUT_DAI(2), + OUT_DAI(3), + OUT_DAI(4), +}; + +static const struct snd_soc_dapm_widget tegra210_adx_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX", NULL, 0, TEGRA210_ADX_ENABLE, + TEGRA210_ADX_ENABLE_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, TEGRA210_ADX_CTRL, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX2", NULL, 0, TEGRA210_ADX_CTRL, 1, 0), + SND_SOC_DAPM_AIF_OUT("TX3", NULL, 0, TEGRA210_ADX_CTRL, 2, 0), + SND_SOC_DAPM_AIF_OUT("TX4", NULL, 0, TEGRA210_ADX_CTRL, 3, 0), +}; + +#define STREAM_ROUTES(id, sname) \ + { "XBAR-" sname, NULL, "XBAR-TX" }, \ + { "RX-CIF-" sname, NULL, "XBAR-" sname }, \ + { "RX", NULL, "RX-CIF-" sname }, \ + { "TX" #id, NULL, "RX" }, \ + { "TX" #id "-CIF-" sname, NULL, "TX" #id }, \ + { "TX" #id " XBAR-" sname, NULL, "TX" #id "-CIF-" sname }, \ + { "TX" #id " XBAR-RX", NULL, "TX" #id " XBAR-" sname } + +#define ADX_ROUTES(id) \ + STREAM_ROUTES(id, "Playback"), \ + STREAM_ROUTES(id, "Capture") + +#define STREAM_ROUTES(id, sname) \ + { "XBAR-" sname, NULL, "XBAR-TX" }, \ + { "RX-CIF-" sname, NULL, "XBAR-" sname }, \ + { "RX", NULL, "RX-CIF-" sname }, \ + { "TX" #id, NULL, "RX" }, \ + { "TX" #id "-CIF-" sname, NULL, "TX" #id }, \ + { "TX" #id " XBAR-" sname, NULL, "TX" #id "-CIF-" sname }, \ + { "TX" #id " XBAR-RX", NULL, "TX" #id " XBAR-" sname } + +#define ADX_ROUTES(id) \ + STREAM_ROUTES(id, "Playback"), \ + STREAM_ROUTES(id, "Capture") + +static const struct snd_soc_dapm_route tegra210_adx_routes[] = { + ADX_ROUTES(1), + ADX_ROUTES(2), + ADX_ROUTES(3), + ADX_ROUTES(4), +}; + +#define TEGRA210_ADX_BYTE_MAP_CTRL(reg) \ + SOC_SINGLE_EXT("Byte Map " #reg, reg, 0, 256, 0, \ + tegra210_adx_get_byte_map, \ + tegra210_adx_put_byte_map) + +static struct snd_kcontrol_new tegra210_adx_controls[] = { + TEGRA210_ADX_BYTE_MAP_CTRL(0), + TEGRA210_ADX_BYTE_MAP_CTRL(1), + TEGRA210_ADX_BYTE_MAP_CTRL(2), + TEGRA210_ADX_BYTE_MAP_CTRL(3), + TEGRA210_ADX_BYTE_MAP_CTRL(4), + TEGRA210_ADX_BYTE_MAP_CTRL(5), + TEGRA210_ADX_BYTE_MAP_CTRL(6), + TEGRA210_ADX_BYTE_MAP_CTRL(7), + TEGRA210_ADX_BYTE_MAP_CTRL(8), + TEGRA210_ADX_BYTE_MAP_CTRL(9), + TEGRA210_ADX_BYTE_MAP_CTRL(10), + TEGRA210_ADX_BYTE_MAP_CTRL(11), + TEGRA210_ADX_BYTE_MAP_CTRL(12), + TEGRA210_ADX_BYTE_MAP_CTRL(13), + TEGRA210_ADX_BYTE_MAP_CTRL(14), + TEGRA210_ADX_BYTE_MAP_CTRL(15), + TEGRA210_ADX_BYTE_MAP_CTRL(16), + TEGRA210_ADX_BYTE_MAP_CTRL(17), + TEGRA210_ADX_BYTE_MAP_CTRL(18), + TEGRA210_ADX_BYTE_MAP_CTRL(19), + TEGRA210_ADX_BYTE_MAP_CTRL(20), + TEGRA210_ADX_BYTE_MAP_CTRL(21), + TEGRA210_ADX_BYTE_MAP_CTRL(22), + TEGRA210_ADX_BYTE_MAP_CTRL(23), + TEGRA210_ADX_BYTE_MAP_CTRL(24), + TEGRA210_ADX_BYTE_MAP_CTRL(25), + TEGRA210_ADX_BYTE_MAP_CTRL(26), + TEGRA210_ADX_BYTE_MAP_CTRL(27), + TEGRA210_ADX_BYTE_MAP_CTRL(28), + TEGRA210_ADX_BYTE_MAP_CTRL(29), + TEGRA210_ADX_BYTE_MAP_CTRL(30), + TEGRA210_ADX_BYTE_MAP_CTRL(31), + TEGRA210_ADX_BYTE_MAP_CTRL(32), + TEGRA210_ADX_BYTE_MAP_CTRL(33), + TEGRA210_ADX_BYTE_MAP_CTRL(34), + TEGRA210_ADX_BYTE_MAP_CTRL(35), + TEGRA210_ADX_BYTE_MAP_CTRL(36), + TEGRA210_ADX_BYTE_MAP_CTRL(37), + TEGRA210_ADX_BYTE_MAP_CTRL(38), + TEGRA210_ADX_BYTE_MAP_CTRL(39), + TEGRA210_ADX_BYTE_MAP_CTRL(40), + TEGRA210_ADX_BYTE_MAP_CTRL(41), + TEGRA210_ADX_BYTE_MAP_CTRL(42), + TEGRA210_ADX_BYTE_MAP_CTRL(43), + TEGRA210_ADX_BYTE_MAP_CTRL(44), + TEGRA210_ADX_BYTE_MAP_CTRL(45), + TEGRA210_ADX_BYTE_MAP_CTRL(46), + TEGRA210_ADX_BYTE_MAP_CTRL(47), + TEGRA210_ADX_BYTE_MAP_CTRL(48), + TEGRA210_ADX_BYTE_MAP_CTRL(49), + TEGRA210_ADX_BYTE_MAP_CTRL(50), + TEGRA210_ADX_BYTE_MAP_CTRL(51), + TEGRA210_ADX_BYTE_MAP_CTRL(52), + TEGRA210_ADX_BYTE_MAP_CTRL(53), + TEGRA210_ADX_BYTE_MAP_CTRL(54), + TEGRA210_ADX_BYTE_MAP_CTRL(55), + TEGRA210_ADX_BYTE_MAP_CTRL(56), + TEGRA210_ADX_BYTE_MAP_CTRL(57), + TEGRA210_ADX_BYTE_MAP_CTRL(58), + TEGRA210_ADX_BYTE_MAP_CTRL(59), + TEGRA210_ADX_BYTE_MAP_CTRL(60), + TEGRA210_ADX_BYTE_MAP_CTRL(61), + TEGRA210_ADX_BYTE_MAP_CTRL(62), + TEGRA210_ADX_BYTE_MAP_CTRL(63), +}; + +static const struct snd_soc_component_driver tegra210_adx_cmpnt = { + .dapm_widgets = tegra210_adx_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_adx_widgets), + .dapm_routes = tegra210_adx_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_adx_routes), + .controls = tegra210_adx_controls, + .num_controls = ARRAY_SIZE(tegra210_adx_controls), +}; + +static bool tegra210_adx_wr_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_ADX_TX_INT_MASK ... TEGRA210_ADX_TX4_CIF_CTRL: + case TEGRA210_ADX_RX_INT_MASK ... TEGRA210_ADX_RX_CIF_CTRL: + case TEGRA210_ADX_ENABLE ... TEGRA210_ADX_CG: + case TEGRA210_ADX_CTRL ... TEGRA210_ADX_IN_BYTE_EN1: + case TEGRA210_ADX_CFG_RAM_CTRL ... TEGRA210_ADX_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_adx_rd_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_ADX_RX_STATUS ... TEGRA210_ADX_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_adx_volatile_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_ADX_RX_STATUS: + case TEGRA210_ADX_RX_INT_STATUS: + case TEGRA210_ADX_RX_INT_SET: + case TEGRA210_ADX_TX_STATUS: + case TEGRA210_ADX_TX_INT_STATUS: + case TEGRA210_ADX_TX_INT_SET: + case TEGRA210_ADX_SOFT_RESET: + case TEGRA210_ADX_STATUS: + case TEGRA210_ADX_INT_STATUS: + case TEGRA210_ADX_CFG_RAM_CTRL: + case TEGRA210_ADX_CFG_RAM_DATA: + return true; + default: + break; + } + + return false; +} + +static const struct regmap_config tegra210_adx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_ADX_CFG_RAM_DATA, + .writeable_reg = tegra210_adx_wr_reg, + .readable_reg = tegra210_adx_rd_reg, + .volatile_reg = tegra210_adx_volatile_reg, + .reg_defaults = tegra210_adx_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_adx_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct of_device_id tegra210_adx_of_match[] = { + { .compatible = "nvidia,tegra210-adx" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_adx_of_match); + +static int tegra210_adx_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_adx *adx; + void __iomem *regs; + int err; + + adx = devm_kzalloc(dev, sizeof(*adx), GFP_KERNEL); + if (!adx) + return -ENOMEM; + + dev_set_drvdata(dev, adx); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + adx->regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_adx_regmap_config); + if (IS_ERR(adx->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(adx->regmap); + } + + regcache_cache_only(adx->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra210_adx_cmpnt, + tegra210_adx_dais, + ARRAY_SIZE(tegra210_adx_dais)); + if (err) { + dev_err(dev, "can't register ADX component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra210_adx_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra210_adx_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_adx_runtime_suspend, + tegra210_adx_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra210_adx_driver = { + .driver = { + .name = "tegra210-adx", + .of_match_table = tegra210_adx_of_match, + .pm = &tegra210_adx_pm_ops, + }, + .probe = tegra210_adx_platform_probe, + .remove = tegra210_adx_platform_remove, +}; +module_platform_driver(tegra210_adx_driver); + +MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 ADX ASoC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_adx.h b/sound/soc/tegra/tegra210_adx.h new file mode 100644 index 000000000000..d7dcb6497978 --- /dev/null +++ b/sound/soc/tegra/tegra210_adx.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_adx.h - Definitions for Tegra210 ADX driver + * + * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_ADX_H__ +#define __TEGRA210_ADX_H__ + +/* Register offsets from TEGRA210_ADX*_BASE */ +#define TEGRA210_ADX_RX_STATUS 0x0c +#define TEGRA210_ADX_RX_INT_STATUS 0x10 +#define TEGRA210_ADX_RX_INT_MASK 0x14 +#define TEGRA210_ADX_RX_INT_SET 0x18 +#define TEGRA210_ADX_RX_INT_CLEAR 0x1c +#define TEGRA210_ADX_RX_CIF_CTRL 0x20 +#define TEGRA210_ADX_TX_STATUS 0x4c +#define TEGRA210_ADX_TX_INT_STATUS 0x50 +#define TEGRA210_ADX_TX_INT_MASK 0x54 +#define TEGRA210_ADX_TX_INT_SET 0x58 +#define TEGRA210_ADX_TX_INT_CLEAR 0x5c +#define TEGRA210_ADX_TX1_CIF_CTRL 0x60 +#define TEGRA210_ADX_TX2_CIF_CTRL 0x64 +#define TEGRA210_ADX_TX3_CIF_CTRL 0x68 +#define TEGRA210_ADX_TX4_CIF_CTRL 0x6c +#define TEGRA210_ADX_ENABLE 0x80 +#define TEGRA210_ADX_SOFT_RESET 0x84 +#define TEGRA210_ADX_CG 0x88 +#define TEGRA210_ADX_STATUS 0x8c +#define TEGRA210_ADX_INT_STATUS 0x90 +#define TEGRA210_ADX_CTRL 0xa4 +#define TEGRA210_ADX_IN_BYTE_EN0 0xa8 +#define TEGRA210_ADX_IN_BYTE_EN1 0xac +#define TEGRA210_ADX_CFG_RAM_CTRL 0xb8 +#define TEGRA210_ADX_CFG_RAM_DATA 0xbc + +/* Fields in TEGRA210_ADX_ENABLE */ +#define TEGRA210_ADX_ENABLE_SHIFT 0 + +/* Fields in TEGRA210_ADX_CFG_RAM_CTRL */ +#define TEGRA210_ADX_CFG_RAM_CTRL_RAM_ADDR_SHIFT 0 + +#define TEGRA210_ADX_CFG_RAM_CTRL_RW_SHIFT 14 +#define TEGRA210_ADX_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_ADX_CFG_RAM_CTRL_RW_SHIFT) + +#define TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13 +#define TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT) + +#define TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12 +#define TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT) + +/* Fields in TEGRA210_ADX_SOFT_RESET */ +#define TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT 0 +#define TEGRA210_ADX_SOFT_RESET_SOFT_RESET_MASK (1 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT) +#define TEGRA210_ADX_SOFT_RESET_SOFT_EN (1 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT) +#define TEGRA210_ADX_SOFT_RESET_SOFT_DEFAULT (0 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT) + +#define TEGRA210_ADX_AUDIOCIF_CH_STRIDE 4 +#define TEGRA210_ADX_RAM_DEPTH 16 +#define TEGRA210_ADX_MAP_STREAM_NUMBER_SHIFT 6 +#define TEGRA210_ADX_MAP_WORD_NUMBER_SHIFT 2 +#define TEGRA210_ADX_MAP_BYTE_NUMBER_SHIFT 0 + +struct tegra210_adx { + struct regmap *regmap; + unsigned int map[TEGRA210_ADX_RAM_DEPTH]; + unsigned int byte_mask[2]; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c index 66287a7c9865..a1989eae2b52 100644 --- a/sound/soc/tegra/tegra210_ahub.c +++ b/sound/soc/tegra/tegra210_ahub.c @@ -105,14 +105,68 @@ static struct snd_soc_dai_driver tegra210_ahub_dais[] = { DAI(ADMAIF8), DAI(ADMAIF9), DAI(ADMAIF10), + /* XBAR <-> I2S <-> Codec */ DAI(I2S1), DAI(I2S2), DAI(I2S3), DAI(I2S4), DAI(I2S5), + /* XBAR <- DMIC <- Codec */ DAI(DMIC1), DAI(DMIC2), DAI(DMIC3), + /* XBAR -> SFC -> XBAR */ + DAI(SFC1 RX), + DAI(SFC1 TX), + DAI(SFC2 RX), + DAI(SFC2 TX), + DAI(SFC3 RX), + DAI(SFC3 TX), + DAI(SFC4 RX), + DAI(SFC4 TX), + /* XBAR -> MVC -> XBAR */ + DAI(MVC1 RX), + DAI(MVC1 TX), + DAI(MVC2 RX), + DAI(MVC2 TX), + /* XBAR -> AMX(4:1) -> XBAR */ + DAI(AMX1 RX1), + DAI(AMX1 RX2), + DAI(AMX1 RX3), + DAI(AMX1 RX4), + DAI(AMX1), + DAI(AMX2 RX1), + DAI(AMX2 RX2), + DAI(AMX2 RX3), + DAI(AMX2 RX4), + DAI(AMX2), + /* XBAR -> ADX(1:4) -> XBAR */ + DAI(ADX1), + DAI(ADX1 TX1), + DAI(ADX1 TX2), + DAI(ADX1 TX3), + DAI(ADX1 TX4), + DAI(ADX2), + DAI(ADX2 TX1), + DAI(ADX2 TX2), + DAI(ADX2 TX3), + DAI(ADX2 TX4), + /* XBAR -> MIXER(10:5) -> XBAR */ + DAI(MIXER1 RX1), + DAI(MIXER1 RX2), + DAI(MIXER1 RX3), + DAI(MIXER1 RX4), + DAI(MIXER1 RX5), + DAI(MIXER1 RX6), + DAI(MIXER1 RX7), + DAI(MIXER1 RX8), + DAI(MIXER1 RX9), + DAI(MIXER1 RX10), + DAI(MIXER1 TX1), + DAI(MIXER1 TX2), + DAI(MIXER1 TX3), + DAI(MIXER1 TX4), + DAI(MIXER1 TX5), }; static struct snd_soc_dai_driver tegra186_ahub_dais[] = { @@ -136,18 +190,93 @@ static struct snd_soc_dai_driver tegra186_ahub_dais[] = { DAI(ADMAIF18), DAI(ADMAIF19), DAI(ADMAIF20), + /* XBAR <-> I2S <-> Codec */ DAI(I2S1), DAI(I2S2), DAI(I2S3), DAI(I2S4), DAI(I2S5), DAI(I2S6), + /* XBAR <- DMIC <- Codec */ DAI(DMIC1), DAI(DMIC2), DAI(DMIC3), DAI(DMIC4), + /* XBAR -> DSPK -> Codec */ DAI(DSPK1), DAI(DSPK2), + /* XBAR -> SFC -> XBAR */ + DAI(SFC1 RX), + DAI(SFC1 TX), + DAI(SFC2 RX), + DAI(SFC2 TX), + DAI(SFC3 RX), + DAI(SFC3 TX), + DAI(SFC4 RX), + DAI(SFC4 TX), + /* XBAR -> MVC -> XBAR */ + DAI(MVC1 RX), + DAI(MVC1 TX), + DAI(MVC2 RX), + DAI(MVC2 TX), + /* XBAR -> AMX(4:1) -> XBAR */ + DAI(AMX1 RX1), + DAI(AMX1 RX2), + DAI(AMX1 RX3), + DAI(AMX1 RX4), + DAI(AMX1), + DAI(AMX2 RX1), + DAI(AMX2 RX2), + DAI(AMX2 RX3), + DAI(AMX2 RX4), + DAI(AMX2), + DAI(AMX3 RX1), + DAI(AMX3 RX2), + DAI(AMX3 RX3), + DAI(AMX3 RX4), + DAI(AMX3), + DAI(AMX4 RX1), + DAI(AMX4 RX2), + DAI(AMX4 RX3), + DAI(AMX4 RX4), + DAI(AMX4), + /* XBAR -> ADX(1:4) -> XBAR */ + DAI(ADX1), + DAI(ADX1 TX1), + DAI(ADX1 TX2), + DAI(ADX1 TX3), + DAI(ADX1 TX4), + DAI(ADX2), + DAI(ADX2 TX1), + DAI(ADX2 TX2), + DAI(ADX2 TX3), + DAI(ADX2 TX4), + DAI(ADX3), + DAI(ADX3 TX1), + DAI(ADX3 TX2), + DAI(ADX3 TX3), + DAI(ADX3 TX4), + DAI(ADX4), + DAI(ADX4 TX1), + DAI(ADX4 TX2), + DAI(ADX4 TX3), + DAI(ADX4 TX4), + /* XBAR -> MIXER1(10:5) -> XBAR */ + DAI(MIXER1 RX1), + DAI(MIXER1 RX2), + DAI(MIXER1 RX3), + DAI(MIXER1 RX4), + DAI(MIXER1 RX5), + DAI(MIXER1 RX6), + DAI(MIXER1 RX7), + DAI(MIXER1 RX8), + DAI(MIXER1 RX9), + DAI(MIXER1 RX10), + DAI(MIXER1 TX1), + DAI(MIXER1 TX2), + DAI(MIXER1 TX3), + DAI(MIXER1 TX4), + DAI(MIXER1 TX5), }; static const char * const tegra210_ahub_mux_texts[] = { @@ -170,6 +299,27 @@ static const char * const tegra210_ahub_mux_texts[] = { "DMIC1", "DMIC2", "DMIC3", + "SFC1", + "SFC2", + "SFC3", + "SFC4", + "MVC1", + "MVC2", + "AMX1", + "AMX2", + "ADX1 TX1", + "ADX1 TX2", + "ADX1 TX3", + "ADX1 TX4", + "ADX2 TX1", + "ADX2 TX2", + "ADX2 TX3", + "ADX2 TX4", + "MIXER1 TX1", + "MIXER1 TX2", + "MIXER1 TX3", + "MIXER1 TX4", + "MIXER1 TX5", }; static const char * const tegra186_ahub_mux_texts[] = { @@ -204,10 +354,42 @@ static const char * const tegra186_ahub_mux_texts[] = { "DMIC2", "DMIC3", "DMIC4", + "SFC1", + "SFC2", + "SFC3", + "SFC4", + "MVC1", + "MVC2", + "AMX1", + "AMX2", + "AMX3", + "AMX4", + "ADX1 TX1", + "ADX1 TX2", + "ADX1 TX3", + "ADX1 TX4", + "ADX2 TX1", + "ADX2 TX2", + "ADX2 TX3", + "ADX2 TX4", + "ADX3 TX1", + "ADX3 TX2", + "ADX3 TX3", + "ADX3 TX4", + "ADX4 TX1", + "ADX4 TX2", + "ADX4 TX3", + "ADX4 TX4", + "MIXER1 TX1", + "MIXER1 TX2", + "MIXER1 TX3", + "MIXER1 TX4", + "MIXER1 TX5", }; static const unsigned int tegra210_ahub_mux_values[] = { 0, + /* ADMAIF */ MUX_VALUE(0, 0), MUX_VALUE(0, 1), MUX_VALUE(0, 2), @@ -218,18 +400,47 @@ static const unsigned int tegra210_ahub_mux_values[] = { MUX_VALUE(0, 7), MUX_VALUE(0, 8), MUX_VALUE(0, 9), + /* I2S */ MUX_VALUE(0, 16), MUX_VALUE(0, 17), MUX_VALUE(0, 18), MUX_VALUE(0, 19), MUX_VALUE(0, 20), + /* DMIC */ MUX_VALUE(2, 18), MUX_VALUE(2, 19), MUX_VALUE(2, 20), + /* SFC */ + MUX_VALUE(0, 24), + MUX_VALUE(0, 25), + MUX_VALUE(0, 26), + MUX_VALUE(0, 27), + /* MVC */ + MUX_VALUE(2, 8), + MUX_VALUE(2, 9), + /* AMX */ + MUX_VALUE(1, 8), + MUX_VALUE(1, 9), + /* ADX */ + MUX_VALUE(2, 24), + MUX_VALUE(2, 25), + MUX_VALUE(2, 26), + MUX_VALUE(2, 27), + MUX_VALUE(2, 28), + MUX_VALUE(2, 29), + MUX_VALUE(2, 30), + MUX_VALUE(2, 31), + /* MIXER */ + MUX_VALUE(1, 0), + MUX_VALUE(1, 1), + MUX_VALUE(1, 2), + MUX_VALUE(1, 3), + MUX_VALUE(1, 4), }; static const unsigned int tegra186_ahub_mux_values[] = { 0, + /* ADMAIF */ MUX_VALUE(0, 0), MUX_VALUE(0, 1), MUX_VALUE(0, 2), @@ -246,20 +457,59 @@ static const unsigned int tegra186_ahub_mux_values[] = { MUX_VALUE(0, 13), MUX_VALUE(0, 14), MUX_VALUE(0, 15), + /* I2S */ MUX_VALUE(0, 16), MUX_VALUE(0, 17), MUX_VALUE(0, 18), MUX_VALUE(0, 19), MUX_VALUE(0, 20), MUX_VALUE(0, 21), + /* ADMAIF */ MUX_VALUE(3, 16), MUX_VALUE(3, 17), MUX_VALUE(3, 18), MUX_VALUE(3, 19), + /* DMIC */ MUX_VALUE(2, 18), MUX_VALUE(2, 19), MUX_VALUE(2, 20), MUX_VALUE(2, 21), + /* SFC */ + MUX_VALUE(0, 24), + MUX_VALUE(0, 25), + MUX_VALUE(0, 26), + MUX_VALUE(0, 27), + /* MVC */ + MUX_VALUE(2, 8), + MUX_VALUE(2, 9), + /* AMX */ + MUX_VALUE(1, 8), + MUX_VALUE(1, 9), + MUX_VALUE(1, 10), + MUX_VALUE(1, 11), + /* ADX */ + MUX_VALUE(2, 24), + MUX_VALUE(2, 25), + MUX_VALUE(2, 26), + MUX_VALUE(2, 27), + MUX_VALUE(2, 28), + MUX_VALUE(2, 29), + MUX_VALUE(2, 30), + MUX_VALUE(2, 31), + MUX_VALUE(3, 0), + MUX_VALUE(3, 1), + MUX_VALUE(3, 2), + MUX_VALUE(3, 3), + MUX_VALUE(3, 4), + MUX_VALUE(3, 5), + MUX_VALUE(3, 6), + MUX_VALUE(3, 7), + /* MIXER */ + MUX_VALUE(1, 0), + MUX_VALUE(1, 1), + MUX_VALUE(1, 2), + MUX_VALUE(1, 3), + MUX_VALUE(1, 4), }; /* Controls for t210 */ @@ -278,6 +528,32 @@ MUX_ENUM_CTRL_DECL(t210_i2s2_tx, 0x11); MUX_ENUM_CTRL_DECL(t210_i2s3_tx, 0x12); MUX_ENUM_CTRL_DECL(t210_i2s4_tx, 0x13); MUX_ENUM_CTRL_DECL(t210_i2s5_tx, 0x14); +MUX_ENUM_CTRL_DECL(t210_sfc1_tx, 0x18); +MUX_ENUM_CTRL_DECL(t210_sfc2_tx, 0x19); +MUX_ENUM_CTRL_DECL(t210_sfc3_tx, 0x1a); +MUX_ENUM_CTRL_DECL(t210_sfc4_tx, 0x1b); +MUX_ENUM_CTRL_DECL(t210_mvc1_tx, 0x48); +MUX_ENUM_CTRL_DECL(t210_mvc2_tx, 0x49); +MUX_ENUM_CTRL_DECL(t210_amx11_tx, 0x50); +MUX_ENUM_CTRL_DECL(t210_amx12_tx, 0x51); +MUX_ENUM_CTRL_DECL(t210_amx13_tx, 0x52); +MUX_ENUM_CTRL_DECL(t210_amx14_tx, 0x53); +MUX_ENUM_CTRL_DECL(t210_amx21_tx, 0x54); +MUX_ENUM_CTRL_DECL(t210_amx22_tx, 0x55); +MUX_ENUM_CTRL_DECL(t210_amx23_tx, 0x56); +MUX_ENUM_CTRL_DECL(t210_amx24_tx, 0x57); +MUX_ENUM_CTRL_DECL(t210_adx1_tx, 0x58); +MUX_ENUM_CTRL_DECL(t210_adx2_tx, 0x59); +MUX_ENUM_CTRL_DECL(t210_mixer11_tx, 0x20); +MUX_ENUM_CTRL_DECL(t210_mixer12_tx, 0x21); +MUX_ENUM_CTRL_DECL(t210_mixer13_tx, 0x22); +MUX_ENUM_CTRL_DECL(t210_mixer14_tx, 0x23); +MUX_ENUM_CTRL_DECL(t210_mixer15_tx, 0x24); +MUX_ENUM_CTRL_DECL(t210_mixer16_tx, 0x25); +MUX_ENUM_CTRL_DECL(t210_mixer17_tx, 0x26); +MUX_ENUM_CTRL_DECL(t210_mixer18_tx, 0x27); +MUX_ENUM_CTRL_DECL(t210_mixer19_tx, 0x28); +MUX_ENUM_CTRL_DECL(t210_mixer110_tx, 0x29); /* Controls for t186 */ MUX_ENUM_CTRL_DECL_186(t186_admaif1_tx, 0x00); @@ -308,6 +584,42 @@ MUX_ENUM_CTRL_DECL_186(t186_admaif17_tx, 0x68); MUX_ENUM_CTRL_DECL_186(t186_admaif18_tx, 0x69); MUX_ENUM_CTRL_DECL_186(t186_admaif19_tx, 0x6a); MUX_ENUM_CTRL_DECL_186(t186_admaif20_tx, 0x6b); +MUX_ENUM_CTRL_DECL_186(t186_sfc1_tx, 0x18); +MUX_ENUM_CTRL_DECL_186(t186_sfc2_tx, 0x19); +MUX_ENUM_CTRL_DECL_186(t186_sfc3_tx, 0x1a); +MUX_ENUM_CTRL_DECL_186(t186_sfc4_tx, 0x1b); +MUX_ENUM_CTRL_DECL_186(t186_mvc1_tx, 0x48); +MUX_ENUM_CTRL_DECL_186(t186_mvc2_tx, 0x49); +MUX_ENUM_CTRL_DECL_186(t186_amx11_tx, 0x50); +MUX_ENUM_CTRL_DECL_186(t186_amx12_tx, 0x51); +MUX_ENUM_CTRL_DECL_186(t186_amx13_tx, 0x52); +MUX_ENUM_CTRL_DECL_186(t186_amx14_tx, 0x53); +MUX_ENUM_CTRL_DECL_186(t186_amx21_tx, 0x54); +MUX_ENUM_CTRL_DECL_186(t186_amx22_tx, 0x55); +MUX_ENUM_CTRL_DECL_186(t186_amx23_tx, 0x56); +MUX_ENUM_CTRL_DECL_186(t186_amx24_tx, 0x57); +MUX_ENUM_CTRL_DECL_186(t186_amx31_tx, 0x58); +MUX_ENUM_CTRL_DECL_186(t186_amx32_tx, 0x59); +MUX_ENUM_CTRL_DECL_186(t186_amx33_tx, 0x5a); +MUX_ENUM_CTRL_DECL_186(t186_amx34_tx, 0x5b); +MUX_ENUM_CTRL_DECL_186(t186_amx41_tx, 0x64); +MUX_ENUM_CTRL_DECL_186(t186_amx42_tx, 0x65); +MUX_ENUM_CTRL_DECL_186(t186_amx43_tx, 0x66); +MUX_ENUM_CTRL_DECL_186(t186_amx44_tx, 0x67); +MUX_ENUM_CTRL_DECL_186(t186_adx1_tx, 0x60); +MUX_ENUM_CTRL_DECL_186(t186_adx2_tx, 0x61); +MUX_ENUM_CTRL_DECL_186(t186_adx3_tx, 0x62); +MUX_ENUM_CTRL_DECL_186(t186_adx4_tx, 0x63); +MUX_ENUM_CTRL_DECL_186(t186_mixer11_tx, 0x20); +MUX_ENUM_CTRL_DECL_186(t186_mixer12_tx, 0x21); +MUX_ENUM_CTRL_DECL_186(t186_mixer13_tx, 0x22); +MUX_ENUM_CTRL_DECL_186(t186_mixer14_tx, 0x23); +MUX_ENUM_CTRL_DECL_186(t186_mixer15_tx, 0x24); +MUX_ENUM_CTRL_DECL_186(t186_mixer16_tx, 0x25); +MUX_ENUM_CTRL_DECL_186(t186_mixer17_tx, 0x26); +MUX_ENUM_CTRL_DECL_186(t186_mixer18_tx, 0x27); +MUX_ENUM_CTRL_DECL_186(t186_mixer19_tx, 0x28); +MUX_ENUM_CTRL_DECL_186(t186_mixer110_tx, 0x29); /* * The number of entries in, and order of, this array is closely tied to the @@ -333,6 +645,47 @@ static const struct snd_soc_dapm_widget tegra210_ahub_widgets[] = { TX_WIDGETS("DMIC1"), TX_WIDGETS("DMIC2"), TX_WIDGETS("DMIC3"), + WIDGETS("SFC1", t210_sfc1_tx), + WIDGETS("SFC2", t210_sfc2_tx), + WIDGETS("SFC3", t210_sfc3_tx), + WIDGETS("SFC4", t210_sfc4_tx), + WIDGETS("MVC1", t210_mvc1_tx), + WIDGETS("MVC2", t210_mvc2_tx), + WIDGETS("AMX1 RX1", t210_amx11_tx), + WIDGETS("AMX1 RX2", t210_amx12_tx), + WIDGETS("AMX1 RX3", t210_amx13_tx), + WIDGETS("AMX1 RX4", t210_amx14_tx), + WIDGETS("AMX2 RX1", t210_amx21_tx), + WIDGETS("AMX2 RX2", t210_amx22_tx), + WIDGETS("AMX2 RX3", t210_amx23_tx), + WIDGETS("AMX2 RX4", t210_amx24_tx), + TX_WIDGETS("AMX1"), + TX_WIDGETS("AMX2"), + WIDGETS("ADX1", t210_adx1_tx), + WIDGETS("ADX2", t210_adx2_tx), + TX_WIDGETS("ADX1 TX1"), + TX_WIDGETS("ADX1 TX2"), + TX_WIDGETS("ADX1 TX3"), + TX_WIDGETS("ADX1 TX4"), + TX_WIDGETS("ADX2 TX1"), + TX_WIDGETS("ADX2 TX2"), + TX_WIDGETS("ADX2 TX3"), + TX_WIDGETS("ADX2 TX4"), + WIDGETS("MIXER1 RX1", t210_mixer11_tx), + WIDGETS("MIXER1 RX2", t210_mixer12_tx), + WIDGETS("MIXER1 RX3", t210_mixer13_tx), + WIDGETS("MIXER1 RX4", t210_mixer14_tx), + WIDGETS("MIXER1 RX5", t210_mixer15_tx), + WIDGETS("MIXER1 RX6", t210_mixer16_tx), + WIDGETS("MIXER1 RX7", t210_mixer17_tx), + WIDGETS("MIXER1 RX8", t210_mixer18_tx), + WIDGETS("MIXER1 RX9", t210_mixer19_tx), + WIDGETS("MIXER1 RX10", t210_mixer110_tx), + TX_WIDGETS("MIXER1 TX1"), + TX_WIDGETS("MIXER1 TX2"), + TX_WIDGETS("MIXER1 TX3"), + TX_WIDGETS("MIXER1 TX4"), + TX_WIDGETS("MIXER1 TX5"), }; static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { @@ -368,6 +721,67 @@ static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { TX_WIDGETS("DMIC4"), WIDGETS("DSPK1", t186_dspk1_tx), WIDGETS("DSPK2", t186_dspk2_tx), + WIDGETS("SFC1", t186_sfc1_tx), + WIDGETS("SFC2", t186_sfc2_tx), + WIDGETS("SFC3", t186_sfc3_tx), + WIDGETS("SFC4", t186_sfc4_tx), + WIDGETS("MVC1", t186_mvc1_tx), + WIDGETS("MVC2", t186_mvc2_tx), + WIDGETS("AMX1 RX1", t186_amx11_tx), + WIDGETS("AMX1 RX2", t186_amx12_tx), + WIDGETS("AMX1 RX3", t186_amx13_tx), + WIDGETS("AMX1 RX4", t186_amx14_tx), + WIDGETS("AMX2 RX1", t186_amx21_tx), + WIDGETS("AMX2 RX2", t186_amx22_tx), + WIDGETS("AMX2 RX3", t186_amx23_tx), + WIDGETS("AMX2 RX4", t186_amx24_tx), + WIDGETS("AMX3 RX1", t186_amx31_tx), + WIDGETS("AMX3 RX2", t186_amx32_tx), + WIDGETS("AMX3 RX3", t186_amx33_tx), + WIDGETS("AMX3 RX4", t186_amx34_tx), + WIDGETS("AMX4 RX1", t186_amx41_tx), + WIDGETS("AMX4 RX2", t186_amx42_tx), + WIDGETS("AMX4 RX3", t186_amx43_tx), + WIDGETS("AMX4 RX4", t186_amx44_tx), + TX_WIDGETS("AMX1"), + TX_WIDGETS("AMX2"), + TX_WIDGETS("AMX3"), + TX_WIDGETS("AMX4"), + WIDGETS("ADX1", t186_adx1_tx), + WIDGETS("ADX2", t186_adx2_tx), + WIDGETS("ADX3", t186_adx3_tx), + WIDGETS("ADX4", t186_adx4_tx), + TX_WIDGETS("ADX1 TX1"), + TX_WIDGETS("ADX1 TX2"), + TX_WIDGETS("ADX1 TX3"), + TX_WIDGETS("ADX1 TX4"), + TX_WIDGETS("ADX2 TX1"), + TX_WIDGETS("ADX2 TX2"), + TX_WIDGETS("ADX2 TX3"), + TX_WIDGETS("ADX2 TX4"), + TX_WIDGETS("ADX3 TX1"), + TX_WIDGETS("ADX3 TX2"), + TX_WIDGETS("ADX3 TX3"), + TX_WIDGETS("ADX3 TX4"), + TX_WIDGETS("ADX4 TX1"), + TX_WIDGETS("ADX4 TX2"), + TX_WIDGETS("ADX4 TX3"), + TX_WIDGETS("ADX4 TX4"), + WIDGETS("MIXER1 RX1", t186_mixer11_tx), + WIDGETS("MIXER1 RX2", t186_mixer12_tx), + WIDGETS("MIXER1 RX3", t186_mixer13_tx), + WIDGETS("MIXER1 RX4", t186_mixer14_tx), + WIDGETS("MIXER1 RX5", t186_mixer15_tx), + WIDGETS("MIXER1 RX6", t186_mixer16_tx), + WIDGETS("MIXER1 RX7", t186_mixer17_tx), + WIDGETS("MIXER1 RX8", t186_mixer18_tx), + WIDGETS("MIXER1 RX9", t186_mixer19_tx), + WIDGETS("MIXER1 RX10", t186_mixer110_tx), + TX_WIDGETS("MIXER1 TX1"), + TX_WIDGETS("MIXER1 TX2"), + TX_WIDGETS("MIXER1 TX3"), + TX_WIDGETS("MIXER1 TX4"), + TX_WIDGETS("MIXER1 TX5"), }; #define TEGRA_COMMON_MUX_ROUTES(name) \ @@ -389,7 +803,28 @@ static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { { name " Mux", "I2S5", "I2S5 XBAR-RX" }, \ { name " Mux", "DMIC1", "DMIC1 XBAR-RX" }, \ { name " Mux", "DMIC2", "DMIC2 XBAR-RX" }, \ - { name " Mux", "DMIC3", "DMIC3 XBAR-RX" }, + { name " Mux", "DMIC3", "DMIC3 XBAR-RX" }, \ + { name " Mux", "SFC1", "SFC1 XBAR-RX" }, \ + { name " Mux", "SFC2", "SFC2 XBAR-RX" }, \ + { name " Mux", "SFC3", "SFC3 XBAR-RX" }, \ + { name " Mux", "SFC4", "SFC4 XBAR-RX" }, \ + { name " Mux", "MVC1", "MVC1 XBAR-RX" }, \ + { name " Mux", "MVC2", "MVC2 XBAR-RX" }, \ + { name " Mux", "AMX1", "AMX1 XBAR-RX" }, \ + { name " Mux", "AMX2", "AMX2 XBAR-RX" }, \ + { name " Mux", "ADX1 TX1", "ADX1 TX1 XBAR-RX" }, \ + { name " Mux", "ADX1 TX2", "ADX1 TX2 XBAR-RX" }, \ + { name " Mux", "ADX1 TX3", "ADX1 TX3 XBAR-RX" }, \ + { name " Mux", "ADX1 TX4", "ADX1 TX4 XBAR-RX" }, \ + { name " Mux", "ADX2 TX1", "ADX2 TX1 XBAR-RX" }, \ + { name " Mux", "ADX2 TX2", "ADX2 TX2 XBAR-RX" }, \ + { name " Mux", "ADX2 TX3", "ADX2 TX3 XBAR-RX" }, \ + { name " Mux", "ADX2 TX4", "ADX2 TX4 XBAR-RX" }, \ + { name " Mux", "MIXER1 TX1", "MIXER1 TX1 XBAR-RX" }, \ + { name " Mux", "MIXER1 TX2", "MIXER1 TX2 XBAR-RX" }, \ + { name " Mux", "MIXER1 TX3", "MIXER1 TX3 XBAR-RX" }, \ + { name " Mux", "MIXER1 TX4", "MIXER1 TX4 XBAR-RX" }, \ + { name " Mux", "MIXER1 TX5", "MIXER1 TX5 XBAR-RX" }, #define TEGRA186_ONLY_MUX_ROUTES(name) \ { name " Mux", "ADMAIF11", "ADMAIF11 XBAR-RX" }, \ @@ -403,7 +838,17 @@ static const struct snd_soc_dapm_widget tegra186_ahub_widgets[] = { { name " Mux", "ADMAIF19", "ADMAIF19 XBAR-RX" }, \ { name " Mux", "ADMAIF20", "ADMAIF20 XBAR-RX" }, \ { name " Mux", "I2S6", "I2S6 XBAR-RX" }, \ - { name " Mux", "DMIC4", "DMIC4 XBAR-RX" }, + { name " Mux", "DMIC4", "DMIC4 XBAR-RX" }, \ + { name " Mux", "AMX3", "AMX3 XBAR-RX" }, \ + { name " Mux", "AMX4", "AMX4 XBAR-RX" }, \ + { name " Mux", "ADX3 TX1", "ADX3 TX1 XBAR-RX" }, \ + { name " Mux", "ADX3 TX2", "ADX3 TX2 XBAR-RX" }, \ + { name " Mux", "ADX3 TX3", "ADX3 TX3 XBAR-RX" }, \ + { name " Mux", "ADX3 TX4", "ADX3 TX4 XBAR-RX" }, \ + { name " Mux", "ADX4 TX1", "ADX4 TX1 XBAR-RX" }, \ + { name " Mux", "ADX4 TX2", "ADX4 TX2 XBAR-RX" }, \ + { name " Mux", "ADX4 TX3", "ADX4 TX3 XBAR-RX" }, \ + { name " Mux", "ADX4 TX4", "ADX4 TX4 XBAR-RX" }, #define TEGRA210_MUX_ROUTES(name) \ TEGRA_COMMON_MUX_ROUTES(name) @@ -450,6 +895,32 @@ static const struct snd_soc_dapm_route tegra210_ahub_routes[] = { TEGRA210_MUX_ROUTES("I2S3") TEGRA210_MUX_ROUTES("I2S4") TEGRA210_MUX_ROUTES("I2S5") + TEGRA210_MUX_ROUTES("SFC1") + TEGRA210_MUX_ROUTES("SFC2") + TEGRA210_MUX_ROUTES("SFC3") + TEGRA210_MUX_ROUTES("SFC4") + TEGRA210_MUX_ROUTES("MVC1") + TEGRA210_MUX_ROUTES("MVC2") + TEGRA210_MUX_ROUTES("AMX1 RX1") + TEGRA210_MUX_ROUTES("AMX1 RX2") + TEGRA210_MUX_ROUTES("AMX1 RX3") + TEGRA210_MUX_ROUTES("AMX1 RX4") + TEGRA210_MUX_ROUTES("AMX2 RX1") + TEGRA210_MUX_ROUTES("AMX2 RX2") + TEGRA210_MUX_ROUTES("AMX2 RX3") + TEGRA210_MUX_ROUTES("AMX2 RX4") + TEGRA210_MUX_ROUTES("ADX1") + TEGRA210_MUX_ROUTES("ADX2") + TEGRA210_MUX_ROUTES("MIXER1 RX1") + TEGRA210_MUX_ROUTES("MIXER1 RX2") + TEGRA210_MUX_ROUTES("MIXER1 RX3") + TEGRA210_MUX_ROUTES("MIXER1 RX4") + TEGRA210_MUX_ROUTES("MIXER1 RX5") + TEGRA210_MUX_ROUTES("MIXER1 RX6") + TEGRA210_MUX_ROUTES("MIXER1 RX7") + TEGRA210_MUX_ROUTES("MIXER1 RX8") + TEGRA210_MUX_ROUTES("MIXER1 RX9") + TEGRA210_MUX_ROUTES("MIXER1 RX10") }; static const struct snd_soc_dapm_route tegra186_ahub_routes[] = { @@ -501,6 +972,42 @@ static const struct snd_soc_dapm_route tegra186_ahub_routes[] = { TEGRA186_MUX_ROUTES("I2S6") TEGRA186_MUX_ROUTES("DSPK1") TEGRA186_MUX_ROUTES("DSPK2") + TEGRA186_MUX_ROUTES("SFC1") + TEGRA186_MUX_ROUTES("SFC2") + TEGRA186_MUX_ROUTES("SFC3") + TEGRA186_MUX_ROUTES("SFC4") + TEGRA186_MUX_ROUTES("MVC1") + TEGRA186_MUX_ROUTES("MVC2") + TEGRA186_MUX_ROUTES("AMX1 RX1") + TEGRA186_MUX_ROUTES("AMX1 RX2") + TEGRA186_MUX_ROUTES("AMX1 RX3") + TEGRA186_MUX_ROUTES("AMX1 RX4") + TEGRA186_MUX_ROUTES("AMX2 RX1") + TEGRA186_MUX_ROUTES("AMX2 RX2") + TEGRA186_MUX_ROUTES("AMX2 RX3") + TEGRA186_MUX_ROUTES("AMX2 RX4") + TEGRA186_MUX_ROUTES("AMX3 RX1") + TEGRA186_MUX_ROUTES("AMX3 RX2") + TEGRA186_MUX_ROUTES("AMX3 RX3") + TEGRA186_MUX_ROUTES("AMX3 RX4") + TEGRA186_MUX_ROUTES("AMX4 RX1") + TEGRA186_MUX_ROUTES("AMX4 RX2") + TEGRA186_MUX_ROUTES("AMX4 RX3") + TEGRA186_MUX_ROUTES("AMX4 RX4") + TEGRA186_MUX_ROUTES("ADX1") + TEGRA186_MUX_ROUTES("ADX2") + TEGRA186_MUX_ROUTES("ADX3") + TEGRA186_MUX_ROUTES("ADX4") + TEGRA186_MUX_ROUTES("MIXER1 RX1") + TEGRA186_MUX_ROUTES("MIXER1 RX2") + TEGRA186_MUX_ROUTES("MIXER1 RX3") + TEGRA186_MUX_ROUTES("MIXER1 RX4") + TEGRA186_MUX_ROUTES("MIXER1 RX5") + TEGRA186_MUX_ROUTES("MIXER1 RX6") + TEGRA186_MUX_ROUTES("MIXER1 RX7") + TEGRA186_MUX_ROUTES("MIXER1 RX8") + TEGRA186_MUX_ROUTES("MIXER1 RX9") + TEGRA186_MUX_ROUTES("MIXER1 RX10") }; static const struct snd_soc_component_driver tegra210_ahub_component = { diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c new file mode 100644 index 000000000000..af9bddfc3120 --- /dev/null +++ b/sound/soc/tegra/tegra210_amx.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_amx.c - Tegra210 AMX driver +// +// Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra210_amx.h" +#include "tegra_cif.h" + +/* + * The counter is in terms of AHUB clock cycles. If a frame is not + * received within these clock cycles, the AMX input channel gets + * automatically disabled. For now the counter is calculated as a + * function of sample rate (8 kHz) and AHUB clock (49.152 MHz). + * If later an accurate number is needed, the counter needs to be + * calculated at runtime. + * + * count = ahub_clk / sample_rate + */ +#define TEGRA194_MAX_FRAME_IDLE_COUNT 0x1800 + +#define AMX_CH_REG(id, reg) ((reg) + ((id) * TEGRA210_AMX_AUDIOCIF_CH_STRIDE)) + +static const struct reg_default tegra210_amx_reg_defaults[] = { + { TEGRA210_AMX_RX_INT_MASK, 0x0000000f}, + { TEGRA210_AMX_RX1_CIF_CTRL, 0x00007000}, + { TEGRA210_AMX_RX2_CIF_CTRL, 0x00007000}, + { TEGRA210_AMX_RX3_CIF_CTRL, 0x00007000}, + { TEGRA210_AMX_RX4_CIF_CTRL, 0x00007000}, + { TEGRA210_AMX_TX_INT_MASK, 0x00000001}, + { TEGRA210_AMX_TX_CIF_CTRL, 0x00007000}, + { TEGRA210_AMX_CG, 0x1}, + { TEGRA210_AMX_CFG_RAM_CTRL, 0x00004000}, +}; + +static void tegra210_amx_write_map_ram(struct tegra210_amx *amx) +{ + int i; + + regmap_write(amx->regmap, TEGRA210_AMX_CFG_RAM_CTRL, + TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN | + TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN | + TEGRA210_AMX_CFG_RAM_CTRL_RW_WRITE); + + for (i = 0; i < TEGRA210_AMX_RAM_DEPTH; i++) + regmap_write(amx->regmap, TEGRA210_AMX_CFG_RAM_DATA, + amx->map[i]); + + regmap_write(amx->regmap, TEGRA210_AMX_OUT_BYTE_EN0, amx->byte_mask[0]); + regmap_write(amx->regmap, TEGRA210_AMX_OUT_BYTE_EN1, amx->byte_mask[1]); +} + +static int tegra210_amx_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai); + unsigned int val; + int err; + + /* Ensure if AMX is disabled */ + err = regmap_read_poll_timeout(amx->regmap, TEGRA210_AMX_STATUS, val, + !(val & 0x1), 10, 10000); + if (err < 0) { + dev_err(dai->dev, "failed to stop AMX, err = %d\n", err); + return err; + } + + /* + * Soft Reset: Below performs module soft reset which clears + * all FSM logic, flushes flow control of FIFO and resets the + * state register. It also brings module back to disabled + * state (without flushing the data in the pipe). + */ + regmap_update_bits(amx->regmap, TEGRA210_AMX_SOFT_RESET, + TEGRA210_AMX_SOFT_RESET_SOFT_RESET_MASK, + TEGRA210_AMX_SOFT_RESET_SOFT_EN); + + err = regmap_read_poll_timeout(amx->regmap, TEGRA210_AMX_SOFT_RESET, + val, !(val & 0x1), 10, 10000); + if (err < 0) { + dev_err(dai->dev, "failed to reset AMX, err = %d\n", err); + return err; + } + + return 0; +} + +static int __maybe_unused tegra210_amx_runtime_suspend(struct device *dev) +{ + struct tegra210_amx *amx = dev_get_drvdata(dev); + + regcache_cache_only(amx->regmap, true); + regcache_mark_dirty(amx->regmap); + + return 0; +} + +static int __maybe_unused tegra210_amx_runtime_resume(struct device *dev) +{ + struct tegra210_amx *amx = dev_get_drvdata(dev); + + regcache_cache_only(amx->regmap, false); + regcache_sync(amx->regmap); + + regmap_update_bits(amx->regmap, + TEGRA210_AMX_CTRL, + TEGRA210_AMX_CTRL_RX_DEP_MASK, + TEGRA210_AMX_WAIT_ON_ANY << TEGRA210_AMX_CTRL_RX_DEP_SHIFT); + + tegra210_amx_write_map_ram(amx); + + return 0; +} + +static int tegra210_amx_set_audio_cif(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai); + int channels, audio_bits; + struct tegra_cif_conf cif_conf; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + audio_bits = TEGRA_ACIF_BITS_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + return -EINVAL; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = audio_bits; + + tegra_set_cif(amx->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_amx_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai); + + if (amx->soc_data->auto_disable) { + regmap_write(amx->regmap, + AMX_CH_REG(dai->id, TEGRA194_AMX_RX1_FRAME_PERIOD), + TEGRA194_MAX_FRAME_IDLE_COUNT); + regmap_write(amx->regmap, TEGRA210_AMX_CYA, 1); + } + + return tegra210_amx_set_audio_cif(dai, params, + AMX_CH_REG(dai->id, TEGRA210_AMX_RX1_CIF_CTRL)); +} + +static int tegra210_amx_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return tegra210_amx_set_audio_cif(dai, params, + TEGRA210_AMX_TX_CIF_CTRL); +} + +static int tegra210_amx_get_byte_map(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct tegra210_amx *amx = snd_soc_component_get_drvdata(cmpnt); + unsigned char *bytes_map = (unsigned char *)&amx->map; + int reg = mc->reg; + int enabled; + + if (reg > 31) + enabled = amx->byte_mask[1] & (1 << (reg - 32)); + else + enabled = amx->byte_mask[0] & (1 << reg); + + if (enabled) + ucontrol->value.integer.value[0] = bytes_map[reg]; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int tegra210_amx_put_byte_map(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_amx *amx = snd_soc_component_get_drvdata(cmpnt); + unsigned char *bytes_map = (unsigned char *)&amx->map; + int reg = mc->reg; + int value = ucontrol->value.integer.value[0]; + + if (value >= 0 && value <= 255) { + /* Update byte map and enable slot */ + bytes_map[reg] = value; + if (reg > 31) + amx->byte_mask[1] |= (1 << (reg - 32)); + else + amx->byte_mask[0] |= (1 << reg); + } else { + /* Reset byte map and disable slot */ + bytes_map[reg] = 0; + if (reg > 31) + amx->byte_mask[1] &= ~(1 << (reg - 32)); + else + amx->byte_mask[0] &= ~(1 << reg); + } + + return 1; +} + +static const struct snd_soc_dai_ops tegra210_amx_out_dai_ops = { + .hw_params = tegra210_amx_out_hw_params, + .startup = tegra210_amx_startup, +}; + +static const struct snd_soc_dai_ops tegra210_amx_in_dai_ops = { + .hw_params = tegra210_amx_in_hw_params, +}; + +#define IN_DAI(id) \ + { \ + .name = "AMX-RX-CIF" #id, \ + .playback = { \ + .stream_name = "RX" #id "-CIF-Playback",\ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "RX" #id "-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra210_amx_in_dai_ops, \ + } + +#define OUT_DAI \ + { \ + .name = "AMX-TX-CIF", \ + .playback = { \ + .stream_name = "TX-CIF-Playback", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "TX-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra210_amx_out_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_amx_dais[] = { + IN_DAI(1), + IN_DAI(2), + IN_DAI(3), + IN_DAI(4), + OUT_DAI, +}; + +static const struct snd_soc_dapm_widget tegra210_amx_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, TEGRA210_AMX_CTRL, 0, 0), + SND_SOC_DAPM_AIF_IN("RX2", NULL, 0, TEGRA210_AMX_CTRL, 1, 0), + SND_SOC_DAPM_AIF_IN("RX3", NULL, 0, TEGRA210_AMX_CTRL, 2, 0), + SND_SOC_DAPM_AIF_IN("RX4", NULL, 0, TEGRA210_AMX_CTRL, 3, 0), + SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_AMX_ENABLE, + TEGRA210_AMX_ENABLE_SHIFT, 0), +}; + +#define STREAM_ROUTES(id, sname) \ + { "RX" #id " XBAR-" sname, NULL, "RX" #id " XBAR-TX" }, \ + { "RX" #id "-CIF-" sname, NULL, "RX" #id " XBAR-" sname },\ + { "RX" #id, NULL, "RX" #id "-CIF-" sname }, \ + { "TX", NULL, "RX" #id }, \ + { "TX-CIF-" sname, NULL, "TX" }, \ + { "XBAR-" sname, NULL, "TX-CIF-" sname }, \ + { "XBAR-RX", NULL, "XBAR-" sname } + +#define AMX_ROUTES(id) \ + STREAM_ROUTES(id, "Playback"), \ + STREAM_ROUTES(id, "Capture") + +static const struct snd_soc_dapm_route tegra210_amx_routes[] = { + AMX_ROUTES(1), + AMX_ROUTES(2), + AMX_ROUTES(3), + AMX_ROUTES(4), +}; + +#define TEGRA210_AMX_BYTE_MAP_CTRL(reg) \ + SOC_SINGLE_EXT("Byte Map " #reg, reg, 0, 256, 0, \ + tegra210_amx_get_byte_map, \ + tegra210_amx_put_byte_map) + +static struct snd_kcontrol_new tegra210_amx_controls[] = { + TEGRA210_AMX_BYTE_MAP_CTRL(0), + TEGRA210_AMX_BYTE_MAP_CTRL(1), + TEGRA210_AMX_BYTE_MAP_CTRL(2), + TEGRA210_AMX_BYTE_MAP_CTRL(3), + TEGRA210_AMX_BYTE_MAP_CTRL(4), + TEGRA210_AMX_BYTE_MAP_CTRL(5), + TEGRA210_AMX_BYTE_MAP_CTRL(6), + TEGRA210_AMX_BYTE_MAP_CTRL(7), + TEGRA210_AMX_BYTE_MAP_CTRL(8), + TEGRA210_AMX_BYTE_MAP_CTRL(9), + TEGRA210_AMX_BYTE_MAP_CTRL(10), + TEGRA210_AMX_BYTE_MAP_CTRL(11), + TEGRA210_AMX_BYTE_MAP_CTRL(12), + TEGRA210_AMX_BYTE_MAP_CTRL(13), + TEGRA210_AMX_BYTE_MAP_CTRL(14), + TEGRA210_AMX_BYTE_MAP_CTRL(15), + TEGRA210_AMX_BYTE_MAP_CTRL(16), + TEGRA210_AMX_BYTE_MAP_CTRL(17), + TEGRA210_AMX_BYTE_MAP_CTRL(18), + TEGRA210_AMX_BYTE_MAP_CTRL(19), + TEGRA210_AMX_BYTE_MAP_CTRL(20), + TEGRA210_AMX_BYTE_MAP_CTRL(21), + TEGRA210_AMX_BYTE_MAP_CTRL(22), + TEGRA210_AMX_BYTE_MAP_CTRL(23), + TEGRA210_AMX_BYTE_MAP_CTRL(24), + TEGRA210_AMX_BYTE_MAP_CTRL(25), + TEGRA210_AMX_BYTE_MAP_CTRL(26), + TEGRA210_AMX_BYTE_MAP_CTRL(27), + TEGRA210_AMX_BYTE_MAP_CTRL(28), + TEGRA210_AMX_BYTE_MAP_CTRL(29), + TEGRA210_AMX_BYTE_MAP_CTRL(30), + TEGRA210_AMX_BYTE_MAP_CTRL(31), + TEGRA210_AMX_BYTE_MAP_CTRL(32), + TEGRA210_AMX_BYTE_MAP_CTRL(33), + TEGRA210_AMX_BYTE_MAP_CTRL(34), + TEGRA210_AMX_BYTE_MAP_CTRL(35), + TEGRA210_AMX_BYTE_MAP_CTRL(36), + TEGRA210_AMX_BYTE_MAP_CTRL(37), + TEGRA210_AMX_BYTE_MAP_CTRL(38), + TEGRA210_AMX_BYTE_MAP_CTRL(39), + TEGRA210_AMX_BYTE_MAP_CTRL(40), + TEGRA210_AMX_BYTE_MAP_CTRL(41), + TEGRA210_AMX_BYTE_MAP_CTRL(42), + TEGRA210_AMX_BYTE_MAP_CTRL(43), + TEGRA210_AMX_BYTE_MAP_CTRL(44), + TEGRA210_AMX_BYTE_MAP_CTRL(45), + TEGRA210_AMX_BYTE_MAP_CTRL(46), + TEGRA210_AMX_BYTE_MAP_CTRL(47), + TEGRA210_AMX_BYTE_MAP_CTRL(48), + TEGRA210_AMX_BYTE_MAP_CTRL(49), + TEGRA210_AMX_BYTE_MAP_CTRL(50), + TEGRA210_AMX_BYTE_MAP_CTRL(51), + TEGRA210_AMX_BYTE_MAP_CTRL(52), + TEGRA210_AMX_BYTE_MAP_CTRL(53), + TEGRA210_AMX_BYTE_MAP_CTRL(54), + TEGRA210_AMX_BYTE_MAP_CTRL(55), + TEGRA210_AMX_BYTE_MAP_CTRL(56), + TEGRA210_AMX_BYTE_MAP_CTRL(57), + TEGRA210_AMX_BYTE_MAP_CTRL(58), + TEGRA210_AMX_BYTE_MAP_CTRL(59), + TEGRA210_AMX_BYTE_MAP_CTRL(60), + TEGRA210_AMX_BYTE_MAP_CTRL(61), + TEGRA210_AMX_BYTE_MAP_CTRL(62), + TEGRA210_AMX_BYTE_MAP_CTRL(63), +}; + +static const struct snd_soc_component_driver tegra210_amx_cmpnt = { + .dapm_widgets = tegra210_amx_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_amx_widgets), + .dapm_routes = tegra210_amx_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_amx_routes), + .controls = tegra210_amx_controls, + .num_controls = ARRAY_SIZE(tegra210_amx_controls), +}; + +static bool tegra210_amx_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_AMX_RX_INT_MASK ... TEGRA210_AMX_RX4_CIF_CTRL: + case TEGRA210_AMX_TX_INT_MASK ... TEGRA210_AMX_CG: + case TEGRA210_AMX_CTRL ... TEGRA210_AMX_CYA: + case TEGRA210_AMX_CFG_RAM_CTRL ... TEGRA210_AMX_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra194_amx_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA194_AMX_RX1_FRAME_PERIOD ... TEGRA194_AMX_RX4_FRAME_PERIOD: + return true; + default: + return tegra210_amx_wr_reg(dev, reg); + } +} + +static bool tegra210_amx_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_AMX_RX_STATUS ... TEGRA210_AMX_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra194_amx_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA194_AMX_RX1_FRAME_PERIOD ... TEGRA194_AMX_RX4_FRAME_PERIOD: + return true; + default: + return tegra210_amx_rd_reg(dev, reg); + } +} + +static bool tegra210_amx_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_AMX_RX_STATUS: + case TEGRA210_AMX_RX_INT_STATUS: + case TEGRA210_AMX_RX_INT_SET: + case TEGRA210_AMX_TX_STATUS: + case TEGRA210_AMX_TX_INT_STATUS: + case TEGRA210_AMX_TX_INT_SET: + case TEGRA210_AMX_SOFT_RESET: + case TEGRA210_AMX_STATUS: + case TEGRA210_AMX_INT_STATUS: + case TEGRA210_AMX_CFG_RAM_CTRL: + case TEGRA210_AMX_CFG_RAM_DATA: + return true; + default: + break; + } + + return false; +} + +static const struct regmap_config tegra210_amx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_AMX_CFG_RAM_DATA, + .writeable_reg = tegra210_amx_wr_reg, + .readable_reg = tegra210_amx_rd_reg, + .volatile_reg = tegra210_amx_volatile_reg, + .reg_defaults = tegra210_amx_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_amx_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config tegra194_amx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA194_AMX_RX4_LAST_FRAME_PERIOD, + .writeable_reg = tegra194_amx_wr_reg, + .readable_reg = tegra194_amx_rd_reg, + .volatile_reg = tegra210_amx_volatile_reg, + .reg_defaults = tegra210_amx_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_amx_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct tegra210_amx_soc_data soc_data_tegra210 = { + .regmap_conf = &tegra210_amx_regmap_config, +}; + +static const struct tegra210_amx_soc_data soc_data_tegra194 = { + .regmap_conf = &tegra194_amx_regmap_config, + .auto_disable = true, +}; + +static const struct of_device_id tegra210_amx_of_match[] = { + { .compatible = "nvidia,tegra210-amx", .data = &soc_data_tegra210 }, + { .compatible = "nvidia,tegra194-amx", .data = &soc_data_tegra194 }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_amx_of_match); + +static int tegra210_amx_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_amx *amx; + void __iomem *regs; + int err; + const struct of_device_id *match; + struct tegra210_amx_soc_data *soc_data; + + match = of_match_device(tegra210_amx_of_match, dev); + + soc_data = (struct tegra210_amx_soc_data *)match->data; + + amx = devm_kzalloc(dev, sizeof(*amx), GFP_KERNEL); + if (!amx) + return -ENOMEM; + + amx->soc_data = soc_data; + + dev_set_drvdata(dev, amx); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + amx->regmap = devm_regmap_init_mmio(dev, regs, + soc_data->regmap_conf); + if (IS_ERR(amx->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(amx->regmap); + } + + regcache_cache_only(amx->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra210_amx_cmpnt, + tegra210_amx_dais, + ARRAY_SIZE(tegra210_amx_dais)); + if (err) { + dev_err(dev, "can't register AMX component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra210_amx_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra210_amx_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_amx_runtime_suspend, + tegra210_amx_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra210_amx_driver = { + .driver = { + .name = "tegra210-amx", + .of_match_table = tegra210_amx_of_match, + .pm = &tegra210_amx_pm_ops, + }, + .probe = tegra210_amx_platform_probe, + .remove = tegra210_amx_platform_remove, +}; +module_platform_driver(tegra210_amx_driver); + +MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 AMX ASoC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_amx.h b/sound/soc/tegra/tegra210_amx.h new file mode 100644 index 000000000000..e277741e4258 --- /dev/null +++ b/sound/soc/tegra/tegra210_amx.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_amx.h - Definitions for Tegra210 AMX driver + * + * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_AMX_H__ +#define __TEGRA210_AMX_H__ + +/* Register offsets from TEGRA210_AMX*_BASE */ +#define TEGRA210_AMX_RX_STATUS 0x0c +#define TEGRA210_AMX_RX_INT_STATUS 0x10 +#define TEGRA210_AMX_RX_INT_MASK 0x14 +#define TEGRA210_AMX_RX_INT_SET 0x18 +#define TEGRA210_AMX_RX_INT_CLEAR 0x1c +#define TEGRA210_AMX_RX1_CIF_CTRL 0x20 +#define TEGRA210_AMX_RX2_CIF_CTRL 0x24 +#define TEGRA210_AMX_RX3_CIF_CTRL 0x28 +#define TEGRA210_AMX_RX4_CIF_CTRL 0x2c +#define TEGRA210_AMX_TX_STATUS 0x4c +#define TEGRA210_AMX_TX_INT_STATUS 0x50 +#define TEGRA210_AMX_TX_INT_MASK 0x54 +#define TEGRA210_AMX_TX_INT_SET 0x58 +#define TEGRA210_AMX_TX_INT_CLEAR 0x5c +#define TEGRA210_AMX_TX_CIF_CTRL 0x60 +#define TEGRA210_AMX_ENABLE 0x80 +#define TEGRA210_AMX_SOFT_RESET 0x84 +#define TEGRA210_AMX_CG 0x88 +#define TEGRA210_AMX_STATUS 0x8c +#define TEGRA210_AMX_INT_STATUS 0x90 +#define TEGRA210_AMX_CTRL 0xa4 +#define TEGRA210_AMX_OUT_BYTE_EN0 0xa8 +#define TEGRA210_AMX_OUT_BYTE_EN1 0xac +#define TEGRA210_AMX_CYA 0xb0 +#define TEGRA210_AMX_CFG_RAM_CTRL 0xb8 +#define TEGRA210_AMX_CFG_RAM_DATA 0xbc + +#define TEGRA194_AMX_RX1_FRAME_PERIOD 0xc0 +#define TEGRA194_AMX_RX4_FRAME_PERIOD 0xcc +#define TEGRA194_AMX_RX4_LAST_FRAME_PERIOD 0xdc + +/* Fields in TEGRA210_AMX_ENABLE */ +#define TEGRA210_AMX_ENABLE_SHIFT 0 + +/* Fields in TEGRA210_AMX_CTRL */ +#define TEGRA210_AMX_CTRL_MSTR_RX_NUM_SHIFT 14 +#define TEGRA210_AMX_CTRL_MSTR_RX_NUM_MASK (3 << TEGRA210_AMX_CTRL_MSTR_RX_NUM_SHIFT) + +#define TEGRA210_AMX_CTRL_RX_DEP_SHIFT 12 +#define TEGRA210_AMX_CTRL_RX_DEP_MASK (3 << TEGRA210_AMX_CTRL_RX_DEP_SHIFT) + +/* Fields in TEGRA210_AMX_CFG_RAM_CTRL */ +#define TEGRA210_AMX_CFG_RAM_CTRL_RW_SHIFT 14 +#define TEGRA210_AMX_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_AMX_CFG_RAM_CTRL_RW_SHIFT) + +#define TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13 +#define TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT) + +#define TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12 +#define TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT) + +#define TEGRA210_AMX_CFG_CTRL_RAM_ADDR_SHIFT 0 + +/* Fields in TEGRA210_AMX_SOFT_RESET */ +#define TEGRA210_AMX_SOFT_RESET_SOFT_EN 1 +#define TEGRA210_AMX_SOFT_RESET_SOFT_RESET_MASK TEGRA210_AMX_SOFT_RESET_SOFT_EN + +#define TEGRA210_AMX_AUDIOCIF_CH_STRIDE 4 +#define TEGRA210_AMX_RAM_DEPTH 16 +#define TEGRA210_AMX_MAP_STREAM_NUM_SHIFT 6 +#define TEGRA210_AMX_MAP_WORD_NUM_SHIFT 2 +#define TEGRA210_AMX_MAP_BYTE_NUM_SHIFT 0 + +enum { + TEGRA210_AMX_WAIT_ON_ALL, + TEGRA210_AMX_WAIT_ON_ANY, +}; + +struct tegra210_amx_soc_data { + const struct regmap_config *regmap_conf; + bool auto_disable; +}; + +struct tegra210_amx { + const struct tegra210_amx_soc_data *soc_data; + unsigned int map[TEGRA210_AMX_RAM_DEPTH]; + struct regmap *regmap; + unsigned int byte_mask[2]; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_mixer.c b/sound/soc/tegra/tegra210_mixer.c new file mode 100644 index 000000000000..55e61776c565 --- /dev/null +++ b/sound/soc/tegra/tegra210_mixer.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_mixer.c - Tegra210 MIXER driver +// +// Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra210_mixer.h" +#include "tegra_cif.h" + +#define MIXER_REG(reg, id) ((reg) + ((id) * TEGRA210_MIXER_REG_STRIDE)) +#define MIXER_REG_BASE(reg) ((reg) % TEGRA210_MIXER_REG_STRIDE) + +#define MIXER_GAIN_CFG_RAM_ADDR(id) \ + (TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0 + \ + ((id) * TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE)) + +#define MIXER_RX_REG_DEFAULTS(id) \ + { MIXER_REG(TEGRA210_MIXER_RX1_CIF_CTRL, id), 0x00007700}, \ + { MIXER_REG(TEGRA210_MIXER_RX1_CTRL, id), 0x00010823}, \ + { MIXER_REG(TEGRA210_MIXER_RX1_PEAK_CTRL, id), 0x000012c0} + +#define MIXER_TX_REG_DEFAULTS(id) \ + { MIXER_REG(TEGRA210_MIXER_TX1_INT_MASK, (id)), 0x00000001}, \ + { MIXER_REG(TEGRA210_MIXER_TX1_CIF_CTRL, (id)), 0x00007700} + +#define REG_DURATION_PARAM(reg, i) ((reg) + NUM_GAIN_POLY_COEFFS + 1 + (i)) + +static const struct reg_default tegra210_mixer_reg_defaults[] = { + /* Inputs */ + MIXER_RX_REG_DEFAULTS(0), + MIXER_RX_REG_DEFAULTS(1), + MIXER_RX_REG_DEFAULTS(2), + MIXER_RX_REG_DEFAULTS(3), + MIXER_RX_REG_DEFAULTS(4), + MIXER_RX_REG_DEFAULTS(5), + MIXER_RX_REG_DEFAULTS(6), + MIXER_RX_REG_DEFAULTS(7), + MIXER_RX_REG_DEFAULTS(8), + MIXER_RX_REG_DEFAULTS(9), + /* Outputs */ + MIXER_TX_REG_DEFAULTS(0), + MIXER_TX_REG_DEFAULTS(1), + MIXER_TX_REG_DEFAULTS(2), + MIXER_TX_REG_DEFAULTS(3), + MIXER_TX_REG_DEFAULTS(4), + + { TEGRA210_MIXER_CG, 0x00000001}, + { TEGRA210_MIXER_GAIN_CFG_RAM_CTRL, 0x00004000}, + { TEGRA210_MIXER_PEAKM_RAM_CTRL, 0x00004000}, + { TEGRA210_MIXER_ENABLE, 0x1 }, +}; + +/* Default gain parameters */ +static const struct tegra210_mixer_gain_params gain_params = { + /* Polynomial coefficients */ + { 0, 0, 0, 0, 0, 0, 0, 0x1000000, 0 }, + /* Gain value */ + 0x10000, + /* Duration Parameters */ + { 0, 0, 0x400, 0x8000000 }, +}; + +static int __maybe_unused tegra210_mixer_runtime_suspend(struct device *dev) +{ + struct tegra210_mixer *mixer = dev_get_drvdata(dev); + + regcache_cache_only(mixer->regmap, true); + regcache_mark_dirty(mixer->regmap); + + return 0; +} + +static int __maybe_unused tegra210_mixer_runtime_resume(struct device *dev) +{ + struct tegra210_mixer *mixer = dev_get_drvdata(dev); + + regcache_cache_only(mixer->regmap, false); + regcache_sync(mixer->regmap); + + return 0; +} + +static int tegra210_mixer_write_ram(struct tegra210_mixer *mixer, + unsigned int addr, + unsigned int coef) +{ + unsigned int reg, val; + int err; + + /* Check if busy */ + err = regmap_read_poll_timeout(mixer->regmap, + TEGRA210_MIXER_GAIN_CFG_RAM_CTRL, + val, !(val & 0x80000000), 10, 10000); + if (err < 0) + return err; + + reg = (addr << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT) & + TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_MASK; + reg |= TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN; + reg |= TEGRA210_MIXER_GAIN_CFG_RAM_RW_WRITE; + reg |= TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN; + + regmap_write(mixer->regmap, + TEGRA210_MIXER_GAIN_CFG_RAM_CTRL, + reg); + regmap_write(mixer->regmap, + TEGRA210_MIXER_GAIN_CFG_RAM_DATA, + coef); + + return 0; +} + +static int tegra210_mixer_configure_gain(struct snd_soc_component *cmpnt, + unsigned int id, bool instant_gain) +{ + struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt); + unsigned int reg = MIXER_GAIN_CFG_RAM_ADDR(id); + int err, i; + + pm_runtime_get_sync(cmpnt->dev); + + /* Write default gain poly coefficients */ + for (i = 0; i < NUM_GAIN_POLY_COEFFS; i++) { + err = tegra210_mixer_write_ram(mixer, reg + i, + gain_params.poly_coeff[i]); + + if (err < 0) + goto rpm_put; + } + + /* Write stored gain value */ + err = tegra210_mixer_write_ram(mixer, reg + NUM_GAIN_POLY_COEFFS, + mixer->gain_value[id]); + if (err < 0) + goto rpm_put; + + /* Write duration parameters */ + for (i = 0; i < NUM_DURATION_PARMS; i++) { + int val; + + if (instant_gain) + val = 1; + else + val = gain_params.duration[i]; + + err = tegra210_mixer_write_ram(mixer, + REG_DURATION_PARAM(reg, i), + val); + if (err < 0) + goto rpm_put; + } + + /* Trigger to apply gain configurations */ + err = tegra210_mixer_write_ram(mixer, reg + REG_CFG_DONE_TRIGGER, + VAL_CFG_DONE_TRIGGER); + +rpm_put: + pm_runtime_put(cmpnt->dev); + + return err; +} + +static int tegra210_mixer_get_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt); + unsigned int reg = mc->reg; + unsigned int i; + + i = (reg - TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0) / + TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE; + + ucontrol->value.integer.value[0] = mixer->gain_value[i]; + + return 0; +} + +static int tegra210_mixer_put_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt); + unsigned int reg = mc->reg, id; + bool instant_gain = false; + int err; + + if (strstr(kcontrol->id.name, "Instant Gain Volume")) + instant_gain = true; + + /* Save gain value for specific MIXER input */ + id = (reg - TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0) / + TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE; + + mixer->gain_value[id] = ucontrol->value.integer.value[0]; + + err = tegra210_mixer_configure_gain(cmpnt, id, instant_gain); + if (err) { + dev_err(cmpnt->dev, "Failed to apply gain\n"); + return err; + } + + return 1; +} + +static int tegra210_mixer_set_audio_cif(struct tegra210_mixer *mixer, + struct snd_pcm_hw_params *params, + unsigned int reg, + unsigned int id) +{ + unsigned int channels, audio_bits; + struct tegra_cif_conf cif_conf; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + return -EINVAL; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = audio_bits; + + tegra_set_cif(mixer->regmap, + reg + (id * TEGRA210_MIXER_REG_STRIDE), + &cif_conf); + + return 0; +} + +static int tegra210_mixer_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai); + int err; + + err = tegra210_mixer_set_audio_cif(mixer, params, + TEGRA210_MIXER_RX1_CIF_CTRL, + dai->id); + if (err < 0) + return err; + + return tegra210_mixer_configure_gain(dai->component, dai->id, false); +} + +static int tegra210_mixer_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai); + + return tegra210_mixer_set_audio_cif(mixer, params, + TEGRA210_MIXER_TX1_CIF_CTRL, + dai->id - TEGRA210_MIXER_RX_MAX); +} + +static const struct snd_soc_dai_ops tegra210_mixer_out_dai_ops = { + .hw_params = tegra210_mixer_out_hw_params, +}; + +static const struct snd_soc_dai_ops tegra210_mixer_in_dai_ops = { + .hw_params = tegra210_mixer_in_hw_params, +}; + +#define IN_DAI(id) \ + { \ + .name = "MIXER-RX-CIF"#id, \ + .playback = { \ + .stream_name = "RX" #id "-CIF-Playback",\ + .channels_min = 1, \ + .channels_max = 8, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "RX" #id "-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 8, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra210_mixer_in_dai_ops, \ + } + +#define OUT_DAI(id) \ + { \ + .name = "MIXER-TX-CIF" #id, \ + .playback = { \ + .stream_name = "TX" #id "-CIF-Playback",\ + .channels_min = 1, \ + .channels_max = 8, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .stream_name = "TX" #id "-CIF-Capture", \ + .channels_min = 1, \ + .channels_max = 8, \ + .rates = SNDRV_PCM_RATE_8000_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &tegra210_mixer_out_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_mixer_dais[] = { + /* Mixer Input */ + IN_DAI(1), + IN_DAI(2), + IN_DAI(3), + IN_DAI(4), + IN_DAI(5), + IN_DAI(6), + IN_DAI(7), + IN_DAI(8), + IN_DAI(9), + IN_DAI(10), + + /* Mixer Output */ + OUT_DAI(1), + OUT_DAI(2), + OUT_DAI(3), + OUT_DAI(4), + OUT_DAI(5), +}; + +#define ADDER_CTRL_DECL(name, reg) \ + static const struct snd_kcontrol_new name[] = { \ + SOC_DAPM_SINGLE("RX1", reg, 0, 1, 0), \ + SOC_DAPM_SINGLE("RX2", reg, 1, 1, 0), \ + SOC_DAPM_SINGLE("RX3", reg, 2, 1, 0), \ + SOC_DAPM_SINGLE("RX4", reg, 3, 1, 0), \ + SOC_DAPM_SINGLE("RX5", reg, 4, 1, 0), \ + SOC_DAPM_SINGLE("RX6", reg, 5, 1, 0), \ + SOC_DAPM_SINGLE("RX7", reg, 6, 1, 0), \ + SOC_DAPM_SINGLE("RX8", reg, 7, 1, 0), \ + SOC_DAPM_SINGLE("RX9", reg, 8, 1, 0), \ + SOC_DAPM_SINGLE("RX10", reg, 9, 1, 0), \ + } + +ADDER_CTRL_DECL(adder1, TEGRA210_MIXER_TX1_ADDER_CONFIG); +ADDER_CTRL_DECL(adder2, TEGRA210_MIXER_TX2_ADDER_CONFIG); +ADDER_CTRL_DECL(adder3, TEGRA210_MIXER_TX3_ADDER_CONFIG); +ADDER_CTRL_DECL(adder4, TEGRA210_MIXER_TX4_ADDER_CONFIG); +ADDER_CTRL_DECL(adder5, TEGRA210_MIXER_TX5_ADDER_CONFIG); + +#define GAIN_CTRL(id) \ + SOC_SINGLE_EXT("RX" #id " Gain Volume", \ + MIXER_GAIN_CFG_RAM_ADDR((id) - 1), 0, \ + 0x20000, 0, tegra210_mixer_get_gain, \ + tegra210_mixer_put_gain), \ + SOC_SINGLE_EXT("RX" #id " Instant Gain Volume", \ + MIXER_GAIN_CFG_RAM_ADDR((id) - 1), 0, \ + 0x20000, 0, tegra210_mixer_get_gain, \ + tegra210_mixer_put_gain), + +/* Volume controls for all MIXER inputs */ +static const struct snd_kcontrol_new tegra210_mixer_gain_ctls[] = { + GAIN_CTRL(1) + GAIN_CTRL(2) + GAIN_CTRL(3) + GAIN_CTRL(4) + GAIN_CTRL(5) + GAIN_CTRL(6) + GAIN_CTRL(7) + GAIN_CTRL(8) + GAIN_CTRL(9) + GAIN_CTRL(10) +}; + +static const struct snd_soc_dapm_widget tegra210_mixer_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX4", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX5", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX6", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX7", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX8", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX9", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX10", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, TEGRA210_MIXER_TX1_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX2", NULL, 0, TEGRA210_MIXER_TX2_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX3", NULL, 0, TEGRA210_MIXER_TX3_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX4", NULL, 0, TEGRA210_MIXER_TX4_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX5", NULL, 0, TEGRA210_MIXER_TX5_ENABLE, 0, 0), + SND_SOC_DAPM_MIXER("Adder1", SND_SOC_NOPM, 1, 0, adder1, + ARRAY_SIZE(adder1)), + SND_SOC_DAPM_MIXER("Adder2", SND_SOC_NOPM, 1, 0, adder2, + ARRAY_SIZE(adder2)), + SND_SOC_DAPM_MIXER("Adder3", SND_SOC_NOPM, 1, 0, adder3, + ARRAY_SIZE(adder3)), + SND_SOC_DAPM_MIXER("Adder4", SND_SOC_NOPM, 1, 0, adder4, + ARRAY_SIZE(adder4)), + SND_SOC_DAPM_MIXER("Adder5", SND_SOC_NOPM, 1, 0, adder5, + ARRAY_SIZE(adder5)), +}; + +#define RX_ROUTES(id, sname) \ + { "RX" #id " XBAR-" sname, NULL, "RX" #id " XBAR-TX" }, \ + { "RX" #id "-CIF-" sname, NULL, "RX" #id " XBAR-" sname }, \ + { "RX" #id, NULL, "RX" #id "-CIF-" sname } + +#define MIXER_RX_ROUTES(id) \ + RX_ROUTES(id, "Playback"), \ + RX_ROUTES(id, "Capture") + +#define ADDER_ROUTES(id, sname) \ + { "Adder" #id, "RX1", "RX1" }, \ + { "Adder" #id, "RX2", "RX2" }, \ + { "Adder" #id, "RX3", "RX3" }, \ + { "Adder" #id, "RX4", "RX4" }, \ + { "Adder" #id, "RX5", "RX5" }, \ + { "Adder" #id, "RX6", "RX6" }, \ + { "Adder" #id, "RX7", "RX7" }, \ + { "Adder" #id, "RX8", "RX8" }, \ + { "Adder" #id, "RX9", "RX9" }, \ + { "Adder" #id, "RX10", "RX10" }, \ + { "TX" #id, NULL, "Adder" #id }, \ + { "TX" #id "-CIF-" sname, NULL, "TX" #id }, \ + { "TX" #id " XBAR-" sname, NULL, "TX" #id "-CIF-" sname }, \ + { "TX" #id " XBAR-RX", NULL, "TX" #id " XBAR-" sname } \ + +#define TX_ROUTES(id, sname) \ + ADDER_ROUTES(1, sname), \ + ADDER_ROUTES(2, sname), \ + ADDER_ROUTES(3, sname), \ + ADDER_ROUTES(4, sname), \ + ADDER_ROUTES(5, sname) + +#define MIXER_TX_ROUTES(id) \ + TX_ROUTES(id, "Playback"), \ + TX_ROUTES(id, "Capture") + +static const struct snd_soc_dapm_route tegra210_mixer_routes[] = { + /* Input */ + MIXER_RX_ROUTES(1), + MIXER_RX_ROUTES(2), + MIXER_RX_ROUTES(3), + MIXER_RX_ROUTES(4), + MIXER_RX_ROUTES(5), + MIXER_RX_ROUTES(6), + MIXER_RX_ROUTES(7), + MIXER_RX_ROUTES(8), + MIXER_RX_ROUTES(9), + MIXER_RX_ROUTES(10), + /* Output */ + MIXER_TX_ROUTES(1), + MIXER_TX_ROUTES(2), + MIXER_TX_ROUTES(3), + MIXER_TX_ROUTES(4), + MIXER_TX_ROUTES(5), +}; + +static const struct snd_soc_component_driver tegra210_mixer_cmpnt = { + .dapm_widgets = tegra210_mixer_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_mixer_widgets), + .dapm_routes = tegra210_mixer_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_mixer_routes), + .controls = tegra210_mixer_gain_ctls, + .num_controls = ARRAY_SIZE(tegra210_mixer_gain_ctls), +}; + +static bool tegra210_mixer_wr_reg(struct device *dev, + unsigned int reg) +{ + if (reg < TEGRA210_MIXER_RX_LIMIT) + reg = MIXER_REG_BASE(reg); + else if (reg < TEGRA210_MIXER_TX_LIMIT) + reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE; + + switch (reg) { + case TEGRA210_MIXER_RX1_SOFT_RESET: + case TEGRA210_MIXER_RX1_CIF_CTRL ... TEGRA210_MIXER_RX1_PEAK_CTRL: + + case TEGRA210_MIXER_TX1_ENABLE: + case TEGRA210_MIXER_TX1_SOFT_RESET: + case TEGRA210_MIXER_TX1_INT_MASK ... TEGRA210_MIXER_TX1_ADDER_CONFIG: + + case TEGRA210_MIXER_ENABLE ... TEGRA210_MIXER_CG: + case TEGRA210_MIXER_GAIN_CFG_RAM_CTRL ... TEGRA210_MIXER_CTRL: + return true; + default: + return false; + } +} + +static bool tegra210_mixer_rd_reg(struct device *dev, + unsigned int reg) +{ + if (reg < TEGRA210_MIXER_RX_LIMIT) + reg = MIXER_REG_BASE(reg); + else if (reg < TEGRA210_MIXER_TX_LIMIT) + reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE; + + switch (reg) { + case TEGRA210_MIXER_RX1_SOFT_RESET ... TEGRA210_MIXER_RX1_SAMPLE_COUNT: + case TEGRA210_MIXER_TX1_ENABLE ... TEGRA210_MIXER_TX1_ADDER_CONFIG: + case TEGRA210_MIXER_ENABLE ... TEGRA210_MIXER_CTRL: + return true; + default: + return false; + } +} + +static bool tegra210_mixer_volatile_reg(struct device *dev, + unsigned int reg) +{ + if (reg < TEGRA210_MIXER_RX_LIMIT) + reg = MIXER_REG_BASE(reg); + else if (reg < TEGRA210_MIXER_TX_LIMIT) + reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE; + + switch (reg) { + case TEGRA210_MIXER_RX1_SOFT_RESET: + case TEGRA210_MIXER_RX1_STATUS: + + case TEGRA210_MIXER_TX1_SOFT_RESET: + case TEGRA210_MIXER_TX1_STATUS: + case TEGRA210_MIXER_TX1_INT_STATUS: + case TEGRA210_MIXER_TX1_INT_SET: + + case TEGRA210_MIXER_SOFT_RESET: + case TEGRA210_MIXER_STATUS: + case TEGRA210_MIXER_INT_STATUS: + case TEGRA210_MIXER_GAIN_CFG_RAM_CTRL: + case TEGRA210_MIXER_GAIN_CFG_RAM_DATA: + case TEGRA210_MIXER_PEAKM_RAM_CTRL: + case TEGRA210_MIXER_PEAKM_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_mixer_precious_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_MIXER_GAIN_CFG_RAM_DATA: + case TEGRA210_MIXER_PEAKM_RAM_DATA: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra210_mixer_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_MIXER_CTRL, + .writeable_reg = tegra210_mixer_wr_reg, + .readable_reg = tegra210_mixer_rd_reg, + .volatile_reg = tegra210_mixer_volatile_reg, + .precious_reg = tegra210_mixer_precious_reg, + .reg_defaults = tegra210_mixer_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_mixer_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct of_device_id tegra210_mixer_of_match[] = { + { .compatible = "nvidia,tegra210-amixer" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_mixer_of_match); + +static int tegra210_mixer_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_mixer *mixer; + void __iomem *regs; + int err, i; + + mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + + dev_set_drvdata(dev, mixer); + + /* Use default gain value for all MIXER inputs */ + for (i = 0; i < TEGRA210_MIXER_RX_MAX; i++) + mixer->gain_value[i] = gain_params.gain_value; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mixer->regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_mixer_regmap_config); + if (IS_ERR(mixer->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(mixer->regmap); + } + + regcache_cache_only(mixer->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra210_mixer_cmpnt, + tegra210_mixer_dais, + ARRAY_SIZE(tegra210_mixer_dais)); + if (err) { + dev_err(dev, "can't register MIXER component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + return 0; +} + +static int tegra210_mixer_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra210_mixer_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_mixer_runtime_suspend, + tegra210_mixer_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra210_mixer_driver = { + .driver = { + .name = "tegra210_mixer", + .of_match_table = tegra210_mixer_of_match, + .pm = &tegra210_mixer_pm_ops, + }, + .probe = tegra210_mixer_platform_probe, + .remove = tegra210_mixer_platform_remove, +}; +module_platform_driver(tegra210_mixer_driver); + +MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 MIXER ASoC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_mixer.h b/sound/soc/tegra/tegra210_mixer.h new file mode 100644 index 000000000000..a330530fbc61 --- /dev/null +++ b/sound/soc/tegra/tegra210_mixer.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_mixer.h - Definitions for Tegra210 MIXER driver + * + * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_MIXER_H__ +#define __TEGRA210_MIXER_H__ + +/* XBAR_RX related MIXER offsets */ +#define TEGRA210_MIXER_RX1_SOFT_RESET 0x04 +#define TEGRA210_MIXER_RX1_STATUS 0x10 +#define TEGRA210_MIXER_RX1_CIF_CTRL 0x24 +#define TEGRA210_MIXER_RX1_CTRL 0x28 +#define TEGRA210_MIXER_RX1_PEAK_CTRL 0x2c +#define TEGRA210_MIXER_RX1_SAMPLE_COUNT 0x30 + +/* XBAR_TX related MIXER offsets */ +#define TEGRA210_MIXER_TX1_ENABLE 0x280 +#define TEGRA210_MIXER_TX1_SOFT_RESET 0x284 +#define TEGRA210_MIXER_TX1_STATUS 0x290 +#define TEGRA210_MIXER_TX1_INT_STATUS 0x294 +#define TEGRA210_MIXER_TX1_INT_MASK 0x298 +#define TEGRA210_MIXER_TX1_INT_SET 0x29c +#define TEGRA210_MIXER_TX1_INT_CLEAR 0x2a0 +#define TEGRA210_MIXER_TX1_CIF_CTRL 0x2a4 +#define TEGRA210_MIXER_TX1_ADDER_CONFIG 0x2a8 + +/* MIXER related offsets */ +#define TEGRA210_MIXER_ENABLE 0x400 +#define TEGRA210_MIXER_SOFT_RESET 0x404 +#define TEGRA210_MIXER_CG 0x408 +#define TEGRA210_MIXER_STATUS 0x410 +#define TEGRA210_MIXER_INT_STATUS 0x414 +#define TEGRA210_MIXER_GAIN_CFG_RAM_CTRL 0x42c +#define TEGRA210_MIXER_GAIN_CFG_RAM_DATA 0x430 +#define TEGRA210_MIXER_PEAKM_RAM_CTRL 0x434 +#define TEGRA210_MIXER_PEAKM_RAM_DATA 0x438 +#define TEGRA210_MIXER_CTRL 0x43c + +#define TEGRA210_MIXER_TX2_ADDER_CONFIG (TEGRA210_MIXER_TX1_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE) +#define TEGRA210_MIXER_TX3_ADDER_CONFIG (TEGRA210_MIXER_TX2_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE) +#define TEGRA210_MIXER_TX4_ADDER_CONFIG (TEGRA210_MIXER_TX3_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE) +#define TEGRA210_MIXER_TX5_ADDER_CONFIG (TEGRA210_MIXER_TX4_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE) + +#define TEGRA210_MIXER_TX2_ENABLE (TEGRA210_MIXER_TX1_ENABLE + TEGRA210_MIXER_REG_STRIDE) +#define TEGRA210_MIXER_TX3_ENABLE (TEGRA210_MIXER_TX2_ENABLE + TEGRA210_MIXER_REG_STRIDE) +#define TEGRA210_MIXER_TX4_ENABLE (TEGRA210_MIXER_TX3_ENABLE + TEGRA210_MIXER_REG_STRIDE) +#define TEGRA210_MIXER_TX5_ENABLE (TEGRA210_MIXER_TX4_ENABLE + TEGRA210_MIXER_REG_STRIDE) + +/* Fields in TEGRA210_MIXER_ENABLE */ +#define TEGRA210_MIXER_ENABLE_SHIFT 0 +#define TEGRA210_MIXER_ENABLE_MASK (1 << TEGRA210_MIXER_ENABLE_SHIFT) +#define TEGRA210_MIXER_EN (1 << TEGRA210_MIXER_ENABLE_SHIFT) + +/* Fields in TEGRA210_MIXER_GAIN_CFG_RAM_CTRL */ +#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0 0x0 +#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE 0x10 + +#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT 14 +#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT) +#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_WRITE (1 << TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT) + +#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT 13 +#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT) +#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN (1 << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT) + +#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT 12 +#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT) +#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN (1 << TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT) + +#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT 0 +#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_MASK (0x1ff << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT) + +#define TEGRA210_MIXER_REG_STRIDE 0x40 +#define TEGRA210_MIXER_RX_MAX 10 +#define TEGRA210_MIXER_RX_LIMIT (TEGRA210_MIXER_RX_MAX * TEGRA210_MIXER_REG_STRIDE) +#define TEGRA210_MIXER_TX_MAX 5 +#define TEGRA210_MIXER_TX_LIMIT (TEGRA210_MIXER_RX_LIMIT + (TEGRA210_MIXER_TX_MAX * TEGRA210_MIXER_REG_STRIDE)) + +#define REG_CFG_DONE_TRIGGER 0xf +#define VAL_CFG_DONE_TRIGGER 0x1 + +#define NUM_GAIN_POLY_COEFFS 9 +#define NUM_DURATION_PARMS 4 + +struct tegra210_mixer_gain_params { + int poly_coeff[NUM_GAIN_POLY_COEFFS]; + int gain_value; + int duration[NUM_DURATION_PARMS]; +}; + +struct tegra210_mixer { + int gain_value[TEGRA210_MIXER_RX_MAX]; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_mvc.c b/sound/soc/tegra/tegra210_mvc.c new file mode 100644 index 000000000000..7b9c7006e419 --- /dev/null +++ b/sound/soc/tegra/tegra210_mvc.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_mvc.c - Tegra210 MVC driver +// +// Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra210_mvc.h" +#include "tegra_cif.h" + +static const struct reg_default tegra210_mvc_reg_defaults[] = { + { TEGRA210_MVC_RX_INT_MASK, 0x00000001}, + { TEGRA210_MVC_RX_CIF_CTRL, 0x00007700}, + { TEGRA210_MVC_TX_INT_MASK, 0x00000001}, + { TEGRA210_MVC_TX_CIF_CTRL, 0x00007700}, + { TEGRA210_MVC_CG, 0x1}, + { TEGRA210_MVC_CTRL, TEGRA210_MVC_CTRL_DEFAULT}, + { TEGRA210_MVC_INIT_VOL, 0x00800000}, + { TEGRA210_MVC_TARGET_VOL, 0x00800000}, + { TEGRA210_MVC_DURATION, 0x000012c0}, + { TEGRA210_MVC_DURATION_INV, 0x0006d3a0}, + { TEGRA210_MVC_POLY_N1, 0x0000007d}, + { TEGRA210_MVC_POLY_N2, 0x00000271}, + { TEGRA210_MVC_PEAK_CTRL, 0x000012c0}, + { TEGRA210_MVC_CFG_RAM_CTRL, 0x00004000}, +}; + +static const struct tegra210_mvc_gain_params gain_params = { + .poly_coeff = { 23738319, 659403, -3680, + 15546680, 2530732, -120985, + 12048422, 5527252, -785042 }, + .poly_n1 = 16, + .poly_n2 = 63, + .duration = 150, + .duration_inv = 14316558, +}; + +static int __maybe_unused tegra210_mvc_runtime_suspend(struct device *dev) +{ + struct tegra210_mvc *mvc = dev_get_drvdata(dev); + + regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &(mvc->ctrl_value)); + + regcache_cache_only(mvc->regmap, true); + regcache_mark_dirty(mvc->regmap); + + return 0; +} + +static int __maybe_unused tegra210_mvc_runtime_resume(struct device *dev) +{ + struct tegra210_mvc *mvc = dev_get_drvdata(dev); + + regcache_cache_only(mvc->regmap, false); + regcache_sync(mvc->regmap); + + regmap_write(mvc->regmap, TEGRA210_MVC_CTRL, mvc->ctrl_value); + regmap_update_bits(mvc->regmap, + TEGRA210_MVC_SWITCH, + TEGRA210_MVC_VOLUME_SWITCH_MASK, + TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); + + return 0; +} + +static void tegra210_mvc_write_ram(struct regmap *regmap) +{ + int i; + + regmap_write(regmap, TEGRA210_MVC_CFG_RAM_CTRL, + TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN | + TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN | + TEGRA210_MVC_CFG_RAM_CTRL_RW_WRITE); + + for (i = 0; i < NUM_GAIN_POLY_COEFFS; i++) + regmap_write(regmap, TEGRA210_MVC_CFG_RAM_DATA, + gain_params.poly_coeff[i]); +} + +static void tegra210_mvc_conv_vol(struct tegra210_mvc *mvc, u8 chan, s32 val) +{ + /* + * Volume control read from mixer control is with + * 100x scaling; for CURVE_POLY the reg range + * is 0-100 (linear, Q24) and for CURVE_LINEAR + * it is -120dB to +40dB (Q8) + */ + if (mvc->curve_type == CURVE_POLY) { + if (val > 10000) + val = 10000; + mvc->volume[chan] = ((val * (1<<8)) / 100) << 16; + } else { + val -= 12000; + mvc->volume[chan] = (val * (1<<8)) / 100; + } +} + +static int tegra210_mvc_get_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); + u8 mute_mask; + u32 val; + + pm_runtime_get_sync(cmpnt->dev); + regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &val); + pm_runtime_put(cmpnt->dev); + + mute_mask = (val >> TEGRA210_MVC_MUTE_SHIFT) & + TEGRA210_MUTE_MASK_EN; + + ucontrol->value.integer.value[0] = mute_mask; + + return 0; +} + +static int tegra210_mvc_put_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); + unsigned int value; + u8 mute_mask; + int err; + + pm_runtime_get_sync(cmpnt->dev); + + /* Check if VOLUME_SWITCH is triggered */ + err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH, + value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK), + 10, 10000); + if (err < 0) + goto end; + + mute_mask = ucontrol->value.integer.value[0]; + + err = regmap_update_bits(mvc->regmap, mc->reg, + TEGRA210_MVC_MUTE_MASK, + mute_mask << TEGRA210_MVC_MUTE_SHIFT); + if (err < 0) + goto end; + + return 1; + +end: + pm_runtime_put(cmpnt->dev); + return err; +} + +static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); + u8 chan = (mc->reg - TEGRA210_MVC_TARGET_VOL) / REG_SIZE; + s32 val = mvc->volume[chan]; + + if (mvc->curve_type == CURVE_POLY) { + val = ((val >> 16) * 100) >> 8; + } else { + val = (val * 100) >> 8; + val += 12000; + } + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); + unsigned int reg = mc->reg; + unsigned int value; + u8 chan; + int err; + + pm_runtime_get_sync(cmpnt->dev); + + /* Check if VOLUME_SWITCH is triggered */ + err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH, + value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK), + 10, 10000); + if (err < 0) + goto end; + + chan = (reg - TEGRA210_MVC_TARGET_VOL) / REG_SIZE; + + tegra210_mvc_conv_vol(mvc, chan, + ucontrol->value.integer.value[0]); + + /* Configure init volume same as target volume */ + regmap_write(mvc->regmap, + TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, chan), + mvc->volume[chan]); + + regmap_write(mvc->regmap, reg, mvc->volume[chan]); + + regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH, + TEGRA210_MVC_VOLUME_SWITCH_MASK, + TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); + + return 1; + +end: + pm_runtime_put(cmpnt->dev); + return err; +} + +static void tegra210_mvc_reset_vol_settings(struct tegra210_mvc *mvc, + struct device *dev) +{ + int i; + + /* Change volume to default init for new curve type */ + if (mvc->curve_type == CURVE_POLY) { + for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) + mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_POLY; + } else { + for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) + mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_LINEAR; + } + + pm_runtime_get_sync(dev); + + /* Program curve type */ + regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL, + TEGRA210_MVC_CURVE_TYPE_MASK, + mvc->curve_type << + TEGRA210_MVC_CURVE_TYPE_SHIFT); + + /* Init volume for all channels */ + for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) { + regmap_write(mvc->regmap, + TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, i), + mvc->volume[i]); + regmap_write(mvc->regmap, + TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, i), + mvc->volume[i]); + } + + /* Trigger volume switch */ + regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH, + TEGRA210_MVC_VOLUME_SWITCH_MASK, + TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); + + pm_runtime_put(dev); +} + +static int tegra210_mvc_get_curve_type(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); + + ucontrol->value.integer.value[0] = mvc->curve_type; + + return 0; +} + +static int tegra210_mvc_put_curve_type(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt); + int value; + + regmap_read(mvc->regmap, TEGRA210_MVC_ENABLE, &value); + if (value & TEGRA210_MVC_EN) { + dev_err(cmpnt->dev, + "Curve type can't be set when MVC is running\n"); + return -EINVAL; + } + + if (mvc->curve_type == ucontrol->value.integer.value[0]) + return 0; + + mvc->curve_type = ucontrol->value.integer.value[0]; + + tegra210_mvc_reset_vol_settings(mvc, cmpnt->dev); + + return 1; +} + +static int tegra210_mvc_set_audio_cif(struct tegra210_mvc *mvc, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + unsigned int channels, audio_bits; + struct tegra_cif_conf cif_conf; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + return -EINVAL; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = audio_bits; + + tegra_set_cif(mvc->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_mvc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra210_mvc *mvc = snd_soc_dai_get_drvdata(dai); + int err, val; + + /* + * Soft Reset: Below performs module soft reset which clears + * all FSM logic, flushes flow control of FIFO and resets the + * state register. It also brings module back to disabled + * state (without flushing the data in the pipe). + */ + regmap_write(mvc->regmap, TEGRA210_MVC_SOFT_RESET, 1); + + err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SOFT_RESET, + val, !val, 10, 10000); + if (err < 0) { + dev_err(dev, "SW reset failed, err = %d\n", err); + return err; + } + + /* Set RX CIF */ + err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_RX_CIF_CTRL); + if (err) { + dev_err(dev, "Can't set MVC RX CIF: %d\n", err); + return err; + } + + /* Set TX CIF */ + err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_TX_CIF_CTRL); + if (err) { + dev_err(dev, "Can't set MVC TX CIF: %d\n", err); + return err; + } + + tegra210_mvc_write_ram(mvc->regmap); + + /* Program poly_n1, poly_n2, duration */ + regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N1, gain_params.poly_n1); + regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N2, gain_params.poly_n2); + regmap_write(mvc->regmap, TEGRA210_MVC_DURATION, gain_params.duration); + + /* Program duration_inv */ + regmap_write(mvc->regmap, TEGRA210_MVC_DURATION_INV, + gain_params.duration_inv); + + return 0; +} + +static const struct snd_soc_dai_ops tegra210_mvc_dai_ops = { + .hw_params = tegra210_mvc_hw_params, +}; + +static const char * const tegra210_mvc_curve_type_text[] = { + "Poly", + "Linear", +}; + +static const struct soc_enum tegra210_mvc_curve_type_ctrl = + SOC_ENUM_SINGLE_EXT(2, tegra210_mvc_curve_type_text); + +#define TEGRA210_MVC_VOL_CTRL(chan) \ + SOC_SINGLE_EXT("Channel" #chan " Volume", \ + TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, \ + (chan - 1)), \ + 0, 16000, 0, tegra210_mvc_get_vol, \ + tegra210_mvc_put_vol) + +static const struct snd_kcontrol_new tegra210_mvc_vol_ctrl[] = { + /* Per channel volume control */ + TEGRA210_MVC_VOL_CTRL(1), + TEGRA210_MVC_VOL_CTRL(2), + TEGRA210_MVC_VOL_CTRL(3), + TEGRA210_MVC_VOL_CTRL(4), + TEGRA210_MVC_VOL_CTRL(5), + TEGRA210_MVC_VOL_CTRL(6), + TEGRA210_MVC_VOL_CTRL(7), + TEGRA210_MVC_VOL_CTRL(8), + + /* Per channel mute */ + SOC_SINGLE_EXT("Per Chan Mute Mask", + TEGRA210_MVC_CTRL, 0, TEGRA210_MUTE_MASK_EN, 0, + tegra210_mvc_get_mute, tegra210_mvc_put_mute), + + SOC_ENUM_EXT("Curve Type", tegra210_mvc_curve_type_ctrl, + tegra210_mvc_get_curve_type, tegra210_mvc_put_curve_type), +}; + +static struct snd_soc_dai_driver tegra210_mvc_dais[] = { + /* Input */ + { + .name = "MVC-RX-CIF", + .playback = { + .stream_name = "RX-CIF-Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "RX-CIF-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + }, + + /* Output */ + { + .name = "MVC-TX-CIF", + .playback = { + .stream_name = "TX-CIF-Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "TX-CIF-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra210_mvc_dai_ops, + } +}; + +static const struct snd_soc_dapm_widget tegra210_mvc_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_MVC_ENABLE, + TEGRA210_MVC_EN_SHIFT, 0), +}; + +#define MVC_ROUTES(sname) \ + { "RX XBAR-" sname, NULL, "XBAR-TX" }, \ + { "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \ + { "RX", NULL, "RX-CIF-" sname }, \ + { "TX-CIF-" sname, NULL, "TX" }, \ + { "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \ + { "XBAR-RX", NULL, "TX XBAR-" sname } + +static const struct snd_soc_dapm_route tegra210_mvc_routes[] = { + { "TX", NULL, "RX" }, + MVC_ROUTES("Playback"), + MVC_ROUTES("Capture"), +}; + +static const struct snd_soc_component_driver tegra210_mvc_cmpnt = { + .dapm_widgets = tegra210_mvc_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_mvc_widgets), + .dapm_routes = tegra210_mvc_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_mvc_routes), + .controls = tegra210_mvc_vol_ctrl, + .num_controls = ARRAY_SIZE(tegra210_mvc_vol_ctrl), +}; + +static bool tegra210_mvc_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_MVC_RX_STATUS ... TEGRA210_MVC_CONFIG_ERR_TYPE: + return true; + default: + return false; + }; +} + +static bool tegra210_mvc_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_MVC_RX_INT_MASK ... TEGRA210_MVC_RX_CIF_CTRL: + case TEGRA210_MVC_TX_INT_MASK ... TEGRA210_MVC_TX_CIF_CTRL: + case TEGRA210_MVC_ENABLE ... TEGRA210_MVC_CG: + case TEGRA210_MVC_CTRL ... TEGRA210_MVC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_mvc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_MVC_RX_STATUS: + case TEGRA210_MVC_RX_INT_STATUS: + case TEGRA210_MVC_RX_INT_SET: + + case TEGRA210_MVC_TX_STATUS: + case TEGRA210_MVC_TX_INT_STATUS: + case TEGRA210_MVC_TX_INT_SET: + + case TEGRA210_MVC_SOFT_RESET: + case TEGRA210_MVC_STATUS: + case TEGRA210_MVC_INT_STATUS: + case TEGRA210_MVC_SWITCH: + case TEGRA210_MVC_CFG_RAM_CTRL: + case TEGRA210_MVC_CFG_RAM_DATA: + case TEGRA210_MVC_PEAK_VALUE: + case TEGRA210_MVC_CTRL: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra210_mvc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_MVC_CONFIG_ERR_TYPE, + .writeable_reg = tegra210_mvc_wr_reg, + .readable_reg = tegra210_mvc_rd_reg, + .volatile_reg = tegra210_mvc_volatile_reg, + .reg_defaults = tegra210_mvc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_mvc_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct of_device_id tegra210_mvc_of_match[] = { + { .compatible = "nvidia,tegra210-mvc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_mvc_of_match); + +static int tegra210_mvc_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_mvc *mvc; + void __iomem *regs; + int err; + + mvc = devm_kzalloc(dev, sizeof(*mvc), GFP_KERNEL); + if (!mvc) + return -ENOMEM; + + dev_set_drvdata(dev, mvc); + + mvc->curve_type = CURVE_LINEAR; + mvc->ctrl_value = TEGRA210_MVC_CTRL_DEFAULT; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mvc->regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_mvc_regmap_config); + if (IS_ERR(mvc->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(mvc->regmap); + } + + regcache_cache_only(mvc->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra210_mvc_cmpnt, + tegra210_mvc_dais, + ARRAY_SIZE(tegra210_mvc_dais)); + if (err) { + dev_err(dev, "can't register MVC component, err: %d\n", err); + return err; + } + + pm_runtime_enable(dev); + + tegra210_mvc_reset_vol_settings(mvc, &pdev->dev); + + return 0; +} + +static int tegra210_mvc_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra210_mvc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_mvc_runtime_suspend, + tegra210_mvc_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra210_mvc_driver = { + .driver = { + .name = "tegra210-mvc", + .of_match_table = tegra210_mvc_of_match, + .pm = &tegra210_mvc_pm_ops, + }, + .probe = tegra210_mvc_platform_probe, + .remove = tegra210_mvc_platform_remove, +}; +module_platform_driver(tegra210_mvc_driver) + +MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 MVC ASoC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_mvc.h b/sound/soc/tegra/tegra210_mvc.h new file mode 100644 index 000000000000..def29c4c7257 --- /dev/null +++ b/sound/soc/tegra/tegra210_mvc.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_mvc.h - Definitions for Tegra210 MVC driver + * + * Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_MVC_H__ +#define __TEGRA210_MVC_H__ + +/* + * MVC_RX registers are with respect to XBAR. + * The data comes from XBAR to MVC. + */ +#define TEGRA210_MVC_RX_STATUS 0x0c +#define TEGRA210_MVC_RX_INT_STATUS 0x10 +#define TEGRA210_MVC_RX_INT_MASK 0x14 +#define TEGRA210_MVC_RX_INT_SET 0x18 +#define TEGRA210_MVC_RX_INT_CLEAR 0x1c +#define TEGRA210_MVC_RX_CIF_CTRL 0x20 + +/* + * MVC_TX registers are with respect to XBAR. + * The data goes out of MVC. + */ +#define TEGRA210_MVC_TX_STATUS 0x4c +#define TEGRA210_MVC_TX_INT_STATUS 0x50 +#define TEGRA210_MVC_TX_INT_MASK 0x54 +#define TEGRA210_MVC_TX_INT_SET 0x58 +#define TEGRA210_MVC_TX_INT_CLEAR 0x5c +#define TEGRA210_MVC_TX_CIF_CTRL 0x60 + +/* Register offsets from TEGRA210_MVC*_BASE */ +#define TEGRA210_MVC_ENABLE 0x80 +#define TEGRA210_MVC_SOFT_RESET 0x84 +#define TEGRA210_MVC_CG 0x88 +#define TEGRA210_MVC_STATUS 0x90 +#define TEGRA210_MVC_INT_STATUS 0x94 +#define TEGRA210_MVC_CTRL 0xa8 +#define TEGRA210_MVC_SWITCH 0xac +#define TEGRA210_MVC_INIT_VOL 0xb0 +#define TEGRA210_MVC_TARGET_VOL 0xd0 +#define TEGRA210_MVC_DURATION 0xf0 +#define TEGRA210_MVC_DURATION_INV 0xf4 +#define TEGRA210_MVC_POLY_N1 0xf8 +#define TEGRA210_MVC_POLY_N2 0xfc +#define TEGRA210_MVC_PEAK_CTRL 0x100 +#define TEGRA210_MVC_CFG_RAM_CTRL 0x104 +#define TEGRA210_MVC_CFG_RAM_DATA 0x108 +#define TEGRA210_MVC_PEAK_VALUE 0x10c +#define TEGRA210_MVC_CONFIG_ERR_TYPE 0x12c + +/* Fields in TEGRA210_MVC_ENABLE */ +#define TEGRA210_MVC_EN_SHIFT 0 +#define TEGRA210_MVC_EN (1 << TEGRA210_MVC_EN_SHIFT) + +#define TEGRA210_MVC_MUTE_SHIFT 8 +#define TEGRA210_MUTE_MASK_EN 0xff +#define TEGRA210_MVC_MUTE_MASK (TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT) +#define TEGRA210_MVC_MUTE_EN (TEGRA210_MUTE_MASK_EN << TEGRA210_MVC_MUTE_SHIFT) + +#define TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT 30 +#define TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK (1 << TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT) +#define TEGRA210_MVC_PER_CHAN_CTRL_EN (1 << TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT) + +#define TEGRA210_MVC_CURVE_TYPE_SHIFT 1 +#define TEGRA210_MVC_CURVE_TYPE_MASK (1 << TEGRA210_MVC_CURVE_TYPE_SHIFT) + +#define TEGRA210_MVC_VOLUME_SWITCH_SHIFT 2 +#define TEGRA210_MVC_VOLUME_SWITCH_MASK (1 << TEGRA210_MVC_VOLUME_SWITCH_SHIFT) +#define TEGRA210_MVC_VOLUME_SWITCH_TRIGGER (1 << TEGRA210_MVC_VOLUME_SWITCH_SHIFT) +#define TEGRA210_MVC_CTRL_DEFAULT 0x40000003 + +#define TEGRA210_MVC_INIT_VOL_DEFAULT_POLY 0x01000000 +#define TEGRA210_MVC_INIT_VOL_DEFAULT_LINEAR 0x00000000 + +/* Fields in TEGRA210_MVC ram ctrl */ +#define TEGRA210_MVC_CFG_RAM_CTRL_RW_SHIFT 14 +#define TEGRA210_MVC_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_MVC_CFG_RAM_CTRL_RW_SHIFT) + +#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13 +#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT) + +#define TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12 +#define TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT) + +#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_SHIFT 0 +#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_MASK (0x1ff << TEGRA210_MVC_CFG_RAM_CTRL_ADDR_SHIFT) + +#define REG_SIZE 4 +#define TEGRA210_MVC_MAX_CHAN_COUNT 8 +#define TEGRA210_MVC_REG_OFFSET(reg, i) (reg + (REG_SIZE * i)) + +#define NUM_GAIN_POLY_COEFFS 9 + +enum { + CURVE_POLY, + CURVE_LINEAR, +}; + +struct tegra210_mvc_gain_params { + int poly_coeff[NUM_GAIN_POLY_COEFFS]; + int poly_n1; + int poly_n2; + int duration; + int duration_inv; +}; + +struct tegra210_mvc { + int volume[TEGRA210_MVC_MAX_CHAN_COUNT]; + unsigned int curve_type; + unsigned int ctrl_value; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra210_sfc.c b/sound/soc/tegra/tegra210_sfc.c new file mode 100644 index 000000000000..dc477ee1b82c --- /dev/null +++ b/sound/soc/tegra/tegra210_sfc.c @@ -0,0 +1,3549 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra210_sfc.c - Tegra210 SFC driver +// +// Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved. + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra210_sfc.h" +#include "tegra_cif.h" + +#define UNSUPP_CONV ((void *)(-EOPNOTSUPP)) +#define BYPASS_CONV NULL + +static const struct reg_default tegra210_sfc_reg_defaults[] = { + { TEGRA210_SFC_RX_INT_MASK, 0x00000001}, + { TEGRA210_SFC_RX_CIF_CTRL, 0x00007700}, + { TEGRA210_SFC_TX_INT_MASK, 0x00000001}, + { TEGRA210_SFC_TX_CIF_CTRL, 0x00007700}, + { TEGRA210_SFC_CG, 0x1}, + { TEGRA210_SFC_CFG_RAM_CTRL, 0x00004000}, +}; + +static const int tegra210_sfc_rates[TEGRA210_SFC_NUM_RATES] = { + 8000, + 11025, + 16000, + 22050, + 24000, + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +/* coeff RAM tables required for SFC */ +static u32 coef_8to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0018a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003,//output gain + 0x00235204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0000015f,//input gain + 0x00a7909c, 0xff241c71, 0x005f5e00, + 0xffca77f4, 0xff20dd50, 0x006855eb, + 0xff86c552, 0xff18137a, 0x00773648, + 0x00000001//output gain +}; + +static u32 coef_8to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002//output gain +}; + +static u32 coef_8to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0018a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003,//output gain + 0x00230204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000001//output gain +}; + +static u32 coef_8to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x0000a105,//header + 0x000005e1,//input gain + 0x00dca92f, 0xff45647a, 0x0046b59c, + 0x00429d1e, 0xff4fec62, 0x00516d30, + 0xffdea779, 0xff5e08ba, 0x0060185e, + 0xffafbab2, 0xff698d5a, 0x006ce3ae, + 0xff9a82d2, 0xff704674, 0x007633c5, + 0xff923433, 0xff721128, 0x007cff42, + 0x00000003//output gain +}; + +static u32 coef_8to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00006102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002//output gain +}; + +static u32 coef_8to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x0156105,//interpolation + IIR filter + 0x0000d649,//input gain + 0x00e87afb, 0xff5f69d0, 0x003df3cf, + 0x007ce488, 0xff99a5c8, 0x0056a6a0, + 0x00344928, 0xffcba3e5, 0x006be470, + 0x00137aa7, 0xffe60276, 0x00773410, + 0x0005fa2a, 0xfff1ac11, 0x007c795b, + 0x00012d36, 0xfff5eca2, 0x007f10ef, + 0x00000002,//ouptut gain + 0x0021a102,//interpolation + IIR filter + 0x00000e00,//input gain + 0x00e2e000, 0xff6e1a00, 0x002aaa00, + 0x00610a00, 0xff5dda00, 0x003ccc00, + 0x00163a00, 0xff3c0400, 0x00633200, + 0x00000003,//Output gain + 0x00000204,//Farrow filter + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_8to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00156105,//interpolation + IIR Filter + 0x0000d649,//input gain + 0x00e87afb, 0xff5f69d0, 0x003df3cf, + 0x007ce488, 0xff99a5c8, 0x0056a6a0, + 0x00344928, 0xffcba3e5, 0x006be470, + 0x00137aa7, 0xffe60276, 0x00773410, + 0x0005fa2a, 0xfff1ac11, 0x007c795b, + 0x00012d36, 0xfff5eca2, 0x007f10ef, + 0x00000002,//ouptut gain + 0x0000a102,//interpolation + IIR filter + 0x00000e00,//input gain + 0x00e2e000, 0xff6e1a00, 0x002aaa00, + 0x00610a00, 0xff5dda00, 0x003ccc00, + 0x00163a00, 0xff3c0400, 0x00633200, + 0x00000003//output gain +}; + +static u32 coef_8to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x0024a102,//header + 0x0000007d,//input gain + 0x007d1f20, 0xff1a540e, 0x00678bf9, + 0xff916625, 0xff16b0ff, 0x006e433a, + 0xff5af660, 0xff0eb91f, 0x00797356, + 0x00000003,//output gain + 0x00000204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_8to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x0000a102,//header + 0x0000007d,//input gain + 0x007d1f20, 0xff1a540e, 0x00678bf9, + 0xff916625, 0xff16b0ff, 0x006e433a, + 0xff5af660, 0xff0eb91f, 0x00797356, + 0x00000003//output gain +}; + +static u32 coef_11to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0000015f,//input gain + 0x00a7909c, 0xff241c71, 0x005f5e00, + 0xffca77f4, 0xff20dd50, 0x006855eb, + 0xff86c552, 0xff18137a, 0x00773648, + 0x00000002,//output gain + 0x00186102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000002,//output gain + 0x00239204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_11to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00009204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_11to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002//output gain +}; + +static u32 coef_11to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_11to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00009204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_11to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00006102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002//output gain +}; + +static u32 coef_11to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_11to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00006102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002//output gain +}; + +static u32 coef_11to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00000204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_16to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_16to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000fa103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000003,//output gain + 0x001a5204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_16to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0018a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003,//output gain + 0x00235204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0000015f,//input gain + 0x00a7909c, 0xff241c71, 0x005f5e00, + 0xffca77f4, 0xff20dd50, 0x006855eb, + 0xff86c552, 0xff18137a, 0x00773648, + 0x00000001//output gain +}; + +static u32 coef_16to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x0015a105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000003,//output gain + 0x00005105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000001//output gain +}; + +static u32 coef_16to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002//output gain +}; + +static u32 coef_16to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00156105,//interpolation + IIR filter + 0x0000d649,//input gain + 0x00e87afb, 0xff5f69d0, 0x003df3cf, + 0x007ce488, 0xff99a5c8, 0x0056a6a0, + 0x00344928, 0xffcba3e5, 0x006be470, + 0x00137aa7, 0xffe60276, 0x00773410, + 0x0005fa2a, 0xfff1ac11, 0x007c795b, + 0x00012d36, 0xfff5eca2, 0x007f10ef, + 0x00000002,//output gain + 0x0021a102,//interpolation + IIR filter + 0x00000e00,//input gain + 0x00e2e000, 0xff6e1a00, 0x002aaa00, + 0x00610a00, 0xff5dda00, 0x003ccc00, + 0x00163a00, 0xff3c0400, 0x00633200, + 0x00000003,//output gain + 0x002c0204,//Farrow Filter + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005101,//IIR Filter + Decimator + 0x0000203c,//input gain + 0x00f52d35, 0xff2e2162, 0x005a21e0, + 0x00c6f0f0, 0xff2ecd69, 0x006fa78d, + 0x00000001//output gain +}; + +static u32 coef_16to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x0000a105,//interpolation + IIR Filter + 0x00000784,//input gain + 0x00cc516e, 0xff2c9639, 0x005ad5b3, + 0x0013ad0d, 0xff3d4799, 0x0063ce75, + 0xffb6f398, 0xff5138d1, 0x006e9e1f, + 0xff9186e5, 0xff5f96a4, 0x0076a86e, + 0xff82089c, 0xff676b81, 0x007b9f8a, + 0xff7c48a5, 0xff6a31e7, 0x007ebb7b, + 0x00000003//output gain +}; + +static u32 coef_16to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0018a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003,//output gain + 0x00000204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_16to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0000a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003//output gain +}; + +static u32 coef_16to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x0024a102,//header + 0x0000007d,//input gain + 0x007d1f20, 0xff1a540e, 0x00678bf9, + 0xff916625, 0xff16b0ff, 0x006e433a, + 0xff5af660, 0xff0eb91f, 0x00797356, + 0x00000003,//output gain + 0x00000204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_16to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x0000a102,//header + 0x0000007d,//input gain + 0x007d1f20, 0xff1a540e, 0x00678bf9, + 0xff916625, 0xff16b0ff, 0x006e433a, + 0xff5af660, 0xff0eb91f, 0x00797356, + 0x00000003//output gain +}; + +static u32 coef_22to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000002,//output gain + 0x00179204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_22to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_22to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0000015f,//input gain + 0x00a7909c, 0xff241c71, 0x005f5e00, + 0xffca77f4, 0xff20dd50, 0x006855eb, + 0xff86c552, 0xff18137a, 0x00773648, + 0x00000002,//output gain + 0x00186102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000002,//output gain + 0x00239204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_22to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00235204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d029,//input gain + 0x00f2a98b, 0xff92aa71, 0x001fcd16, + 0x00ae9004, 0xffb85140, 0x0041813a, + 0x007f8ed1, 0xffd585fc, 0x006a69e6, + 0x00000001//output gain +}; + +static u32 coef_22to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00009204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_22to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002//output gain +}; + +static u32 coef_22to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_22to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00006102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002//output gain +}; + +static u32 coef_22to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_22to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00006102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002//output gain +}; + +static u32 coef_22to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00000204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_24to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00009105,//header + 0x000005e1,//input gain + 0x00dca92f, 0xff45647a, 0x0046b59c, + 0x00429d1e, 0xff4fec62, 0x00516d30, + 0xffdea779, 0xff5e08ba, 0x0060185e, + 0xffafbab2, 0xff698d5a, 0x006ce3ae, + 0xff9a82d2, 0xff704674, 0x007633c5, + 0xff923433, 0xff721128, 0x007cff42, + 0x00000001//output gain +}; + +static u32 coef_24to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000f6103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002,//output gain + 0x001a5204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_24to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00156105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000002,//output gain + 0x00009105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000001//output gain +}; + +static u32 coef_24to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d029,//input gain + 0x00f2a98b, 0xff92aa71, 0x001fcd16, + 0x00ae9004, 0xffb85140, 0x0041813a, + 0x007f8ed1, 0xffd585fc, 0x006a69e6, + 0x00000002,//output gain + 0x001b6103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002,//output gain + 0x00265204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_24to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00009102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001//output gain +}; + +static u32 coef_24to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00230204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x00001685,//input gain + 0x00f53ae9, 0xff52f196, 0x003e3e08, + 0x00b9f857, 0xff5d8985, 0x0050070a, + 0x008c3e86, 0xff6053f0, 0x006d98ef, + 0x00000001//output gain +}; + +static u32 coef_24to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002//output gain +}; + +static u32 coef_24to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x002f0204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x00000138,//input gain + 0x00d5d232, 0xff2a3bf8, 0x005a785c, + 0x0034001b, 0xff283109, 0x006462a6, + 0xffe6746a, 0xff1fb09c, 0x00758a91, + 0x00000001//output gain +}; + +static u32 coef_24to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00006102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002//output gain +}; + +static u32 coef_24to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00000204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_24to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00006102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002//output gain +}; + +static u32 coef_32to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_32to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000ca102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000003,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x0000d102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001//output gain +}; + +static u32 coef_32to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_32to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000fa103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000003,//output gain + 0x001a5204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_32to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000ca102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000003,//output gain + 0x0000d102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001//output gain +}; + +static u32 coef_32to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0018a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003,//output gain + 0x00235204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0000015f,//input gain + 0x00a7909c, 0xff241c71, 0x005f5e00, + 0xffca77f4, 0xff20dd50, 0x006855eb, + 0xff86c552, 0xff18137a, 0x00773648, + 0x00000001//output gain +}; + +static u32 coef_32to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x0015a105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000003,//output gain + 0x00005105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000001//output gain +}; + +static u32 coef_32to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0018a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003,//output gain + 0x00230204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000001//output gain +}; + +static u32 coef_32to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x0000a105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000003//output gain +}; + +static u32 coef_32to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0018a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003,//output gain + 0x00000204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_32to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x0000a102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000003//output gain +}; + +static u32 coef_44to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00120104,//IIR Filter + 0x00000af2,//input gain + 0x0057eebe, 0xff1e9863, 0x00652604, + 0xff7206ea, 0xff22ad7e, 0x006d47e1, + 0xff42a4d7, 0xff26e722, 0x0075fd83, + 0xff352f66, 0xff29312b, 0x007b986b, + 0xff310a07, 0xff296f51, 0x007eca7c, + 0x00000001,//output gain + 0x001d9204,//Farrow Filter + decimation + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005105,//IIR Filter + Decimator + 0x0000d649,//input gain + 0x00e87afb, 0xff5f69d0, 0x003df3cf, + 0x007ce488, 0xff99a5c8, 0x0056a6a0, + 0x00344928, 0xffcba3e5, 0x006be470, + 0x00137aa7, 0xffe60276, 0x00773410, + 0x0005fa2a, 0xfff1ac11, 0x007c795b, + 0x00012d36, 0xfff5eca2, 0x007f10ef, + 0x00000001//output gain +}; + +static u32 coef_44to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_44to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00126104,//IIR Filter + interpolation + 0x00000af2,//input gain + 0x0057eebe, 0xff1e9863, 0x00652604, + 0xff7206ea, 0xff22ad7e, 0x006d47e1, + 0xff42a4d7, 0xff26e722, 0x0075fd83, + 0xff352f66, 0xff29312b, 0x007b986b, + 0xff310a07, 0xff296f51, 0x007eca7c, + 0x00000002,//output gain + 0x001d9204,//Farrow Filter + decimation + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005105,//IIR Filter + Decimator + 0x0000d649,//input gain + 0x00e87afb, 0xff5f69d0, 0x003df3cf, + 0x007ce488, 0xff99a5c8, 0x0056a6a0, + 0x00344928, 0xffcba3e5, 0x006be470, + 0x00137aa7, 0xffe60276, 0x00773410, + 0x0005fa2a, 0xfff1ac11, 0x007c795b, + 0x00012d36, 0xfff5eca2, 0x007f10ef, + 0x00000001//output gain +}; + +static u32 coef_44to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_44to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x00001685,//input gain + 0x00f53ae9, 0xff52f196, 0x003e3e08, + 0x00b9f857, 0xff5d8985, 0x0050070a, + 0x008c3e86, 0xff6053f0, 0x006d98ef, + 0x00000002,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_44to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0000015f,//input gain + 0x00a7909c, 0xff241c71, 0x005f5e00, + 0xffca77f4, 0xff20dd50, 0x006855eb, + 0xff86c552, 0xff18137a, 0x00773648, + 0x00000002,//output gain + 0x00186102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000002,//output gain + 0x00239204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_44to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00235204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d029,//input gain + 0x00f2a98b, 0xff92aa71, 0x001fcd16, + 0x00ae9004, 0xffb85140, 0x0041813a, + 0x007f8ed1, 0xffd585fc, 0x006a69e6, + 0x00000001//output gain +}; + +static u32 coef_44to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002//output gain +}; + +static u32 coef_44to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_44to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00006102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002//output gain +}; + +static u32 coef_44to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_48to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c9102,//IIR Filter + Decimator + 0x00000e00,//input gain + 0x00e2e000, 0xff6e1a00, 0x002aaa00, + 0x00610a00, 0xff5dda00, 0x003ccc00, + 0x00163a00, 0xff3c0400, 0x00633200, + 0x00000001,//output gain + 0x00005105,//IIR Filter + Decimator + 0x0000d649,//input gain + 0x00e87afb, 0xff5f69d0, 0x003df3cf, + 0x007ce488, 0xff99a5c8, 0x0056a6a0, + 0x00344928, 0xffcba3e5, 0x006be470, + 0x00137aa7, 0xffe60276, 0x00773410, + 0x0005fa2a, 0xfff1ac11, 0x007c795b, + 0x00012d36, 0xfff5eca2, 0x007f10ef, + 0x00000001//output gain +}; + +static u32 coef_48to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000002,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_48to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00009105,//IIR Filter + Decimator + 0x00000784,//input gain + 0x00cc516e, 0xff2c9639, 0x005ad5b3, + 0x0013ad0d, 0xff3d4799, 0x0063ce75, + 0xffb6f398, 0xff5138d1, 0x006e9e1f, + 0xff9186e5, 0xff5f96a4, 0x0076a86e, + 0xff82089c, 0xff676b81, 0x007b9f8a, + 0xff7c48a5, 0xff6a31e7, 0x007ebb7b, + 0x00000001//output gain +}; + +static u32 coef_48to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000f6103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002,//output gain + 0x001a5204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_48to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_48to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00156105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000002,//output gain + 0x00009105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000001//output gain +}; + +static u32 coef_48to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d029,//input gain + 0x00f2a98b, 0xff92aa71, 0x001fcd16, + 0x00ae9004, 0xffb85140, 0x0041813a, + 0x007f8ed1, 0xffd585fc, 0x006a69e6, + 0x00000002,//output gain + 0x001b6103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002,//output gain + 0x00265204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_48to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00230204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x00001685,//input gain + 0x00f53ae9, 0xff52f196, 0x003e3e08, + 0x00b9f857, 0xff5d8985, 0x0050070a, + 0x008c3e86, 0xff6053f0, 0x006d98ef, + 0x00000001//output gain +}; + +static u32 coef_48to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002//output gain +}; + +static u32 coef_48to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00186102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00246102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x002f0204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x00000138,//input gain + 0x00d5d232, 0xff2a3bf8, 0x005a785c, + 0x0034001b, 0xff283109, 0x006462a6, + 0xffe6746a, 0xff1fb09c, 0x00758a91, + 0x00000001//output gain +}; + +static u32 coef_48to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000002,//output gain + 0x00006102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002//output gain +}; + +static u32 coef_88to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x00000057,//input gain + 0x00a8e717, 0xff1c748d, 0x0065b976, + 0xffcbccab, 0xff190aff, 0x006cc1cf, + 0xff871ce1, 0xff10d878, 0x0078cfc5, + 0x00000001,//output gain + 0x00179204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000001,//output gain + 0x00185102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000001,//output gain + 0x00179204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x00001685,//input gain + 0x00f53ae9, 0xff52f196, 0x003e3e08, + 0x00b9f857, 0xff5d8985, 0x0050070a, + 0x008c3e86, 0xff6053f0, 0x006d98ef, + 0x00000001,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000002,//output gain + 0x00179204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x00001685,//input gain + 0x00f53ae9, 0xff52f196, 0x003e3e08, + 0x00b9f857, 0xff5d8985, 0x0050070a, + 0x008c3e86, 0xff6053f0, 0x006d98ef, + 0x00000002,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_88to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_88to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002//output gain +}; + +static u32 coef_88to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000002,//output gain + 0x00186102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_96to8[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c9102,//header + 0x0000007d,//input gain + 0x007d1f20, 0xff1a540e, 0x00678bf9, + 0xff916625, 0xff16b0ff, 0x006e433a, + 0xff5af660, 0xff0eb91f, 0x00797356, + 0x00000001,//output gain + 0x00185102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_96to11[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000001,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_96to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c9102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_96to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000002,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_96to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_96to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00009105,//header + 0x00000292,//input gain + 0x00e4320a, 0xff41d2d9, 0x004911ac, + 0x005dd9e3, 0xff4c7d80, 0x0052103e, + 0xfff8ebef, 0xff5b6fab, 0x005f0a0d, + 0xffc4b414, 0xff68582c, 0x006b38e5, + 0xffabb861, 0xff704bec, 0x0074de52, + 0xffa19f4c, 0xff729059, 0x007c7e90, + 0x00000001//output gain +}; + +static u32 coef_96to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000f6103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002,//output gain + 0x001a5204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_96to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_96to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000f6103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002,//output gain + 0x001a0204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001//output gain +}; + +static u32 coef_96to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000f6103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002,//output gain + 0x001b6102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000002,//output gain + 0x00260204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000001//output gain +}; + +static u32 coef_96to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00006103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000002//output gain +}; + +static u32 coef_176to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x00000057,//input gain + 0x00a8e717, 0xff1c748d, 0x0065b976, + 0xffcbccab, 0xff190aff, 0x006cc1cf, + 0xff871ce1, 0xff10d878, 0x0078cfc5, + 0x00000001,//output gain + 0x00179204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_176to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000001,//output gain + 0x00185102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_176to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x00000138,//input gain + 0x00d5d232, 0xff2a3bf8, 0x005a785c, + 0x0034001b, 0xff283109, 0x006462a6, + 0xffe6746a, 0xff1fb09c, 0x00758a91, + 0x00000001,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_176to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x000005f3,//input gain + 0x00d816d6, 0xff385383, 0x004fe566, + 0x003c548d, 0xff38c23d, 0x005d0b1c, + 0xfff02f7d, 0xff31e983, 0x0072d65d, + 0x00000001,//output gain + 0x00179204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_176to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_176to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x00001685,//input gain + 0x00f53ae9, 0xff52f196, 0x003e3e08, + 0x00b9f857, 0xff5d8985, 0x0050070a, + 0x008c3e86, 0xff6053f0, 0x006d98ef, + 0x00000001,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_176to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001//output gain +}; + +static u32 coef_176to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000001//output gain +}; + +static u32 coef_176to192[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000002,//output gain + 0x00005204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000 +}; + +static u32 coef_192to16[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c9102,//header + 0x0000007d,//input gain + 0x007d1f20, 0xff1a540e, 0x00678bf9, + 0xff916625, 0xff16b0ff, 0x006e433a, + 0xff5af660, 0xff0eb91f, 0x00797356, + 0x00000001,//output gain + 0x00185102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_192to22[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c0102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000001,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_192to24[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000001,//output gain + 0x00185102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_192to32[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c9102,//header + 0x000005d6,//input gain + 0x00c6543e, 0xff342935, 0x0052f116, + 0x000a1d78, 0xff3330c0, 0x005f88a3, + 0xffbee7c0, 0xff2b5ba5, 0x0073eb26, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_192to44[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000002,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00235102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_192to48[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c5102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001,//output gain + 0x00005102,//header + 0x0001d727,//input gain + 0x00fc2fc7, 0xff9bb27b, 0x001c564c, + 0x00e55557, 0xffcadd5b, 0x003d80ba, + 0x00d13397, 0xfff232f8, 0x00683337, + 0x00000001//output gain +}; + +static u32 coef_192to88[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000002,//output gain + 0x00175204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x000013d9,//input gain + 0x00ebd477, 0xff4ce383, 0x0042049d, + 0x0089c278, 0xff54414d, 0x00531ded, + 0x004a5e07, 0xff53cf41, 0x006efbdc, + 0x00000001//output gain +}; + +static u32 coef_192to96[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x00005103,//header + 0x000001e0,//input gain + 0x00de44c0, 0xff380b7f, 0x004ffc73, + 0x00494b44, 0xff3d493a, 0x005908bf, + 0xffe9a3c8, 0xff425647, 0x006745f7, + 0xffc42d61, 0xff40a6c7, 0x00776709, + 0x00000001//output gain +}; + +static u32 coef_192to176[TEGRA210_SFC_COEF_RAM_DEPTH] = { + 0x000c6102,//header + 0x000000af,//input gain + 0x00c65663, 0xff23d2ce, 0x005f97d6, + 0x00086ad6, 0xff20ec4f, 0x00683201, + 0xffbbbef6, 0xff184447, 0x00770963, + 0x00000002,//output gain + 0x00170204,//farrow + 0x000aaaab, + 0xffaaaaab, + 0xfffaaaab, + 0x00555555, + 0xff600000, + 0xfff55555, + 0x00155555, + 0x00055555, + 0xffeaaaab, + 0x00200000, + 0x00005102,//header + 0x0000010a,//input gain + 0x00c93dc4, 0xff26f5f6, 0x005d1041, + 0x001002c4, 0xff245b76, 0x00666002, + 0xffc30a45, 0xff1baecd, 0x00765921, + 0x00000001//output gain +}; + +/* + * Coefficient table for various sample rate conversions. The sample + * rates available are as per tegra210_sfc_rates[]. + */ +static s32 *coef_addr_table[TEGRA210_SFC_NUM_RATES][TEGRA210_SFC_NUM_RATES] = { + /* Convertions from 8 kHz */ + { + BYPASS_CONV, + coef_8to11, + coef_8to16, + coef_8to22, + coef_8to24, + coef_8to32, + coef_8to44, + coef_8to48, + coef_8to88, + coef_8to96, + UNSUPP_CONV, + UNSUPP_CONV, + }, + /* Convertions from 11.025 kHz */ + { + coef_11to8, + BYPASS_CONV, + coef_11to16, + coef_11to22, + coef_11to24, + coef_11to32, + coef_11to44, + coef_11to48, + coef_11to88, + coef_11to96, + UNSUPP_CONV, + UNSUPP_CONV, + }, + /* Convertions from 16 kHz */ + { + coef_16to8, + coef_16to11, + BYPASS_CONV, + coef_16to22, + coef_16to24, + coef_16to32, + coef_16to44, + coef_16to48, + coef_16to88, + coef_16to96, + coef_16to176, + coef_16to192, + }, + /* Convertions from 22.05 kHz */ + { + coef_22to8, + coef_22to11, + coef_22to16, + BYPASS_CONV, + coef_22to24, + coef_22to32, + coef_22to44, + coef_22to48, + coef_22to88, + coef_22to96, + coef_22to176, + coef_22to192, + }, + /* Convertions from 24 kHz */ + { + coef_24to8, + coef_24to11, + coef_24to16, + coef_24to22, + BYPASS_CONV, + coef_24to32, + coef_24to44, + coef_24to48, + coef_24to88, + coef_24to96, + coef_24to176, + coef_24to192, + }, + /* Convertions from 32 kHz */ + { + coef_32to8, + coef_32to11, + coef_32to16, + coef_32to22, + coef_32to24, + BYPASS_CONV, + coef_32to44, + coef_32to48, + coef_32to88, + coef_32to96, + coef_32to176, + coef_32to192, + }, + /* Convertions from 44.1 kHz */ + { + coef_44to8, + coef_44to11, + coef_44to16, + coef_44to22, + coef_44to24, + coef_44to32, + BYPASS_CONV, + coef_44to48, + coef_44to88, + coef_44to96, + coef_44to176, + coef_44to192, + }, + /* Convertions from 48 kHz */ + { + coef_48to8, + coef_48to11, + coef_48to16, + coef_48to22, + coef_48to24, + coef_48to32, + coef_48to44, + BYPASS_CONV, + coef_48to88, + coef_48to96, + coef_48to176, + coef_48to192, + }, + /* Convertions from 88.2 kHz */ + { + coef_88to8, + coef_88to11, + coef_88to16, + coef_88to22, + coef_88to24, + coef_88to32, + coef_88to44, + coef_88to48, + BYPASS_CONV, + coef_88to96, + coef_88to176, + coef_88to192, + }, + /* Convertions from 96 kHz */ + { coef_96to8, + coef_96to11, + coef_96to16, + coef_96to22, + coef_96to24, + coef_96to32, + coef_96to44, + coef_96to48, + coef_96to88, + BYPASS_CONV, + coef_96to176, + coef_96to192, + }, + /* Convertions from 176.4 kHz */ + { + UNSUPP_CONV, + UNSUPP_CONV, + coef_176to16, + coef_176to22, + coef_176to24, + coef_176to32, + coef_176to44, + coef_176to48, + coef_176to88, + coef_176to96, + BYPASS_CONV, + coef_176to192, + }, + /* Convertions from 192 kHz */ + { + UNSUPP_CONV, + UNSUPP_CONV, + coef_192to16, + coef_192to22, + coef_192to24, + coef_192to32, + coef_192to44, + coef_192to48, + coef_192to88, + coef_192to96, + coef_192to176, + BYPASS_CONV, + }, +}; + +static int __maybe_unused tegra210_sfc_runtime_suspend(struct device *dev) +{ + struct tegra210_sfc *sfc = dev_get_drvdata(dev); + + regcache_cache_only(sfc->regmap, true); + regcache_mark_dirty(sfc->regmap); + + return 0; +} + +static int __maybe_unused tegra210_sfc_runtime_resume(struct device *dev) +{ + struct tegra210_sfc *sfc = dev_get_drvdata(dev); + + regcache_cache_only(sfc->regmap, false); + regcache_sync(sfc->regmap); + + return 0; +} + +static inline void tegra210_sfc_write_ram(struct regmap *regmap, + s32 *data) +{ + int i; + + regmap_write(regmap, TEGRA210_SFC_CFG_RAM_CTRL, + TEGRA210_SFC_RAM_CTRL_SEQ_ACCESS_EN | + TEGRA210_SFC_RAM_CTRL_ADDR_INIT_EN | + TEGRA210_SFC_RAM_CTRL_RW_WRITE); + + for (i = 0; i < TEGRA210_SFC_COEF_RAM_DEPTH; i++) + regmap_write(regmap, TEGRA210_SFC_CFG_RAM_DATA, data[i]); +} + +static int tegra210_sfc_write_coeff_ram(struct snd_soc_component *cmpnt) +{ + struct tegra210_sfc *sfc = dev_get_drvdata(cmpnt->dev); + s32 *coeff_ram; + + /* Bypass */ + if (sfc->srate_in == sfc->srate_out) + return 0; + + coeff_ram = coef_addr_table[sfc->srate_in][sfc->srate_out]; + if (IS_ERR_OR_NULL(coeff_ram)) { + dev_err(cmpnt->dev, + "Conversion from %d to %d Hz is not supported\n", + sfc->srate_in, sfc->srate_out); + + return PTR_ERR_OR_ZERO(coeff_ram); + } + + tegra210_sfc_write_ram(sfc->regmap, coeff_ram); + + regmap_update_bits(sfc->regmap, + TEGRA210_SFC_COEF_RAM, + TEGRA210_SFC_COEF_RAM_EN, + TEGRA210_SFC_COEF_RAM_EN); + + return 0; +} + +static int tegra210_sfc_set_audio_cif(struct tegra210_sfc *sfc, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + unsigned int channels, audio_bits, path; + struct tegra_cif_conf cif_conf; + + memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); + + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA_ACIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA_ACIF_BITS_32; + break; + default: + return -EOPNOTSUPP; + } + + cif_conf.audio_ch = channels; + cif_conf.client_ch = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = TEGRA_ACIF_BITS_32; + + if (reg == TEGRA210_SFC_RX_CIF_CTRL) + path = SFC_RX_PATH; + else + path = SFC_TX_PATH; + + cif_conf.stereo_conv = sfc->stereo_to_mono[path]; + cif_conf.mono_conv = sfc->mono_to_stereo[path]; + + tegra_set_cif(sfc->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_sfc_soft_reset(struct tegra210_sfc *sfc) +{ + u32 val; + + /* + * Soft Reset: Below performs module soft reset which clears + * all FSM logic, flushes flow control of FIFO and resets the + * state register. It also brings module back to disabled + * state (without flushing the data in the pipe). + */ + regmap_update_bits(sfc->regmap, TEGRA210_SFC_SOFT_RESET, + TEGRA210_SFC_SOFT_RESET_EN, 1); + + return regmap_read_poll_timeout(sfc->regmap, + TEGRA210_SFC_SOFT_RESET, + val, + !(val & TEGRA210_SFC_SOFT_RESET_EN), + 10, 10000); +} + +static int tegra210_sfc_rate_to_idx(struct device *dev, int rate, + int *rate_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tegra210_sfc_rates); i++) { + if (rate == tegra210_sfc_rates[i]) { + *rate_idx = i; + + return 0; + } + } + + dev_err(dev, "Sample rate %d Hz is not supported\n", rate); + + return -EOPNOTSUPP; +} + +static int tegra210_sfc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tegra210_sfc *sfc = snd_soc_dai_get_drvdata(dai); + int err; + + regmap_update_bits(sfc->regmap, TEGRA210_SFC_COEF_RAM, + TEGRA210_SFC_COEF_RAM_EN, 0); + + err = tegra210_sfc_soft_reset(sfc); + if (err < 0) { + dev_err(dai->dev, "Failed to reset SFC in %s, err = %d\n", + __func__, err); + + return err; + } + + return 0; +} + +static int tegra210_sfc_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_sfc *sfc = snd_soc_dai_get_drvdata(dai); + struct device *dev = dai->dev; + int err; + + err = tegra210_sfc_rate_to_idx(dev, params_rate(params), + &sfc->srate_in); + if (err < 0) + return err; + + err = tegra210_sfc_set_audio_cif(sfc, params, TEGRA210_SFC_RX_CIF_CTRL); + if (err < 0) { + dev_err(dev, "Can't set SFC RX CIF: %d\n", err); + return err; + } + + regmap_write(sfc->regmap, TEGRA210_SFC_RX_FREQ, sfc->srate_in); + + return err; +} + +static int tegra210_sfc_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_sfc *sfc = snd_soc_dai_get_drvdata(dai); + struct device *dev = dai->dev; + int err; + + err = tegra210_sfc_rate_to_idx(dev, params_rate(params), + &sfc->srate_out); + if (err < 0) + return err; + + err = tegra210_sfc_set_audio_cif(sfc, params, TEGRA210_SFC_TX_CIF_CTRL); + if (err < 0) { + dev_err(dev, "Can't set SFC TX CIF: %d\n", err); + return err; + } + + regmap_write(sfc->regmap, TEGRA210_SFC_TX_FREQ, sfc->srate_out); + + return 0; +} + +static int tegra210_sfc_init(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + return tegra210_sfc_write_coeff_ram(cmpnt); +} + +static int tegra210_sfc_get_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt); + + if (strstr(kcontrol->id.name, "Input Stereo To Mono")) + ucontrol->value.integer.value[0] = + sfc->stereo_to_mono[SFC_RX_PATH]; + else if (strstr(kcontrol->id.name, "Input Mono To Stereo")) + ucontrol->value.integer.value[0] = + sfc->mono_to_stereo[SFC_RX_PATH]; + else if (strstr(kcontrol->id.name, "Output Stereo To Mono")) + ucontrol->value.integer.value[0] = + sfc->stereo_to_mono[SFC_TX_PATH]; + else if (strstr(kcontrol->id.name, "Output Mono To Stereo")) + ucontrol->value.integer.value[0] = + sfc->mono_to_stereo[SFC_TX_PATH]; + + return 0; +} + +static int tegra210_sfc_put_control(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt); + int value = ucontrol->value.integer.value[0]; + + if (strstr(kcontrol->id.name, "Input Stereo To Mono")) + sfc->stereo_to_mono[SFC_RX_PATH] = value; + else if (strstr(kcontrol->id.name, "Input Mono To Stereo")) + sfc->mono_to_stereo[SFC_RX_PATH] = value; + else if (strstr(kcontrol->id.name, "Output Stereo To Mono")) + sfc->stereo_to_mono[SFC_TX_PATH] = value; + else if (strstr(kcontrol->id.name, "Output Mono To Stereo")) + sfc->mono_to_stereo[SFC_TX_PATH] = value; + else + return 0; + + return 1; +} + +static const struct snd_soc_dai_ops tegra210_sfc_in_dai_ops = { + .hw_params = tegra210_sfc_in_hw_params, + .startup = tegra210_sfc_startup, +}; + +static const struct snd_soc_dai_ops tegra210_sfc_out_dai_ops = { + .hw_params = tegra210_sfc_out_hw_params, +}; + +static struct snd_soc_dai_driver tegra210_sfc_dais[] = { + { + .name = "SFC-RX-CIF", + .playback = { + .stream_name = "RX-CIF-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "RX-CIF-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra210_sfc_in_dai_ops, + }, + { + .name = "SFC-TX-CIF", + .playback = { + .stream_name = "TX-CIF-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "TX-CIF-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra210_sfc_out_dai_ops, + }, +}; + +static const struct snd_soc_dapm_widget tegra210_sfc_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT_E("TX", NULL, 0, TEGRA210_SFC_ENABLE, + TEGRA210_SFC_EN_SHIFT, 0, + tegra210_sfc_init, SND_SOC_DAPM_PRE_PMU), +}; + +#define RESAMPLE_ROUTE(sname) \ + { "RX XBAR-" sname, NULL, "XBAR-TX" }, \ + { "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \ + { "RX", NULL, "RX-CIF-" sname }, \ + { "TX-CIF-" sname, NULL, "TX" }, \ + { "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \ + { "XBAR-RX", NULL, "TX XBAR-" sname } + +static const struct snd_soc_dapm_route tegra210_sfc_routes[] = { + { "TX", NULL, "RX" }, + RESAMPLE_ROUTE("Playback"), + RESAMPLE_ROUTE("Capture"), +}; + +static const char * const tegra210_sfc_stereo_conv_text[] = { + "CH0", "CH1", "AVG", +}; + +static const char * const tegra210_sfc_mono_conv_text[] = { + "Zero", "Copy", +}; + +static const struct soc_enum tegra210_sfc_stereo_conv_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(tegra210_sfc_stereo_conv_text), + tegra210_sfc_stereo_conv_text); + +static const struct soc_enum tegra210_sfc_mono_conv_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(tegra210_sfc_mono_conv_text), + tegra210_sfc_mono_conv_text); + +static const struct snd_kcontrol_new tegra210_sfc_controls[] = { + SOC_ENUM_EXT("Input Stereo To Mono", tegra210_sfc_stereo_conv_enum, + tegra210_sfc_get_control, tegra210_sfc_put_control), + SOC_ENUM_EXT("Input Mono To Stereo", tegra210_sfc_mono_conv_enum, + tegra210_sfc_get_control, tegra210_sfc_put_control), + SOC_ENUM_EXT("Output Stereo To Mono", tegra210_sfc_stereo_conv_enum, + tegra210_sfc_get_control, tegra210_sfc_put_control), + SOC_ENUM_EXT("Output Mono To Stereo", tegra210_sfc_mono_conv_enum, + tegra210_sfc_get_control, tegra210_sfc_put_control), +}; + +static const struct snd_soc_component_driver tegra210_sfc_cmpnt = { + .dapm_widgets = tegra210_sfc_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_sfc_widgets), + .dapm_routes = tegra210_sfc_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_sfc_routes), + .controls = tegra210_sfc_controls, + .num_controls = ARRAY_SIZE(tegra210_sfc_controls), +}; + +static bool tegra210_sfc_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_RX_INT_MASK ... TEGRA210_SFC_RX_FREQ: + case TEGRA210_SFC_TX_INT_MASK ... TEGRA210_SFC_TX_FREQ: + case TEGRA210_SFC_ENABLE ... TEGRA210_SFC_CG: + case TEGRA210_SFC_COEF_RAM ... TEGRA210_SFC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_sfc_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_RX_STATUS ... TEGRA210_SFC_RX_FREQ: + case TEGRA210_SFC_TX_STATUS ... TEGRA210_SFC_TX_FREQ: + case TEGRA210_SFC_ENABLE ... TEGRA210_SFC_INT_STATUS: + case TEGRA210_SFC_COEF_RAM ... TEGRA210_SFC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_sfc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_RX_STATUS: + case TEGRA210_SFC_RX_INT_STATUS: + case TEGRA210_SFC_RX_INT_SET: + + case TEGRA210_SFC_TX_STATUS: + case TEGRA210_SFC_TX_INT_STATUS: + case TEGRA210_SFC_TX_INT_SET: + + case TEGRA210_SFC_SOFT_RESET: + case TEGRA210_SFC_STATUS: + case TEGRA210_SFC_INT_STATUS: + case TEGRA210_SFC_CFG_RAM_CTRL: + case TEGRA210_SFC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static bool tegra210_sfc_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_CFG_RAM_DATA: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra210_sfc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_SFC_CFG_RAM_DATA, + .writeable_reg = tegra210_sfc_wr_reg, + .readable_reg = tegra210_sfc_rd_reg, + .volatile_reg = tegra210_sfc_volatile_reg, + .precious_reg = tegra210_sfc_precious_reg, + .reg_defaults = tegra210_sfc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tegra210_sfc_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static const struct of_device_id tegra210_sfc_of_match[] = { + { .compatible = "nvidia,tegra210-sfc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra210_sfc_of_match); + +static int tegra210_sfc_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra210_sfc *sfc; + void __iomem *regs; + int err; + + sfc = devm_kzalloc(dev, sizeof(*sfc), GFP_KERNEL); + if (!sfc) + return -ENOMEM; + + dev_set_drvdata(dev, sfc); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + sfc->regmap = devm_regmap_init_mmio(dev, regs, + &tegra210_sfc_regmap_config); + if (IS_ERR(sfc->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(sfc->regmap); + } + + regcache_cache_only(sfc->regmap, true); + + err = devm_snd_soc_register_component(dev, &tegra210_sfc_cmpnt, + tegra210_sfc_dais, + ARRAY_SIZE(tegra210_sfc_dais)); + if (err) { + dev_err(dev, "can't register SFC component, err: %d\n", err); + return err; + } + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int tegra210_sfc_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra210_sfc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_sfc_runtime_suspend, + tegra210_sfc_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver tegra210_sfc_driver = { + .driver = { + .name = "tegra210-sfc", + .of_match_table = tegra210_sfc_of_match, + .pm = &tegra210_sfc_pm_ops, + }, + .probe = tegra210_sfc_platform_probe, + .remove = tegra210_sfc_platform_remove, +}; +module_platform_driver(tegra210_sfc_driver) + +MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>"); +MODULE_DESCRIPTION("Tegra210 SFC ASoC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/tegra/tegra210_sfc.h b/sound/soc/tegra/tegra210_sfc.h new file mode 100644 index 000000000000..5a6b66e297d8 --- /dev/null +++ b/sound/soc/tegra/tegra210_sfc.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tegra210_sfc.h - Definitions for Tegra210 SFC driver + * + * Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved. + * + */ + +#ifndef __TEGRA210_SFC_H__ +#define __TEGRA210_SFC_H__ + +/* + * SFC_RX registers are with respect to XBAR. + * The data comes from XBAR to SFC. + */ +#define TEGRA210_SFC_RX_STATUS 0x0c +#define TEGRA210_SFC_RX_INT_STATUS 0x10 +#define TEGRA210_SFC_RX_INT_MASK 0x14 +#define TEGRA210_SFC_RX_INT_SET 0x18 +#define TEGRA210_SFC_RX_INT_CLEAR 0x1c +#define TEGRA210_SFC_RX_CIF_CTRL 0x20 +#define TEGRA210_SFC_RX_FREQ 0x24 + +/* + * SFC_TX registers are with respect to XBAR. + * The data goes out of SFC. + */ +#define TEGRA210_SFC_TX_STATUS 0x4c +#define TEGRA210_SFC_TX_INT_STATUS 0x50 +#define TEGRA210_SFC_TX_INT_MASK 0x54 +#define TEGRA210_SFC_TX_INT_SET 0x58 +#define TEGRA210_SFC_TX_INT_CLEAR 0x5c +#define TEGRA210_SFC_TX_CIF_CTRL 0x60 +#define TEGRA210_SFC_TX_FREQ 0x64 + +/* Register offsets from TEGRA210_SFC*_BASE */ +#define TEGRA210_SFC_ENABLE 0x80 +#define TEGRA210_SFC_SOFT_RESET 0x84 +#define TEGRA210_SFC_CG 0x88 +#define TEGRA210_SFC_STATUS 0x8c +#define TEGRA210_SFC_INT_STATUS 0x90 +#define TEGRA210_SFC_COEF_RAM 0xbc +#define TEGRA210_SFC_CFG_RAM_CTRL 0xc0 +#define TEGRA210_SFC_CFG_RAM_DATA 0xc4 + +/* Fields in TEGRA210_SFC_ENABLE */ +#define TEGRA210_SFC_EN_SHIFT 0 +#define TEGRA210_SFC_EN (1 << TEGRA210_SFC_EN_SHIFT) + +#define TEGRA210_SFC_NUM_RATES 12 + +/* Fields in TEGRA210_SFC_COEF_RAM */ +#define TEGRA210_SFC_COEF_RAM_EN BIT(0) + +#define TEGRA210_SFC_SOFT_RESET_EN BIT(0) + +/* Coefficients */ +#define TEGRA210_SFC_COEF_RAM_DEPTH 64 +#define TEGRA210_SFC_RAM_CTRL_RW_WRITE (1 << 14) +#define TEGRA210_SFC_RAM_CTRL_ADDR_INIT_EN (1 << 13) +#define TEGRA210_SFC_RAM_CTRL_SEQ_ACCESS_EN (1 << 12) + + +enum tegra210_sfc_path { + SFC_RX_PATH, + SFC_TX_PATH, + SFC_PATHS, +}; + +struct tegra210_sfc { + unsigned int mono_to_stereo[SFC_PATHS]; + unsigned int stereo_to_mono[SFC_PATHS]; + unsigned int srate_out; + unsigned int srate_in; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index 735909310a26..b95438c3dbf7 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -313,7 +313,7 @@ static int tegra_machine_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops tegra_machine_snd_ops = { +static const struct snd_soc_ops tegra_machine_snd_ops = { .hw_params = tegra_machine_hw_params, }; @@ -341,9 +341,34 @@ tegra_machine_parse_phandle(struct device *dev, const char *name) return np; } +static void tegra_machine_unregister_codec(void *pdev) +{ + platform_device_unregister(pdev); +} + +static int tegra_machine_register_codec(struct device *dev, const char *name) +{ + struct platform_device *pdev; + int err; + + if (!name) + return 0; + + pdev = platform_device_register_simple(name, -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + err = devm_add_action_or_reset(dev, tegra_machine_unregister_codec, + pdev); + if (err) + return err; + + return 0; +} + int tegra_asoc_machine_probe(struct platform_device *pdev) { - struct device_node *np_codec, *np_i2s; + struct device_node *np_codec, *np_i2s, *np_ac97; const struct tegra_asoc_data *asoc; struct device *dev = &pdev->dev; struct tegra_machine *machine; @@ -404,17 +429,30 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) return err; } - np_codec = tegra_machine_parse_phandle(dev, "nvidia,audio-codec"); - if (IS_ERR(np_codec)) - return PTR_ERR(np_codec); + if (asoc->set_ac97) { + err = tegra_machine_register_codec(dev, asoc->codec_dev_name); + if (err) + return err; + + np_ac97 = tegra_machine_parse_phandle(dev, "nvidia,ac97-controller"); + if (IS_ERR(np_ac97)) + return PTR_ERR(np_ac97); - np_i2s = tegra_machine_parse_phandle(dev, "nvidia,i2s-controller"); - if (IS_ERR(np_i2s)) - return PTR_ERR(np_i2s); + card->dai_link->cpus->of_node = np_ac97; + card->dai_link->platforms->of_node = np_ac97; + } else { + np_codec = tegra_machine_parse_phandle(dev, "nvidia,audio-codec"); + if (IS_ERR(np_codec)) + return PTR_ERR(np_codec); - card->dai_link->cpus->of_node = np_i2s; - card->dai_link->codecs->of_node = np_codec; - card->dai_link->platforms->of_node = np_i2s; + np_i2s = tegra_machine_parse_phandle(dev, "nvidia,i2s-controller"); + if (IS_ERR(np_i2s)) + return PTR_ERR(np_i2s); + + card->dai_link->cpus->of_node = np_i2s; + card->dai_link->codecs->of_node = np_codec; + card->dai_link->platforms->of_node = np_i2s; + } if (asoc->add_common_controls) { card->controls = tegra_machine_controls; @@ -589,6 +627,7 @@ static struct snd_soc_card snd_soc_tegra_wm9712 = { static const struct tegra_asoc_data tegra_wm9712_data = { .card = &snd_soc_tegra_wm9712, .add_common_dapm_widgets = true, + .codec_dev_name = "wm9712-codec", .set_ac97 = true, }; @@ -686,6 +725,7 @@ static struct snd_soc_dai_link tegra_tlv320aic23_dai = { }; static struct snd_soc_card snd_soc_tegra_trimslice = { + .name = "tegra-trimslice", .components = "codec:tlv320aic23", .dai_link = &tegra_tlv320aic23_dai, .num_links = 1, diff --git a/sound/soc/tegra/tegra_asoc_machine.h b/sound/soc/tegra/tegra_asoc_machine.h index 8ee0ec814f67..d6a8d1320551 100644 --- a/sound/soc/tegra/tegra_asoc_machine.h +++ b/sound/soc/tegra/tegra_asoc_machine.h @@ -13,6 +13,7 @@ struct snd_soc_pcm_runtime; struct tegra_asoc_data { unsigned int (*mclk_rate)(unsigned int srate); + const char *codec_dev_name; struct snd_soc_card *card; unsigned int mclk_id; bool hp_jack_gpio_active_low; diff --git a/sound/soc/ti/Kconfig b/sound/soc/ti/Kconfig index 1d9fe3fca193..40110e9a9e8a 100644 --- a/sound/soc/ti/Kconfig +++ b/sound/soc/ti/Kconfig @@ -212,7 +212,7 @@ config SND_SOC_DM365_VOICE_CODEC Say Y if you want to add support for SoC On-chip voice codec endchoice -config SND_SOC_DM365_VOICE_CODEC_MODULE +config SND_SOC_DM365_SELECT_VOICE_CODECS def_tristate y depends on SND_SOC_DM365_VOICE_CODEC && SND_SOC select MFD_DAVINCI_VOICECODEC diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c index b043a0070d20..68d69e32681a 100644 --- a/sound/soc/ti/davinci-evm.c +++ b/sound/soc/ti/davinci-evm.c @@ -73,7 +73,7 @@ static int evm_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops evm_ops = { +static const struct snd_soc_ops evm_ops = { .startup = evm_startup, .shutdown = evm_shutdown, .hw_params = evm_hw_params, diff --git a/sound/soc/ti/omap-abe-twl6040.c b/sound/soc/ti/omap-abe-twl6040.c index 2e3d1eea77c1..da809c7f25a4 100644 --- a/sound/soc/ti/omap-abe-twl6040.c +++ b/sound/soc/ti/omap-abe-twl6040.c @@ -96,7 +96,7 @@ static int omap_abe_dmic_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops omap_abe_dmic_ops = { +static const struct snd_soc_ops omap_abe_dmic_ops = { .hw_params = omap_abe_dmic_hw_params, }; diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c index 2c39c7a2fd7d..3e654e708f78 100644 --- a/sound/soc/ux500/mop500_ab8500.c +++ b/sound/soc/ux500/mop500_ab8500.c @@ -348,7 +348,7 @@ static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream) return 0; } -struct snd_soc_ops mop500_ab8500_ops[] = { +const struct snd_soc_ops mop500_ab8500_ops[] = { { .hw_params = mop500_ab8500_hw_params, .hw_free = mop500_ab8500_hw_free, diff --git a/sound/soc/ux500/mop500_ab8500.h b/sound/soc/ux500/mop500_ab8500.h index 8138a4e9aaf5..087ef246d87d 100644 --- a/sound/soc/ux500/mop500_ab8500.h +++ b/sound/soc/ux500/mop500_ab8500.h @@ -11,7 +11,7 @@ #ifndef MOP500_AB8500_H #define MOP500_AB8500_H -extern struct snd_soc_ops mop500_ab8500_ops[]; +extern const struct snd_soc_ops mop500_ab8500_ops[]; int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd); void mop500_ab8500_remove(struct snd_soc_card *card); diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c index 43a2a62d66f7..49629d4bb327 100644 --- a/sound/usb/6fire/comm.c +++ b/sound/usb/6fire/comm.c @@ -95,7 +95,7 @@ static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev) int actual_len; ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP), - buffer, buffer[1] + 2, &actual_len, HZ); + buffer, buffer[1] + 2, &actual_len, 1000); if (ret < 0) return ret; else if (actual_len != buffer[1] + 2) diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c index 8981e61f2da4..c51abc54d2f8 100644 --- a/sound/usb/6fire/firmware.c +++ b/sound/usb/6fire/firmware.c @@ -160,7 +160,7 @@ static int usb6fire_fw_ezusb_write(struct usb_device *device, { return usb_control_msg_send(device, 0, type, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - value, 0, data, len, HZ, GFP_KERNEL); + value, 0, data, len, 1000, GFP_KERNEL); } static int usb6fire_fw_ezusb_read(struct usb_device *device, @@ -168,7 +168,7 @@ static int usb6fire_fw_ezusb_read(struct usb_device *device, { return usb_control_msg_recv(device, 0, type, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - value, 0, data, len, HZ, GFP_KERNEL); + value, 0, data, len, 1000, GFP_KERNEL); } static int usb6fire_fw_fpga_write(struct usb_device *device, @@ -178,7 +178,7 @@ static int usb6fire_fw_fpga_write(struct usb_device *device, int ret; ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len, - &actual_len, HZ); + &actual_len, 1000); if (ret < 0) return ret; else if (actual_len != len) diff --git a/sound/usb/card.h b/sound/usb/card.h index 5b19901f305a..87f042d06ce0 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -74,8 +74,9 @@ struct snd_usb_endpoint { atomic_t state; /* running state */ - void (*prepare_data_urb) (struct snd_usb_substream *subs, - struct urb *urb); + int (*prepare_data_urb) (struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock); void (*retire_data_urb) (struct snd_usb_substream *subs, struct urb *urb); @@ -94,9 +95,9 @@ struct snd_usb_endpoint { struct list_head ready_playback_urbs; /* playback URB FIFO for implicit fb */ unsigned int nurbs; /* # urbs */ - unsigned int nominal_queue_size; /* total buffer sizes in URBs */ unsigned long active_mask; /* bitmask of active urbs */ unsigned long unlink_mask; /* bitmask of unlinked urbs */ + atomic_t submitted_urbs; /* currently submitted urbs */ char *syncbuf; /* sync buffer for all sync URBs */ dma_addr_t sync_dma; /* DMA address of syncbuf */ @@ -125,6 +126,7 @@ struct snd_usb_endpoint { int skip_packets; /* quirks for devices to ignore the first n packets in a stream */ bool implicit_fb_sync; /* syncs with implicit feedback */ + bool lowlatency_playback; /* low-latency playback mode */ bool need_setup; /* (re-)need for configure? */ /* for hw constraints */ @@ -136,6 +138,7 @@ struct snd_usb_endpoint { unsigned int cur_period_frames; unsigned int cur_period_bytes; unsigned int cur_buffer_periods; + unsigned char cur_clock; spinlock_t lock; struct list_head list; @@ -188,7 +191,7 @@ struct snd_usb_substream { } dsd_dop; bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */ - bool early_playback_start; /* early start needed for playback? */ + bool lowlatency_playback; /* low-latency playback mode */ struct media_ctl *media_ctl; }; diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 81d5ce07d548..4dfe76416794 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -271,7 +271,7 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip, return -EINVAL; } - /* first, see if the ID we're looking for is a clock source already */ + /* first, see if the ID we're looking at is a clock source already */ source = snd_usb_find_clock_source(chip, entity_id, proto); if (source) { entity_id = GET_VAL(source, proto, bClockID); @@ -297,7 +297,7 @@ static int __uac_clock_find_source(struct snd_usb_audio *chip, goto find_source; } - /* the entity ID we are looking for is a selector. + /* the entity ID we are looking at is a selector. * find out what it currently selects */ ret = uac_clock_selector_get_val(chip, clock_id); if (ret < 0) { @@ -496,6 +496,10 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, union uac23_clock_source_desc *cs_desc; cs_desc = snd_usb_find_clock_source(chip, clock, fmt->protocol); + + if (!cs_desc) + return 0; + if (fmt->protocol == UAC_VERSION_3) bmControls = le32_to_cpu(cs_desc->v3.bmControls); else diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 533919a28856..743b8287cfcd 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -148,18 +148,23 @@ int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep) * This won't be used for implicit feedback which takes the packet size * returned from the sync source */ -static int slave_next_packet_size(struct snd_usb_endpoint *ep) +static int slave_next_packet_size(struct snd_usb_endpoint *ep, + unsigned int avail) { unsigned long flags; + unsigned int phase; int ret; if (ep->fill_max) return ep->maxframesize; spin_lock_irqsave(&ep->lock, flags); - ep->phase = (ep->phase & 0xffff) - + (ep->freqm << ep->datainterval); - ret = min(ep->phase >> 16, ep->maxframesize); + phase = (ep->phase & 0xffff) + (ep->freqm << ep->datainterval); + ret = min(phase >> 16, ep->maxframesize); + if (avail && ret >= avail) + ret = -EAGAIN; + else + ep->phase = phase; spin_unlock_irqrestore(&ep->lock, flags); return ret; @@ -169,20 +174,25 @@ static int slave_next_packet_size(struct snd_usb_endpoint *ep) * Return the number of samples to be sent in the next packet * for adaptive and synchronous endpoints */ -static int next_packet_size(struct snd_usb_endpoint *ep) +static int next_packet_size(struct snd_usb_endpoint *ep, unsigned int avail) { + unsigned int sample_accum; int ret; if (ep->fill_max) return ep->maxframesize; - ep->sample_accum += ep->sample_rem; - if (ep->sample_accum >= ep->pps) { - ep->sample_accum -= ep->pps; + sample_accum = ep->sample_accum + ep->sample_rem; + if (sample_accum >= ep->pps) { + sample_accum -= ep->pps; ret = ep->packsize[1]; } else { ret = ep->packsize[0]; } + if (avail && ret >= avail) + ret = -EAGAIN; + else + ep->sample_accum = sample_accum; return ret; } @@ -190,16 +200,27 @@ static int next_packet_size(struct snd_usb_endpoint *ep) /* * snd_usb_endpoint_next_packet_size: Return the number of samples to be sent * in the next packet + * + * If the size is equal or exceeds @avail, don't proceed but return -EAGAIN + * Exception: @avail = 0 for skipping the check. */ int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep, - struct snd_urb_ctx *ctx, int idx) + struct snd_urb_ctx *ctx, int idx, + unsigned int avail) { - if (ctx->packet_size[idx]) - return ctx->packet_size[idx]; - else if (ep->sync_source) - return slave_next_packet_size(ep); + unsigned int packet; + + packet = ctx->packet_size[idx]; + if (packet) { + if (avail && packet >= avail) + return -EAGAIN; + return packet; + } + + if (ep->sync_source) + return slave_next_packet_size(ep, avail); else - return next_packet_size(ep); + return next_packet_size(ep, avail); } static void call_retire_callback(struct snd_usb_endpoint *ep, @@ -263,7 +284,7 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep, unsigned int length; int counts; - counts = snd_usb_endpoint_next_packet_size(ep, ctx, i); + counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0); length = counts * ep->stride; /* number of silent bytes */ offset = offs * ep->stride + extra * i; urb->iso_frame_desc[i].offset = offset; @@ -286,8 +307,9 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep, /* * Prepare a PLAYBACK urb for submission to the bus. */ -static void prepare_outbound_urb(struct snd_usb_endpoint *ep, - struct snd_urb_ctx *ctx) +static int prepare_outbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx, + bool in_stream_lock) { struct urb *urb = ctx->urb; unsigned char *cp = urb->transfer_buffer; @@ -299,9 +321,9 @@ static void prepare_outbound_urb(struct snd_usb_endpoint *ep, case SND_USB_ENDPOINT_TYPE_DATA: data_subs = READ_ONCE(ep->data_subs); if (data_subs && ep->prepare_data_urb) - ep->prepare_data_urb(data_subs, urb); - else /* no data provider, so send silence */ - prepare_silent_urb(ep, ctx); + return ep->prepare_data_urb(data_subs, urb, in_stream_lock); + /* no data provider, so send silence */ + prepare_silent_urb(ep, ctx); break; case SND_USB_ENDPOINT_TYPE_SYNC: @@ -330,13 +352,14 @@ static void prepare_outbound_urb(struct snd_usb_endpoint *ep, break; } + return 0; } /* * Prepare a CAPTURE or SYNC urb for submission to the bus. */ -static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep, - struct snd_urb_ctx *urb_ctx) +static int prepare_inbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *urb_ctx) { int i, offs; struct urb *urb = urb_ctx->urb; @@ -361,6 +384,7 @@ static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep, urb->iso_frame_desc[0].offset = 0; break; } + return 0; } /* notify an error as XRUN to the assigned PCM data substream */ @@ -396,6 +420,16 @@ next_packet_fifo_dequeue(struct snd_usb_endpoint *ep) return p; } +static void push_back_to_ready_list(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + spin_unlock_irqrestore(&ep->lock, flags); +} + /* * Send output urbs that have been prepared previously. URBs are dequeued * from ep->ready_playback_urbs and in case there aren't any available @@ -406,12 +440,14 @@ next_packet_fifo_dequeue(struct snd_usb_endpoint *ep) * is that host controllers don't guarantee the order in which they return * inbound and outbound packets to their submitters. * - * This function is only used for implicit feedback endpoints. For endpoints - * driven by dedicated sync endpoints, URBs are immediately re-submitted - * from their completion handler. + * This function is used both for implicit feedback endpoints and in low- + * latency playback mode. */ -static void queue_pending_output_urbs(struct snd_usb_endpoint *ep) +void snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep, + bool in_stream_lock) { + bool implicit_fb = snd_usb_endpoint_implicit_feedback_sink(ep); + while (ep_state_running(ep)) { unsigned long flags; @@ -420,14 +456,14 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep) int err, i; spin_lock_irqsave(&ep->lock, flags); - if (ep->next_packet_queued > 0 && + if ((!implicit_fb || ep->next_packet_queued > 0) && !list_empty(&ep->ready_playback_urbs)) { /* take URB out of FIFO */ ctx = list_first_entry(&ep->ready_playback_urbs, struct snd_urb_ctx, ready_list); list_del_init(&ctx->ready_list); - - packet = next_packet_fifo_dequeue(ep); + if (implicit_fb) + packet = next_packet_fifo_dequeue(ep); } spin_unlock_irqrestore(&ep->lock, flags); @@ -435,11 +471,24 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep) return; /* copy over the length information */ - for (i = 0; i < packet->packets; i++) - ctx->packet_size[i] = packet->packet_size[i]; + if (implicit_fb) { + for (i = 0; i < packet->packets; i++) + ctx->packet_size[i] = packet->packet_size[i]; + } /* call the data handler to fill in playback data */ - prepare_outbound_urb(ep, ctx); + err = prepare_outbound_urb(ep, ctx, in_stream_lock); + /* can be stopped during prepare callback */ + if (unlikely(!ep_state_running(ep))) + break; + if (err < 0) { + /* push back to ready list again for -EAGAIN */ + if (err == -EAGAIN) + push_back_to_ready_list(ep, ctx); + else + notify_xrun(ep); + return; + } err = usb_submit_urb(ctx->urb, GFP_ATOMIC); if (err < 0) { @@ -451,6 +500,7 @@ static void queue_pending_output_urbs(struct snd_usb_endpoint *ep) } set_bit(ctx->index, &ep->active_mask); + atomic_inc(&ep->submitted_urbs); } } @@ -461,7 +511,6 @@ static void snd_complete_urb(struct urb *urb) { struct snd_urb_ctx *ctx = urb->context; struct snd_usb_endpoint *ep = ctx->ep; - unsigned long flags; int err; if (unlikely(urb->status == -ENOENT || /* unlinked */ @@ -482,16 +531,20 @@ static void snd_complete_urb(struct urb *urb) if (unlikely(!ep_state_running(ep))) goto exit_clear; - if (snd_usb_endpoint_implicit_feedback_sink(ep)) { - spin_lock_irqsave(&ep->lock, flags); - list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + /* in low-latency and implicit-feedback modes, push back the + * URB to ready list at first, then process as much as possible + */ + if (ep->lowlatency_playback || + snd_usb_endpoint_implicit_feedback_sink(ep)) { + push_back_to_ready_list(ep, ctx); clear_bit(ctx->index, &ep->active_mask); - spin_unlock_irqrestore(&ep->lock, flags); - queue_pending_output_urbs(ep); + snd_usb_queue_pending_output_urbs(ep, false); + atomic_dec(&ep->submitted_urbs); /* decrement at last */ return; } - prepare_outbound_urb(ep, ctx); + /* in non-lowlatency mode, no error handling for prepare */ + prepare_outbound_urb(ep, ctx, false); /* can be stopped during prepare callback */ if (unlikely(!ep_state_running(ep))) goto exit_clear; @@ -513,6 +566,7 @@ static void snd_complete_urb(struct urb *urb) exit_clear: clear_bit(ctx->index, &ep->active_mask); + atomic_dec(&ep->submitted_urbs); } /* @@ -596,6 +650,7 @@ int snd_usb_add_endpoint(struct snd_usb_audio *chip, int ep_num, int type) ep->type = type; ep->ep_num = ep_num; INIT_LIST_HEAD(&ep->ready_playback_urbs); + atomic_set(&ep->submitted_urbs, 0); is_playback = ((ep_num & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); ep_num &= USB_ENDPOINT_NUMBER_MASK; @@ -722,6 +777,7 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip, ep->cur_period_frames = params_period_size(params); ep->cur_period_bytes = ep->cur_period_frames * ep->cur_frame_bytes; ep->cur_buffer_periods = params_periods(params); + ep->cur_clock = fp->clock; if (ep->type == SND_USB_ENDPOINT_TYPE_SYNC) endpoint_set_syncinterval(chip, ep); @@ -781,14 +837,19 @@ void snd_usb_endpoint_set_sync(struct snd_usb_audio *chip, * Pass NULL to deactivate each callback. */ void snd_usb_endpoint_set_callback(struct snd_usb_endpoint *ep, - void (*prepare)(struct snd_usb_substream *subs, - struct urb *urb), + int (*prepare)(struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock), void (*retire)(struct snd_usb_substream *subs, struct urb *urb), struct snd_usb_substream *data_subs) { ep->prepare_data_urb = prepare; ep->retire_data_urb = retire; + if (data_subs) + ep->lowlatency_playback = data_subs->lowlatency_playback; + else + ep->lowlatency_playback = false; WRITE_ONCE(ep->data_subs, data_subs); } @@ -833,6 +894,7 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip, ep->altsetting = 0; ep->cur_audiofmt = NULL; ep->cur_rate = 0; + ep->cur_clock = 0; ep->iface_ref = NULL; usb_audio_dbg(chip, "EP 0x%x closed\n", ep->ep_num); } @@ -859,7 +921,7 @@ static int wait_clear_urbs(struct snd_usb_endpoint *ep) return 0; do { - alive = bitmap_weight(&ep->active_mask, ep->nurbs); + alive = atomic_read(&ep->submitted_urbs); if (!alive) break; @@ -893,9 +955,10 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) * * This function moves the EP to STOPPING state if it's being RUNNING. */ -static int stop_urbs(struct snd_usb_endpoint *ep, bool force) +static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) { unsigned int i; + unsigned long flags; if (!force && atomic_read(&ep->running)) return -EBUSY; @@ -903,9 +966,14 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force) if (!ep_state_update(ep, EP_STATE_RUNNING, EP_STATE_STOPPING)) return 0; + spin_lock_irqsave(&ep->lock, flags); INIT_LIST_HEAD(&ep->ready_playback_urbs); ep->next_packet_head = 0; ep->next_packet_queued = 0; + spin_unlock_irqrestore(&ep->lock, flags); + + if (keep_pending) + return 0; for (i = 0; i < ep->nurbs; i++) { if (test_bit(i, &ep->active_mask)) { @@ -930,7 +998,7 @@ static int release_urbs(struct snd_usb_endpoint *ep, bool force) snd_usb_endpoint_set_callback(ep, NULL, NULL, NULL); /* stop and unlink urbs */ - err = stop_urbs(ep, force); + err = stop_urbs(ep, force, false); if (err) return err; @@ -1132,10 +1200,6 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) INIT_LIST_HEAD(&u->ready_list); } - /* total buffer bytes of all URBs plus the next queue; - * referred in pcm.c - */ - ep->nominal_queue_size = maxsize * urb_packs * (ep->nurbs + 1); return 0; out_of_memory: @@ -1340,6 +1404,25 @@ unlock: return err; } +/* get the current rate set to the given clock by any endpoint */ +int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock) +{ + struct snd_usb_endpoint *ep; + int rate = 0; + + if (!clock) + return 0; + mutex_lock(&chip->mutex); + list_for_each_entry(ep, &chip->ep_list, list) { + if (ep->cur_clock == clock && ep->cur_rate) { + rate = ep->cur_rate; + break; + } + } + mutex_unlock(&chip->mutex); + return rate; +} + /** * snd_usb_endpoint_start: start an snd_usb_endpoint * @@ -1355,6 +1438,7 @@ unlock: */ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) { + bool is_playback = usb_pipeout(ep->pipe); int err; unsigned int i; @@ -1391,13 +1475,9 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) if (snd_usb_endpoint_implicit_feedback_sink(ep) && !(ep->chip->quirk_flags & QUIRK_FLAG_PLAYBACK_FIRST)) { - for (i = 0; i < ep->nurbs; i++) { - struct snd_urb_ctx *ctx = ep->urb + i; - list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); - } - usb_audio_dbg(ep->chip, "No URB submission due to implicit fb sync\n"); - return 0; + i = 0; + goto fill_rest; } for (i = 0; i < ep->nurbs; i++) { @@ -1406,10 +1486,18 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) if (snd_BUG_ON(!urb)) goto __error; - if (usb_pipeout(ep->pipe)) { - prepare_outbound_urb(ep, urb->context); - } else { - prepare_inbound_urb(ep, urb->context); + if (is_playback) + err = prepare_outbound_urb(ep, urb->context, true); + else + err = prepare_inbound_urb(ep, urb->context); + if (err < 0) { + /* stop filling at applptr */ + if (err == -EAGAIN) + break; + usb_audio_dbg(ep->chip, + "EP 0x%x: failed to prepare urb: %d\n", + ep->ep_num, err); + goto __error; } err = usb_submit_urb(urb, GFP_ATOMIC); @@ -1420,14 +1508,29 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) goto __error; } set_bit(i, &ep->active_mask); + atomic_inc(&ep->submitted_urbs); + } + + if (!i) { + usb_audio_dbg(ep->chip, "XRUN at starting EP 0x%x\n", + ep->ep_num); + goto __error; } usb_audio_dbg(ep->chip, "%d URBs submitted for EP 0x%x\n", - ep->nurbs, ep->ep_num); + i, ep->ep_num); + + fill_rest: + /* put the remaining URBs to ready list */ + if (is_playback) { + for (; i < ep->nurbs; i++) + push_back_to_ready_list(ep, ep->urb + i); + } + return 0; __error: - snd_usb_endpoint_stop(ep); + snd_usb_endpoint_stop(ep, false); return -EPIPE; } @@ -1435,6 +1538,7 @@ __error: * snd_usb_endpoint_stop: stop an snd_usb_endpoint * * @ep: the endpoint to stop (may be NULL) + * @keep_pending: keep in-flight URBs * * A call to this function will decrement the running count of the endpoint. * In case the last user has requested the endpoint stop, the URBs will @@ -1445,7 +1549,7 @@ __error: * The caller needs to synchronize the pending stop operation via * snd_usb_endpoint_sync_pending_stop(). */ -void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep) +void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending) { if (!ep) return; @@ -1460,7 +1564,7 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep) if (!atomic_dec_return(&ep->running)) { if (ep->sync_source) WRITE_ONCE(ep->sync_source->sync_sink, NULL); - stop_urbs(ep, false); + stop_urbs(ep, false, keep_pending); } } @@ -1575,7 +1679,7 @@ static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, } spin_unlock_irqrestore(&ep->lock, flags); - queue_pending_output_urbs(ep); + snd_usb_queue_pending_output_urbs(ep, false); return; } diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h index a668f675b52b..6a9af04cf175 100644 --- a/sound/usb/endpoint.h +++ b/sound/usb/endpoint.h @@ -19,6 +19,7 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip, struct snd_usb_endpoint *ep); int snd_usb_endpoint_configure(struct snd_usb_audio *chip, struct snd_usb_endpoint *ep); +int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock); bool snd_usb_endpoint_compatible(struct snd_usb_audio *chip, struct snd_usb_endpoint *ep, @@ -29,14 +30,15 @@ void snd_usb_endpoint_set_sync(struct snd_usb_audio *chip, struct snd_usb_endpoint *data_ep, struct snd_usb_endpoint *sync_ep); void snd_usb_endpoint_set_callback(struct snd_usb_endpoint *ep, - void (*prepare)(struct snd_usb_substream *subs, - struct urb *urb), + int (*prepare)(struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock), void (*retire)(struct snd_usb_substream *subs, struct urb *urb), struct snd_usb_substream *data_subs); int snd_usb_endpoint_start(struct snd_usb_endpoint *ep); -void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending); void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep); void snd_usb_endpoint_suspend(struct snd_usb_endpoint *ep); int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep); @@ -45,6 +47,9 @@ void snd_usb_endpoint_free_all(struct snd_usb_audio *chip); int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep); int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep, - struct snd_urb_ctx *ctx, int idx); + struct snd_urb_ctx *ctx, int idx, + unsigned int avail); +void snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep, + bool in_stream_lock); #endif /* __USBAUDIO_ENDPOINT_H */ diff --git a/sound/usb/format.c b/sound/usb/format.c index 50efccbffb8a..f5e676a51b30 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -414,6 +414,7 @@ static int line6_parse_audio_format_rates_quirk(struct snd_usb_audio *chip, case USB_ID(0x0e41, 0x4242): /* Line6 Helix Rack */ case USB_ID(0x0e41, 0x4244): /* Line6 Helix LT */ case USB_ID(0x0e41, 0x4246): /* Line6 HX-Stomp */ + case USB_ID(0x0e41, 0x4253): /* Line6 HX-Stomp XL */ case USB_ID(0x0e41, 0x4247): /* Line6 Pod Go */ case USB_ID(0x0e41, 0x4248): /* Line6 Helix >= fw 2.82 */ case USB_ID(0x0e41, 0x4249): /* Line6 Helix Rack >= fw 2.82 */ diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c index 23767a14d126..70319c822c10 100644 --- a/sound/usb/implicit.c +++ b/sound/usb/implicit.c @@ -54,8 +54,6 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { /* Fixed EP */ /* FIXME: check the availability of generic matching */ - IMPLICIT_FB_FIXED_DEV(0x1397, 0x0001, 0x81, 1), /* Behringer UFX1604 */ - IMPLICIT_FB_FIXED_DEV(0x1397, 0x0002, 0x81, 1), /* Behringer UFX1204 */ IMPLICIT_FB_FIXED_DEV(0x2466, 0x8010, 0x81, 2), /* Fractal Audio Axe-Fx III */ IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0001, 0x81, 2), /* Solid State Logic SSL2 */ IMPLICIT_FB_FIXED_DEV(0x31e9, 0x0002, 0x81, 2), /* Solid State Logic SSL2+ */ diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c index 9602929b7de9..59faa5a9a714 100644 --- a/sound/usb/line6/driver.c +++ b/sound/usb/line6/driver.c @@ -113,12 +113,12 @@ int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, retval = usb_interrupt_msg(line6->usbdev, usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w), (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + &partial, LINE6_TIMEOUT); } else { retval = usb_bulk_msg(line6->usbdev, usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w), (char *)frag_buf, frag_size, - &partial, LINE6_TIMEOUT * HZ); + &partial, LINE6_TIMEOUT); } if (retval) { @@ -347,7 +347,7 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data, ret = usb_control_msg_send(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, (datalen << 8) | 0x21, address, NULL, 0, - LINE6_TIMEOUT * HZ, GFP_KERNEL); + LINE6_TIMEOUT, GFP_KERNEL); if (ret) { dev_err(line6->ifcdev, "read request failed (error %d)\n", ret); goto exit; @@ -360,7 +360,7 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data, ret = usb_control_msg_recv(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, 0x0012, 0x0000, &len, 1, - LINE6_TIMEOUT * HZ, GFP_KERNEL); + LINE6_TIMEOUT, GFP_KERNEL); if (ret) { dev_err(line6->ifcdev, "receive length failed (error %d)\n", ret); @@ -387,7 +387,7 @@ int line6_read_data(struct usb_line6 *line6, unsigned address, void *data, /* receive the result: */ ret = usb_control_msg_recv(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x0013, 0x0000, data, datalen, LINE6_TIMEOUT * HZ, + 0x0013, 0x0000, data, datalen, LINE6_TIMEOUT, GFP_KERNEL); if (ret) dev_err(line6->ifcdev, "read failed (error %d)\n", ret); @@ -417,7 +417,7 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data, ret = usb_control_msg_send(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - 0x0022, address, data, datalen, LINE6_TIMEOUT * HZ, + 0x0022, address, data, datalen, LINE6_TIMEOUT, GFP_KERNEL); if (ret) { dev_err(line6->ifcdev, @@ -430,7 +430,7 @@ int line6_write_data(struct usb_line6 *line6, unsigned address, void *data, ret = usb_control_msg_recv(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x0012, 0x0000, status, 1, LINE6_TIMEOUT * HZ, + 0x0012, 0x0000, status, 1, LINE6_TIMEOUT, GFP_KERNEL); if (ret) { dev_err(line6->ifcdev, diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h index 71d3da1db8c8..ecf3a2b39c7e 100644 --- a/sound/usb/line6/driver.h +++ b/sound/usb/line6/driver.h @@ -27,7 +27,7 @@ #define LINE6_FALLBACK_INTERVAL 10 #define LINE6_FALLBACK_MAXPACKETSIZE 16 -#define LINE6_TIMEOUT 1 +#define LINE6_TIMEOUT 1000 #define LINE6_BUFSIZE_LISTEN 64 #define LINE6_MIDI_MESSAGE_MAXLEN 256 diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index 28794a35949d..b24bc82f89e3 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -190,7 +190,7 @@ static int podhd_dev_start(struct usb_line6_podhd *pod) ret = usb_control_msg_send(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, 0x11, 0, - NULL, 0, LINE6_TIMEOUT * HZ, GFP_KERNEL); + NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); if (ret) { dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); goto exit; @@ -200,7 +200,7 @@ static int podhd_dev_start(struct usb_line6_podhd *pod) ret = usb_control_msg_recv(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, 0x11, 0x0, - init_bytes, 3, LINE6_TIMEOUT * HZ, GFP_KERNEL); + init_bytes, 3, LINE6_TIMEOUT, GFP_KERNEL); if (ret) { dev_err(pod->line6.ifcdev, "receive length failed (error %d)\n", ret); @@ -220,7 +220,7 @@ static int podhd_dev_start(struct usb_line6_podhd *pod) USB_REQ_SET_FEATURE, USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, 1, 0, - NULL, 0, LINE6_TIMEOUT * HZ, GFP_KERNEL); + NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); exit: return ret; } diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 4e5693c97aa4..e33df58740a9 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -128,7 +128,7 @@ static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2) ret = usb_control_msg_send(usbdev, 0, 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, - cmd1, cmd2, NULL, 0, LINE6_TIMEOUT * HZ, + cmd1, cmd2, NULL, 0, LINE6_TIMEOUT, GFP_KERNEL); if (ret) { diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index 5834d1dc317e..4f6b20ed29dd 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -1000,7 +1000,7 @@ static int detect_usb_format(struct ua101 *ua) fmt_playback->bSubframeSize * ua->playback.channels; epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc; - if (!usb_endpoint_is_isoc_in(epd)) { + if (!usb_endpoint_is_isoc_in(epd) || usb_endpoint_maxp(epd) == 0) { dev_err(&ua->dev->dev, "invalid capture endpoint\n"); return -ENXIO; } @@ -1008,7 +1008,7 @@ static int detect_usb_format(struct ua101 *ua) ua->capture.max_packet_bytes = usb_endpoint_maxp(epd); epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc; - if (!usb_endpoint_is_isoc_out(epd)) { + if (!usb_endpoint_is_isoc_out(epd) || usb_endpoint_maxp(epd) == 0) { dev_err(&ua->dev->dev, "invalid playback endpoint\n"); return -ENXIO; } diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 8e030b1c061a..6e7bac8203ba 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -361,9 +361,8 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, memset(buf, 0, sizeof(buf)); - ret = snd_usb_lock_shutdown(chip) ? -EIO : 0; - if (ret) - goto error; + if (snd_usb_lock_shutdown(chip)) + return -EIO; idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, @@ -372,8 +371,7 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, snd_usb_unlock_shutdown(chip); if (ret < 0) { -error: - usb_audio_err(chip, + usb_audio_dbg(chip, "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, idx, cval->val_type); return ret; @@ -1208,12 +1206,32 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, } } +/* forcibly initialize the current mixer value; if GET_CUR fails, set to + * the minimum as default + */ +static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) +{ + int val, err; + + err = snd_usb_get_cur_mix_value(cval, ch, idx, &val); + if (!err) + return; + if (!cval->head.mixer->ignore_ctl_error) + usb_audio_warn(cval->head.mixer->chip, + "%d:%d: failed to get current value for ch %d (%d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + ch, err); + snd_usb_set_cur_mix_value(cval, ch, idx, cval->min); +} + /* * retrieve the minimum and maximum values for the specified control */ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, int default_min, struct snd_kcontrol *kctl) { + int i, idx; + /* for failsafe */ cval->min = default_min; cval->max = cval->min + 1; @@ -1226,7 +1244,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, } else { int minchn = 0; if (cval->cmask) { - int i; for (i = 0; i < MAX_CHANNELS; i++) if (cval->cmask & (1 << i)) { minchn = i + 1; @@ -1327,6 +1344,19 @@ no_res_check: } } + /* initialize all elements */ + if (!cval->cmask) { + init_cur_mix_raw(cval, 0, 0); + } else { + idx = 0; + for (i = 0; i < MAX_CHANNELS; i++) { + if (cval->cmask & (1 << i)) { + init_cur_mix_raw(cval, i + 1, idx); + idx++; + } + } + } + return 0; } diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 46082dc57be0..d489c1de3bae 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -2795,6 +2795,7 @@ static int snd_bbfpro_controls_create(struct usb_mixer_interface *mixer) #define SND_DJM_750_IDX 0x1 #define SND_DJM_850_IDX 0x2 #define SND_DJM_900NXS2_IDX 0x3 +#define SND_DJM_750MK2_IDX 0x4 #define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ @@ -2984,10 +2985,40 @@ static const struct snd_djm_ctl snd_djm_ctls_900nxs2[] = { SND_DJM_CTL("Ch5 Input", 900nxs2_cap5, 3, SND_DJM_WINDEX_CAP) }; +// DJM-750MK2 +static const u16 snd_djm_opts_750mk2_cap1[] = { + 0x0100, 0x0102, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a }; +static const u16 snd_djm_opts_750mk2_cap2[] = { + 0x0200, 0x0202, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a }; +static const u16 snd_djm_opts_750mk2_cap3[] = { + 0x0300, 0x0302, 0x0303, 0x0306, 0x0307, 0x0308, 0x0309, 0x030a }; +static const u16 snd_djm_opts_750mk2_cap4[] = { + 0x0400, 0x0402, 0x0403, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a }; +static const u16 snd_djm_opts_750mk2_cap5[] = { + 0x0507, 0x0508, 0x0509, 0x050a, 0x0511, 0x0512, 0x0513, 0x0514 }; + +static const u16 snd_djm_opts_750mk2_pb1[] = { 0x0100, 0x0101, 0x0104 }; +static const u16 snd_djm_opts_750mk2_pb2[] = { 0x0200, 0x0201, 0x0204 }; +static const u16 snd_djm_opts_750mk2_pb3[] = { 0x0300, 0x0301, 0x0304 }; + + +static const struct snd_djm_ctl snd_djm_ctls_750mk2[] = { + SND_DJM_CTL("Capture Level", cap_level, 0, SND_DJM_WINDEX_CAPLVL), + SND_DJM_CTL("Ch1 Input", 750mk2_cap1, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Ch2 Input", 750mk2_cap2, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Ch3 Input", 750mk2_cap3, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Ch4 Input", 750mk2_cap4, 2, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Ch5 Input", 750mk2_cap5, 3, SND_DJM_WINDEX_CAP), + SND_DJM_CTL("Ch1 Output", 750mk2_pb1, 0, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Ch2 Output", 750mk2_pb2, 1, SND_DJM_WINDEX_PB), + SND_DJM_CTL("Ch3 Output", 750mk2_pb3, 2, SND_DJM_WINDEX_PB) +}; + static const struct snd_djm_device snd_djm_devices[] = { SND_DJM_DEVICE(250mk2), SND_DJM_DEVICE(750), + SND_DJM_DEVICE(750mk2), SND_DJM_DEVICE(850), SND_DJM_DEVICE(900nxs2) }; @@ -3235,6 +3266,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x08e4, 0x017f): /* Pioneer DJ DJM-750 */ err = snd_djm_controls_create(mixer, SND_DJM_750_IDX); break; + case USB_ID(0x2b73, 0x001b): /* Pioneer DJ DJM-750MK2 */ + err = snd_djm_controls_create(mixer, SND_DJM_750MK2_IDX); + break; case USB_ID(0x08e4, 0x0163): /* Pioneer DJ DJM-850 */ err = snd_djm_controls_create(mixer, SND_DJM_850_IDX); break; diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 5dc9266180e3..95ec8eec1bb0 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -219,16 +219,16 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip, return 0; } -static bool stop_endpoints(struct snd_usb_substream *subs) +static bool stop_endpoints(struct snd_usb_substream *subs, bool keep_pending) { bool stopped = 0; if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) { - snd_usb_endpoint_stop(subs->sync_endpoint); + snd_usb_endpoint_stop(subs->sync_endpoint, keep_pending); stopped = true; } if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) { - snd_usb_endpoint_stop(subs->data_endpoint); + snd_usb_endpoint_stop(subs->data_endpoint, keep_pending); stopped = true; } return stopped; @@ -261,7 +261,7 @@ static int start_endpoints(struct snd_usb_substream *subs) return 0; error: - stop_endpoints(subs); + stop_endpoints(subs, false); return err; } @@ -437,7 +437,7 @@ static int configure_endpoints(struct snd_usb_audio *chip, if (subs->data_endpoint->need_setup) { /* stop any running stream beforehand */ - if (stop_endpoints(subs)) + if (stop_endpoints(subs, false)) sync_pending_stops(subs); err = snd_usb_endpoint_configure(chip, subs->data_endpoint); if (err < 0) @@ -572,7 +572,7 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream) subs->cur_audiofmt = NULL; mutex_unlock(&chip->mutex); if (!snd_usb_lock_shutdown(chip)) { - if (stop_endpoints(subs)) + if (stop_endpoints(subs, false)) sync_pending_stops(subs); close_endpoints(chip, subs); snd_usb_unlock_shutdown(chip); @@ -581,6 +581,26 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream) return 0; } +/* check whether early start is needed for playback stream */ +static int lowlatency_playback_available(struct snd_pcm_runtime *runtime, + struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + + if (subs->direction == SNDRV_PCM_STREAM_CAPTURE) + return false; + /* disabled via module option? */ + if (!chip->lowlatency) + return false; + /* free-wheeling mode? (e.g. dmix) */ + if (runtime->stop_threshold > runtime->buffer_size) + return false; + /* implicit feedback mode has own operation mode */ + if (snd_usb_endpoint_implicit_feedback_sink(subs->data_endpoint)) + return false; + return true; +} + /* * prepare callback * @@ -614,13 +634,8 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) subs->period_elapsed_pending = 0; runtime->delay = 0; - /* check whether early start is needed for playback stream */ - subs->early_playback_start = - subs->direction == SNDRV_PCM_STREAM_PLAYBACK && - (!chip->lowlatency || - (subs->data_endpoint->nominal_queue_size >= subs->buffer_bytes)); - - if (subs->early_playback_start) + subs->lowlatency_playback = lowlatency_playback_available(runtime, subs); + if (!subs->lowlatency_playback) ret = start_endpoints(subs); unlock: @@ -734,6 +749,7 @@ static int hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_usb_substream *subs = rule->private; + struct snd_usb_audio *chip = subs->stream->chip; const struct audioformat *fp; struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); unsigned int rmin, rmax, r; @@ -745,6 +761,14 @@ static int hw_rule_rate(struct snd_pcm_hw_params *params, list_for_each_entry(fp, &subs->fmt_list, list) { if (!hw_check_valid_format(subs, params, fp)) continue; + r = snd_usb_endpoint_get_clock_rate(chip, fp->clock); + if (r > 0) { + if (!snd_interval_test(it, r)) + continue; + rmin = min(rmin, r); + rmax = max(rmax, r); + continue; + } if (fp->rate_table && fp->nr_rates) { for (i = 0; i < fp->nr_rates; i++) { r = fp->rate_table[i]; @@ -1056,6 +1080,13 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre if (err < 0) return err; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (fp->implicit_fb) { + runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + break; + } + } + return 0; } @@ -1068,6 +1099,10 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream) int ret; runtime->hw = snd_usb_hardware; + /* need an explicit sync to catch applptr update in low-latency mode */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK && + as->chip->lowlatency) + runtime->hw.info |= SNDRV_PCM_INFO_SYNC_APPLPTR; runtime->private_data = subs; subs->pcm_substream = substream; /* runtime PM is also done there */ @@ -1320,44 +1355,66 @@ static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs, return bytes; } -static void prepare_playback_urb(struct snd_usb_substream *subs, - struct urb *urb) +static int prepare_playback_urb(struct snd_usb_substream *subs, + struct urb *urb, + bool in_stream_lock) { struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; struct snd_usb_endpoint *ep = subs->data_endpoint; struct snd_urb_ctx *ctx = urb->context; - unsigned int counts, frames, bytes; + unsigned int frames, bytes; + int counts; + unsigned int transfer_done, frame_limit, avail = 0; int i, stride, period_elapsed = 0; unsigned long flags; + int err = 0; stride = ep->stride; frames = 0; ctx->queued = 0; urb->number_of_packets = 0; + spin_lock_irqsave(&subs->lock, flags); - subs->frame_limit += ep->max_urb_frames; + frame_limit = subs->frame_limit + ep->max_urb_frames; + transfer_done = subs->transfer_done; + + if (subs->lowlatency_playback && + runtime->status->state != SNDRV_PCM_STATE_DRAINING) { + unsigned int hwptr = subs->hwptr_done / stride; + + /* calculate the byte offset-in-buffer of the appl_ptr */ + avail = (runtime->control->appl_ptr - runtime->hw_ptr_base) + % runtime->buffer_size; + if (avail <= hwptr) + avail += runtime->buffer_size; + avail -= hwptr; + } + for (i = 0; i < ctx->packets; i++) { - counts = snd_usb_endpoint_next_packet_size(ep, ctx, i); + counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail); + if (counts < 0) + break; /* set up descriptor */ urb->iso_frame_desc[i].offset = frames * stride; urb->iso_frame_desc[i].length = counts * stride; frames += counts; + avail -= counts; urb->number_of_packets++; - subs->transfer_done += counts; - if (subs->transfer_done >= runtime->period_size) { - subs->transfer_done -= runtime->period_size; - subs->frame_limit = 0; + transfer_done += counts; + if (transfer_done >= runtime->period_size) { + transfer_done -= runtime->period_size; + frame_limit = 0; period_elapsed = 1; if (subs->fmt_type == UAC_FORMAT_TYPE_II) { - if (subs->transfer_done > 0) { + if (transfer_done > 0) { /* FIXME: fill-max mode is not * supported yet */ - frames -= subs->transfer_done; - counts -= subs->transfer_done; + frames -= transfer_done; + counts -= transfer_done; urb->iso_frame_desc[i].length = counts * stride; - subs->transfer_done = 0; + transfer_done = 0; } i++; if (i < ctx->packets) { @@ -1371,13 +1428,19 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, } } /* finish at the period boundary or after enough frames */ - if ((period_elapsed || - subs->transfer_done >= subs->frame_limit) && + if ((period_elapsed || transfer_done >= frame_limit) && !snd_usb_endpoint_implicit_feedback_sink(ep)) break; } - bytes = frames * stride; + if (!frames) { + err = -EAGAIN; + goto unlock; + } + + bytes = frames * stride; + subs->transfer_done = transfer_done; + subs->frame_limit = frame_limit; if (unlikely(ep->cur_format == SNDRV_PCM_FORMAT_DSD_U16_LE && subs->cur_audiofmt->dsd_dop)) { fill_playback_urb_dsd_dop(subs, urb, bytes); @@ -1403,14 +1466,23 @@ static void prepare_playback_urb(struct snd_usb_substream *subs, subs->trigger_tstamp_pending_update = false; } - if (period_elapsed && !subs->running && !subs->early_playback_start) { + if (period_elapsed && !subs->running && subs->lowlatency_playback) { subs->period_elapsed_pending = 1; period_elapsed = 0; } + + unlock: spin_unlock_irqrestore(&subs->lock, flags); + if (err < 0) + return err; urb->transfer_buffer_length = bytes; - if (period_elapsed) - snd_pcm_period_elapsed(subs->pcm_substream); + if (period_elapsed) { + if (in_stream_lock) + snd_pcm_period_elapsed_under_stream_lock(subs->pcm_substream); + else + snd_pcm_period_elapsed(subs->pcm_substream); + } + return 0; } /* @@ -1442,6 +1514,27 @@ static void retire_playback_urb(struct snd_usb_substream *subs, snd_pcm_period_elapsed(subs->pcm_substream); } +/* PCM ack callback for the playback stream; + * this plays a role only when the stream is running in low-latency mode. + */ +static int snd_usb_pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + struct snd_usb_endpoint *ep; + + if (!subs->lowlatency_playback || !subs->running) + return 0; + ep = subs->data_endpoint; + if (!ep) + return 0; + /* When no more in-flight URBs available, try to process the pending + * outputs here + */ + if (!ep->active_mask) + snd_usb_queue_pending_output_urbs(ep, true); + return 0; +} + static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, int cmd) { @@ -1457,7 +1550,7 @@ static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substrea prepare_playback_urb, retire_playback_urb, subs); - if (!subs->early_playback_start && + if (subs->lowlatency_playback && cmd == SNDRV_PCM_TRIGGER_START) { err = start_endpoints(subs); if (err < 0) { @@ -1473,7 +1566,7 @@ static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substrea return 0; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: - stop_endpoints(subs); + stop_endpoints(subs, substream->runtime->status->state == SNDRV_PCM_STATE_DRAINING); snd_usb_endpoint_set_callback(subs->data_endpoint, NULL, NULL, NULL); subs->running = 0; @@ -1521,7 +1614,7 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream return 0; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: - stop_endpoints(subs); + stop_endpoints(subs, false); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: snd_usb_endpoint_set_callback(subs->data_endpoint, @@ -1545,6 +1638,7 @@ static const struct snd_pcm_ops snd_usb_playback_ops = { .trigger = snd_usb_substream_playback_trigger, .sync_stop = snd_usb_pcm_sync_stop, .pointer = snd_usb_pcm_pointer, + .ack = snd_usb_pcm_playback_ack, }; static const struct snd_pcm_ops snd_usb_capture_ops = { diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 2af8c68fac27..b1522e43173e 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3894,6 +3894,64 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { /* + * Pioneer DJ DJM-750MK2 + * 10 channels playback & 12 channels capture @ 48kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x001b), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* * Pioneer DJ DJM-850 * 8 channels playback and 8 channels capture @ 44.1/48/96kHz S24LE * Playback on EP 0x05 diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 8929d9abe8aa..64e1c20311ed 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1749,6 +1749,7 @@ static const struct registration_quirk registration_quirks[] = { REG_QUIRK_ENTRY(0x0951, 0x16ea, 2), /* Kingston HyperX Cloud Flight S */ REG_QUIRK_ENTRY(0x0ecb, 0x1f46, 2), /* JBL Quantum 600 */ REG_QUIRK_ENTRY(0x0ecb, 0x1f47, 2), /* JBL Quantum 800 */ + REG_QUIRK_ENTRY(0x0ecb, 0x1f4c, 2), /* JBL Quantum 400 */ REG_QUIRK_ENTRY(0x0ecb, 0x2039, 2), /* JBL Quantum 400 */ REG_QUIRK_ENTRY(0x0ecb, 0x203c, 2), /* JBL Quantum 600 */ REG_QUIRK_ENTRY(0x0ecb, 0x203e, 2), /* JBL Quantum 800 */ @@ -1887,6 +1888,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x21b4, 0x0081, /* AudioQuest DragonFly */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ + QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x30be, 0x0101, /* Schiit Hel */ diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index c39cc6851e2d..cfc1ea53978d 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -668,14 +668,15 @@ static void i_usx2y_04int(struct urb *urb) static int usx2y_rate_set(struct usx2ydev *usx2y, int rate) { - int err = 0, i; - struct snd_usx2y_urb_seq *us = NULL; - int *usbdata = NULL; - const struct s_c2 *ra = rate == 48000 ? setrate_48000 : setrate_44100; + int err = 0, i; + struct snd_usx2y_urb_seq *us = NULL; + int *usbdata = NULL; + const struct s_c2 *ra = rate == 48000 ? setrate_48000 : setrate_44100; struct urb *urb; if (usx2y->rate != rate) { - us = kzalloc(sizeof(*us) + sizeof(struct urb *) * NOOF_SETRATE_URBS, GFP_KERNEL); + us = kzalloc(struct_size(us, urb, NOOF_SETRATE_URBS), + GFP_KERNEL); if (!us) { err = -ENOMEM; goto cleanup; diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c index f88c8f29cbd8..aca2dc1989ba 100644 --- a/sound/virtio/virtio_pcm_msg.c +++ b/sound/virtio/virtio_pcm_msg.c @@ -20,7 +20,7 @@ struct virtio_pcm_msg { struct virtio_snd_pcm_xfer xfer; struct virtio_snd_pcm_status status; size_t length; - struct scatterlist sgs[0]; + struct scatterlist sgs[]; }; /** @@ -146,8 +146,7 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, int sg_num = virtsnd_pcm_sg_num(data, period_bytes); struct virtio_pcm_msg *msg; - msg = kzalloc(sizeof(*msg) + sizeof(*msg->sgs) * (sg_num + 2), - GFP_KERNEL); + msg = kzalloc(struct_size(msg, sgs, sg_num + 2), GFP_KERNEL); if (!msg) return -ENOMEM; |