[PATCH 00/12] Add support for the Cirrus Logic CS48L32 audio codecs
The CS48L32 is a high-performance low-power audio DSP for smartphones and other portable audio devices. It has various digital audio I/O, a programmable Halo Core DSP, fixed-function audio processors, configurable GPIO and microphone bias regulators.
The CS48L31 and CS48L33 were derivatives of the CS48L32.
Piotr Stankiewicz (2): mfd: cs48l32: Add support for CS48L31/32/33 codecs pinctrl: cirrus: Add support for CS48L31/32/33 codecs
Richard Fitzgerald (9): dt-bindings: mfd: Add Cirrus Logic CS48L32 audio codec mfd: cs48l32: Add register definitions for Cirrus Logic CS48L31/32/33 dt-bindings: pinctrl: Add Cirrus Logic CS48L31/32/33 regulator: arizona-micsupp: Don't hardcode use of ARIZONA defines regulator: arizona-micsupp: Don't use a common regulator name regulator: arizona-micsupp: Support Cirrus Logic CS48L31/32/33 irqchip: cirrus: Add driver for Cirrus Logic CS48L31/32/33 codecs ASoC: wm_adsp: Allow client to hook into pre_run callback dt-bindings: sound: Add Cirrus Logic CS48L31/32/33 codecs
Stuart Henderson (1): ASoC: cs48l32: Add codec driver for Cirrus Logic CS48L31/32/33
.../bindings/mfd/cirrus,cs48l32.yaml | 166 + .../bindings/pinctrl/cirrus,cs48l32.yaml | 98 + .../bindings/sound/cirrus,cs48l32.yaml | 96 + MAINTAINERS | 12 +- drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-cirrus-cs48l32.c | 281 ++ drivers/irqchip/irq-cirrus-cs48l32.h | 74 + drivers/mfd/Kconfig | 13 + drivers/mfd/Makefile | 2 + drivers/mfd/cs48l32-tables.c | 541 ++++ drivers/mfd/cs48l32.c | 434 +++ drivers/mfd/cs48l32.h | 28 + drivers/pinctrl/cirrus/Kconfig | 5 + drivers/pinctrl/cirrus/Makefile | 2 + drivers/pinctrl/cirrus/pinctrl-cs48l32.c | 932 ++++++ drivers/pinctrl/cirrus/pinctrl-cs48l32.h | 62 + drivers/regulator/Kconfig | 8 +- drivers/regulator/arizona-micsupp.c | 78 +- include/dt-bindings/sound/cs48l32.h | 25 + include/linux/irqchip/irq-cirrus-cs48l32.h | 101 + include/linux/mfd/cs48l32/core.h | 49 + include/linux/mfd/cs48l32/registers.h | 509 +++ include/sound/cs48l32.h | 89 + sound/soc/codecs/Kconfig | 9 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cs48l32-core.c | 2782 +++++++++++++++++ sound/soc/codecs/cs48l32.c | 1211 +++++++ sound/soc/codecs/cs48l32.h | 386 +++ sound/soc/codecs/wm_adsp.c | 11 + sound/soc/codecs/wm_adsp.h | 1 + 31 files changed, 7997 insertions(+), 14 deletions(-) create mode 100644 Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml create mode 100644 Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml create mode 100644 drivers/irqchip/irq-cirrus-cs48l32.c create mode 100644 drivers/irqchip/irq-cirrus-cs48l32.h create mode 100644 drivers/mfd/cs48l32-tables.c create mode 100644 drivers/mfd/cs48l32.c create mode 100644 drivers/mfd/cs48l32.h create mode 100644 drivers/pinctrl/cirrus/pinctrl-cs48l32.c create mode 100644 drivers/pinctrl/cirrus/pinctrl-cs48l32.h create mode 100644 include/dt-bindings/sound/cs48l32.h create mode 100644 include/linux/irqchip/irq-cirrus-cs48l32.h create mode 100644 include/linux/mfd/cs48l32/core.h create mode 100644 include/linux/mfd/cs48l32/registers.h create mode 100644 include/sound/cs48l32.h create mode 100644 sound/soc/codecs/cs48l32-core.c create mode 100644 sound/soc/codecs/cs48l32.c create mode 100644 sound/soc/codecs/cs48l32.h
The CS48L32 has multiple digital and analog audio I/O, a high-performance low-power programmable audio DSP, and a variety of power-efficient fixed-function audio processors, with digital mixing and routing.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- .../bindings/mfd/cirrus,cs48l32.yaml | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
diff --git a/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..d128600c0b72 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml @@ -0,0 +1,166 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cirrus Logic CS48L32 audio CODECs Multi-Functional Device + +maintainers: + - patches@opensource.cirrus.com + +description: | + The CS48L32 is an audio SoC with extensive digital capabilities + and a range of digital and analogue I/O. + + See also the child driver bindings in: + + bindings/pinctrl/cirrus,cs48l32.yaml + bindings/regulator/wlf,arizona.yaml + bindings/sound/cirrus,cs48l32.yaml + +allOf: + - $ref: /schemas/pinctrl/cirrus,cs48l32.yaml# + - $ref: /schemas/regulator/wlf,arizona.yaml# + - $ref: /schemas/sound/cirrus,cs48l32.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - cirrus,cs48l31 + - cirrus,cs48l32 + - cirrus,cs48l33 + + reg: + maxItems: 1 + + gpio-controller: true + + '#gpio-cells': + description: + The first cell is the pin number. The second cell is reserved for + future use and must be zero + const: 2 + + interrupt-controller: true + + '#interrupt-cells': + description: + The first cell is the IRQ number. + The second cell is the flags, encoded as the trigger masks from + bindings/interrupt-controller/interrupts.txt + const: 2 + + interrupts: + maxItems: 1 + + reset-gpios: + description: + One entry specifying the GPIO controlling /RESET. As defined in + bindings/gpio.txt. Although optional, it is strongly recommended + to use a hardware reset. + maxItems: 1 + + clocks: + description: + Should reference the clocks supplied on MCLK1 + minItems: 1 + maxItems: 1 + + clock-names: + description: | + Must be "mclk1" + const: mclk1 + + VDD_A-supply: + description: + Analogue power supply. + + VDD_D-supply: + description: + Digital core power supply. + + VDD_IO-supply: + description: + Digital buffer (I/O) supply. + + VDD_CP-supply: + description: + Charge pump power supply. + + VOUT_MIC-supply: + description: + Microphone power supply, normally supplied internally. + + pinctrl-0: + description: + A phandle to the node containing the subnodes containing pinctrl + configurations. + + pinctrl-1: + description: + A phandle to the node containing the subnodes containing pinctrl + configurations. + + pinctrl-names: + items: + - const: init + - const: default + +required: + - compatible + - interrupt-parent + - interrupts + - VDD_A-supply + - VDD_D-supply + - VDD_IO-supply + - VDD_CP-supply + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/sound/cs48l32.h> + &spi1 { + cs48l32: cs48l32@1 { + compatible = "cirrus,cs48l32"; + reg = <0x1>; + + spi-max-frequency = <25000000>; + + interrupts = <56 8>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&gpio0>; + gpio-controller; + #gpio-cells = <2>; + #sound-dai-cells = <1>; + + VDD_A-supply = <®ulator_1v8>; + VDD_D-supply = <&lochnagar_1v2>; + VDD_IO-supply = <®ulator_1v8>; + VDD_CP-supply = <®ulator_1v8>; + + reset-gpios = <&gpio 0 0>; + + pinctrl-names = "default"; + pinctrl-0 = <&cs48l32_defaults>; + + cs48l32_pinctrl: pinctrl { + compatible = "cirrus,cs48l32-pinctrl"; + + cs48l32_defaults: defaults { + asp1 { + groups = "asp1"; + function = "asp1"; + bias-bus-hold; + }; + asp2 { + groups = "asp2"; + function = "asp2"; + bias-bus-hold; + }; + }; + }; + };
On Wed, 09 Nov 2022 16:53:20 +0000, Richard Fitzgerald wrote:
The CS48L32 has multiple digital and analog audio I/O, a high-performance low-power programmable audio DSP, and a variety of power-efficient fixed-function audio processors, with digital mixing and routing.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/mfd/cirrus,cs48l32.yaml | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check' on your patch (DT_CHECKER_FLAGS is new in v5.13):
yamllint warnings/errors: ./Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml:107:7: [warning] wrong indentation: expected 4 but found 6 (indentation) ./Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml:108:7: [warning] wrong indentation: expected 8 but found 6 (indentation)
dtschema/dtc warnings/errors: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml: properties:clocks: 'oneOf' conditional failed, one must be fixed: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml: properties:clocks: 'anyOf' conditional failed, one must be fixed: 'minItems' is not one of ['maxItems', 'description', 'deprecated'] hint: Only "maxItems" is required for a single entry if there are no constraints defined for the values. 'minItems' is not one of ['description', 'deprecated', 'const', 'enum', 'minimum', 'maximum', 'multipleOf', 'default', '$ref', 'oneOf'] 'maxItems' is not one of ['description', 'deprecated', 'const', 'enum', 'minimum', 'maximum', 'multipleOf', 'default', '$ref', 'oneOf'] 1 is less than the minimum of 2 hint: Arrays must be described with a combination of minItems/maxItems/items hint: cell array properties must define how many entries and what the entries are when there is more than one entry. from schema $id: http://devicetree.org/meta-schemas/clocks.yaml# 'minItems' is not one of ['type', 'description', 'dependencies', 'dependentRequired', 'dependentSchemas', 'properties', 'patternProperties', 'additionalProperties', 'unevaluatedProperties', 'deprecated', 'required', 'allOf', 'anyOf', 'oneOf', '$ref'] 'maxItems' is not one of ['type', 'description', 'dependencies', 'dependentRequired', 'dependentSchemas', 'properties', 'patternProperties', 'additionalProperties', 'unevaluatedProperties', 'deprecated', 'required', 'allOf', 'anyOf', 'oneOf', '$ref'] 'type' is a required property hint: DT nodes ("object" type in schemas) can only use a subset of json-schema keywords from schema $id: http://devicetree.org/meta-schemas/clocks.yaml# ./Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml: Unable to find schema file matching $id: http://devicetree.org/schemas/pinctrl/cirrus,cs48l32.yaml Documentation/devicetree/bindings/mfd/cirrus,cs48l32.example.dts:21:18: fatal error: dt-bindings/sound/cs48l32.h: No such file or directory 21 | #include <dt-bindings/sound/cs48l32.h> | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ compilation terminated. make[1]: *** [scripts/Makefile.lib:406: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.example.dtb] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:1492: dt_binding_check] Error 2
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/patch/
This check can fail if there are any dependencies. The base for a patch series is generally the most recent rc1.
If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date:
pip3 install dtschema --upgrade
Please check and re-submit.
On 09/11/2022 17:53, Richard Fitzgerald wrote:
The CS48L32 has multiple digital and analog audio I/O, a high-performance low-power programmable audio DSP, and a variety of power-efficient fixed-function audio processors, with digital mixing and routing.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/mfd/cirrus,cs48l32.yaml | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
diff --git a/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..d128600c0b72 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml @@ -0,0 +1,166 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Cirrus Logic CS48L32 audio CODECs Multi-Functional Device
+maintainers:
- patches@opensource.cirrus.com
+description: |
- The CS48L32 is an audio SoC with extensive digital capabilities
- and a range of digital and analogue I/O.
- See also the child driver bindings in:
- bindings/pinctrl/cirrus,cs48l32.yaml
- bindings/regulator/wlf,arizona.yaml
- bindings/sound/cirrus,cs48l32.yaml
+allOf:
- $ref: /schemas/pinctrl/cirrus,cs48l32.yaml#
There is no such file.
Best regards, Krzysztof
This adds the register definitions for the CS48L31/32/33.
The CS48L31/32/33 audio codecs are multi-function devices containing gpios, irq controller and regulators in addition to the core audio functionality.
Signed-off-by: Piotr Stankiewicz piotrs@opensource.cirrus.com Signed-off-by: Stuart Henderson stuarth@opensource.cirrus.com Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- include/linux/mfd/cs48l32/registers.h | 509 ++++++++++++++++++++++++++ 1 file changed, 509 insertions(+) create mode 100644 include/linux/mfd/cs48l32/registers.h
diff --git a/include/linux/mfd/cs48l32/registers.h b/include/linux/mfd/cs48l32/registers.h new file mode 100644 index 000000000000..b8125950d74c --- /dev/null +++ b/include/linux/mfd/cs48l32/registers.h @@ -0,0 +1,509 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definitions for Cirrus Logic CS48L32 + * + * Copyright (C) 2017-2018, 2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef CS48L32_REGISTERS_H +#define CS48L32_REGISTERS_H + +/* Register Addresses. */ +#define CS48L32_DEVID 0x0 +#define CS48L32_REVID 0x4 +#define CS48L32_OTPID 0x10 +#define CS48L32_SFT_RESET 0x20 +#define CS48L32_CTRL_IF_DEBUG3 0xA8 +#define CS48L32_MCU_CTRL1 0x804 +#define CS48L32_GPIO1_CTRL1 0xc08 +#define CS48L32_GPIO16_CTRL1 0xc44 +#define CS48L32_OUTPUT_SYS_CLK 0x1020 +#define CS48L32_AUXPDM_CTRL 0x1044 +#define CS48L32_AUXPDM_CTRL2 0x105c +#define CS48L32_CLOCK32K 0x1400 +#define CS48L32_SYSTEM_CLOCK1 0x1404 +#define CS48L32_SYSTEM_CLOCK2 0x1408 +#define CS48L32_SAMPLE_RATE1 0x1420 +#define CS48L32_SAMPLE_RATE2 0x1424 +#define CS48L32_SAMPLE_RATE3 0x1428 +#define CS48L32_SAMPLE_RATE4 0x142c +#define CS48L32_DSP_CLOCK1 0x1510 +#define CS48L32_FLL1_CONTROL1 0x1c00 +#define CS48L32_FLL1_CONTROL5 0x1c10 +#define CS48L32_FLL1_CONTROL6 0x1c14 +#define CS48L32_FLL1_GPIO_CLOCK 0x1ca0 +#define CS48L32_CHARGE_PUMP1 0x2000 +#define CS48L32_LDO2_CTRL1 0x2408 +#define CS48L32_MICBIAS_CTRL1 0x2410 +#define CS48L32_MICBIAS_CTRL5 0x2418 +#define CS48L32_IRQ1_CTRL_AOD 0x2710 +#define CS48L32_AOD_PAD_CTRL 0x2718 +#define CS48L32_INPUT_CONTROL 0x4000 +#define CS48L32_INPUT_STATUS 0x4004 +#define CS48L32_INPUT_RATE_CONTROL 0x4008 +#define CS48L32_INPUT_CONTROL2 0x400c +#define CS48L32_INPUT_CONTROL3 0x4014 +#define CS48L32_INPUT1_CONTROL1 0x4020 +#define CS48L32_IN1L_CONTROL1 0x4024 +#define CS48L32_IN1L_CONTROL2 0x4028 +#define CS48L32_IN1R_CONTROL1 0x4044 +#define CS48L32_IN1R_CONTROL2 0x4048 +#define CS48L32_INPUT2_CONTROL1 0x4060 +#define CS48L32_IN2L_CONTROL1 0x4064 +#define CS48L32_IN2L_CONTROL2 0x4068 +#define CS48L32_IN2R_CONTROL1 0x4084 +#define CS48L32_IN2R_CONTROL2 0x4088 +#define CS48L32_INPUT_HPF_CONTROL 0x4244 +#define CS48L32_INPUT_VOL_CONTROL 0x4248 +#define CS48L32_AUXPDM_CONTROL1 0x4300 +#define CS48L32_AUXPDM_CONTROL2 0x4304 +#define CS48L32_AUXPDM1_CONTROL1 0x4308 +#define CS48L32_AUXPDM2_CONTROL1 0x4310 +#define CS48L32_ADC1L_ANA_CONTROL1 0x4688 +#define CS48L32_ADC1R_ANA_CONTROL1 0x468c +#define CS48L32_ASP1_ENABLES1 0x6000 +#define CS48L32_ASP1_DATA_CONTROL5 0x6040 +#define CS48L32_ASP2_ENABLES1 0x6080 +#define CS48L32_ASP2_DATA_CONTROL5 0x60c0 +#define CS48L32_ASP1TX1_INPUT1 0x8200 +#define CS48L32_ASP1TX2_INPUT1 0x8210 +#define CS48L32_ASP1TX3_INPUT1 0x8220 +#define CS48L32_ASP1TX4_INPUT1 0x8230 +#define CS48L32_ASP1TX5_INPUT1 0x8240 +#define CS48L32_ASP1TX6_INPUT1 0x8250 +#define CS48L32_ASP1TX7_INPUT1 0x8260 +#define CS48L32_ASP1TX8_INPUT1 0x8270 +#define CS48L32_ASP1TX8_INPUT4 0x827c +#define CS48L32_ASP2TX1_INPUT1 0x8300 +#define CS48L32_ASP2TX2_INPUT1 0x8310 +#define CS48L32_ASP2TX3_INPUT1 0x8320 +#define CS48L32_ASP2TX4_INPUT1 0x8330 +#define CS48L32_ASP2TX4_INPUT4 0x833c +#define CS48L32_ISRC1INT1_INPUT1 0x8980 +#define CS48L32_ISRC1INT2_INPUT1 0x8990 +#define CS48L32_ISRC1INT3_INPUT1 0x89a0 +#define CS48L32_ISRC1INT4_INPUT1 0x89b0 +#define CS48L32_ISRC1DEC1_INPUT1 0x89c0 +#define CS48L32_ISRC1DEC2_INPUT1 0x89d0 +#define CS48L32_ISRC1DEC3_INPUT1 0x89e0 +#define CS48L32_ISRC1DEC4_INPUT1 0x89f0 +#define CS48L32_ISRC2INT1_INPUT1 0x8a00 +#define CS48L32_ISRC2INT2_INPUT1 0x8a10 +#define CS48L32_ISRC2DEC1_INPUT1 0x8a40 +#define CS48L32_ISRC2DEC2_INPUT1 0x8a50 +#define CS48L32_ISRC3INT1_INPUT1 0x8a80 +#define CS48L32_ISRC3INT2_INPUT1 0x8a90 +#define CS48L32_ISRC3DEC1_INPUT1 0x8ac0 +#define CS48L32_ISRC3DEC2_INPUT1 0x8ad0 +#define CS48L32_EQ1_INPUT1 0x8b80 +#define CS48L32_EQ2_INPUT1 0x8b90 +#define CS48L32_EQ3_INPUT1 0x8ba0 +#define CS48L32_EQ4_INPUT1 0x8bb0 +#define CS48L32_EQ4_INPUT4 0x8bbc +#define CS48L32_DRC1L_INPUT1 0x8c00 +#define CS48L32_DRC1R_INPUT1 0x8c10 +#define CS48L32_DRC1R_INPUT4 0x8c1c +#define CS48L32_DRC2L_INPUT1 0x8c20 +#define CS48L32_DRC2R_INPUT1 0x8c30 +#define CS48L32_DRC2R_INPUT4 0x8c3c +#define CS48L32_LHPF1_INPUT1 0x8c80 +#define CS48L32_LHPF1_INPUT4 0x8c8c +#define CS48L32_LHPF2_INPUT1 0x8c90 +#define CS48L32_LHPF2_INPUT4 0x8c9c +#define CS48L32_LHPF3_INPUT1 0x8ca0 +#define CS48L32_LHPF3_INPUT4 0x8cac +#define CS48L32_LHPF4_INPUT1 0x8cb0 +#define CS48L32_LHPF4_INPUT4 0x8cbc +#define CS48L32_DSP1RX1_INPUT1 0x9000 +#define CS48L32_DSP1RX2_INPUT1 0x9010 +#define CS48L32_DSP1RX3_INPUT1 0x9020 +#define CS48L32_DSP1RX4_INPUT1 0x9030 +#define CS48L32_DSP1RX5_INPUT1 0x9040 +#define CS48L32_DSP1RX6_INPUT1 0x9050 +#define CS48L32_DSP1RX7_INPUT1 0x9060 +#define CS48L32_DSP1RX8_INPUT1 0x9070 +#define CS48L32_DSP1RX8_INPUT4 0x907c +#define CS48L32_ISRC1_CONTROL1 0xa400 +#define CS48L32_ISRC1_CONTROL2 0xa404 +#define CS48L32_ISRC2_CONTROL1 0xa510 +#define CS48L32_ISRC2_CONTROL2 0xa514 +#define CS48L32_ISRC3_CONTROL1 0xa620 +#define CS48L32_ISRC3_CONTROL2 0xa624 +#define CS48L32_FX_SAMPLE_RATE 0xa800 +#define CS48L32_EQ_CONTROL1 0xa808 +#define CS48L32_EQ_CONTROL2 0xa80c +#define CS48L32_EQ1_GAIN1 0xa810 +#define CS48L32_EQ1_GAIN2 0xa814 +#define CS48L32_EQ1_BAND1_COEFF1 0xa818 +#define CS48L32_EQ1_BAND1_COEFF2 0xa81c +#define CS48L32_EQ1_BAND1_PG 0xa820 +#define CS48L32_EQ1_BAND2_COEFF1 0xa824 +#define CS48L32_EQ1_BAND2_COEFF2 0xa828 +#define CS48L32_EQ1_BAND2_PG 0xa82c +#define CS48L32_EQ1_BAND3_COEFF1 0xa830 +#define CS48L32_EQ1_BAND3_COEFF2 0xa834 +#define CS48L32_EQ1_BAND3_PG 0xa838 +#define CS48L32_EQ1_BAND4_COEFF1 0xa83c +#define CS48L32_EQ1_BAND4_COEFF2 0xa840 +#define CS48L32_EQ1_BAND4_PG 0xa844 +#define CS48L32_EQ1_BAND5_COEFF1 0xa848 +#define CS48L32_EQ1_BAND5_PG 0xa850 +#define CS48L32_EQ2_GAIN1 0xa854 +#define CS48L32_EQ2_GAIN2 0xa858 +#define CS48L32_EQ2_BAND1_COEFF1 0xa85c +#define CS48L32_EQ2_BAND1_COEFF2 0xa860 +#define CS48L32_EQ2_BAND1_PG 0xa864 +#define CS48L32_EQ2_BAND2_COEFF1 0xa868 +#define CS48L32_EQ2_BAND2_COEFF2 0xa86c +#define CS48L32_EQ2_BAND2_PG 0xa870 +#define CS48L32_EQ2_BAND3_COEFF1 0xa874 +#define CS48L32_EQ2_BAND3_COEFF2 0xa878 +#define CS48L32_EQ2_BAND3_PG 0xa87c +#define CS48L32_EQ2_BAND4_COEFF1 0xa880 +#define CS48L32_EQ2_BAND4_COEFF2 0xa884 +#define CS48L32_EQ2_BAND4_PG 0xa888 +#define CS48L32_EQ2_BAND5_COEFF1 0xa88c +#define CS48L32_EQ2_BAND5_PG 0xa894 +#define CS48L32_EQ3_GAIN1 0xa898 +#define CS48L32_EQ3_GAIN2 0xa89c +#define CS48L32_EQ3_BAND1_COEFF1 0xa8a0 +#define CS48L32_EQ3_BAND1_COEFF2 0xa8a4 +#define CS48L32_EQ3_BAND1_PG 0xa8a8 +#define CS48L32_EQ3_BAND2_COEFF1 0xa8ac +#define CS48L32_EQ3_BAND2_COEFF2 0xa8b0 +#define CS48L32_EQ3_BAND2_PG 0xa8b4 +#define CS48L32_EQ3_BAND3_COEFF1 0xa8b8 +#define CS48L32_EQ3_BAND3_COEFF2 0xa8bc +#define CS48L32_EQ3_BAND3_PG 0xa8c0 +#define CS48L32_EQ3_BAND4_COEFF1 0xa8c4 +#define CS48L32_EQ3_BAND4_COEFF2 0xa8c8 +#define CS48L32_EQ3_BAND4_PG 0xa8cc +#define CS48L32_EQ3_BAND5_COEFF1 0xa8d0 +#define CS48L32_EQ3_BAND5_PG 0xa8d8 +#define CS48L32_EQ4_GAIN1 0xa8dc +#define CS48L32_EQ4_GAIN2 0xa8e0 +#define CS48L32_EQ4_BAND1_COEFF1 0xa8e4 +#define CS48L32_EQ4_BAND1_COEFF2 0xa8e8 +#define CS48L32_EQ4_BAND1_PG 0xa8ec +#define CS48L32_EQ4_BAND2_COEFF1 0xa8f0 +#define CS48L32_EQ4_BAND2_COEFF2 0xa8f4 +#define CS48L32_EQ4_BAND2_PG 0xa8f8 +#define CS48L32_EQ4_BAND3_COEFF1 0xa8fc +#define CS48L32_EQ4_BAND3_COEFF2 0xa900 +#define CS48L32_EQ4_BAND3_PG 0xa904 +#define CS48L32_EQ4_BAND4_COEFF1 0xa908 +#define CS48L32_EQ4_BAND4_COEFF2 0xa90c +#define CS48L32_EQ4_BAND4_PG 0xa910 +#define CS48L32_EQ4_BAND5_COEFF1 0xa914 +#define CS48L32_EQ4_BAND5_PG 0xa91c +#define CS48L32_LHPF_CONTROL1 0xaa30 +#define CS48L32_LHPF_CONTROL2 0xaa34 +#define CS48L32_LHPF1_COEFF 0xaa38 +#define CS48L32_LHPF2_COEFF 0xaa3c +#define CS48L32_LHPF3_COEFF 0xaa40 +#define CS48L32_LHPF4_COEFF 0xaa44 +#define CS48L32_DRC1_CONTROL1 0xab00 +#define CS48L32_DRC1_CONTROL4 0xab0c +#define CS48L32_DRC2_CONTROL1 0xab14 +#define CS48L32_DRC2_CONTROL4 0xab20 +#define CS48L32_TONE_GENERATOR1 0xb000 +#define CS48L32_TONE_GENERATOR2 0xb004 +#define CS48L32_COMFORT_NOISE_GENERATOR 0xb400 +#define CS48L32_US_CONTROL 0xb800 +#define CS48L32_US1_CONTROL 0xb804 +#define CS48L32_US1_DET_CONTROL 0xb808 +#define CS48L32_US2_CONTROL 0xb814 +#define CS48L32_US2_DET_CONTROL 0xb818 +#define CS48L32_DSP1_XM_SRAM_IBUS_SETUP_0 0x1700c +#define CS48L32_DSP1_XM_SRAM_IBUS_SETUP_1 0x17010 +#define CS48L32_DSP1_XM_SRAM_IBUS_SETUP_24 0x1706c +#define CS48L32_DSP1_YM_SRAM_IBUS_SETUP_0 0x17070 +#define CS48L32_DSP1_YM_SRAM_IBUS_SETUP_1 0x17074 +#define CS48L32_DSP1_YM_SRAM_IBUS_SETUP_8 0x17090 +#define CS48L32_DSP1_PM_SRAM_IBUS_SETUP_0 0x17094 +#define CS48L32_DSP1_PM_SRAM_IBUS_SETUP_1 0x17098 +#define CS48L32_DSP1_PM_SRAM_IBUS_SETUP_7 0x170b0 +#define CS48L32_IRQ1_STATUS 0x18004 +#define CS48L32_IRQ1_EINT_1 0x18010 +#define CS48L32_IRQ1_EINT_2 0x18014 +#define CS48L32_IRQ1_EINT_5 0x18020 +#define CS48L32_IRQ1_EINT_6 0x18024 +#define CS48L32_IRQ1_EINT_7 0x18028 +#define CS48L32_IRQ1_EINT_9 0x18030 +#define CS48L32_IRQ1_EINT_11 0x18038 +#define CS48L32_IRQ1_STS_1 0x18090 +#define CS48L32_IRQ1_STS_6 0x180a4 +#define CS48L32_IRQ1_STS_11 0x180b8 +#define CS48L32_IRQ1_MASK_1 0x18110 +#define CS48L32_IRQ1_MASK_11 0x18138 +#define CS48L32_DSP1_XMEM_PACKED_0 0x2000000 +#define CS48L32_DSP1_XMEM_PACKED_147455 0x208fffc +#define CS48L32_DSP1_SYS_INFO_ID 0x25e0000 +#define CS48L32_DSP1_AHBM_WINDOW_DEBUG_1 0x25e2044 +#define CS48L32_DSP1_XMEM_UNPACKED24_0 0x2800000 +#define CS48L32_DSP1_XMEM_UNPACKED24_196607 0x28bfffc +#define CS48L32_DSP1_CLOCK_FREQ 0x2b80000 +#define CS48L32_DSP1_SAMPLE_RATE_TX8 0x2b802b8 +#define CS48L32_DSP1_SCRATCH1 0x2b805c0 +#define CS48L32_DSP1_SCRATCH4 0x2b805d8 +#define CS48L32_DSP1_CCM_CORE_CONTROL 0x2bc1000 +#define CS48L32_DSP1_STREAM_ARB_RESYNC_MSK1 0x2bc5a00 +#define CS48L32_DSP1_YMEM_PACKED_0 0x2c00000 +#define CS48L32_DSP1_YMEM_PACKED_49151 0x2c2fffc +#define CS48L32_DSP1_YMEM_UNPACKED24_0 0x3400000 +#define CS48L32_DSP1_YMEM_UNPACKED24_65535 0x343fffc +#define CS48L32_DSP1_PMEM_0 0x3800000 +#define CS48L32_DSP1_PMEM_71679 0x3845ffc + +/* (0x0) DEVID */ +#define CS48L32_DEVID_MASK 0x00ffffff +#define CS48L32_DEVID_SHIFT 0 + +/* (0x4) REVID */ +#define CS48L32_AREVID_MASK 0x000000f0 +#define CS48L32_AREVID_SHIFT 4 +#define CS48L32_MTLREVID_MASK 0x0000000f +#define CS48L32_MTLREVID_SHIFT 0 + +/* (0x10) OTPID */ +#define CS48L32_OTPID_MASK 0x0000000f + +/* (0x0804) MCU_CTRL1 */ +#define CS48L32_MCU_STS_MASK 0x0000ff00 +#define CS48L32_MCU_STS_SHIFT 8 + +/* (0x1020) OUTPUT_SYS_CLK */ +#define CS48L32_OPCLK_EN_SHIFT 15 +#define CS48L32_OPCLK_DIV_MASK 0x000000f8 +#define CS48L32_OPCLK_DIV_SHIFT 3 +#define CS48L32_OPCLK_SEL_MASK 0x00000007 + +/* (0x105c) AUXPDM_CTRL2 */ +#define CS48L32_AUXPDMDAT2_SRC_SHIFT 4 +#define CS48L32_AUXPDMDAT1_SRC_SHIFT 0 + +/* (0x1400) CLOCK32K */ +#define CS48L32_CLK_32K_EN_MASK 0x00000040 +#define CS48L32_CLK_32K_SRC_MASK 0x00000003 + +/* (0x1404) SYSTEM_CLOCK1 */ +#define CS48L32_SYSCLK_FRAC_MASK 0x00008000 +#define CS48L32_SYSCLK_FREQ_MASK 0x00000700 +#define CS48L32_SYSCLK_FREQ_SHIFT 8 +#define CS48L32_SYSCLK_EN_SHIFT 6 +#define CS48L32_SYSCLK_SRC_MASK 0x0000001f +#define CS48L32_SYSCLK_SRC_SHIFT 0 + +/* (0x1408) SYSTEM_CLOCK2 */ +#define CS48L32_SYSCLK_FREQ_STS_MASK 0x00000700 +#define CS48L32_SYSCLK_FREQ_STS_SHIFT 8 + +/* (0x1420) SAMPLE_RATE1 */ +#define CS48L32_SAMPLE_RATE_1_MASK 0x0000001f +#define CS48L32_SAMPLE_RATE_1_SHIFT 0 + +/* (0x1510) DSP_CLOCK1 */ +#define CS48L32_DSP_CLK_FREQ_MASK 0xffff0000 +#define CS48L32_DSP_CLK_FREQ_SHIFT 16 + +/* (0x1c00) FLL_CONTROL1 */ +#define CS48L32_FLL_CTRL_UPD_MASK 0x00000004 +#define CS48L32_FLL_HOLD_MASK 0x00000002 +#define CS48L32_FLL_EN_MASK 0x00000001 + +/* (0x1c04) FLL_CONTROL2 */ +#define CS48L32_FLL_LOCKDET_THR_MASK 0xf0000000 +#define CS48L32_FLL_LOCKDET_THR_SHIFT 28 +#define CS48L32_FLL_LOCKDET_MASK 0x08000000 +#define CS48L32_FLL_PHASEDET_MASK 0x00400000 +#define CS48L32_FLL_PHASEDET_SHIFT 22 +#define CS48L32_FLL_REFCLK_DIV_MASK 0x00030000 +#define CS48L32_FLL_REFCLK_DIV_SHIFT 16 +#define CS48L32_FLL_REFCLK_SRC_MASK 0x0000f000 +#define CS48L32_FLL_REFCLK_SRC_SHIFT 12 +#define CS48L32_FLL_N_MASK 0x000003ff +#define CS48L32_FLL_N_SHIFT 0 + +/* (0x1c08) FLL_CONTROL3 */ +#define CS48L32_FLL_LAMBDA_MASK 0xffff0000 +#define CS48L32_FLL_LAMBDA_SHIFT 16 +#define CS48L32_FLL_THETA_MASK 0x0000ffff +#define CS48L32_FLL_THETA_SHIFT 0 + +/* (0x1c0c) FLL_CONTROL4 */ +#define CS48L32_FLL_FD_GAIN_COARSE_SHIFT 16 +#define CS48L32_FLL_HP_MASK 0x00003000 +#define CS48L32_FLL_HP_SHIFT 12 +#define CS48L32_FLL_FB_DIV_MASK 0x000003ff +#define CS48L32_FLL_FB_DIV_SHIFT 0 + +/* (0x1c10) FLL_CONTROL5 */ +#define CS48L32_FLL_FRC_INTEG_UPD_MASK 0x00008000 + +/* (0x2000) CHARGE_PUMP1 */ +#define CS48L32_CP2_BYPASS_MASK 0x00000002 +#define CS48L32_CP2_EN_MASK 0x00000001 + +/* (0x2408) LDO2_CTRL1 */ +#define CS48L32_LDO2_VSEL_MASK 0x000007e0 + +/* (0x2410) MICBIAS_CTRL1 */ +#define CS48L32_MICB1_EN_SHIFT 0 + +/* (0x2418) MICBIAS_CTRL5 */ +#define CS48L32_MICB1C_EN_SHIFT 8 +#define CS48L32_MICB1B_EN_SHIFT 4 +#define CS48L32_MICB1A_EN_SHIFT 0 + +/* (0x2710) IRQ1_CTRL_AOD */ +#define CS48L32_IRQ_POL_MASK 0x00000400 + +/* (0x4000) INPUT_CONTROL */ +#define CS48L32_IN2L_EN_SHIFT 3 +#define CS48L32_IN2R_EN_SHIFT 2 +#define CS48L32_IN1L_EN_SHIFT 1 +#define CS48L32_IN1R_EN_SHIFT 0 + +/* (0x400c) INPUT_CONTROL2 */ +#define CS48L32_PDM_FLLCLK_SRC_MASK 0x0000000f +#define CS48L32_PDM_FLLCLK_SRC_SHIFT 0 + +/* (0x4014) INPUT_CONTROL3 */ +#define CS48L32_IN_VU 0x20000000 +#define CS48L32_IN_VU_MASK 0x20000000 +#define CS48L32_IN_VU_SHIFT 29 +#define CS48L32_IN_VU_WIDTH 1 + +/* (0x4020) INPUT1_CONTROL1 */ +#define CS48L32_IN1_OSR_SHIFT 16 +#define CS48L32_IN1_PDM_SUP_MASK 0x00000300 +#define CS48L32_IN1_PDM_SUP_SHIFT 8 +#define CS48L32_IN1_MODE_SHIFT 0 + +/* + * (0x4024) IN1L_CONTROL1 + * (0x4044) IN1R_CONTROL1 + */ +#define CS48L32_INx_SRC_MASK 0x30000000 +#define CS48L32_INx_SRC_SHIFT 28 +#define CS48L32_INx_RATE_MASK 0x0000f800 +#define CS48L32_INx_RATE_SHIFT 11 +#define CS48L32_INx_HPF_SHIFT 2 +#define CS48L32_INx_LP_MODE_SHIFT 0 + +/* + * (0x4028) IN1L_CONTROL2 + * (0x4048) IN1R_CONTROL2 + */ +#define CS48L32_INx_MUTE_MASK 0x10000000 +#define CS48L32_INx_VOL_SHIFT 16 +#define CS48L32_INx_PGA_VOL_SHIFT 1 + +/* (0x4244) INPUT_HPF_CONTROL */ +#define CS48L32_IN_HPF_CUT_SHIFT 0 + +/* (0x4248) INPUT_VOL_CONTROL */ +#define CS48L32_IN_VD_RAMP_SHIFT 4 +#define CS48L32_IN_VI_RAMP_SHIFT 0 + +/* (0x4308) AUXPDM1_CONTROL1 */ +#define CS48L32_AUXPDM1_FREQ_SHIFT 16 +#define CS48L32_AUXPDM1_SRC_MASK 0x00000f00 +#define CS48L32_AUXPDM1_SRC_SHIFT 8 + +/* (0x4688) ADC1L_ANA_CONTROL1 */ +/* (0x468c) ADC1R_ANA_CONTROL1 */ +#define CS48L32_ADC1x_INT_ENA_FRC_MASK 0x00000002 + +/* (0x6004) ASPn_CONTROL1 */ +#define CS48L32_ASP_RATE_MASK 0x00001f00 +#define CS48L32_ASP_RATE_SHIFT 8 +#define CS48L32_ASP_BCLK_FREQ_MASK 0x0000003f + +/* (0x6008) ASPn_CONTROL2 */ +#define CS48L32_ASP_RX_WIDTH_MASK 0xff000000 +#define CS48L32_ASP_RX_WIDTH_SHIFT 24 +#define CS48L32_ASP_TX_WIDTH_MASK 0x00ff0000 +#define CS48L32_ASP_TX_WIDTH_SHIFT 16 +#define CS48L32_ASP_FMT_MASK 0x00000700 +#define CS48L32_ASP_FMT_SHIFT 8 +#define CS48L32_ASP_BCLK_INV_MASK 0x00000040 +#define CS48L32_ASP_BCLK_MSTR_MASK 0x00000010 +#define CS48L32_ASP_FSYNC_INV_MASK 0x00000004 +#define CS48L32_ASP_FSYNC_MSTR_MASK 0x00000001 + +/* (0x6030) ASPn_DATA_CONTROL1 */ +#define CS48L32_ASP_TX_WL_MASK 0x0000003f + +/* (0x6040) ASPn_DATA_CONTROL5 */ +#define CS48L32_ASP_RX_WL_MASK 0x0000003f + +/* (0x82xx - 0x90xx) *_INPUT[1-4] */ +#define CS48L32_MIXER_VOL_MASK 0x00FE0000 +#define CS48L32_MIXER_VOL_SHIFT 17 +#define CS48L32_MIXER_VOL_WIDTH 7 +#define CS48L32_MIXER_SRC_MASK 0x000001ff +#define CS48L32_MIXER_SRC_SHIFT 0 +#define CS48L32_MIXER_SRC_WIDTH 9 + +/* (0xa400) ISRC1_CONTROL1 */ +#define CS48L32_ISRC1_FSL_MASK 0xf8000000 +#define CS48L32_ISRC1_FSL_SHIFT 27 +#define CS48L32_ISRC1_FSH_MASK 0x0000f800 +#define CS48L32_ISRC1_FSH_SHIFT 11 + +/* (0xa404) ISRC1_CONTROL2 */ +#define CS48L32_ISRC1_INT4_EN_SHIFT 11 +#define CS48L32_ISRC1_INT3_EN_SHIFT 10 +#define CS48L32_ISRC1_INT2_EN_SHIFT 9 +#define CS48L32_ISRC1_INT1_EN_SHIFT 8 +#define CS48L32_ISRC1_DEC4_EN_SHIFT 3 +#define CS48L32_ISRC1_DEC3_EN_SHIFT 2 +#define CS48L32_ISRC1_DEC2_EN_SHIFT 1 +#define CS48L32_ISRC1_DEC1_EN_SHIFT 0 + +/* (0xa800) FX_SAMPLE_RATE */ +#define CS48L32_FX_RATE_MASK 0x0000f800 +#define CS48L32_FX_RATE_SHIFT 11 + +/* (0xab00) DRC1_CONTROL1 */ +#define CS48L32_DRC1L_EN_SHIFT 1 +#define CS48L32_DRC1R_EN_SHIFT 0 + +/* (0xb400) Comfort_Noise_Generator */ +#define CS48L32_NOISE_GEN_RATE_MASK 0x0000f800 +#define CS48L32_NOISE_GEN_RATE_SHIFT 11 +#define CS48L32_NOISE_GEN_EN_SHIFT 5 +#define CS48L32_NOISE_GEN_GAIN_SHIFT 0 + +/* (0xb800) US_CONTROL */ +#define CS48L32_US1_DET_EN_SHIFT 8 + +/* (0xb804) US1_CONTROL */ +#define CS48L32_US1_RATE_MASK 0xf8000000 +#define CS48L32_US1_RATE_SHIFT 27 +#define CS48L32_US1_GAIN_SHIFT 12 +#define CS48L32_US1_SRC_MASK 0x00000f00 +#define CS48L32_US1_SRC_SHIFT 8 +#define CS48L32_US1_FREQ_MASK 0x00000070 +#define CS48L32_US1_FREQ_SHIFT 4 + +/* (0xb808) US1_DET_CONTROL */ +#define CS48L32_US1_DET_DCY_SHIFT 28 +#define CS48L32_US1_DET_HOLD_SHIFT 24 +#define CS48L32_US1_DET_NUM_SHIFT 20 +#define CS48L32_US1_DET_THR_SHIFT 16 +#define CS48L32_US1_DET_LPF_CUT_SHIFT 5 +#define CS48L32_US1_DET_LPF_SHIFT 4 + +/* (0x18004) IRQ1_STATUS */ +#define CS48L32_IRQ1_STS_MASK 0x00000001 + +/* (0x18014) IRQ1_EINT_2 */ +#define CS48L32_BOOT_DONE_EINT1_MASK 0x00000008 + +/* (0x180a4) IRQ1_STS_6 */ +#define CS48L32_FLL1_LOCK_STS1_MASK 0x00000001 + +#endif
From: Piotr Stankiewicz piotrs@opensource.cirrus.com
The CS48L31/32/33 audio codecs are multi-function devices containing gpios, irq controller and regulators in addition to the core audio functionality.
Signed-off-by: Piotr Stankiewicz piotrs@opensource.cirrus.com Signed-off-by: Qi Zhou qi.zhou@cirrus.com Signed-off-by: Stuart Henderson stuarth@opensource.cirrus.com Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- MAINTAINERS | 5 +- drivers/mfd/Kconfig | 13 + drivers/mfd/Makefile | 2 + drivers/mfd/cs48l32-tables.c | 541 +++++++++++++++++++++++++++++++ drivers/mfd/cs48l32.c | 434 +++++++++++++++++++++++++ drivers/mfd/cs48l32.h | 28 ++ include/linux/mfd/cs48l32/core.h | 49 +++ 7 files changed, 1071 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/cs48l32-tables.c create mode 100644 drivers/mfd/cs48l32.c create mode 100644 drivers/mfd/cs48l32.h create mode 100644 include/linux/mfd/cs48l32/core.h
diff --git a/MAINTAINERS b/MAINTAINERS index 3f94ed38089b..f1d696f29f11 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5001,7 +5001,7 @@ F: include/dt-bindings/pinctrl/lochnagar.h F: include/linux/mfd/lochnagar* F: sound/soc/codecs/lochnagar-sc.c
-CIRRUS LOGIC MADERA CODEC DRIVERS +CIRRUS LOGIC MADERA/CS48L32 CODEC DRIVERS M: Charles Keepax ckeepax@opensource.cirrus.com M: Richard Fitzgerald rf@opensource.cirrus.com L: alsa-devel@alsa-project.org (moderated for non-subscribers) @@ -5009,16 +5009,19 @@ 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: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/mfd/cirrus,madera.yaml F: Documentation/devicetree/bindings/pinctrl/cirrus,madera.yaml F: Documentation/devicetree/bindings/sound/cirrus,madera.yaml F: drivers/gpio/gpio-madera* F: drivers/irqchip/irq-madera* F: drivers/mfd/cs47l* +F: drivers/mfd/cs48l* F: drivers/mfd/madera* F: drivers/pinctrl/cirrus/* F: include/dt-bindings/sound/madera* F: include/linux/irqchip/irq-madera* +F: include/linux/mfd/cs48l32/* F: include/linux/mfd/madera/* F: include/sound/madera* F: sound/soc/codecs/cs47l* diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6653d03e0fe3..2be52ba23c7a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -300,6 +300,19 @@ config MFD_CS47L92 help Support for Cirrus Logic CS42L92, CS47L92 and CS47L93 Smart Codecs
+config MFD_CS48L32 + bool "Cirrus Logic CS48L31/32/33" + depends on SPI_MASTER + select MFD_CORE + select REGMAP + select REGMAP_SPI + select REGMAP_IRQ + select CIRRUS_CS48L32_IRQ + select PINCTRL + select PINCTRL_CS48L32 + help + Support for Cirrus Logic CS48L31, CS48L32 and CS48L33 Smart Codecs. + config MFD_ASIC3 bool "Compaq ASIC3" depends on GPIOLIB diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4dd479212b3a..4edbeb9b7a31 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -91,6 +91,8 @@ obj-$(CONFIG_MFD_MADERA) += madera.o obj-$(CONFIG_MFD_MADERA_I2C) += madera-i2c.o obj-$(CONFIG_MFD_MADERA_SPI) += madera-spi.o
+obj-$(CONFIG_MFD_CS48L32) += cs48l32.o cs48l32-tables.o + obj-$(CONFIG_TPS6105X) += tps6105x.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_TPS6507X) += tps6507x.o diff --git a/drivers/mfd/cs48l32-tables.c b/drivers/mfd/cs48l32-tables.c new file mode 100644 index 000000000000..5dab9753deb0 --- /dev/null +++ b/drivers/mfd/cs48l32-tables.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Regmap tables for Cirrus Logic CS48L32 audio codec. + * + * Copyright (C) 2018, 2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> + +#include "cs48l32.h" + +static const struct reg_sequence cs48l32_reva_patch[] = { + { 0x00001044, 0x0005000f }, + { 0x00001c34, 0x000037e8 }, + { 0x000046d8, 0x00000fe0 }, +}; + +int cs48l32_patch(struct cs48l32_mfd *cs48l32) +{ + int ret; + + ret = regmap_register_patch(cs48l32->regmap, cs48l32_reva_patch, + ARRAY_SIZE(cs48l32_reva_patch)); + if (ret < 0) + dev_err(cs48l32->dev, "Error applying patch: %d\n", ret); + + return ret; +} + +static const struct reg_default cs48l32_reg_default[] = { + { 0x00000c08, 0xe1000001 }, /* GPIO1_CTRL1 */ + { 0x00000c0c, 0xe1000001 }, /* GPIO2_CTRL1 */ + { 0x00000c10, 0xe1000001 }, /* GPIO3_CTRL1 */ + { 0x00000c14, 0xe1000001 }, /* GPIO4_CTRL1 */ + { 0x00000c18, 0xe1000001 }, /* GPIO5_CTRL1 */ + { 0x00000c1c, 0xe1000001 }, /* GPIO6_CTRL1 */ + { 0x00000c20, 0xe1000001 }, /* GPIO7_CTRL1 */ + { 0x00000c24, 0xe1000001 }, /* GPIO8_CTRL1 */ + { 0x00000c28, 0xe1000001 }, /* GPIO9_CTRL1 */ + { 0x00000c2c, 0xe1000001 }, /* GPIO10_CTRL1 */ + { 0x00000c30, 0xe1000001 }, /* GPIO11_CTRL1 */ + { 0x00000c34, 0xe1000001 }, /* GPIO12_CTRL1 */ + { 0x00000c38, 0xe1000001 }, /* GPIO13_CTRL1 */ + { 0x00000c3c, 0xe1000001 }, /* GPIO14_CTRL1 */ + { 0x00000c40, 0xe1000001 }, /* GPIO15_CTRL1 */ + { 0x00000c44, 0xe1000001 }, /* GPIO16_CTRL1 */ + { 0x00001020, 0x00000000 }, /* OUTPUT_SYS_CLK */ + { 0x00001044, 0x0005000f }, /* AUXPDM_CTRL */ + { 0x0000105c, 0x00000000 }, /* AUXPDM_CTRL2 */ + { 0x00001400, 0x00000002 }, /* CLOCK32K */ + { 0x00001404, 0x00000404 }, /* SYSTEM_CLOCK1 */ + { 0x00001420, 0x00000003 }, /* SAMPLE_RATE1 */ + { 0x00001424, 0x00000003 }, /* SAMPLE_RATE2 */ + { 0x00001428, 0x00000003 }, /* SAMPLE_RATE3 */ + { 0x0000142c, 0x00000003 }, /* SAMPLE_RATE4 */ + { 0x00001c00, 0x00000002 }, /* FLL1_CONTROL1 */ + { 0x00001c04, 0x88203004 }, /* FLL1_CONTROL2 */ + { 0x00001c08, 0x00000000 }, /* FLL1_CONTROL3 */ + { 0x00001c0c, 0x21f05001 }, /* FLL1_CONTROL4 */ + { 0x00001ca0, 0x00000c04 }, /* FLL1_GPIO_CLOCK */ + { 0x00002000, 0x00000006 }, /* CHARGE_PUMP1 */ + { 0x00002408, 0x000003e4 }, /* LDO2_CTRL1 */ + { 0x00002410, 0x000000e6 }, /* MICBIAS_CTRL1 */ + { 0x00002418, 0x00000222 }, /* MICBIAS_CTRL5 */ + { 0x00002710, 0x00004600 }, /* IRQ1_CTRL_AOD */ + { 0x00004000, 0x00000000 }, /* INPUT_CONTROL */ + { 0x00004008, 0x00000400 }, /* INPUT_RATE_CONTROL */ + { 0x0000400c, 0x00000000 }, /* INPUT_CONTROL2 */ + { 0x00004020, 0x00050020 }, /* INPUT1_CONTROL1 */ + { 0x00004024, 0x00000000 }, /* IN1L_CONTROL1 */ + { 0x00004028, 0x10800080 }, /* IN1L_CONTROL2 */ + { 0x00004044, 0x00000000 }, /* IN1R_CONTROL1 */ + { 0x00004048, 0x10800080 }, /* IN1R_CONTROL2 */ + { 0x00004060, 0x00050020 }, /* INPUT2_CONTROL1 */ + { 0x00004064, 0x00000000 }, /* IN2L_CONTROL1 */ + { 0x00004068, 0x10800000 }, /* IN2L_CONTROL2 */ + { 0x00004084, 0x00000000 }, /* IN2R_CONTROL1 */ + { 0x00004088, 0x10800000 }, /* IN2R_CONTROL2 */ + { 0x00004244, 0x00000002 }, /* INPUT_HPF_CONTROL */ + { 0x00004248, 0x00000022 }, /* INPUT_VOL_CONTROL */ + { 0x00004300, 0x00000000 }, /* AUXPDM_CONTROL1 */ + { 0x00004304, 0x00000000 }, /* AUXPDM_CONTROL2 */ + { 0x00004308, 0x00010008 }, /* AUXPDM1_CONTROL1 */ + { 0x00004310, 0x00010008 }, /* AUXPDM2_CONTROL1 */ + { 0x00004688, 0x00000000 }, /* ADC1L_ANA_CONTROL1 */ + { 0x0000468c, 0x00000000 }, /* ADC1R_ANA_CONTROL1 */ + { 0x00006000, 0x00000000 }, /* ASP1_ENABLES1 */ + { 0x00006004, 0x00000028 }, /* ASP1_CONTROL1 */ + { 0x00006008, 0x18180200 }, /* ASP1_CONTROL2 */ + { 0x0000600c, 0x00000002 }, /* ASP1_CONTROL3 */ + { 0x00006010, 0x03020100 }, /* ASP1_FRAME_CONTROL1 */ + { 0x00006014, 0x07060504 }, /* ASP1_FRAME_CONTROL2 */ + { 0x00006020, 0x03020100 }, /* ASP1_FRAME_CONTROL5 */ + { 0x00006024, 0x07060504 }, /* ASP1_FRAME_CONTROL6 */ + { 0x00006030, 0x00000020 }, /* ASP1_DATA_CONTROL1 */ + { 0x00006040, 0x00000020 }, /* ASP1_DATA_CONTROL5 */ + { 0x00006080, 0x00000000 }, /* ASP2_ENABLES1 */ + { 0x00006084, 0x00000028 }, /* ASP2_CONTROL1 */ + { 0x00006088, 0x18180200 }, /* ASP2_CONTROL2 */ + { 0x0000608c, 0x00000002 }, /* ASP2_CONTROL3 */ + { 0x00006090, 0x03020100 }, /* ASP2_FRAME_CONTROL1 */ + { 0x000060a0, 0x03020100 }, /* ASP2_FRAME_CONTROL5 */ + { 0x000060b0, 0x00000020 }, /* ASP2_DATA_CONTROL1 */ + { 0x000060c0, 0x00000020 }, /* ASP2_DATA_CONTROL5 */ + { 0x00008200, 0x00800000 }, /* ASP1TX1_INPUT1 */ + { 0x00008204, 0x00800000 }, /* ASP1TX1_INPUT2 */ + { 0x00008208, 0x00800000 }, /* ASP1TX1_INPUT3 */ + { 0x0000820c, 0x00800000 }, /* ASP1TX1_INPUT4 */ + { 0x00008210, 0x00800000 }, /* ASP1TX2_INPUT1 */ + { 0x00008214, 0x00800000 }, /* ASP1TX2_INPUT2 */ + { 0x00008218, 0x00800000 }, /* ASP1TX2_INPUT3 */ + { 0x0000821c, 0x00800000 }, /* ASP1TX2_INPUT4 */ + { 0x00008220, 0x00800000 }, /* ASP1TX3_INPUT1 */ + { 0x00008224, 0x00800000 }, /* ASP1TX3_INPUT2 */ + { 0x00008228, 0x00800000 }, /* ASP1TX3_INPUT3 */ + { 0x0000822c, 0x00800000 }, /* ASP1TX3_INPUT4 */ + { 0x00008230, 0x00800000 }, /* ASP1TX4_INPUT1 */ + { 0x00008234, 0x00800000 }, /* ASP1TX4_INPUT2 */ + { 0x00008238, 0x00800000 }, /* ASP1TX4_INPUT3 */ + { 0x0000823c, 0x00800000 }, /* ASP1TX4_INPUT4 */ + { 0x00008240, 0x00800000 }, /* ASP1TX5_INPUT1 */ + { 0x00008244, 0x00800000 }, /* ASP1TX5_INPUT2 */ + { 0x00008248, 0x00800000 }, /* ASP1TX5_INPUT3 */ + { 0x0000824c, 0x00800000 }, /* ASP1TX5_INPUT4 */ + { 0x00008250, 0x00800000 }, /* ASP1TX6_INPUT1 */ + { 0x00008254, 0x00800000 }, /* ASP1TX6_INPUT2 */ + { 0x00008258, 0x00800000 }, /* ASP1TX6_INPUT3 */ + { 0x0000825c, 0x00800000 }, /* ASP1TX6_INPUT4 */ + { 0x00008260, 0x00800000 }, /* ASP1TX7_INPUT1 */ + { 0x00008264, 0x00800000 }, /* ASP1TX7_INPUT2 */ + { 0x00008268, 0x00800000 }, /* ASP1TX7_INPUT3 */ + { 0x0000826c, 0x00800000 }, /* ASP1TX7_INPUT4 */ + { 0x00008270, 0x00800000 }, /* ASP1TX8_INPUT1 */ + { 0x00008274, 0x00800000 }, /* ASP1TX8_INPUT2 */ + { 0x00008278, 0x00800000 }, /* ASP1TX8_INPUT3 */ + { 0x0000827c, 0x00800000 }, /* ASP1TX8_INPUT4 */ + { 0x00008300, 0x00800000 }, /* ASP2TX1_INPUT1 */ + { 0x00008304, 0x00800000 }, /* ASP2TX1_INPUT2 */ + { 0x00008308, 0x00800000 }, /* ASP2TX1_INPUT3 */ + { 0x0000830c, 0x00800000 }, /* ASP2TX1_INPUT4 */ + { 0x00008310, 0x00800000 }, /* ASP2TX2_INPUT1 */ + { 0x00008314, 0x00800000 }, /* ASP2TX2_INPUT2 */ + { 0x00008318, 0x00800000 }, /* ASP2TX2_INPUT3 */ + { 0x0000831c, 0x00800000 }, /* ASP2TX2_INPUT4 */ + { 0x00008320, 0x00800000 }, /* ASP2TX3_INPUT1 */ + { 0x00008324, 0x00800000 }, /* ASP2TX3_INPUT2 */ + { 0x00008328, 0x00800000 }, /* ASP2TX3_INPUT3 */ + { 0x0000832c, 0x00800000 }, /* ASP2TX3_INPUT4 */ + { 0x00008330, 0x00800000 }, /* ASP2TX4_INPUT1 */ + { 0x00008334, 0x00800000 }, /* ASP2TX4_INPUT2 */ + { 0x00008338, 0x00800000 }, /* ASP2TX4_INPUT3 */ + { 0x0000833c, 0x00800000 }, /* ASP2TX4_INPUT4 */ + { 0x00008980, 0x00000000 }, /* ISRC1INT1_INPUT1 */ + { 0x00008990, 0x00000000 }, /* ISRC1INT2_INPUT1 */ + { 0x000089a0, 0x00000000 }, /* ISRC1INT3_INPUT1 */ + { 0x000089b0, 0x00000000 }, /* ISRC1INT4_INPUT1 */ + { 0x000089c0, 0x00000000 }, /* ISRC1DEC1_INPUT1 */ + { 0x000089d0, 0x00000000 }, /* ISRC1DEC2_INPUT1 */ + { 0x000089e0, 0x00000000 }, /* ISRC1DEC3_INPUT1 */ + { 0x000089f0, 0x00000000 }, /* ISRC1DEC4_INPUT1 */ + { 0x00008a00, 0x00000000 }, /* ISRC2INT1_INPUT1 */ + { 0x00008a10, 0x00000000 }, /* ISRC2INT2_INPUT1 */ + { 0x00008a40, 0x00000000 }, /* ISRC2DEC1_INPUT1 */ + { 0x00008a50, 0x00000000 }, /* ISRC2DEC2_INPUT1 */ + { 0x00008a80, 0x00000000 }, /* ISRC3INT1_INPUT1 */ + { 0x00008a90, 0x00000000 }, /* ISRC3INT2_INPUT1 */ + { 0x00008ac0, 0x00000000 }, /* ISRC3DEC1_INPUT1 */ + { 0x00008ad0, 0x00000000 }, /* ISRC3DEC2_INPUT1 */ + { 0x00008b80, 0x00800000 }, /* EQ1_INPUT1 */ + { 0x00008b84, 0x00800000 }, /* EQ1_INPUT2 */ + { 0x00008b88, 0x00800000 }, /* EQ1_INPUT3 */ + { 0x00008b8c, 0x00800000 }, /* EQ1_INPUT4 */ + { 0x00008b90, 0x00800000 }, /* EQ2_INPUT1 */ + { 0x00008b94, 0x00800000 }, /* EQ2_INPUT2 */ + { 0x00008b98, 0x00800000 }, /* EQ2_INPUT3 */ + { 0x00008b9c, 0x00800000 }, /* EQ2_INPUT4 */ + { 0x00008ba0, 0x00800000 }, /* EQ3_INPUT1 */ + { 0x00008ba4, 0x00800000 }, /* EQ3_INPUT2 */ + { 0x00008ba8, 0x00800000 }, /* EQ3_INPUT3 */ + { 0x00008bac, 0x00800000 }, /* EQ3_INPUT4 */ + { 0x00008bb0, 0x00800000 }, /* EQ4_INPUT1 */ + { 0x00008bb4, 0x00800000 }, /* EQ4_INPUT2 */ + { 0x00008bb8, 0x00800000 }, /* EQ4_INPUT3 */ + { 0x00008bbc, 0x00800000 }, /* EQ4_INPUT4 */ + { 0x00008c00, 0x00800000 }, /* DRC1L_INPUT1 */ + { 0x00008c04, 0x00800000 }, /* DRC1L_INPUT2 */ + { 0x00008c08, 0x00800000 }, /* DRC1L_INPUT3 */ + { 0x00008c0c, 0x00800000 }, /* DRC1L_INPUT4 */ + { 0x00008c10, 0x00800000 }, /* DRC1R_INPUT1 */ + { 0x00008c14, 0x00800000 }, /* DRC1R_INPUT2 */ + { 0x00008c18, 0x00800000 }, /* DRC1R_INPUT3 */ + { 0x00008c1c, 0x00800000 }, /* DRC1R_INPUT4 */ + { 0x00008c20, 0x00800000 }, /* DRC2L_INPUT1 */ + { 0x00008c24, 0x00800000 }, /* DRC2L_INPUT2 */ + { 0x00008c28, 0x00800000 }, /* DRC2L_INPUT3 */ + { 0x00008c2c, 0x00800000 }, /* DRC2L_INPUT4 */ + { 0x00008c30, 0x00800000 }, /* DRC2R_INPUT1 */ + { 0x00008c34, 0x00800000 }, /* DRC2R_INPUT2 */ + { 0x00008c38, 0x00800000 }, /* DRC2R_INPUT3 */ + { 0x00008c3c, 0x00800000 }, /* DRC2R_INPUT4 */ + { 0x00008c80, 0x00800000 }, /* LHPF1_INPUT1 */ + { 0x00008c84, 0x00800000 }, /* LHPF1_INPUT2 */ + { 0x00008c88, 0x00800000 }, /* LHPF1_INPUT3 */ + { 0x00008c8c, 0x00800000 }, /* LHPF1_INPUT4 */ + { 0x00008c90, 0x00800000 }, /* LHPF2_INPUT1 */ + { 0x00008c94, 0x00800000 }, /* LHPF2_INPUT2 */ + { 0x00008c98, 0x00800000 }, /* LHPF2_INPUT3 */ + { 0x00008c9c, 0x00800000 }, /* LHPF2_INPUT4 */ + { 0x00008ca0, 0x00800000 }, /* LHPF3_INPUT1 */ + { 0x00008ca4, 0x00800000 }, /* LHPF3_INPUT2 */ + { 0x00008ca8, 0x00800000 }, /* LHPF3_INPUT3 */ + { 0x00008cac, 0x00800000 }, /* LHPF3_INPUT4 */ + { 0x00008cb0, 0x00800000 }, /* LHPF4_INPUT1 */ + { 0x00008cb4, 0x00800000 }, /* LHPF4_INPUT2 */ + { 0x00008cb8, 0x00800000 }, /* LHPF4_INPUT3 */ + { 0x00008cbc, 0x00800000 }, /* LHPF4_INPUT4 */ + { 0x00009000, 0x00800000 }, /* DSP1RX1_INPUT1 */ + { 0x00009004, 0x00800000 }, /* DSP1RX1_INPUT2 */ + { 0x00009008, 0x00800000 }, /* DSP1RX1_INPUT3 */ + { 0x0000900c, 0x00800000 }, /* DSP1RX1_INPUT4 */ + { 0x00009010, 0x00800000 }, /* DSP1RX2_INPUT1 */ + { 0x00009014, 0x00800000 }, /* DSP1RX2_INPUT2 */ + { 0x00009018, 0x00800000 }, /* DSP1RX2_INPUT3 */ + { 0x0000901c, 0x00800000 }, /* DSP1RX2_INPUT4 */ + { 0x00009020, 0x00800000 }, /* DSP1RX3_INPUT1 */ + { 0x00009024, 0x00800000 }, /* DSP1RX3_INPUT2 */ + { 0x00009028, 0x00800000 }, /* DSP1RX3_INPUT3 */ + { 0x0000902c, 0x00800000 }, /* DSP1RX3_INPUT4 */ + { 0x00009030, 0x00800000 }, /* DSP1RX4_INPUT1 */ + { 0x00009034, 0x00800000 }, /* DSP1RX4_INPUT2 */ + { 0x00009038, 0x00800000 }, /* DSP1RX4_INPUT3 */ + { 0x0000903c, 0x00800000 }, /* DSP1RX4_INPUT4 */ + { 0x00009040, 0x00800000 }, /* DSP1RX5_INPUT1 */ + { 0x00009044, 0x00800000 }, /* DSP1RX5_INPUT2 */ + { 0x00009048, 0x00800000 }, /* DSP1RX5_INPUT3 */ + { 0x0000904c, 0x00800000 }, /* DSP1RX5_INPUT4 */ + { 0x00009050, 0x00800000 }, /* DSP1RX6_INPUT1 */ + { 0x00009054, 0x00800000 }, /* DSP1RX6_INPUT2 */ + { 0x00009058, 0x00800000 }, /* DSP1RX6_INPUT3 */ + { 0x0000905c, 0x00800000 }, /* DSP1RX6_INPUT4 */ + { 0x00009060, 0x00800000 }, /* DSP1RX7_INPUT1 */ + { 0x00009064, 0x00800000 }, /* DSP1RX7_INPUT2 */ + { 0x00009068, 0x00800000 }, /* DSP1RX7_INPUT3 */ + { 0x0000906c, 0x00800000 }, /* DSP1RX7_INPUT4 */ + { 0x00009070, 0x00800000 }, /* DSP1RX8_INPUT1 */ + { 0x00009074, 0x00800000 }, /* DSP1RX8_INPUT2 */ + { 0x00009078, 0x00800000 }, /* DSP1RX8_INPUT3 */ + { 0x0000907c, 0x00800000 }, /* DSP1RX8_INPUT4 */ + { 0x0000a400, 0x00000000 }, /* ISRC1_CONTROL1 */ + { 0x0000a404, 0x00000000 }, /* ISRC1_CONTROL2 */ + { 0x0000a510, 0x00000000 }, /* ISRC2_CONTROL1 */ + { 0x0000a514, 0x00000000 }, /* ISRC2_CONTROL2 */ + { 0x0000a620, 0x00000000 }, /* ISRC3_CONTROL1 */ + { 0x0000a624, 0x00000000 }, /* ISRC3_CONTROL2 */ + { 0x0000a800, 0x00000000 }, /* FX_SAMPLE_RATE */ + { 0x0000a808, 0x00000000 }, /* EQ_CONTROL1 */ + { 0x0000a80c, 0x00000000 }, /* EQ_CONTROL2 */ + { 0x0000a810, 0x0c0c0c0c }, /* EQ1_GAIN1 */ + { 0x0000a814, 0x0000000c }, /* EQ1_GAIN2 */ + { 0x0000a818, 0x03fe0fc8 }, /* EQ1_BAND1_COEFF1 */ + { 0x0000a81c, 0x00000b75 }, /* EQ1_BAND1_COEFF2 */ + { 0x0000a820, 0x000000e0 }, /* EQ1_BAND1_PG */ + { 0x0000a824, 0xf1361ec4 }, /* EQ1_BAND2_COEFF1 */ + { 0x0000a828, 0x00000409 }, /* EQ1_BAND2_COEFF2 */ + { 0x0000a82c, 0x000004cc }, /* EQ1_BAND2_PG */ + { 0x0000a830, 0xf3371c9b }, /* EQ1_BAND3_COEFF1 */ + { 0x0000a834, 0x0000040b }, /* EQ1_BAND3_COEFF2 */ + { 0x0000a838, 0x00000cbb }, /* EQ1_BAND3_PG */ + { 0x0000a83c, 0xf7d916f8 }, /* EQ1_BAND4_COEFF1 */ + { 0x0000a840, 0x0000040a }, /* EQ1_BAND4_COEFF2 */ + { 0x0000a844, 0x00001f14 }, /* EQ1_BAND4_PG */ + { 0x0000a848, 0x0563058c }, /* EQ1_BAND5_COEFF1 */ + { 0x0000a84c, 0x00000000 }, /* EQ1_BAND5_COEFF1 + 4 */ + { 0x0000a850, 0x00004000 }, /* EQ1_BAND5_PG */ + { 0x0000a854, 0x0c0c0c0c }, /* EQ2_GAIN1 */ + { 0x0000a858, 0x0000000c }, /* EQ2_GAIN2 */ + { 0x0000a85c, 0x03fe0fc8 }, /* EQ2_BAND1_COEFF1 */ + { 0x0000a860, 0x00000b75 }, /* EQ2_BAND1_COEFF2 */ + { 0x0000a864, 0x000000e0 }, /* EQ2_BAND1_PG */ + { 0x0000a868, 0xf1361ec4 }, /* EQ2_BAND2_COEFF1 */ + { 0x0000a86c, 0x00000409 }, /* EQ2_BAND2_COEFF2 */ + { 0x0000a870, 0x000004cc }, /* EQ2_BAND2_PG */ + { 0x0000a874, 0xf3371c9b }, /* EQ2_BAND3_COEFF1 */ + { 0x0000a878, 0x0000040b }, /* EQ2_BAND3_COEFF2 */ + { 0x0000a87c, 0x00000cbb }, /* EQ2_BAND3_PG */ + { 0x0000a880, 0xf7d916f8 }, /* EQ2_BAND4_COEFF1 */ + { 0x0000a884, 0x0000040a }, /* EQ2_BAND4_COEFF2 */ + { 0x0000a888, 0x00001f14 }, /* EQ2_BAND4_PG */ + { 0x0000a88c, 0x0563058c }, /* EQ2_BAND5_COEFF1 */ + { 0x0000a890, 0x00000000 }, /* EQ2_BAND5_COEFF1 + 4 */ + { 0x0000a894, 0x00004000 }, /* EQ2_BAND5_PG */ + { 0x0000a898, 0x0c0c0c0c }, /* EQ3_GAIN1 */ + { 0x0000a89c, 0x0000000c }, /* EQ3_GAIN2 */ + { 0x0000a8a0, 0x03fe0fc8 }, /* EQ3_BAND1_COEFF1 */ + { 0x0000a8a4, 0x00000b75 }, /* EQ3_BAND1_COEFF2 */ + { 0x0000a8a8, 0x000000e0 }, /* EQ3_BAND1_PG */ + { 0x0000a8ac, 0xf1361ec4 }, /* EQ3_BAND2_COEFF1 */ + { 0x0000a8b0, 0x00000409 }, /* EQ3_BAND2_COEFF2 */ + { 0x0000a8b4, 0x000004cc }, /* EQ3_BAND2_PG */ + { 0x0000a8b8, 0xf3371c9b }, /* EQ3_BAND3_COEFF1 */ + { 0x0000a8bc, 0x0000040b }, /* EQ3_BAND3_COEFF2 */ + { 0x0000a8c0, 0x00000cbb }, /* EQ3_BAND3_PG */ + { 0x0000a8c4, 0xf7d916f8 }, /* EQ3_BAND4_COEFF1 */ + { 0x0000a8c8, 0x0000040a }, /* EQ3_BAND4_COEFF2 */ + { 0x0000a8cc, 0x00001f14 }, /* EQ3_BAND4_PG */ + { 0x0000a8d0, 0x0563058c }, /* EQ3_BAND5_COEFF1 */ + { 0x0000a8d4, 0x00000000 }, /* EQ3_BAND5_COEFF1 + 4 */ + { 0x0000a8d8, 0x00004000 }, /* EQ3_BAND5_PG */ + { 0x0000a8dc, 0x0c0c0c0c }, /* EQ4_GAIN1 */ + { 0x0000a8e0, 0x0000000c }, /* EQ4_GAIN2 */ + { 0x0000a8e4, 0x03fe0fc8 }, /* EQ4_BAND1_COEFF1 */ + { 0x0000a8e8, 0x00000b75 }, /* EQ4_BAND1_COEFF2 */ + { 0x0000a8ec, 0x000000e0 }, /* EQ4_BAND1_PG */ + { 0x0000a8f0, 0xf1361ec4 }, /* EQ4_BAND2_COEFF1 */ + { 0x0000a8f4, 0x00000409 }, /* EQ4_BAND2_COEFF2 */ + { 0x0000a8f8, 0x000004cc }, /* EQ4_BAND2_PG */ + { 0x0000a8fc, 0xf3371c9b }, /* EQ4_BAND3_COEFF1 */ + { 0x0000a900, 0x0000040b }, /* EQ4_BAND3_COEFF2 */ + { 0x0000a904, 0x00000cbb }, /* EQ4_BAND3_PG */ + { 0x0000a908, 0xf7d916f8 }, /* EQ4_BAND4_COEFF1 */ + { 0x0000a90c, 0x0000040a }, /* EQ4_BAND4_COEFF2 */ + { 0x0000a910, 0x00001f14 }, /* EQ4_BAND4_PG */ + { 0x0000a914, 0x0563058c }, /* EQ4_BAND5_COEFF1 */ + { 0x0000a918, 0x00000000 }, /* EQ4_BAND5_COEFF1 + 4 */ + { 0x0000a91c, 0x00004000 }, /* EQ4_BAND5_PG */ + { 0x0000aa30, 0x00000000 }, /* LHPF_CONTROL1 */ + { 0x0000aa34, 0x00000000 }, /* LHPF_CONTROL2 */ + { 0x0000aa38, 0x00000000 }, /* LHPF1_COEFF */ + { 0x0000aa3c, 0x00000000 }, /* LHPF2_COEFF */ + { 0x0000aa40, 0x00000000 }, /* LHPF3_COEFF */ + { 0x0000aa44, 0x00000000 }, /* LHPF4_COEFF */ + { 0x0000ab00, 0x00000000 }, /* DRC1_CONTROL1 */ + { 0x0000ab04, 0x49130018 }, /* DRC1_CONTROL2 */ + { 0x0000ab08, 0x00000018 }, /* DRC1_CONTROL3 */ + { 0x0000ab0c, 0x00000000 }, /* DRC1_CONTROL4 */ + { 0x0000ab14, 0x00000000 }, /* DRC2_CONTROL1 */ + { 0x0000ab18, 0x49130018 }, /* DRC2_CONTROL2 */ + { 0x0000ab1c, 0x00000018 }, /* DRC2_CONTROL3 */ + { 0x0000ab20, 0x00000000 }, /* DRC2_CONTROL4 */ + { 0x0000b000, 0x00000000 }, /* TONE_GENERATOR1 */ + { 0x0000b004, 0x00100000 }, /* TONE_GENERATOR2 */ + { 0x0000b400, 0x00000000 }, /* COMFORT_NOISE_GENERATOR */ + { 0x0000b800, 0x00000000 }, /* US_CONTROL */ + { 0x0000b804, 0x00002020 }, /* US1_CONTROL */ + { 0x0000b808, 0x00000000 }, /* US1_DET_CONTROL */ + { 0x0000b814, 0x00002020 }, /* US2_CONTROL */ + { 0x0000b818, 0x00000000 }, /* US2_DET_CONTROL */ + { 0x00018110, 0xffffffff }, /* IRQ1_MASK_1 */ + { 0x00018114, 0xfffffff7 }, /* IRQ1_MASK_2 */ + { 0x00018118, 0xffffffff }, /* IRQ1_MASK_3 */ + { 0x0001811c, 0xffffffff }, /* IRQ1_MASK_4 */ + { 0x00018120, 0xffffffff }, /* IRQ1_MASK_5 */ + { 0x00018124, 0xffffffff }, /* IRQ1_MASK_6 */ + { 0x00018128, 0xffffffff }, /* IRQ1_MASK_7 */ + { 0x0001812c, 0xffffffff }, /* IRQ1_MASK_8 */ + { 0x00018130, 0xffffffff }, /* IRQ1_MASK_9 */ + { 0x00018134, 0xffffffff }, /* IRQ1_MASK_10 */ + { 0x00018138, 0xffffffff }, /* IRQ1_MASK_11 */ +}; + +static bool cs48l32_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS48L32_DEVID: + case CS48L32_REVID: + case CS48L32_OTPID: + case CS48L32_SFT_RESET: + case CS48L32_CTRL_IF_DEBUG3: + case CS48L32_MCU_CTRL1: + case CS48L32_GPIO1_CTRL1 ... CS48L32_GPIO16_CTRL1: + case CS48L32_OUTPUT_SYS_CLK: + case CS48L32_AUXPDM_CTRL: + case CS48L32_AUXPDM_CTRL2: + case CS48L32_CLOCK32K: + case CS48L32_SYSTEM_CLOCK1 ... CS48L32_SYSTEM_CLOCK2: + case CS48L32_SAMPLE_RATE1 ... CS48L32_SAMPLE_RATE4: + case CS48L32_FLL1_CONTROL1 ... CS48L32_FLL1_GPIO_CLOCK: + case CS48L32_CHARGE_PUMP1: + case CS48L32_LDO2_CTRL1: + case CS48L32_MICBIAS_CTRL1: + case CS48L32_MICBIAS_CTRL5: + case CS48L32_IRQ1_CTRL_AOD: + case CS48L32_INPUT_CONTROL: + case CS48L32_INPUT_STATUS: + case CS48L32_INPUT_RATE_CONTROL: + case CS48L32_INPUT_CONTROL2: + case CS48L32_INPUT_CONTROL3: + case CS48L32_INPUT1_CONTROL1: + case CS48L32_IN1L_CONTROL1 ... CS48L32_IN1L_CONTROL2: + case CS48L32_IN1R_CONTROL1 ... CS48L32_IN1R_CONTROL2: + case CS48L32_INPUT2_CONTROL1: + case CS48L32_IN2L_CONTROL1 ... CS48L32_IN2L_CONTROL2: + case CS48L32_IN2R_CONTROL1 ... CS48L32_IN2R_CONTROL2: + case CS48L32_INPUT_HPF_CONTROL: + case CS48L32_INPUT_VOL_CONTROL: + case CS48L32_AUXPDM_CONTROL1: + case CS48L32_AUXPDM_CONTROL2: + case CS48L32_AUXPDM1_CONTROL1: + case CS48L32_AUXPDM2_CONTROL1: + case CS48L32_ADC1L_ANA_CONTROL1: + case CS48L32_ADC1R_ANA_CONTROL1: + case CS48L32_ASP1_ENABLES1 ... CS48L32_ASP1_DATA_CONTROL5: + case CS48L32_ASP2_ENABLES1 ... CS48L32_ASP2_DATA_CONTROL5: + case CS48L32_ASP1TX1_INPUT1 ... CS48L32_ASP1TX8_INPUT4: + case CS48L32_ASP2TX1_INPUT1 ... CS48L32_ASP2TX4_INPUT4: + case CS48L32_ISRC1INT1_INPUT1 ... CS48L32_ISRC1DEC4_INPUT1: + case CS48L32_ISRC2INT1_INPUT1 ... CS48L32_ISRC2DEC2_INPUT1: + case CS48L32_ISRC3INT1_INPUT1 ... CS48L32_ISRC3DEC2_INPUT1: + case CS48L32_EQ1_INPUT1 ... CS48L32_EQ4_INPUT4: + case CS48L32_DRC1L_INPUT1 ... CS48L32_DRC1R_INPUT4: + case CS48L32_DRC2L_INPUT1 ... CS48L32_DRC2R_INPUT4: + case CS48L32_LHPF1_INPUT1 ... CS48L32_LHPF1_INPUT4: + case CS48L32_LHPF2_INPUT1 ... CS48L32_LHPF2_INPUT4: + case CS48L32_LHPF3_INPUT1 ... CS48L32_LHPF3_INPUT4: + case CS48L32_LHPF4_INPUT1 ... CS48L32_LHPF4_INPUT4: + case CS48L32_DSP1RX1_INPUT1 ... CS48L32_DSP1RX8_INPUT4: + case CS48L32_ISRC1_CONTROL1 ... CS48L32_ISRC1_CONTROL2: + case CS48L32_ISRC2_CONTROL1 ... CS48L32_ISRC2_CONTROL2: + case CS48L32_ISRC3_CONTROL1 ... CS48L32_ISRC3_CONTROL2: + case CS48L32_FX_SAMPLE_RATE: + case CS48L32_EQ_CONTROL1 ... CS48L32_EQ_CONTROL2: + case CS48L32_EQ1_GAIN1 ... CS48L32_EQ1_BAND5_PG: + case CS48L32_EQ2_GAIN1 ... CS48L32_EQ2_BAND5_PG: + case CS48L32_EQ3_GAIN1 ... CS48L32_EQ3_BAND5_PG: + case CS48L32_EQ4_GAIN1 ... CS48L32_EQ4_BAND5_PG: + case CS48L32_LHPF_CONTROL1 ... CS48L32_LHPF_CONTROL2: + case CS48L32_LHPF1_COEFF ... CS48L32_LHPF4_COEFF: + case CS48L32_DRC1_CONTROL1 ... CS48L32_DRC1_CONTROL4: + case CS48L32_DRC2_CONTROL1 ... CS48L32_DRC2_CONTROL4: + case CS48L32_TONE_GENERATOR1 ... CS48L32_TONE_GENERATOR2: + case CS48L32_COMFORT_NOISE_GENERATOR: + case CS48L32_US_CONTROL: + case CS48L32_US1_CONTROL: + case CS48L32_US1_DET_CONTROL: + case CS48L32_US2_CONTROL: + case CS48L32_US2_DET_CONTROL: + case CS48L32_DSP1_XM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_XM_SRAM_IBUS_SETUP_24: + case CS48L32_DSP1_YM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_YM_SRAM_IBUS_SETUP_8: + case CS48L32_DSP1_PM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_PM_SRAM_IBUS_SETUP_7: + case CS48L32_IRQ1_STATUS: + case CS48L32_IRQ1_EINT_1 ... CS48L32_IRQ1_EINT_11: + case CS48L32_IRQ1_STS_1 ... CS48L32_IRQ1_STS_11: + case CS48L32_IRQ1_MASK_1 ... CS48L32_IRQ1_MASK_11: + case CS48L32_DSP1_XMEM_PACKED_0 ... CS48L32_DSP1_XMEM_PACKED_147455: + case CS48L32_DSP1_SYS_INFO_ID ... CS48L32_DSP1_AHBM_WINDOW_DEBUG_1: + case CS48L32_DSP1_XMEM_UNPACKED24_0 ... CS48L32_DSP1_XMEM_UNPACKED24_196607: + case CS48L32_DSP1_CLOCK_FREQ ... CS48L32_DSP1_SAMPLE_RATE_TX8: + case CS48L32_DSP1_SCRATCH1 ... CS48L32_DSP1_SCRATCH4: + case CS48L32_DSP1_CCM_CORE_CONTROL ... CS48L32_DSP1_STREAM_ARB_RESYNC_MSK1: + case CS48L32_DSP1_YMEM_PACKED_0 ... CS48L32_DSP1_YMEM_PACKED_49151: + case CS48L32_DSP1_YMEM_UNPACKED24_0 ... CS48L32_DSP1_YMEM_UNPACKED24_65535: + case CS48L32_DSP1_PMEM_0 ... CS48L32_DSP1_PMEM_71679: + return true; + default: + return false; + } +} + +static bool cs48l32_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS48L32_DEVID: + case CS48L32_REVID: + case CS48L32_OTPID: + case CS48L32_SFT_RESET: + case CS48L32_CTRL_IF_DEBUG3: + case CS48L32_MCU_CTRL1: + case CS48L32_SYSTEM_CLOCK2: + case CS48L32_FLL1_CONTROL5: + case CS48L32_FLL1_CONTROL6: + case CS48L32_INPUT_STATUS: + case CS48L32_INPUT_CONTROL3: + case CS48L32_DSP1_XM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_XM_SRAM_IBUS_SETUP_24: + case CS48L32_DSP1_YM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_YM_SRAM_IBUS_SETUP_8: + case CS48L32_DSP1_PM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_PM_SRAM_IBUS_SETUP_7: + case CS48L32_IRQ1_STATUS: + case CS48L32_IRQ1_EINT_1 ... CS48L32_IRQ1_EINT_11: + case CS48L32_IRQ1_STS_1 ... CS48L32_IRQ1_STS_11: + case CS48L32_DSP1_XMEM_PACKED_0 ... CS48L32_DSP1_XMEM_PACKED_147455: + case CS48L32_DSP1_SYS_INFO_ID ... CS48L32_DSP1_AHBM_WINDOW_DEBUG_1: + case CS48L32_DSP1_XMEM_UNPACKED24_0 ... CS48L32_DSP1_XMEM_UNPACKED24_196607: + case CS48L32_DSP1_CLOCK_FREQ ... CS48L32_DSP1_SAMPLE_RATE_TX8: + case CS48L32_DSP1_SCRATCH1 ... CS48L32_DSP1_SCRATCH4: + case CS48L32_DSP1_CCM_CORE_CONTROL ... CS48L32_DSP1_STREAM_ARB_RESYNC_MSK1: + case CS48L32_DSP1_YMEM_PACKED_0 ... CS48L32_DSP1_YMEM_PACKED_49151: + case CS48L32_DSP1_YMEM_UNPACKED24_0 ... CS48L32_DSP1_YMEM_UNPACKED24_65535: + case CS48L32_DSP1_PMEM_0 ... CS48L32_DSP1_PMEM_71679: + return true; + default: + return false; + } +} + +/* + * The bus bridge requires DSP packed memory registers to be accessed in + * aligned block multiples. + * Mark precious to prevent regmap debugfs causing an illegal bus transaction. + */ +static bool cs48l32_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS48L32_DSP1_XMEM_PACKED_0 ... CS48L32_DSP1_XMEM_PACKED_147455: + case CS48L32_DSP1_YMEM_PACKED_0 ... CS48L32_DSP1_YMEM_PACKED_49151: + case CS48L32_DSP1_PMEM_0 ... CS48L32_DSP1_PMEM_71679: + return true; + default: + return false; + } +} + +static const struct regmap_config cs48l32_spi_regmap = { + .name = "cs48l32", + .reg_bits = 32, + .reg_stride = 4, + .pad_bits = 32, + .val_bits = 32, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + + .max_register = CS48L32_DSP1_PMEM_71679, + .readable_reg = &cs48l32_readable_register, + .volatile_reg = &cs48l32_volatile_register, + .precious_reg = &cs48l32_precious_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = cs48l32_reg_default, + .num_reg_defaults = ARRAY_SIZE(cs48l32_reg_default), +}; + +int cs48l32_init_regmap(struct spi_device *spi, struct cs48l32_mfd *cs48l32) +{ + cs48l32->regmap = devm_regmap_init_spi(spi, &cs48l32_spi_regmap); + if (IS_ERR(cs48l32->regmap)) + return PTR_ERR(cs48l32->regmap); + + return 0; +} diff --git a/drivers/mfd/cs48l32.c b/drivers/mfd/cs48l32.c new file mode 100644 index 000000000000..9506a1e0560b --- /dev/null +++ b/drivers/mfd/cs48l32.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MFD for Cirrus Logic CS48L32 audio codec. + * + * Copyright (C) 2016-2018, 2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/mfd/core.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/spi/spi.h> + +#include "cs48l32.h" + +static const char * const cs48l32_core_supplies[] = { + "VDD_A", + "VDD_IO", +}; + +static const char * const cs48l32_supplies[] = { + "VOUT_MIC", + "VDD_CP", +}; + +static const struct mfd_cell cs48l32_devs[] = { + { .name = "cs48l32-pinctrl", }, + { .name = "cs48l32-irq", }, + { .name = "cs48l32-micsupp", }, + { + .name = "cs48l32-codec", + .parent_supplies = cs48l32_supplies, + .num_parent_supplies = ARRAY_SIZE(cs48l32_supplies), + }, +}; + +static int cs48l32_wait_for_boot(struct cs48l32_mfd *cs48l32) +{ + unsigned int val; + int i, ret; + + /* regmap_read_poll_timeout would fail on read errors so roll our own */ + for (i = 0; i < CS48L32_BOOT_TIMEOUT_MS; ++i) { + val = 0; + regmap_read(cs48l32->regmap, CS48L32_IRQ1_EINT_2, &val); + if (val & CS48L32_BOOT_DONE_EINT1_MASK) + break; + + usleep_range(1000, 2000); + } + if (i == CS48L32_BOOT_TIMEOUT_MS) { + dev_err(cs48l32->dev, "BOOT_DONE timed out\n"); + return -ETIMEDOUT; + } + + ret = regmap_read(cs48l32->regmap, CS48L32_MCU_CTRL1, &val); + if (ret) { + dev_err(cs48l32->dev, "Failed to read MCU_CTRL1: %d\n", ret); + return ret; + } + + if (val & BIT(CS48L32_MCU_STS_SHIFT)) { + dev_err(cs48l32->dev, "MCU boot failed\n"); + return -EIO; + } + + ret = regmap_set_bits(cs48l32->regmap, CS48L32_CTRL_IF_DEBUG3, CS48L32_SEEN_BOOT_DONE); + if (ret) { + dev_err(cs48l32->dev, "Failed to write CTRL_IF_DEBUG3: %d\n", ret); + return ret; + } + + pm_runtime_mark_last_busy(cs48l32->dev); + + return 0; +} + +static int cs48l32_soft_reset(struct cs48l32_mfd *cs48l32) +{ + int ret; + + ret = regmap_write(cs48l32->regmap, CS48L32_SFT_RESET, CS48L32_SFT_RESET_MAGIC); + if (ret != 0) { + dev_err(cs48l32->dev, "Failed to write soft reset: %d\n", ret); + return ret; + } + + usleep_range(2000, 3000); + + return 0; +} + +static void cs48l32_enable_hard_reset(struct cs48l32_mfd *cs48l32) +{ + if (cs48l32->reset_gpio) + gpiod_set_value_cansleep(cs48l32->reset_gpio, 0); +} + +static void cs48l32_disable_hard_reset(struct cs48l32_mfd *cs48l32) +{ + if (cs48l32->reset_gpio) { + gpiod_set_value_cansleep(cs48l32->reset_gpio, 1); + usleep_range(2000, 3000); + } +} + +static int __maybe_unused cs48l32_runtime_resume(struct device *dev) +{ + struct cs48l32_mfd *cs48l32 = dev_get_drvdata(dev); + unsigned int val; + int ret; + + dev_dbg(cs48l32->dev, "Leaving sleep mode\n"); + + ret = regulator_enable(cs48l32->vdd_d); + if (ret) { + dev_err(cs48l32->dev, "Failed to enable VDD_D: %d\n", ret); + return ret; + } + + usleep_range(2000, 3000); + + regcache_cache_only(cs48l32->regmap, false); + + /* Did it power down during suspend? */ + ret = regmap_read(cs48l32->regmap, CS48L32_CTRL_IF_DEBUG3, &val); + if ((ret == 0) && (val & CS48L32_SEEN_BOOT_DONE)) { + dev_dbg(cs48l32->dev, "VDD_D didn't power off during suspend\n"); + } else { + ret = cs48l32_wait_for_boot(cs48l32); + if (ret) + goto err; + + /* Flag that the registers have reset to defaults */ + regcache_mark_dirty(cs48l32->regmap); + } + + ret = regcache_sync(cs48l32->regmap); + if (ret) { + dev_err(cs48l32->dev, "Failed to restore register cache\n"); + goto err; + } + + return 0; + +err: + regcache_cache_only(cs48l32->regmap, true); + regulator_disable(cs48l32->vdd_d); + return ret; +} + +static int __maybe_unused cs48l32_runtime_suspend(struct device *dev) +{ + struct cs48l32_mfd *cs48l32 = dev_get_drvdata(dev); + + dev_dbg(cs48l32->dev, "Entering sleep mode\n"); + + regcache_cache_only(cs48l32->regmap, true); + regulator_disable(cs48l32->vdd_d); + + return 0; +} + +static const struct dev_pm_ops cs48l32_pm_ops = { + SET_RUNTIME_PM_OPS(cs48l32_runtime_suspend, cs48l32_runtime_resume, NULL) +}; + +static const struct __maybe_unused of_device_id cs48l32_of_match[] = { + { .compatible = "cirrus,cs48l31", }, + { .compatible = "cirrus,cs48l32", }, + { .compatible = "cirrus,cs48l33", }, + {}, +}; + +static int cs48l32_configure_clk32k(struct cs48l32_mfd *cs48l32) +{ + int ret = 0; + + ret = clk_prepare_enable(cs48l32->mclk1); + if (ret) { + dev_err(cs48l32->dev, "Failed to enable 32k clock: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(cs48l32->regmap, CS48L32_CLOCK32K, + CS48L32_CLK_32K_EN_MASK | CS48L32_CLK_32K_SRC_MASK, + CS48L32_CLK_32K_EN_MASK | CS48L32_32K_MCLK1); + if (ret) { + dev_err(cs48l32->dev, "Failed to init 32k clock: %d\n", ret); + clk_disable_unprepare(cs48l32->mclk1); + return ret; + } + + return 0; +} + +static int cs48l32_get_clocks(struct cs48l32_mfd *cs48l32) +{ + cs48l32->mclk1 = devm_clk_get_optional(cs48l32->dev, "mclk1"); + if (IS_ERR(cs48l32->mclk1)) + return dev_err_probe(cs48l32->dev, PTR_ERR(cs48l32->mclk1), + "Failed to get mclk1\n"); + + return 0; +} + +static int cs48l32_get_reset_gpio(struct cs48l32_mfd *cs48l32) +{ + struct gpio_desc *reset; + + reset = devm_gpiod_get_optional(cs48l32->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset)) + return dev_err_probe(cs48l32->dev, PTR_ERR(reset), "Failed to request /RESET\n"); + + /* + * A hard reset is needed for full reset of the chip. We allow running + * without hard reset only because it can be useful for early + * prototyping and some debugging, but we need to warn it's not ideal. + */ + if (!reset) + dev_warn(cs48l32->dev, "Running without reset GPIO is not recommended\n"); + + cs48l32->reset_gpio = reset; + + return 0; +} + +static int cs48l32_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct cs48l32_mfd *cs48l32; + unsigned int hwid, rev, otp_rev; + int i, ret; + + cs48l32 = devm_kzalloc(&spi->dev, sizeof(*cs48l32), GFP_KERNEL); + if (!cs48l32) + return -ENOMEM; + + ret = cs48l32_init_regmap(spi, cs48l32); + if (ret) + return dev_err_probe(&spi->dev, ret, "Failed to allocate regmap\n"); + + cs48l32->dev = dev; + cs48l32->irq = spi->irq; + + dev_set_drvdata(cs48l32->dev, cs48l32); + + BLOCKING_INIT_NOTIFIER_HEAD(&cs48l32->notifier); + + regcache_cache_only(cs48l32->regmap, true); + + ret = cs48l32_get_reset_gpio(cs48l32); + if (ret) + return ret; + + ret = cs48l32_get_clocks(cs48l32); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(cs48l32_core_supplies); i++) + cs48l32->core_supplies[i].supply = cs48l32_core_supplies[i]; + + cs48l32->num_core_supplies = ARRAY_SIZE(cs48l32_core_supplies); + + ret = devm_regulator_bulk_get(dev, cs48l32->num_core_supplies, cs48l32->core_supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to request core supplies\n"); + + cs48l32->vdd_d = devm_regulator_get(cs48l32->dev, "VDD_D"); + if (IS_ERR(cs48l32->vdd_d)) + return dev_err_probe(dev, PTR_ERR(cs48l32->vdd_d), "Failed to request VDD_D\n"); + + ret = regulator_set_voltage(cs48l32->vdd_d, 1200000, 1200000); + if (ret) + return dev_err_probe(dev, ret, "Failed to request VDD_D=1.2v\n"); + + ret = regulator_bulk_enable(cs48l32->num_core_supplies, cs48l32->core_supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable core supplies\n"); + + ret = regulator_enable(cs48l32->vdd_d); + if (ret) { + dev_err(dev, "Failed to enable VDD_D: %d\n", ret); + goto err_enable; + } + + cs48l32_disable_hard_reset(cs48l32); + + regcache_cache_only(cs48l32->regmap, false); + + /* If we don't have a reset GPIO use a soft reset */ + if (!cs48l32->reset_gpio) { + ret = cs48l32_soft_reset(cs48l32); + if (ret) + goto err_reset; + } + + ret = cs48l32_wait_for_boot(cs48l32); + if (ret) { + dev_err(cs48l32->dev, "Device failed initial boot: %d\n", ret); + goto err_reset; + } + + ret = regmap_read(cs48l32->regmap, CS48L32_DEVID, &hwid); + if (ret) { + dev_err(dev, "Failed to read ID register: %d\n", ret); + goto err_reset; + } + hwid &= CS48L32_DEVID_MASK; + + switch (hwid) { + case CS48L31_SILICON_ID: + case CS48L32_SILICON_ID: + case CS48L33_SILICON_ID: + break; + default: + dev_err(cs48l32->dev, "Unknown device ID: %x\n", hwid); + ret = -EINVAL; + goto err_reset; + } + + ret = regmap_read(cs48l32->regmap, CS48L32_REVID, &rev); + if (ret) { + dev_err(dev, "Failed to read revision register: %d\n", ret); + goto err_reset; + } + rev &= CS48L32_AREVID_MASK | CS48L32_MTLREVID_MASK; + + ret = regmap_read(cs48l32->regmap, CS48L32_OTPID, &otp_rev); + if (ret) { + dev_err(dev, "Failed to read OTP revision register: %d\n", ret); + goto err_reset; + } + otp_rev &= CS48L32_OTPID_MASK; + + cs48l32->part = hwid & 0xff; + + dev_info(dev, "CS48L%x revision %X%u.%u\n", cs48l32->part, + rev >> CS48L32_AREVID_SHIFT, rev & CS48L32_MTLREVID_MASK, otp_rev); + + /* Apply hardware patch */ + ret = cs48l32_patch(cs48l32); + if (ret) { + dev_err(cs48l32->dev, "Failed to apply patch %d\n", ret); + goto err_reset; + } + + ret = cs48l32_configure_clk32k(cs48l32); + if (ret) + goto err_reset; + + pm_runtime_set_active(cs48l32->dev); + pm_runtime_enable(cs48l32->dev); + pm_runtime_set_autosuspend_delay(cs48l32->dev, 100); + pm_runtime_use_autosuspend(cs48l32->dev); + + ret = mfd_add_devices(cs48l32->dev, PLATFORM_DEVID_NONE, + cs48l32_devs, ARRAY_SIZE(cs48l32_devs), NULL, 0, NULL); + if (ret) { + dev_err(cs48l32->dev, "Failed to add subdevices: %d\n", ret); + goto err_clk32k; + } + + return 0; + +err_clk32k: + clk_disable_unprepare(cs48l32->mclk1); +err_reset: + cs48l32_enable_hard_reset(cs48l32); + regulator_disable(cs48l32->vdd_d); +err_enable: + regulator_bulk_disable(cs48l32->num_core_supplies, cs48l32->core_supplies); + + return ret; +} + +static void cs48l32_spi_remove(struct spi_device *spi) +{ + struct cs48l32_mfd *cs48l32 = spi_get_drvdata(spi); + + /* Prevent any IRQs being serviced while we clean up */ + disable_irq(cs48l32->irq); + + mfd_remove_devices(cs48l32->dev); + + pm_runtime_disable(cs48l32->dev); + regulator_disable(cs48l32->vdd_d); + clk_disable_unprepare(cs48l32->mclk1); + cs48l32_enable_hard_reset(cs48l32); + regulator_bulk_disable(cs48l32->num_core_supplies, cs48l32->core_supplies); +} + +static const struct spi_device_id cs48l32_spi_ids[] = { + { "cs48l31", }, + { "cs48l32", }, + { "cs48l33", }, + { }, +}; +MODULE_DEVICE_TABLE(spi, cs48l32_spi_ids); + +static struct spi_driver cs48l32_spi_driver = { + .driver = { + .name = "cs48l32", + .owner = THIS_MODULE, + .pm = &cs48l32_pm_ops, + .of_match_table = of_match_ptr(cs48l32_of_match), + }, + .probe = &cs48l32_spi_probe, + .remove = &cs48l32_spi_remove, + .id_table = cs48l32_spi_ids, +}; + +module_spi_driver(cs48l32_spi_driver); + +MODULE_SOFTDEP("pre: pinctrl-cs48l32 irq-cirrus-cs48l32"); +MODULE_DESCRIPTION("CS48L32 MFD driver"); +MODULE_AUTHOR("Richard Fitzgerald rf@opensource.cirrus.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cs48l32.h b/drivers/mfd/cs48l32.h new file mode 100644 index 000000000000..36020c0cd686 --- /dev/null +++ b/drivers/mfd/cs48l32.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * MFD for Cirrus Logic CS48L32 audio codec. + * + * Copyright (C) 2016-2018, 2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef CS48L32_MFD_H +#define CS48L32_MFD_H + +#include <linux/bits.h> + +#define CS48L31_SILICON_ID 0x48a31 +#define CS48L32_SILICON_ID 0x48a32 +#define CS48L33_SILICON_ID 0x48a33 + +#define CS48L32_32K_MCLK1 0 + +#define CS48L32_SFT_RESET_MAGIC 0x5a000000 +#define CS48L32_SEEN_BOOT_DONE BIT(0) + +#define CS48L32_BOOT_TIMEOUT_MS 25 + +int cs48l32_init_regmap(struct spi_device *spi, struct cs48l32_mfd *cs48l32); +int cs48l32_patch(struct cs48l32_mfd *cs48l32); + +#endif diff --git a/include/linux/mfd/cs48l32/core.h b/include/linux/mfd/cs48l32/core.h new file mode 100644 index 000000000000..1c4269d70c8c --- /dev/null +++ b/include/linux/mfd/cs48l32/core.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * MFD for Cirrus Logic CS48L32 audio codec. + * + * Copyright (C) 2016-2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef CS48L32_CORE_H +#define CS48L32_CORE_H + +#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/notifier.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#define CS48L32_MAX_CORE_SUPPLIES 2 + +struct snd_soc_dapm_context; + +struct cs48l32_mfd { + struct regmap *regmap; + struct device *dev; + struct gpio_desc *reset_gpio; + struct clk *mclk1; + struct snd_soc_dapm_context *dapm; + + int num_core_supplies; + struct regulator_bulk_data core_supplies[CS48L32_MAX_CORE_SUPPLIES]; + struct regulator *vdd_d; + + struct device *irq_dev; + struct regmap_irq_chip_data *irq_data; + int irq; + + u8 part; + + struct blocking_notifier_head notifier; +}; + +static inline int cs48l32_call_notifiers(struct cs48l32_mfd *mfd, + unsigned long event, + void *data) +{ + return blocking_notifier_call_chain(&mfd->notifier, event, data); +} +#endif
Hi Richard,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on broonie-sound/for-next] [also build test WARNING on lee-mfd/for-mfd-next linusw-pinctrl/devel linusw-pinctrl/for-next broonie-regulator/for-next linus/master v6.1-rc4 next-20221111] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Richard-Fitzgerald/Add-suppor... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next patch link: https://lore.kernel.org/r/20221109165331.29332-4-rf%40opensource.cirrus.com patch subject: [PATCH 03/12] mfd: cs48l32: Add support for CS48L31/32/33 codecs config: arm-randconfig-c002-20221111 compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 463da45892e2d2a262277b91b96f5f8c05dc25d0) reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # install arm cross compiling tool for clang build # apt-get install binutils-arm-linux-gnueabi # https://github.com/intel-lab-lkp/linux/commit/e5417b42692fa57b7987ecb90833e0... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Richard-Fitzgerald/Add-support-for-the-Cirrus-Logic-CS48L32-audio-codecs/20221110-005630 git checkout e5417b42692fa57b7987ecb90833e01e67186d0e # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=arm SHELL=/bin/bash drivers/mfd/
If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot lkp@intel.com
All warnings (new ones prefixed by >>):
drivers/mfd/cs48l32.c:184:21: warning: attribute declaration must precede definition [-Wignored-attributes]
static const struct __maybe_unused of_device_id cs48l32_of_match[] = { ^ include/linux/compiler_attributes.h:326:56: note: expanded from macro '__maybe_unused' #define __maybe_unused __attribute__((__unused__)) ^ include/linux/mod_devicetable.h:268:8: note: previous definition is here struct of_device_id { ^ 1 warning generated.
vim +184 drivers/mfd/cs48l32.c
183
184 static const struct __maybe_unused of_device_id cs48l32_of_match[] = {
185 { .compatible = "cirrus,cs48l31", }, 186 { .compatible = "cirrus,cs48l32", }, 187 { .compatible = "cirrus,cs48l33", }, 188 {}, 189 }; 190
On Wed, 09 Nov 2022, Richard Fitzgerald wrote:
From: Piotr Stankiewicz piotrs@opensource.cirrus.com
The CS48L31/32/33 audio codecs are multi-function devices containing gpios, irq controller and regulators in addition to the core audio functionality.
Signed-off-by: Piotr Stankiewicz piotrs@opensource.cirrus.com Signed-off-by: Qi Zhou qi.zhou@cirrus.com Signed-off-by: Stuart Henderson stuarth@opensource.cirrus.com Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
MAINTAINERS | 5 +- drivers/mfd/Kconfig | 13 + drivers/mfd/Makefile | 2 + drivers/mfd/cs48l32-tables.c | 541 +++++++++++++++++++++++++++++++ drivers/mfd/cs48l32.c | 434 +++++++++++++++++++++++++ drivers/mfd/cs48l32.h | 28 ++ include/linux/mfd/cs48l32/core.h | 49 +++ 7 files changed, 1071 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/cs48l32-tables.c create mode 100644 drivers/mfd/cs48l32.c create mode 100644 drivers/mfd/cs48l32.h create mode 100644 include/linux/mfd/cs48l32/core.h
Nice first attempt.
Few nits below.
diff --git a/MAINTAINERS b/MAINTAINERS index 3f94ed38089b..f1d696f29f11 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5001,7 +5001,7 @@ F: include/dt-bindings/pinctrl/lochnagar.h F: include/linux/mfd/lochnagar* F: sound/soc/codecs/lochnagar-sc.c
-CIRRUS LOGIC MADERA CODEC DRIVERS +CIRRUS LOGIC MADERA/CS48L32 CODEC DRIVERS M: Charles Keepax ckeepax@opensource.cirrus.com M: Richard Fitzgerald rf@opensource.cirrus.com L: alsa-devel@alsa-project.org (moderated for non-subscribers) @@ -5009,16 +5009,19 @@ 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: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/mfd/cirrus,madera.yaml F: Documentation/devicetree/bindings/pinctrl/cirrus,madera.yaml F: Documentation/devicetree/bindings/sound/cirrus,madera.yaml F: drivers/gpio/gpio-madera* F: drivers/irqchip/irq-madera* F: drivers/mfd/cs47l* +F: drivers/mfd/cs48l* F: drivers/mfd/madera* F: drivers/pinctrl/cirrus/* F: include/dt-bindings/sound/madera* F: include/linux/irqchip/irq-madera* +F: include/linux/mfd/cs48l32/* F: include/linux/mfd/madera/* F: include/sound/madera* F: sound/soc/codecs/cs47l* diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6653d03e0fe3..2be52ba23c7a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -300,6 +300,19 @@ config MFD_CS47L92 help Support for Cirrus Logic CS42L92, CS47L92 and CS47L93 Smart Codecs
+config MFD_CS48L32
- bool "Cirrus Logic CS48L31/32/33"
- depends on SPI_MASTER
- select MFD_CORE
- select REGMAP
- select REGMAP_SPI
- select REGMAP_IRQ
- select CIRRUS_CS48L32_IRQ
- select PINCTRL
- select PINCTRL_CS48L32
- help
Support for Cirrus Logic CS48L31, CS48L32 and CS48L33 Smart Codecs.
config MFD_ASIC3 bool "Compaq ASIC3" depends on GPIOLIB diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4dd479212b3a..4edbeb9b7a31 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -91,6 +91,8 @@ obj-$(CONFIG_MFD_MADERA) += madera.o obj-$(CONFIG_MFD_MADERA_I2C) += madera-i2c.o obj-$(CONFIG_MFD_MADERA_SPI) += madera-spi.o
+obj-$(CONFIG_MFD_CS48L32) += cs48l32.o cs48l32-tables.o
obj-$(CONFIG_TPS6105X) += tps6105x.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_TPS6507X) += tps6507x.o diff --git a/drivers/mfd/cs48l32-tables.c b/drivers/mfd/cs48l32-tables.c new file mode 100644 index 000000000000..5dab9753deb0 --- /dev/null +++ b/drivers/mfd/cs48l32-tables.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Regmap tables for Cirrus Logic CS48L32 audio codec.
- Copyright (C) 2018, 2020, 2022 Cirrus Logic, Inc. and
Cirrus Logic International Semiconductor Ltd.
- */
+#include <linux/device.h> +#include <linux/module.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h>
+#include "cs48l32.h"
+static const struct reg_sequence cs48l32_reva_patch[] = {
- { 0x00001044, 0x0005000f },
- { 0x00001c34, 0x000037e8 },
- { 0x000046d8, 0x00000fe0 },
+};
+int cs48l32_patch(struct cs48l32_mfd *cs48l32) +{
- int ret;
- ret = regmap_register_patch(cs48l32->regmap, cs48l32_reva_patch,
ARRAY_SIZE(cs48l32_reva_patch));
- if (ret < 0)
dev_err(cs48l32->dev, "Error applying patch: %d\n", ret);
- return ret;
+}
+static const struct reg_default cs48l32_reg_default[] = {
- { 0x00000c08, 0xe1000001 }, /* GPIO1_CTRL1 */
- { 0x00000c0c, 0xe1000001 }, /* GPIO2_CTRL1 */
- { 0x00000c10, 0xe1000001 }, /* GPIO3_CTRL1 */
- { 0x00000c14, 0xe1000001 }, /* GPIO4_CTRL1 */
- { 0x00000c18, 0xe1000001 }, /* GPIO5_CTRL1 */
- { 0x00000c1c, 0xe1000001 }, /* GPIO6_CTRL1 */
- { 0x00000c20, 0xe1000001 }, /* GPIO7_CTRL1 */
- { 0x00000c24, 0xe1000001 }, /* GPIO8_CTRL1 */
- { 0x00000c28, 0xe1000001 }, /* GPIO9_CTRL1 */
- { 0x00000c2c, 0xe1000001 }, /* GPIO10_CTRL1 */
- { 0x00000c30, 0xe1000001 }, /* GPIO11_CTRL1 */
- { 0x00000c34, 0xe1000001 }, /* GPIO12_CTRL1 */
- { 0x00000c38, 0xe1000001 }, /* GPIO13_CTRL1 */
- { 0x00000c3c, 0xe1000001 }, /* GPIO14_CTRL1 */
- { 0x00000c40, 0xe1000001 }, /* GPIO15_CTRL1 */
- { 0x00000c44, 0xe1000001 }, /* GPIO16_CTRL1 */
- { 0x00001020, 0x00000000 }, /* OUTPUT_SYS_CLK */
- { 0x00001044, 0x0005000f }, /* AUXPDM_CTRL */
- { 0x0000105c, 0x00000000 }, /* AUXPDM_CTRL2 */
- { 0x00001400, 0x00000002 }, /* CLOCK32K */
- { 0x00001404, 0x00000404 }, /* SYSTEM_CLOCK1 */
- { 0x00001420, 0x00000003 }, /* SAMPLE_RATE1 */
- { 0x00001424, 0x00000003 }, /* SAMPLE_RATE2 */
- { 0x00001428, 0x00000003 }, /* SAMPLE_RATE3 */
- { 0x0000142c, 0x00000003 }, /* SAMPLE_RATE4 */
- { 0x00001c00, 0x00000002 }, /* FLL1_CONTROL1 */
- { 0x00001c04, 0x88203004 }, /* FLL1_CONTROL2 */
- { 0x00001c08, 0x00000000 }, /* FLL1_CONTROL3 */
- { 0x00001c0c, 0x21f05001 }, /* FLL1_CONTROL4 */
- { 0x00001ca0, 0x00000c04 }, /* FLL1_GPIO_CLOCK */
- { 0x00002000, 0x00000006 }, /* CHARGE_PUMP1 */
- { 0x00002408, 0x000003e4 }, /* LDO2_CTRL1 */
- { 0x00002410, 0x000000e6 }, /* MICBIAS_CTRL1 */
- { 0x00002418, 0x00000222 }, /* MICBIAS_CTRL5 */
- { 0x00002710, 0x00004600 }, /* IRQ1_CTRL_AOD */
- { 0x00004000, 0x00000000 }, /* INPUT_CONTROL */
- { 0x00004008, 0x00000400 }, /* INPUT_RATE_CONTROL */
- { 0x0000400c, 0x00000000 }, /* INPUT_CONTROL2 */
- { 0x00004020, 0x00050020 }, /* INPUT1_CONTROL1 */
- { 0x00004024, 0x00000000 }, /* IN1L_CONTROL1 */
- { 0x00004028, 0x10800080 }, /* IN1L_CONTROL2 */
- { 0x00004044, 0x00000000 }, /* IN1R_CONTROL1 */
- { 0x00004048, 0x10800080 }, /* IN1R_CONTROL2 */
- { 0x00004060, 0x00050020 }, /* INPUT2_CONTROL1 */
- { 0x00004064, 0x00000000 }, /* IN2L_CONTROL1 */
- { 0x00004068, 0x10800000 }, /* IN2L_CONTROL2 */
- { 0x00004084, 0x00000000 }, /* IN2R_CONTROL1 */
- { 0x00004088, 0x10800000 }, /* IN2R_CONTROL2 */
- { 0x00004244, 0x00000002 }, /* INPUT_HPF_CONTROL */
- { 0x00004248, 0x00000022 }, /* INPUT_VOL_CONTROL */
- { 0x00004300, 0x00000000 }, /* AUXPDM_CONTROL1 */
- { 0x00004304, 0x00000000 }, /* AUXPDM_CONTROL2 */
- { 0x00004308, 0x00010008 }, /* AUXPDM1_CONTROL1 */
- { 0x00004310, 0x00010008 }, /* AUXPDM2_CONTROL1 */
- { 0x00004688, 0x00000000 }, /* ADC1L_ANA_CONTROL1 */
- { 0x0000468c, 0x00000000 }, /* ADC1R_ANA_CONTROL1 */
- { 0x00006000, 0x00000000 }, /* ASP1_ENABLES1 */
- { 0x00006004, 0x00000028 }, /* ASP1_CONTROL1 */
- { 0x00006008, 0x18180200 }, /* ASP1_CONTROL2 */
- { 0x0000600c, 0x00000002 }, /* ASP1_CONTROL3 */
- { 0x00006010, 0x03020100 }, /* ASP1_FRAME_CONTROL1 */
- { 0x00006014, 0x07060504 }, /* ASP1_FRAME_CONTROL2 */
- { 0x00006020, 0x03020100 }, /* ASP1_FRAME_CONTROL5 */
- { 0x00006024, 0x07060504 }, /* ASP1_FRAME_CONTROL6 */
- { 0x00006030, 0x00000020 }, /* ASP1_DATA_CONTROL1 */
- { 0x00006040, 0x00000020 }, /* ASP1_DATA_CONTROL5 */
- { 0x00006080, 0x00000000 }, /* ASP2_ENABLES1 */
- { 0x00006084, 0x00000028 }, /* ASP2_CONTROL1 */
- { 0x00006088, 0x18180200 }, /* ASP2_CONTROL2 */
- { 0x0000608c, 0x00000002 }, /* ASP2_CONTROL3 */
- { 0x00006090, 0x03020100 }, /* ASP2_FRAME_CONTROL1 */
- { 0x000060a0, 0x03020100 }, /* ASP2_FRAME_CONTROL5 */
- { 0x000060b0, 0x00000020 }, /* ASP2_DATA_CONTROL1 */
- { 0x000060c0, 0x00000020 }, /* ASP2_DATA_CONTROL5 */
- { 0x00008200, 0x00800000 }, /* ASP1TX1_INPUT1 */
- { 0x00008204, 0x00800000 }, /* ASP1TX1_INPUT2 */
- { 0x00008208, 0x00800000 }, /* ASP1TX1_INPUT3 */
- { 0x0000820c, 0x00800000 }, /* ASP1TX1_INPUT4 */
- { 0x00008210, 0x00800000 }, /* ASP1TX2_INPUT1 */
- { 0x00008214, 0x00800000 }, /* ASP1TX2_INPUT2 */
- { 0x00008218, 0x00800000 }, /* ASP1TX2_INPUT3 */
- { 0x0000821c, 0x00800000 }, /* ASP1TX2_INPUT4 */
- { 0x00008220, 0x00800000 }, /* ASP1TX3_INPUT1 */
- { 0x00008224, 0x00800000 }, /* ASP1TX3_INPUT2 */
- { 0x00008228, 0x00800000 }, /* ASP1TX3_INPUT3 */
- { 0x0000822c, 0x00800000 }, /* ASP1TX3_INPUT4 */
- { 0x00008230, 0x00800000 }, /* ASP1TX4_INPUT1 */
- { 0x00008234, 0x00800000 }, /* ASP1TX4_INPUT2 */
- { 0x00008238, 0x00800000 }, /* ASP1TX4_INPUT3 */
- { 0x0000823c, 0x00800000 }, /* ASP1TX4_INPUT4 */
- { 0x00008240, 0x00800000 }, /* ASP1TX5_INPUT1 */
- { 0x00008244, 0x00800000 }, /* ASP1TX5_INPUT2 */
- { 0x00008248, 0x00800000 }, /* ASP1TX5_INPUT3 */
- { 0x0000824c, 0x00800000 }, /* ASP1TX5_INPUT4 */
- { 0x00008250, 0x00800000 }, /* ASP1TX6_INPUT1 */
- { 0x00008254, 0x00800000 }, /* ASP1TX6_INPUT2 */
- { 0x00008258, 0x00800000 }, /* ASP1TX6_INPUT3 */
- { 0x0000825c, 0x00800000 }, /* ASP1TX6_INPUT4 */
- { 0x00008260, 0x00800000 }, /* ASP1TX7_INPUT1 */
- { 0x00008264, 0x00800000 }, /* ASP1TX7_INPUT2 */
- { 0x00008268, 0x00800000 }, /* ASP1TX7_INPUT3 */
- { 0x0000826c, 0x00800000 }, /* ASP1TX7_INPUT4 */
- { 0x00008270, 0x00800000 }, /* ASP1TX8_INPUT1 */
- { 0x00008274, 0x00800000 }, /* ASP1TX8_INPUT2 */
- { 0x00008278, 0x00800000 }, /* ASP1TX8_INPUT3 */
- { 0x0000827c, 0x00800000 }, /* ASP1TX8_INPUT4 */
- { 0x00008300, 0x00800000 }, /* ASP2TX1_INPUT1 */
- { 0x00008304, 0x00800000 }, /* ASP2TX1_INPUT2 */
- { 0x00008308, 0x00800000 }, /* ASP2TX1_INPUT3 */
- { 0x0000830c, 0x00800000 }, /* ASP2TX1_INPUT4 */
- { 0x00008310, 0x00800000 }, /* ASP2TX2_INPUT1 */
- { 0x00008314, 0x00800000 }, /* ASP2TX2_INPUT2 */
- { 0x00008318, 0x00800000 }, /* ASP2TX2_INPUT3 */
- { 0x0000831c, 0x00800000 }, /* ASP2TX2_INPUT4 */
- { 0x00008320, 0x00800000 }, /* ASP2TX3_INPUT1 */
- { 0x00008324, 0x00800000 }, /* ASP2TX3_INPUT2 */
- { 0x00008328, 0x00800000 }, /* ASP2TX3_INPUT3 */
- { 0x0000832c, 0x00800000 }, /* ASP2TX3_INPUT4 */
- { 0x00008330, 0x00800000 }, /* ASP2TX4_INPUT1 */
- { 0x00008334, 0x00800000 }, /* ASP2TX4_INPUT2 */
- { 0x00008338, 0x00800000 }, /* ASP2TX4_INPUT3 */
- { 0x0000833c, 0x00800000 }, /* ASP2TX4_INPUT4 */
- { 0x00008980, 0x00000000 }, /* ISRC1INT1_INPUT1 */
- { 0x00008990, 0x00000000 }, /* ISRC1INT2_INPUT1 */
- { 0x000089a0, 0x00000000 }, /* ISRC1INT3_INPUT1 */
- { 0x000089b0, 0x00000000 }, /* ISRC1INT4_INPUT1 */
- { 0x000089c0, 0x00000000 }, /* ISRC1DEC1_INPUT1 */
- { 0x000089d0, 0x00000000 }, /* ISRC1DEC2_INPUT1 */
- { 0x000089e0, 0x00000000 }, /* ISRC1DEC3_INPUT1 */
- { 0x000089f0, 0x00000000 }, /* ISRC1DEC4_INPUT1 */
- { 0x00008a00, 0x00000000 }, /* ISRC2INT1_INPUT1 */
- { 0x00008a10, 0x00000000 }, /* ISRC2INT2_INPUT1 */
- { 0x00008a40, 0x00000000 }, /* ISRC2DEC1_INPUT1 */
- { 0x00008a50, 0x00000000 }, /* ISRC2DEC2_INPUT1 */
- { 0x00008a80, 0x00000000 }, /* ISRC3INT1_INPUT1 */
- { 0x00008a90, 0x00000000 }, /* ISRC3INT2_INPUT1 */
- { 0x00008ac0, 0x00000000 }, /* ISRC3DEC1_INPUT1 */
- { 0x00008ad0, 0x00000000 }, /* ISRC3DEC2_INPUT1 */
- { 0x00008b80, 0x00800000 }, /* EQ1_INPUT1 */
- { 0x00008b84, 0x00800000 }, /* EQ1_INPUT2 */
- { 0x00008b88, 0x00800000 }, /* EQ1_INPUT3 */
- { 0x00008b8c, 0x00800000 }, /* EQ1_INPUT4 */
- { 0x00008b90, 0x00800000 }, /* EQ2_INPUT1 */
- { 0x00008b94, 0x00800000 }, /* EQ2_INPUT2 */
- { 0x00008b98, 0x00800000 }, /* EQ2_INPUT3 */
- { 0x00008b9c, 0x00800000 }, /* EQ2_INPUT4 */
- { 0x00008ba0, 0x00800000 }, /* EQ3_INPUT1 */
- { 0x00008ba4, 0x00800000 }, /* EQ3_INPUT2 */
- { 0x00008ba8, 0x00800000 }, /* EQ3_INPUT3 */
- { 0x00008bac, 0x00800000 }, /* EQ3_INPUT4 */
- { 0x00008bb0, 0x00800000 }, /* EQ4_INPUT1 */
- { 0x00008bb4, 0x00800000 }, /* EQ4_INPUT2 */
- { 0x00008bb8, 0x00800000 }, /* EQ4_INPUT3 */
- { 0x00008bbc, 0x00800000 }, /* EQ4_INPUT4 */
- { 0x00008c00, 0x00800000 }, /* DRC1L_INPUT1 */
- { 0x00008c04, 0x00800000 }, /* DRC1L_INPUT2 */
- { 0x00008c08, 0x00800000 }, /* DRC1L_INPUT3 */
- { 0x00008c0c, 0x00800000 }, /* DRC1L_INPUT4 */
- { 0x00008c10, 0x00800000 }, /* DRC1R_INPUT1 */
- { 0x00008c14, 0x00800000 }, /* DRC1R_INPUT2 */
- { 0x00008c18, 0x00800000 }, /* DRC1R_INPUT3 */
- { 0x00008c1c, 0x00800000 }, /* DRC1R_INPUT4 */
- { 0x00008c20, 0x00800000 }, /* DRC2L_INPUT1 */
- { 0x00008c24, 0x00800000 }, /* DRC2L_INPUT2 */
- { 0x00008c28, 0x00800000 }, /* DRC2L_INPUT3 */
- { 0x00008c2c, 0x00800000 }, /* DRC2L_INPUT4 */
- { 0x00008c30, 0x00800000 }, /* DRC2R_INPUT1 */
- { 0x00008c34, 0x00800000 }, /* DRC2R_INPUT2 */
- { 0x00008c38, 0x00800000 }, /* DRC2R_INPUT3 */
- { 0x00008c3c, 0x00800000 }, /* DRC2R_INPUT4 */
- { 0x00008c80, 0x00800000 }, /* LHPF1_INPUT1 */
- { 0x00008c84, 0x00800000 }, /* LHPF1_INPUT2 */
- { 0x00008c88, 0x00800000 }, /* LHPF1_INPUT3 */
- { 0x00008c8c, 0x00800000 }, /* LHPF1_INPUT4 */
- { 0x00008c90, 0x00800000 }, /* LHPF2_INPUT1 */
- { 0x00008c94, 0x00800000 }, /* LHPF2_INPUT2 */
- { 0x00008c98, 0x00800000 }, /* LHPF2_INPUT3 */
- { 0x00008c9c, 0x00800000 }, /* LHPF2_INPUT4 */
- { 0x00008ca0, 0x00800000 }, /* LHPF3_INPUT1 */
- { 0x00008ca4, 0x00800000 }, /* LHPF3_INPUT2 */
- { 0x00008ca8, 0x00800000 }, /* LHPF3_INPUT3 */
- { 0x00008cac, 0x00800000 }, /* LHPF3_INPUT4 */
- { 0x00008cb0, 0x00800000 }, /* LHPF4_INPUT1 */
- { 0x00008cb4, 0x00800000 }, /* LHPF4_INPUT2 */
- { 0x00008cb8, 0x00800000 }, /* LHPF4_INPUT3 */
- { 0x00008cbc, 0x00800000 }, /* LHPF4_INPUT4 */
- { 0x00009000, 0x00800000 }, /* DSP1RX1_INPUT1 */
- { 0x00009004, 0x00800000 }, /* DSP1RX1_INPUT2 */
- { 0x00009008, 0x00800000 }, /* DSP1RX1_INPUT3 */
- { 0x0000900c, 0x00800000 }, /* DSP1RX1_INPUT4 */
- { 0x00009010, 0x00800000 }, /* DSP1RX2_INPUT1 */
- { 0x00009014, 0x00800000 }, /* DSP1RX2_INPUT2 */
- { 0x00009018, 0x00800000 }, /* DSP1RX2_INPUT3 */
- { 0x0000901c, 0x00800000 }, /* DSP1RX2_INPUT4 */
- { 0x00009020, 0x00800000 }, /* DSP1RX3_INPUT1 */
- { 0x00009024, 0x00800000 }, /* DSP1RX3_INPUT2 */
- { 0x00009028, 0x00800000 }, /* DSP1RX3_INPUT3 */
- { 0x0000902c, 0x00800000 }, /* DSP1RX3_INPUT4 */
- { 0x00009030, 0x00800000 }, /* DSP1RX4_INPUT1 */
- { 0x00009034, 0x00800000 }, /* DSP1RX4_INPUT2 */
- { 0x00009038, 0x00800000 }, /* DSP1RX4_INPUT3 */
- { 0x0000903c, 0x00800000 }, /* DSP1RX4_INPUT4 */
- { 0x00009040, 0x00800000 }, /* DSP1RX5_INPUT1 */
- { 0x00009044, 0x00800000 }, /* DSP1RX5_INPUT2 */
- { 0x00009048, 0x00800000 }, /* DSP1RX5_INPUT3 */
- { 0x0000904c, 0x00800000 }, /* DSP1RX5_INPUT4 */
- { 0x00009050, 0x00800000 }, /* DSP1RX6_INPUT1 */
- { 0x00009054, 0x00800000 }, /* DSP1RX6_INPUT2 */
- { 0x00009058, 0x00800000 }, /* DSP1RX6_INPUT3 */
- { 0x0000905c, 0x00800000 }, /* DSP1RX6_INPUT4 */
- { 0x00009060, 0x00800000 }, /* DSP1RX7_INPUT1 */
- { 0x00009064, 0x00800000 }, /* DSP1RX7_INPUT2 */
- { 0x00009068, 0x00800000 }, /* DSP1RX7_INPUT3 */
- { 0x0000906c, 0x00800000 }, /* DSP1RX7_INPUT4 */
- { 0x00009070, 0x00800000 }, /* DSP1RX8_INPUT1 */
- { 0x00009074, 0x00800000 }, /* DSP1RX8_INPUT2 */
- { 0x00009078, 0x00800000 }, /* DSP1RX8_INPUT3 */
- { 0x0000907c, 0x00800000 }, /* DSP1RX8_INPUT4 */
- { 0x0000a400, 0x00000000 }, /* ISRC1_CONTROL1 */
- { 0x0000a404, 0x00000000 }, /* ISRC1_CONTROL2 */
- { 0x0000a510, 0x00000000 }, /* ISRC2_CONTROL1 */
- { 0x0000a514, 0x00000000 }, /* ISRC2_CONTROL2 */
- { 0x0000a620, 0x00000000 }, /* ISRC3_CONTROL1 */
- { 0x0000a624, 0x00000000 }, /* ISRC3_CONTROL2 */
- { 0x0000a800, 0x00000000 }, /* FX_SAMPLE_RATE */
- { 0x0000a808, 0x00000000 }, /* EQ_CONTROL1 */
- { 0x0000a80c, 0x00000000 }, /* EQ_CONTROL2 */
- { 0x0000a810, 0x0c0c0c0c }, /* EQ1_GAIN1 */
- { 0x0000a814, 0x0000000c }, /* EQ1_GAIN2 */
- { 0x0000a818, 0x03fe0fc8 }, /* EQ1_BAND1_COEFF1 */
- { 0x0000a81c, 0x00000b75 }, /* EQ1_BAND1_COEFF2 */
- { 0x0000a820, 0x000000e0 }, /* EQ1_BAND1_PG */
- { 0x0000a824, 0xf1361ec4 }, /* EQ1_BAND2_COEFF1 */
- { 0x0000a828, 0x00000409 }, /* EQ1_BAND2_COEFF2 */
- { 0x0000a82c, 0x000004cc }, /* EQ1_BAND2_PG */
- { 0x0000a830, 0xf3371c9b }, /* EQ1_BAND3_COEFF1 */
- { 0x0000a834, 0x0000040b }, /* EQ1_BAND3_COEFF2 */
- { 0x0000a838, 0x00000cbb }, /* EQ1_BAND3_PG */
- { 0x0000a83c, 0xf7d916f8 }, /* EQ1_BAND4_COEFF1 */
- { 0x0000a840, 0x0000040a }, /* EQ1_BAND4_COEFF2 */
- { 0x0000a844, 0x00001f14 }, /* EQ1_BAND4_PG */
- { 0x0000a848, 0x0563058c }, /* EQ1_BAND5_COEFF1 */
- { 0x0000a84c, 0x00000000 }, /* EQ1_BAND5_COEFF1 + 4 */
- { 0x0000a850, 0x00004000 }, /* EQ1_BAND5_PG */
- { 0x0000a854, 0x0c0c0c0c }, /* EQ2_GAIN1 */
- { 0x0000a858, 0x0000000c }, /* EQ2_GAIN2 */
- { 0x0000a85c, 0x03fe0fc8 }, /* EQ2_BAND1_COEFF1 */
- { 0x0000a860, 0x00000b75 }, /* EQ2_BAND1_COEFF2 */
- { 0x0000a864, 0x000000e0 }, /* EQ2_BAND1_PG */
- { 0x0000a868, 0xf1361ec4 }, /* EQ2_BAND2_COEFF1 */
- { 0x0000a86c, 0x00000409 }, /* EQ2_BAND2_COEFF2 */
- { 0x0000a870, 0x000004cc }, /* EQ2_BAND2_PG */
- { 0x0000a874, 0xf3371c9b }, /* EQ2_BAND3_COEFF1 */
- { 0x0000a878, 0x0000040b }, /* EQ2_BAND3_COEFF2 */
- { 0x0000a87c, 0x00000cbb }, /* EQ2_BAND3_PG */
- { 0x0000a880, 0xf7d916f8 }, /* EQ2_BAND4_COEFF1 */
- { 0x0000a884, 0x0000040a }, /* EQ2_BAND4_COEFF2 */
- { 0x0000a888, 0x00001f14 }, /* EQ2_BAND4_PG */
- { 0x0000a88c, 0x0563058c }, /* EQ2_BAND5_COEFF1 */
- { 0x0000a890, 0x00000000 }, /* EQ2_BAND5_COEFF1 + 4 */
- { 0x0000a894, 0x00004000 }, /* EQ2_BAND5_PG */
- { 0x0000a898, 0x0c0c0c0c }, /* EQ3_GAIN1 */
- { 0x0000a89c, 0x0000000c }, /* EQ3_GAIN2 */
- { 0x0000a8a0, 0x03fe0fc8 }, /* EQ3_BAND1_COEFF1 */
- { 0x0000a8a4, 0x00000b75 }, /* EQ3_BAND1_COEFF2 */
- { 0x0000a8a8, 0x000000e0 }, /* EQ3_BAND1_PG */
- { 0x0000a8ac, 0xf1361ec4 }, /* EQ3_BAND2_COEFF1 */
- { 0x0000a8b0, 0x00000409 }, /* EQ3_BAND2_COEFF2 */
- { 0x0000a8b4, 0x000004cc }, /* EQ3_BAND2_PG */
- { 0x0000a8b8, 0xf3371c9b }, /* EQ3_BAND3_COEFF1 */
- { 0x0000a8bc, 0x0000040b }, /* EQ3_BAND3_COEFF2 */
- { 0x0000a8c0, 0x00000cbb }, /* EQ3_BAND3_PG */
- { 0x0000a8c4, 0xf7d916f8 }, /* EQ3_BAND4_COEFF1 */
- { 0x0000a8c8, 0x0000040a }, /* EQ3_BAND4_COEFF2 */
- { 0x0000a8cc, 0x00001f14 }, /* EQ3_BAND4_PG */
- { 0x0000a8d0, 0x0563058c }, /* EQ3_BAND5_COEFF1 */
- { 0x0000a8d4, 0x00000000 }, /* EQ3_BAND5_COEFF1 + 4 */
- { 0x0000a8d8, 0x00004000 }, /* EQ3_BAND5_PG */
- { 0x0000a8dc, 0x0c0c0c0c }, /* EQ4_GAIN1 */
- { 0x0000a8e0, 0x0000000c }, /* EQ4_GAIN2 */
- { 0x0000a8e4, 0x03fe0fc8 }, /* EQ4_BAND1_COEFF1 */
- { 0x0000a8e8, 0x00000b75 }, /* EQ4_BAND1_COEFF2 */
- { 0x0000a8ec, 0x000000e0 }, /* EQ4_BAND1_PG */
- { 0x0000a8f0, 0xf1361ec4 }, /* EQ4_BAND2_COEFF1 */
- { 0x0000a8f4, 0x00000409 }, /* EQ4_BAND2_COEFF2 */
- { 0x0000a8f8, 0x000004cc }, /* EQ4_BAND2_PG */
- { 0x0000a8fc, 0xf3371c9b }, /* EQ4_BAND3_COEFF1 */
- { 0x0000a900, 0x0000040b }, /* EQ4_BAND3_COEFF2 */
- { 0x0000a904, 0x00000cbb }, /* EQ4_BAND3_PG */
- { 0x0000a908, 0xf7d916f8 }, /* EQ4_BAND4_COEFF1 */
- { 0x0000a90c, 0x0000040a }, /* EQ4_BAND4_COEFF2 */
- { 0x0000a910, 0x00001f14 }, /* EQ4_BAND4_PG */
- { 0x0000a914, 0x0563058c }, /* EQ4_BAND5_COEFF1 */
- { 0x0000a918, 0x00000000 }, /* EQ4_BAND5_COEFF1 + 4 */
- { 0x0000a91c, 0x00004000 }, /* EQ4_BAND5_PG */
- { 0x0000aa30, 0x00000000 }, /* LHPF_CONTROL1 */
- { 0x0000aa34, 0x00000000 }, /* LHPF_CONTROL2 */
- { 0x0000aa38, 0x00000000 }, /* LHPF1_COEFF */
- { 0x0000aa3c, 0x00000000 }, /* LHPF2_COEFF */
- { 0x0000aa40, 0x00000000 }, /* LHPF3_COEFF */
- { 0x0000aa44, 0x00000000 }, /* LHPF4_COEFF */
- { 0x0000ab00, 0x00000000 }, /* DRC1_CONTROL1 */
- { 0x0000ab04, 0x49130018 }, /* DRC1_CONTROL2 */
- { 0x0000ab08, 0x00000018 }, /* DRC1_CONTROL3 */
- { 0x0000ab0c, 0x00000000 }, /* DRC1_CONTROL4 */
- { 0x0000ab14, 0x00000000 }, /* DRC2_CONTROL1 */
- { 0x0000ab18, 0x49130018 }, /* DRC2_CONTROL2 */
- { 0x0000ab1c, 0x00000018 }, /* DRC2_CONTROL3 */
- { 0x0000ab20, 0x00000000 }, /* DRC2_CONTROL4 */
- { 0x0000b000, 0x00000000 }, /* TONE_GENERATOR1 */
- { 0x0000b004, 0x00100000 }, /* TONE_GENERATOR2 */
- { 0x0000b400, 0x00000000 }, /* COMFORT_NOISE_GENERATOR */
- { 0x0000b800, 0x00000000 }, /* US_CONTROL */
- { 0x0000b804, 0x00002020 }, /* US1_CONTROL */
- { 0x0000b808, 0x00000000 }, /* US1_DET_CONTROL */
- { 0x0000b814, 0x00002020 }, /* US2_CONTROL */
- { 0x0000b818, 0x00000000 }, /* US2_DET_CONTROL */
- { 0x00018110, 0xffffffff }, /* IRQ1_MASK_1 */
- { 0x00018114, 0xfffffff7 }, /* IRQ1_MASK_2 */
- { 0x00018118, 0xffffffff }, /* IRQ1_MASK_3 */
- { 0x0001811c, 0xffffffff }, /* IRQ1_MASK_4 */
- { 0x00018120, 0xffffffff }, /* IRQ1_MASK_5 */
- { 0x00018124, 0xffffffff }, /* IRQ1_MASK_6 */
- { 0x00018128, 0xffffffff }, /* IRQ1_MASK_7 */
- { 0x0001812c, 0xffffffff }, /* IRQ1_MASK_8 */
- { 0x00018130, 0xffffffff }, /* IRQ1_MASK_9 */
- { 0x00018134, 0xffffffff }, /* IRQ1_MASK_10 */
- { 0x00018138, 0xffffffff }, /* IRQ1_MASK_11 */
+};
+static bool cs48l32_readable_register(struct device *dev, unsigned int reg) +{
- switch (reg) {
- case CS48L32_DEVID:
- case CS48L32_REVID:
- case CS48L32_OTPID:
- case CS48L32_SFT_RESET:
- case CS48L32_CTRL_IF_DEBUG3:
- case CS48L32_MCU_CTRL1:
- case CS48L32_GPIO1_CTRL1 ... CS48L32_GPIO16_CTRL1:
- case CS48L32_OUTPUT_SYS_CLK:
- case CS48L32_AUXPDM_CTRL:
- case CS48L32_AUXPDM_CTRL2:
- case CS48L32_CLOCK32K:
- case CS48L32_SYSTEM_CLOCK1 ... CS48L32_SYSTEM_CLOCK2:
- case CS48L32_SAMPLE_RATE1 ... CS48L32_SAMPLE_RATE4:
- case CS48L32_FLL1_CONTROL1 ... CS48L32_FLL1_GPIO_CLOCK:
- case CS48L32_CHARGE_PUMP1:
- case CS48L32_LDO2_CTRL1:
- case CS48L32_MICBIAS_CTRL1:
- case CS48L32_MICBIAS_CTRL5:
- case CS48L32_IRQ1_CTRL_AOD:
- case CS48L32_INPUT_CONTROL:
- case CS48L32_INPUT_STATUS:
- case CS48L32_INPUT_RATE_CONTROL:
- case CS48L32_INPUT_CONTROL2:
- case CS48L32_INPUT_CONTROL3:
- case CS48L32_INPUT1_CONTROL1:
- case CS48L32_IN1L_CONTROL1 ... CS48L32_IN1L_CONTROL2:
- case CS48L32_IN1R_CONTROL1 ... CS48L32_IN1R_CONTROL2:
- case CS48L32_INPUT2_CONTROL1:
- case CS48L32_IN2L_CONTROL1 ... CS48L32_IN2L_CONTROL2:
- case CS48L32_IN2R_CONTROL1 ... CS48L32_IN2R_CONTROL2:
- case CS48L32_INPUT_HPF_CONTROL:
- case CS48L32_INPUT_VOL_CONTROL:
- case CS48L32_AUXPDM_CONTROL1:
- case CS48L32_AUXPDM_CONTROL2:
- case CS48L32_AUXPDM1_CONTROL1:
- case CS48L32_AUXPDM2_CONTROL1:
- case CS48L32_ADC1L_ANA_CONTROL1:
- case CS48L32_ADC1R_ANA_CONTROL1:
- case CS48L32_ASP1_ENABLES1 ... CS48L32_ASP1_DATA_CONTROL5:
- case CS48L32_ASP2_ENABLES1 ... CS48L32_ASP2_DATA_CONTROL5:
- case CS48L32_ASP1TX1_INPUT1 ... CS48L32_ASP1TX8_INPUT4:
- case CS48L32_ASP2TX1_INPUT1 ... CS48L32_ASP2TX4_INPUT4:
- case CS48L32_ISRC1INT1_INPUT1 ... CS48L32_ISRC1DEC4_INPUT1:
- case CS48L32_ISRC2INT1_INPUT1 ... CS48L32_ISRC2DEC2_INPUT1:
- case CS48L32_ISRC3INT1_INPUT1 ... CS48L32_ISRC3DEC2_INPUT1:
- case CS48L32_EQ1_INPUT1 ... CS48L32_EQ4_INPUT4:
- case CS48L32_DRC1L_INPUT1 ... CS48L32_DRC1R_INPUT4:
- case CS48L32_DRC2L_INPUT1 ... CS48L32_DRC2R_INPUT4:
- case CS48L32_LHPF1_INPUT1 ... CS48L32_LHPF1_INPUT4:
- case CS48L32_LHPF2_INPUT1 ... CS48L32_LHPF2_INPUT4:
- case CS48L32_LHPF3_INPUT1 ... CS48L32_LHPF3_INPUT4:
- case CS48L32_LHPF4_INPUT1 ... CS48L32_LHPF4_INPUT4:
- case CS48L32_DSP1RX1_INPUT1 ... CS48L32_DSP1RX8_INPUT4:
- case CS48L32_ISRC1_CONTROL1 ... CS48L32_ISRC1_CONTROL2:
- case CS48L32_ISRC2_CONTROL1 ... CS48L32_ISRC2_CONTROL2:
- case CS48L32_ISRC3_CONTROL1 ... CS48L32_ISRC3_CONTROL2:
- case CS48L32_FX_SAMPLE_RATE:
- case CS48L32_EQ_CONTROL1 ... CS48L32_EQ_CONTROL2:
- case CS48L32_EQ1_GAIN1 ... CS48L32_EQ1_BAND5_PG:
- case CS48L32_EQ2_GAIN1 ... CS48L32_EQ2_BAND5_PG:
- case CS48L32_EQ3_GAIN1 ... CS48L32_EQ3_BAND5_PG:
- case CS48L32_EQ4_GAIN1 ... CS48L32_EQ4_BAND5_PG:
- case CS48L32_LHPF_CONTROL1 ... CS48L32_LHPF_CONTROL2:
- case CS48L32_LHPF1_COEFF ... CS48L32_LHPF4_COEFF:
- case CS48L32_DRC1_CONTROL1 ... CS48L32_DRC1_CONTROL4:
- case CS48L32_DRC2_CONTROL1 ... CS48L32_DRC2_CONTROL4:
- case CS48L32_TONE_GENERATOR1 ... CS48L32_TONE_GENERATOR2:
- case CS48L32_COMFORT_NOISE_GENERATOR:
- case CS48L32_US_CONTROL:
- case CS48L32_US1_CONTROL:
- case CS48L32_US1_DET_CONTROL:
- case CS48L32_US2_CONTROL:
- case CS48L32_US2_DET_CONTROL:
- case CS48L32_DSP1_XM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_XM_SRAM_IBUS_SETUP_24:
- case CS48L32_DSP1_YM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_YM_SRAM_IBUS_SETUP_8:
- case CS48L32_DSP1_PM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_PM_SRAM_IBUS_SETUP_7:
- case CS48L32_IRQ1_STATUS:
- case CS48L32_IRQ1_EINT_1 ... CS48L32_IRQ1_EINT_11:
- case CS48L32_IRQ1_STS_1 ... CS48L32_IRQ1_STS_11:
- case CS48L32_IRQ1_MASK_1 ... CS48L32_IRQ1_MASK_11:
- case CS48L32_DSP1_XMEM_PACKED_0 ... CS48L32_DSP1_XMEM_PACKED_147455:
- case CS48L32_DSP1_SYS_INFO_ID ... CS48L32_DSP1_AHBM_WINDOW_DEBUG_1:
- case CS48L32_DSP1_XMEM_UNPACKED24_0 ... CS48L32_DSP1_XMEM_UNPACKED24_196607:
- case CS48L32_DSP1_CLOCK_FREQ ... CS48L32_DSP1_SAMPLE_RATE_TX8:
- case CS48L32_DSP1_SCRATCH1 ... CS48L32_DSP1_SCRATCH4:
- case CS48L32_DSP1_CCM_CORE_CONTROL ... CS48L32_DSP1_STREAM_ARB_RESYNC_MSK1:
- case CS48L32_DSP1_YMEM_PACKED_0 ... CS48L32_DSP1_YMEM_PACKED_49151:
- case CS48L32_DSP1_YMEM_UNPACKED24_0 ... CS48L32_DSP1_YMEM_UNPACKED24_65535:
- case CS48L32_DSP1_PMEM_0 ... CS48L32_DSP1_PMEM_71679:
return true;
- default:
return false;
- }
+}
+static bool cs48l32_volatile_register(struct device *dev, unsigned int reg) +{
- switch (reg) {
- case CS48L32_DEVID:
- case CS48L32_REVID:
- case CS48L32_OTPID:
- case CS48L32_SFT_RESET:
- case CS48L32_CTRL_IF_DEBUG3:
- case CS48L32_MCU_CTRL1:
- case CS48L32_SYSTEM_CLOCK2:
- case CS48L32_FLL1_CONTROL5:
- case CS48L32_FLL1_CONTROL6:
- case CS48L32_INPUT_STATUS:
- case CS48L32_INPUT_CONTROL3:
- case CS48L32_DSP1_XM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_XM_SRAM_IBUS_SETUP_24:
- case CS48L32_DSP1_YM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_YM_SRAM_IBUS_SETUP_8:
- case CS48L32_DSP1_PM_SRAM_IBUS_SETUP_0 ... CS48L32_DSP1_PM_SRAM_IBUS_SETUP_7:
- case CS48L32_IRQ1_STATUS:
- case CS48L32_IRQ1_EINT_1 ... CS48L32_IRQ1_EINT_11:
- case CS48L32_IRQ1_STS_1 ... CS48L32_IRQ1_STS_11:
- case CS48L32_DSP1_XMEM_PACKED_0 ... CS48L32_DSP1_XMEM_PACKED_147455:
- case CS48L32_DSP1_SYS_INFO_ID ... CS48L32_DSP1_AHBM_WINDOW_DEBUG_1:
- case CS48L32_DSP1_XMEM_UNPACKED24_0 ... CS48L32_DSP1_XMEM_UNPACKED24_196607:
- case CS48L32_DSP1_CLOCK_FREQ ... CS48L32_DSP1_SAMPLE_RATE_TX8:
- case CS48L32_DSP1_SCRATCH1 ... CS48L32_DSP1_SCRATCH4:
- case CS48L32_DSP1_CCM_CORE_CONTROL ... CS48L32_DSP1_STREAM_ARB_RESYNC_MSK1:
- case CS48L32_DSP1_YMEM_PACKED_0 ... CS48L32_DSP1_YMEM_PACKED_49151:
- case CS48L32_DSP1_YMEM_UNPACKED24_0 ... CS48L32_DSP1_YMEM_UNPACKED24_65535:
- case CS48L32_DSP1_PMEM_0 ... CS48L32_DSP1_PMEM_71679:
return true;
- default:
return false;
- }
+}
+/*
- The bus bridge requires DSP packed memory registers to be accessed in
- aligned block multiples.
- Mark precious to prevent regmap debugfs causing an illegal bus transaction.
- */
+static bool cs48l32_precious_register(struct device *dev, unsigned int reg) +{
- switch (reg) {
- case CS48L32_DSP1_XMEM_PACKED_0 ... CS48L32_DSP1_XMEM_PACKED_147455:
- case CS48L32_DSP1_YMEM_PACKED_0 ... CS48L32_DSP1_YMEM_PACKED_49151:
- case CS48L32_DSP1_PMEM_0 ... CS48L32_DSP1_PMEM_71679:
return true;
- default:
return false;
- }
+}
+static const struct regmap_config cs48l32_spi_regmap = {
- .name = "cs48l32",
- .reg_bits = 32,
- .reg_stride = 4,
- .pad_bits = 32,
- .val_bits = 32,
- .reg_format_endian = REGMAP_ENDIAN_BIG,
- .val_format_endian = REGMAP_ENDIAN_BIG,
- .max_register = CS48L32_DSP1_PMEM_71679,
- .readable_reg = &cs48l32_readable_register,
- .volatile_reg = &cs48l32_volatile_register,
- .precious_reg = &cs48l32_precious_register,
- .cache_type = REGCACHE_RBTREE,
- .reg_defaults = cs48l32_reg_default,
- .num_reg_defaults = ARRAY_SIZE(cs48l32_reg_default),
+};
+int cs48l32_init_regmap(struct spi_device *spi, struct cs48l32_mfd *cs48l32) +{
- cs48l32->regmap = devm_regmap_init_spi(spi, &cs48l32_spi_regmap);
- if (IS_ERR(cs48l32->regmap))
return PTR_ERR(cs48l32->regmap);
- return 0;
+} diff --git a/drivers/mfd/cs48l32.c b/drivers/mfd/cs48l32.c new file mode 100644 index 000000000000..9506a1e0560b --- /dev/null +++ b/drivers/mfd/cs48l32.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- MFD for Cirrus Logic CS48L32 audio codec.
The name MFD should be omitted.
If you must, you can say 'core driver' or something.
- Copyright (C) 2016-2018, 2020, 2022 Cirrus Logic, Inc. and
Cirrus Logic International Semiconductor Ltd.
- */
+#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/mfd/core.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/spi/spi.h>
Could you review these include files please.
Are they all utilised?
+#include "cs48l32.h"
+static const char * const cs48l32_core_supplies[] = {
- "VDD_A",
- "VDD_IO",
+};
+static const char * const cs48l32_supplies[] = {
- "VOUT_MIC",
- "VDD_CP",
+};
+static const struct mfd_cell cs48l32_devs[] = {
- { .name = "cs48l32-pinctrl", },
- { .name = "cs48l32-irq", },
- { .name = "cs48l32-micsupp", },
- {
.name = "cs48l32-codec",
.parent_supplies = cs48l32_supplies,
.num_parent_supplies = ARRAY_SIZE(cs48l32_supplies),
- },
+};
+static int cs48l32_wait_for_boot(struct cs48l32_mfd *cs48l32) +{
- unsigned int val;
- int i, ret;
- /* regmap_read_poll_timeout would fail on read errors so roll our own */
It would, why? Is it fixable? Has it been reported?
- for (i = 0; i < CS48L32_BOOT_TIMEOUT_MS; ++i) {
val = 0;
What happens if you don't initialise this?
Why is that better than checking the return value?
regmap_read(cs48l32->regmap, CS48L32_IRQ1_EINT_2, &val);
if (val & CS48L32_BOOT_DONE_EINT1_MASK)
break;
usleep_range(1000, 2000);
- }
- if (i == CS48L32_BOOT_TIMEOUT_MS) {
dev_err(cs48l32->dev, "BOOT_DONE timed out\n");
return -ETIMEDOUT;
- }
- ret = regmap_read(cs48l32->regmap, CS48L32_MCU_CTRL1, &val);
- if (ret) {
dev_err(cs48l32->dev, "Failed to read MCU_CTRL1: %d\n", ret);
return ret;
- }
- if (val & BIT(CS48L32_MCU_STS_SHIFT)) {
dev_err(cs48l32->dev, "MCU boot failed\n");
return -EIO;
- }
- ret = regmap_set_bits(cs48l32->regmap, CS48L32_CTRL_IF_DEBUG3, CS48L32_SEEN_BOOT_DONE);
- if (ret) {
dev_err(cs48l32->dev, "Failed to write CTRL_IF_DEBUG3: %d\n", ret);
return ret;
- }
- pm_runtime_mark_last_busy(cs48l32->dev);
- return 0;
+}
+static int cs48l32_soft_reset(struct cs48l32_mfd *cs48l32) +{
- int ret;
- ret = regmap_write(cs48l32->regmap, CS48L32_SFT_RESET, CS48L32_SFT_RESET_MAGIC);
- if (ret != 0) {
dev_err(cs48l32->dev, "Failed to write soft reset: %d\n", ret);
return ret;
- }
- usleep_range(2000, 3000);
- return 0;
+}
+static void cs48l32_enable_hard_reset(struct cs48l32_mfd *cs48l32) +{
- if (cs48l32->reset_gpio)
gpiod_set_value_cansleep(cs48l32->reset_gpio, 0);
+}
+static void cs48l32_disable_hard_reset(struct cs48l32_mfd *cs48l32) +{
- if (cs48l32->reset_gpio) {
gpiod_set_value_cansleep(cs48l32->reset_gpio, 1);
usleep_range(2000, 3000);
- }
+}
+static int __maybe_unused cs48l32_runtime_resume(struct device *dev) +{
- struct cs48l32_mfd *cs48l32 = dev_get_drvdata(dev);
- unsigned int val;
- int ret;
- dev_dbg(cs48l32->dev, "Leaving sleep mode\n");
- ret = regulator_enable(cs48l32->vdd_d);
- if (ret) {
dev_err(cs48l32->dev, "Failed to enable VDD_D: %d\n", ret);
return ret;
- }
- usleep_range(2000, 3000);
- regcache_cache_only(cs48l32->regmap, false);
- /* Did it power down during suspend? */
- ret = regmap_read(cs48l32->regmap, CS48L32_CTRL_IF_DEBUG3, &val);
- if ((ret == 0) && (val & CS48L32_SEEN_BOOT_DONE)) {
dev_dbg(cs48l32->dev, "VDD_D didn't power off during suspend\n");
- } else {
ret = cs48l32_wait_for_boot(cs48l32);
if (ret)
goto err;
/* Flag that the registers have reset to defaults */
regcache_mark_dirty(cs48l32->regmap);
- }
- ret = regcache_sync(cs48l32->regmap);
- if (ret) {
dev_err(cs48l32->dev, "Failed to restore register cache\n");
goto err;
- }
- return 0;
+err:
- regcache_cache_only(cs48l32->regmap, true);
- regulator_disable(cs48l32->vdd_d);
- return ret;
+}
+static int __maybe_unused cs48l32_runtime_suspend(struct device *dev) +{
- struct cs48l32_mfd *cs48l32 = dev_get_drvdata(dev);
- dev_dbg(cs48l32->dev, "Entering sleep mode\n");
- regcache_cache_only(cs48l32->regmap, true);
- regulator_disable(cs48l32->vdd_d);
- return 0;
+}
+static const struct dev_pm_ops cs48l32_pm_ops = {
- SET_RUNTIME_PM_OPS(cs48l32_runtime_suspend, cs48l32_runtime_resume, NULL)
+};
+static const struct __maybe_unused of_device_id cs48l32_of_match[] = {
- { .compatible = "cirrus,cs48l31", },
- { .compatible = "cirrus,cs48l32", },
- { .compatible = "cirrus,cs48l33", },
- {},
+};
+static int cs48l32_configure_clk32k(struct cs48l32_mfd *cs48l32) +{
- int ret = 0;
- ret = clk_prepare_enable(cs48l32->mclk1);
- if (ret) {
dev_err(cs48l32->dev, "Failed to enable 32k clock: %d\n", ret);
return ret;
- }
- ret = regmap_update_bits(cs48l32->regmap, CS48L32_CLOCK32K,
CS48L32_CLK_32K_EN_MASK | CS48L32_CLK_32K_SRC_MASK,
CS48L32_CLK_32K_EN_MASK | CS48L32_32K_MCLK1);
- if (ret) {
dev_err(cs48l32->dev, "Failed to init 32k clock: %d\n", ret);
clk_disable_unprepare(cs48l32->mclk1);
return ret;
- }
- return 0;
+}
+static int cs48l32_get_clocks(struct cs48l32_mfd *cs48l32) +{
- cs48l32->mclk1 = devm_clk_get_optional(cs48l32->dev, "mclk1");
- if (IS_ERR(cs48l32->mclk1))
return dev_err_probe(cs48l32->dev, PTR_ERR(cs48l32->mclk1),
"Failed to get mclk1\n");
- return 0;
+}
+static int cs48l32_get_reset_gpio(struct cs48l32_mfd *cs48l32) +{
- struct gpio_desc *reset;
- reset = devm_gpiod_get_optional(cs48l32->dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(reset))
return dev_err_probe(cs48l32->dev, PTR_ERR(reset), "Failed to request /RESET\n");
- /*
* A hard reset is needed for full reset of the chip. We allow running
* without hard reset only because it can be useful for early
* prototyping and some debugging, but we need to warn it's not ideal.
*/
- if (!reset)
dev_warn(cs48l32->dev, "Running without reset GPIO is not recommended\n");
- cs48l32->reset_gpio = reset;
- return 0;
+}
+static int cs48l32_spi_probe(struct spi_device *spi) +{
- struct device *dev = &spi->dev;
- struct cs48l32_mfd *cs48l32;
Prefer if you didn't put 'mfd' in the name.
This is more traditional:
struct cs48l32 *ddata;
- unsigned int hwid, rev, otp_rev;
- int i, ret;
- cs48l32 = devm_kzalloc(&spi->dev, sizeof(*cs48l32), GFP_KERNEL);
- if (!cs48l32)
return -ENOMEM;
- ret = cs48l32_init_regmap(spi, cs48l32);
- if (ret)
return dev_err_probe(&spi->dev, ret, "Failed to allocate regmap\n");
- cs48l32->dev = dev;
- cs48l32->irq = spi->irq;
- dev_set_drvdata(cs48l32->dev, cs48l32);
- BLOCKING_INIT_NOTIFIER_HEAD(&cs48l32->notifier);
- regcache_cache_only(cs48l32->regmap, true);
- ret = cs48l32_get_reset_gpio(cs48l32);
- if (ret)
return ret;
- ret = cs48l32_get_clocks(cs48l32);
- if (ret)
return ret;
- for (i = 0; i < ARRAY_SIZE(cs48l32_core_supplies); i++)
cs48l32->core_supplies[i].supply = cs48l32_core_supplies[i];
- cs48l32->num_core_supplies = ARRAY_SIZE(cs48l32_core_supplies);
Set this before the for(), then use the variable instead of ARRAY_SIZE() again.
- ret = devm_regulator_bulk_get(dev, cs48l32->num_core_supplies, cs48l32->core_supplies);
- if (ret)
return dev_err_probe(dev, ret, "Failed to request core supplies\n");
- cs48l32->vdd_d = devm_regulator_get(cs48l32->dev, "VDD_D");
- if (IS_ERR(cs48l32->vdd_d))
return dev_err_probe(dev, PTR_ERR(cs48l32->vdd_d), "Failed to request VDD_D\n");
- ret = regulator_set_voltage(cs48l32->vdd_d, 1200000, 1200000);
- if (ret)
return dev_err_probe(dev, ret, "Failed to request VDD_D=1.2v\n");
- ret = regulator_bulk_enable(cs48l32->num_core_supplies, cs48l32->core_supplies);
- if (ret)
return dev_err_probe(dev, ret, "Failed to enable core supplies\n");
- ret = regulator_enable(cs48l32->vdd_d);
- if (ret) {
dev_err(dev, "Failed to enable VDD_D: %d\n", ret);
goto err_enable;
- }
- cs48l32_disable_hard_reset(cs48l32);
- regcache_cache_only(cs48l32->regmap, false);
- /* If we don't have a reset GPIO use a soft reset */
- if (!cs48l32->reset_gpio) {
ret = cs48l32_soft_reset(cs48l32);
if (ret)
goto err_reset;
- }
- ret = cs48l32_wait_for_boot(cs48l32);
- if (ret) {
dev_err(cs48l32->dev, "Device failed initial boot: %d\n", ret);
goto err_reset;
- }
- ret = regmap_read(cs48l32->regmap, CS48L32_DEVID, &hwid);
- if (ret) {
dev_err(dev, "Failed to read ID register: %d\n", ret);
goto err_reset;
- }
- hwid &= CS48L32_DEVID_MASK;
- switch (hwid) {
- case CS48L31_SILICON_ID:
- case CS48L32_SILICON_ID:
- case CS48L33_SILICON_ID:
break;
- default:
dev_err(cs48l32->dev, "Unknown device ID: %x\n", hwid);
ret = -EINVAL;
goto err_reset;
- }
- ret = regmap_read(cs48l32->regmap, CS48L32_REVID, &rev);
- if (ret) {
dev_err(dev, "Failed to read revision register: %d\n", ret);
goto err_reset;
- }
- rev &= CS48L32_AREVID_MASK | CS48L32_MTLREVID_MASK;
- ret = regmap_read(cs48l32->regmap, CS48L32_OTPID, &otp_rev);
- if (ret) {
dev_err(dev, "Failed to read OTP revision register: %d\n", ret);
goto err_reset;
- }
- otp_rev &= CS48L32_OTPID_MASK;
- cs48l32->part = hwid & 0xff;
- dev_info(dev, "CS48L%x revision %X%u.%u\n", cs48l32->part,
rev >> CS48L32_AREVID_SHIFT, rev & CS48L32_MTLREVID_MASK, otp_rev);
- /* Apply hardware patch */
- ret = cs48l32_patch(cs48l32);
- if (ret) {
dev_err(cs48l32->dev, "Failed to apply patch %d\n", ret);
goto err_reset;
- }
- ret = cs48l32_configure_clk32k(cs48l32);
- if (ret)
goto err_reset;
- pm_runtime_set_active(cs48l32->dev);
- pm_runtime_enable(cs48l32->dev);
- pm_runtime_set_autosuspend_delay(cs48l32->dev, 100);
- pm_runtime_use_autosuspend(cs48l32->dev);
- ret = mfd_add_devices(cs48l32->dev, PLATFORM_DEVID_NONE,
cs48l32_devs, ARRAY_SIZE(cs48l32_devs), NULL, 0, NULL);
- if (ret) {
dev_err(cs48l32->dev, "Failed to add subdevices: %d\n", ret);
goto err_clk32k;
- }
- return 0;
+err_clk32k:
- clk_disable_unprepare(cs48l32->mclk1);
+err_reset:
- cs48l32_enable_hard_reset(cs48l32);
- regulator_disable(cs48l32->vdd_d);
+err_enable:
- regulator_bulk_disable(cs48l32->num_core_supplies, cs48l32->core_supplies);
- return ret;
+}
+static void cs48l32_spi_remove(struct spi_device *spi) +{
- struct cs48l32_mfd *cs48l32 = spi_get_drvdata(spi);
- /* Prevent any IRQs being serviced while we clean up */
- disable_irq(cs48l32->irq);
- mfd_remove_devices(cs48l32->dev);
devm_*?
- pm_runtime_disable(cs48l32->dev);
- regulator_disable(cs48l32->vdd_d);
- clk_disable_unprepare(cs48l32->mclk1);
- cs48l32_enable_hard_reset(cs48l32);
- regulator_bulk_disable(cs48l32->num_core_supplies, cs48l32->core_supplies);
+}
+static const struct spi_device_id cs48l32_spi_ids[] = {
- { "cs48l31", },
- { "cs48l32", },
- { "cs48l33", },
- { },
+}; +MODULE_DEVICE_TABLE(spi, cs48l32_spi_ids);
+static struct spi_driver cs48l32_spi_driver = {
- .driver = {
.name = "cs48l32",
.owner = THIS_MODULE,
.pm = &cs48l32_pm_ops,
.of_match_table = of_match_ptr(cs48l32_of_match),
- },
- .probe = &cs48l32_spi_probe,
- .remove = &cs48l32_spi_remove,
- .id_table = cs48l32_spi_ids,
+};
Remove this line please.
+module_spi_driver(cs48l32_spi_driver);
+MODULE_SOFTDEP("pre: pinctrl-cs48l32 irq-cirrus-cs48l32"); +MODULE_DESCRIPTION("CS48L32 MFD driver");
!MFD
+MODULE_AUTHOR("Richard Fitzgerald rf@opensource.cirrus.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cs48l32.h b/drivers/mfd/cs48l32.h new file mode 100644 index 000000000000..36020c0cd686 --- /dev/null +++ b/drivers/mfd/cs48l32.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/*
- MFD for Cirrus Logic CS48L32 audio codec.
!MFD
- Copyright (C) 2016-2018, 2020, 2022 Cirrus Logic, Inc. and
Cirrus Logic International Semiconductor Ltd.
- */
+#ifndef CS48L32_MFD_H +#define CS48L32_MFD_H
MFD_ at the front please.
+#include <linux/bits.h>
+#define CS48L31_SILICON_ID 0x48a31 +#define CS48L32_SILICON_ID 0x48a32 +#define CS48L33_SILICON_ID 0x48a33
+#define CS48L32_32K_MCLK1 0
+#define CS48L32_SFT_RESET_MAGIC 0x5a000000 +#define CS48L32_SEEN_BOOT_DONE BIT(0)
+#define CS48L32_BOOT_TIMEOUT_MS 25
+int cs48l32_init_regmap(struct spi_device *spi, struct cs48l32_mfd *cs48l32); +int cs48l32_patch(struct cs48l32_mfd *cs48l32);
+#endif diff --git a/include/linux/mfd/cs48l32/core.h b/include/linux/mfd/cs48l32/core.h new file mode 100644 index 000000000000..1c4269d70c8c --- /dev/null +++ b/include/linux/mfd/cs48l32/core.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/*
- MFD for Cirrus Logic CS48L32 audio codec.
!MFD
- Copyright (C) 2016-2020, 2022 Cirrus Logic, Inc. and
Cirrus Logic International Semiconductor Ltd.
- */
+#ifndef CS48L32_CORE_H +#define CS48L32_CORE_H
+#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/notifier.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h>
+#define CS48L32_MAX_CORE_SUPPLIES 2
+struct snd_soc_dapm_context;
+struct cs48l32_mfd {
!mfd
- struct regmap *regmap;
- struct device *dev;
- struct gpio_desc *reset_gpio;
- struct clk *mclk1;
- struct snd_soc_dapm_context *dapm;
- int num_core_supplies;
- struct regulator_bulk_data core_supplies[CS48L32_MAX_CORE_SUPPLIES];
- struct regulator *vdd_d;
- struct device *irq_dev;
- struct regmap_irq_chip_data *irq_data;
- int irq;
- u8 part;
- struct blocking_notifier_head notifier;
+};
+static inline int cs48l32_call_notifiers(struct cs48l32_mfd *mfd,
unsigned long event,
void *data)
+{
- return blocking_notifier_call_chain(&mfd->notifier, event, data);
+} +#endif
Codecs in this family have multiple digital I/O functions for audio, DSP subsystem, GPIO and various special functions. All muxable pins are selectable as either a GPIO or one of the available alternate functions.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- .../bindings/pinctrl/cirrus,cs48l32.yaml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml
diff --git a/Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..b24fbae6a8f8 --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pinctrl/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cirrus Logic CS48L32 audio codec pinctrl driver + +maintainers: + - patches@opensource.cirrus.com + +description: | +The Cirrus Logic CS48L32 codec has a number of GPIO functions for +interfacing to external hardware. Certain groups of GPIO pins also +have an alternate function. + +The properties for this driver exist within the parent MFD driver node. +See the core bindings for the parent MFD driver for an example: + + Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml + +And the generic pinctrl bindings: + + Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt + +properties: + pin-settings: + description: + One subnode is required to contain the default settings. It + contains an arbitrary number of configuration subnodes, one for + each group or pin configuration you want to apply as a default. + type: object + patternProperties: + '-pins$': + type: object + allOf: + - $ref: "pincfg-node.yaml#" + - $ref: "pinmux-node.yaml#" + properties: + groups: + description: + Name of one pin group to configure. + enum: [ asp1, asp2, in1-pdm, in2-pdm, + gpio1, gpio2, gpio3, gpio4, gpio5, gpio6, gpio7, + gpio8, gpio9, gpio10, gpio11, gpio12, gpio13, + gpio14, gpio15, gpio16 ] + + function: + description: + Name of function to assign to this group. + enum: [ asp1, asp2, in1-pdm, in2-pdm, spi2, io, dsp-gpio, + irq1, fll1-clk, fll1-lock, opclk, opclk-dsp, uart, + input-path-signal-detect, + ultrasonic-in1-activity-detect, ultrasonic-in2-activity-detect, + dma-ch0-programmable-transfer-complete, + dma-ch1-programmable-transfer-complete, + dma-ch2-programmable-transfer-complete, + dma-ch3-programmable-transfer-complete, + dma-ch4-programmable-transfer-complete, + dma-ch5-programmable-transfer-complete, + dma-ch6-programmable-transfer-complete, + dma-ch7-programmable-transfer-complete, + sample-rate-change-trigger-a, sample-rate-change-trigger-b, + sample-rate-change-trigger-c, sample-rate-change-trigger-d, + timer1-irq-ch1, timer1-irq-ch2, timer1-irq-ch3, timer1-irq-ch4, + timer2-irq-ch1, timer2-irq-ch2, timer2-irq-ch3, timer2-irq-ch4, + timer3-irq-ch1, timer3-irq-ch2, timer3-irq-ch3, timer3-irq-ch4, + timer4-irq-ch1, timer4-irq-ch2, timer4-irq-ch3, timer4-irq-ch4, + timer5-irq-ch1, timer5-irq-ch2, timer5-irq-ch3, timer5-irq-ch4, + timer-1, timer-2, timer-3, timer-4, timer-5 ] + + bias-disable: true + + bias-bus-hold: true + + bias-pull-up: true + + bias-pull-down: true + + drive-push-pull: true + + drive-open-drain: true + + drive-strength: + description: + Drive strength in mA. + enum: [ 4, 8 ] + + output-low: true + + output-high: true + + additionalProperties: false + + required: + - groups + + additionalProperties: false
On Wed, 09 Nov 2022 16:53:23 +0000, Richard Fitzgerald wrote:
Codecs in this family have multiple digital I/O functions for audio, DSP subsystem, GPIO and various special functions. All muxable pins are selectable as either a GPIO or one of the available alternate functions.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/pinctrl/cirrus,cs48l32.yaml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml
My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check' on your patch (DT_CHECKER_FLAGS is new in v5.13):
yamllint warnings/errors: ./Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml:14:1: [error] syntax error: could not find expected ':' (syntax)
dtschema/dtc warnings/errors: make[1]: *** Deleting file 'Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.example.dts' Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml:14:1: could not find expected ':' make[1]: *** [Documentation/devicetree/bindings/Makefile:26: Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.example.dts] Error 1 make[1]: *** Waiting for unfinished jobs.... ./Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml:14:1: could not find expected ':' /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml: ignoring, error parsing file make: *** [Makefile:1492: dt_binding_check] Error 2
doc reference errors (make refcheckdocs): Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
See https://patchwork.ozlabs.org/patch/
This check can fail if there are any dependencies. The base for a patch series is generally the most recent rc1.
If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date:
pip3 install dtschema --upgrade
Please check and re-submit.
On 09/11/2022 17:53, Richard Fitzgerald wrote:
Codecs in this family have multiple digital I/O functions for audio, DSP subsystem, GPIO and various special functions. All muxable pins are selectable as either a GPIO or one of the available alternate functions.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/pinctrl/cirrus,cs48l32.yaml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml
diff --git a/Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..b24fbae6a8f8 --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pinctrl/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Cirrus Logic CS48L32 audio codec pinctrl driver
Drop driver. s/pinctrl/Pin Controller/
+maintainers:
- patches@opensource.cirrus.com
+description: | +The Cirrus Logic CS48L32 codec has a number of GPIO functions for +interfacing to external hardware. Certain groups of GPIO pins also +have an alternate function.
+The properties for this driver exist within the parent MFD driver node.
Drop driver... so probably entire sentence.
+See the core bindings for the parent MFD driver for an example:
Drop driver. Describe hardware instead.
- Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
+And the generic pinctrl bindings:
- Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
Drop entire sentence.
+properties:
Your schema does not match on its own. Where is the compatible? This is not how schemas for devices (also MFD) is done.
- pin-settings:
- description:
One subnode is required to contain the default settings. It
contains an arbitrary number of configuration subnodes, one for
each group or pin configuration you want to apply as a default.
- type: object
- patternProperties:
'-pins$':
type: object
allOf:
- $ref: "pincfg-node.yaml#"
- $ref: "pinmux-node.yaml#"
Drop quotes.
Except this, test your patches before sending.
Best regards, Krzysztof
From: Piotr Stankiewicz piotrs@opensource.cirrus.com
Codecs in this family have multiple digital I/O functions for audio, DSP subsystem, GPIO and various special functions. All muxable pins are selectable as either a GPIO or an alternate function.
Signed-off-by: Piotr Stankiewicz piotrs@opensource.cirrus.com Signed-off-by: Qi Zhou qi.zhou@cirrus.com Signed-off-by: Stuart Henderson stuarth@opensource.cirrus.com Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- MAINTAINERS | 1 + drivers/pinctrl/cirrus/Kconfig | 5 + drivers/pinctrl/cirrus/Makefile | 2 + drivers/pinctrl/cirrus/pinctrl-cs48l32.c | 932 +++++++++++++++++++++++ drivers/pinctrl/cirrus/pinctrl-cs48l32.h | 62 ++ 5 files changed, 1002 insertions(+) create mode 100644 drivers/pinctrl/cirrus/pinctrl-cs48l32.c create mode 100644 drivers/pinctrl/cirrus/pinctrl-cs48l32.h
diff --git a/MAINTAINERS b/MAINTAINERS index f1d696f29f11..cd1773d39dd8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5011,6 +5011,7 @@ W: https://github.com/CirrusLogic/linux-drivers/wiki T: git https://github.com/CirrusLogic/linux-drivers.git F: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/mfd/cirrus,madera.yaml +F: Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/pinctrl/cirrus,madera.yaml F: Documentation/devicetree/bindings/sound/cirrus,madera.yaml F: drivers/gpio/gpio-madera* diff --git a/drivers/pinctrl/cirrus/Kconfig b/drivers/pinctrl/cirrus/Kconfig index 530426a74f75..c51192bde87a 100644 --- a/drivers/pinctrl/cirrus/Kconfig +++ b/drivers/pinctrl/cirrus/Kconfig @@ -30,3 +30,8 @@ config PINCTRL_CS47L90
config PINCTRL_CS47L92 bool + +config PINCTRL_CS48L32 + tristate + select PINMUX + select GENERIC_PINCONF diff --git a/drivers/pinctrl/cirrus/Makefile b/drivers/pinctrl/cirrus/Makefile index a484518c840e..18290f6be00c 100644 --- a/drivers/pinctrl/cirrus/Makefile +++ b/drivers/pinctrl/cirrus/Makefile @@ -19,4 +19,6 @@ ifeq ($(CONFIG_PINCTRL_CS47L92),y) pinctrl-madera-objs += pinctrl-cs47l92.o endif
+obj-$(CONFIG_PINCTRL_CS48L32) += pinctrl-cs48l32.o + obj-$(CONFIG_PINCTRL_MADERA) += pinctrl-madera.o diff --git a/drivers/pinctrl/cirrus/pinctrl-cs48l32.c b/drivers/pinctrl/cirrus/pinctrl-cs48l32.c new file mode 100644 index 000000000000..cb5031d6d0ce --- /dev/null +++ b/drivers/pinctrl/cirrus/pinctrl-cs48l32.c @@ -0,0 +1,932 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pinctrl for Cirrus Logic CS48L32 + * + * Copyright (C) 2017-2018, 2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ +#include <linux/err.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> +#include <linux/module.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinconf-generic.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include "../pinctrl-utils.h" +#include "pinctrl-cs48l32.h" + +/* + * Pins are named after their GPIO number + * NOTE: IDs are zero-indexed for coding convenience + */ +static const struct pinctrl_pin_desc cs48l32_pins[] = { + PINCTRL_PIN(0, "gpio1"), + PINCTRL_PIN(1, "gpio2"), + PINCTRL_PIN(2, "gpio3"), + PINCTRL_PIN(3, "gpio4"), + PINCTRL_PIN(4, "gpio5"), + PINCTRL_PIN(5, "gpio6"), + PINCTRL_PIN(6, "gpio7"), + PINCTRL_PIN(7, "gpio8"), + PINCTRL_PIN(8, "gpio9"), + PINCTRL_PIN(9, "gpio10"), + PINCTRL_PIN(10, "gpio11"), + PINCTRL_PIN(11, "gpio12"), + PINCTRL_PIN(12, "gpio13"), + PINCTRL_PIN(13, "gpio14"), + PINCTRL_PIN(14, "gpio15"), + PINCTRL_PIN(15, "gpio16"), +}; + +/* + * All single-pin functions can be mapped to any GPIO, however pinmux applies + * functions to pin groups and only those groups declared as supporting that + * function. To make this work we must put each pin in its own dummy group so + * that the functions can be described as applying to all pins. + * Since these do not correspond to anything in the actual hardware - they are + * merely an adaptation to pinctrl's view of the world - we use the same name + * as the pin to avoid confusion when comparing with datasheet instructions + */ +static const char * const cs48l32_pin_single_group_names[] = { + "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", + "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", + "gpio15", "gpio16", +}; + +/* set of pin numbers for single-pin groups */ +static const unsigned int cs48l32_pin_single_group_pins[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +}; + +static const char * const cs48l32_asp1_group_names[] = { "asp1" }; +static const char * const cs48l32_asp2_group_names[] = { "asp2" }; +static const char * const cs48l32_in1pdm_group_names[] = { "in1-pdm" }; +static const char * const cs48l32_in2pdm_group_names[] = { "in2-pdm" }; +static const char * const cs48l32_spi2_group_names[] = { "spi2" }; + +/* + * alt-functions always apply to only one group, other functions always + * apply to all pins + */ +static const struct { + const char *name; + const char * const *group_names; + u32 func; +} cs48l32_mux_funcs[] = { + { + .name = "asp1", + .group_names = cs48l32_asp1_group_names, + .func = 0x000 + }, + { + .name = "asp2", + .group_names = cs48l32_asp2_group_names, + .func = 0x000 + }, + { + .name = "in1-pdm", + .group_names = cs48l32_in1pdm_group_names, + .func = 0x000 + }, + { + .name = "in2-pdm", + .group_names = cs48l32_in2pdm_group_names, + .func = 0x000, + }, + { + .name = "spi2", + .group_names = cs48l32_spi2_group_names, + .func = 0x000 + }, + { + .name = "io", + .group_names = cs48l32_pin_single_group_names, + .func = 0x001 + }, + { + .name = "dsp-gpio", + .group_names = cs48l32_pin_single_group_names, + .func = 0x002 + }, + { + .name = "irq1", + .group_names = cs48l32_pin_single_group_names, + .func = 0x003 + }, + { + .name = "fll1-clk", + .group_names = cs48l32_pin_single_group_names, + .func = 0x010 + }, + { + .name = "fll1-lock", + .group_names = cs48l32_pin_single_group_names, + .func = 0x018 + }, + { + .name = "opclk", + .group_names = cs48l32_pin_single_group_names, + .func = 0x048 + }, + { + .name = "opclk-dsp", + .group_names = cs48l32_pin_single_group_names, + .func = 0x04a + }, + { + .name = "uart", + .group_names = cs48l32_pin_single_group_names, + .func = 0x04c + }, + { + .name = "input-path-signal-detect", + .group_names = cs48l32_pin_single_group_names, + .func = 0x08c + }, + { + .name = "ultrasonic-in1-activity-detect", + .group_names = cs48l32_pin_single_group_names, + .func = 0x090 + }, + { + .name = "ultrasonic-in2-activity-detect", + .group_names = cs48l32_pin_single_group_names, + .func = 0x092 + }, + { + .name = "dma-ch0-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x190 + }, + { + .name = "dma-ch1-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x191 + }, + { + .name = "dma-ch2-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x192 + }, + { + .name = "dma-ch3-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x193 + }, + { + .name = "dma-ch4-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x194 + }, + { + .name = "dma-ch5-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x195 + }, + { + .name = "dma-ch6-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x196 + }, + { + .name = "dma-ch7-programmable-transfer-complete", + .group_names = cs48l32_pin_single_group_names, + .func = 0x197 + }, + { + .name = "sample-rate-change-trigger-a", + .group_names = cs48l32_pin_single_group_names, + .func = 0x214 + }, + { + .name = "sample-rate-change-trigger-b", + .group_names = cs48l32_pin_single_group_names, + .func = 0x215 + }, + { + .name = "sample-rate-change-trigger-c", + .group_names = cs48l32_pin_single_group_names, + .func = 0x216 + }, + { + .name = "sample-rate-change-trigger-d", + .group_names = cs48l32_pin_single_group_names, + .func = 0x217 + }, + { + .name = "timer1-irq-ch1", + .group_names = cs48l32_pin_single_group_names, + .func = 0x230 + }, + { + .name = "timer1-irq-ch2", + .group_names = cs48l32_pin_single_group_names, + .func = 0x231 + }, + { + .name = "timer1-irq-ch3", + .group_names = cs48l32_pin_single_group_names, + .func = 0x232 + }, + { + .name = "timer1-irq-ch4", + .group_names = cs48l32_pin_single_group_names, + .func = 0x233 + }, + { + .name = "timer2-irq-ch1", + .group_names = cs48l32_pin_single_group_names, + .func = 0x234 + }, + { + .name = "timer2-irq-ch2", + .group_names = cs48l32_pin_single_group_names, + .func = 0x235 + }, + { + .name = "timer2-irq-ch3", + .group_names = cs48l32_pin_single_group_names, + .func = 0x236 + }, + { + .name = "timer2-irq-ch4", + .group_names = cs48l32_pin_single_group_names, + .func = 0x237 + }, + { + .name = "timer3-irq-ch1", + .group_names = cs48l32_pin_single_group_names, + .func = 0x238 + }, + { + .name = "timer3-irq-ch2", + .group_names = cs48l32_pin_single_group_names, + .func = 0x239 + }, + { + .name = "timer3-irq-ch3", + .group_names = cs48l32_pin_single_group_names, + .func = 0x23a + }, + { + .name = "timer3-irq-ch4", + .group_names = cs48l32_pin_single_group_names, + .func = 0x23b + }, + { + .name = "timer4-irq-ch1", + .group_names = cs48l32_pin_single_group_names, + .func = 0x23c + }, + { + .name = "timer4-irq-ch2", + .group_names = cs48l32_pin_single_group_names, + .func = 0x23d + }, + { + .name = "timer4-irq-ch3", + .group_names = cs48l32_pin_single_group_names, + .func = 0x23e + }, + { + .name = "timer4-irq-ch4", + .group_names = cs48l32_pin_single_group_names, + .func = 0x23f + }, + { + .name = "timer5-irq-ch1", + .group_names = cs48l32_pin_single_group_names, + .func = 0x240 + }, + { + .name = "timer5-irq-ch2", + .group_names = cs48l32_pin_single_group_names, + .func = 0x241 + }, + { + .name = "timer5-irq-ch3", + .group_names = cs48l32_pin_single_group_names, + .func = 0x242 + }, + { + .name = "timer5-irq-ch4", + .group_names = cs48l32_pin_single_group_names, + .func = 0x243 + }, + { + .name = "timer-1", + .group_names = cs48l32_pin_single_group_names, + .func = 0x250 + }, + { + .name = "timer-2", + .group_names = cs48l32_pin_single_group_names, + .func = 0x251 + }, + { + .name = "timer-3", + .group_names = cs48l32_pin_single_group_names, + .func = 0x252 + }, + { + .name = "timer-4", + .group_names = cs48l32_pin_single_group_names, + .func = 0x253 + }, + { + .name = "timer-5", + .group_names = cs48l32_pin_single_group_names, + .func = 0x254 + }, +}; + +/* Note - all 1 less than in datasheet because these are zero-indexed */ +static const unsigned int cs48l32_asp1_pins[] = { 2, 3, 4, 5 }; +static const unsigned int cs48l32_asp2_pins[] = { 6, 7, 8, 9 }; +static const unsigned int cs48l32_spi2_pins[] = { 10, 11, 12, 13, 14, 15 }; + +static const struct cs48l32_pin_groups cs48l32_pin_groups[] = { + { "asp1", cs48l32_asp1_pins, ARRAY_SIZE(cs48l32_asp1_pins) }, + { "asp2", cs48l32_asp2_pins, ARRAY_SIZE(cs48l32_asp2_pins) }, + { "spi2", cs48l32_spi2_pins, ARRAY_SIZE(cs48l32_spi2_pins) }, +}; + +static const struct cs48l32_pin_chip cs48l32_pin_chip = { + .n_pins = CS48L32_NUM_GPIOS, + .pin_groups = cs48l32_pin_groups, + .n_pin_groups = ARRAY_SIZE(cs48l32_pin_groups), +}; + +static unsigned int cs48l32_pin_make_drv_str(struct cs48l32_pin_private *priv, + unsigned int milliamps) +{ + switch (milliamps) { + case 4: + return 0; + case 8: + return 1 << CS48L32_GP_DRV_STR_SHIFT; + default: + break; + } + + dev_warn(priv->dev, "%u mA is not a valid drive strength\n", milliamps); + + return 0; +} + +static unsigned int cs48l32_pin_unmake_drv_str(struct cs48l32_pin_private *priv, + unsigned int regval) +{ + regval = (regval & CS48L32_GP_DRV_STR_MASK) >> CS48L32_GP_DRV_STR_SHIFT; + + switch (regval) { + case 0: + return 4; + case 1: + return 8; + default: + break; + } + + return 0; +} + +static int cs48l32_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + + /* Number of alt function groups plus number of single-pin groups */ + return priv->chip->n_pin_groups + priv->chip->n_pins; +} + +static const char *cs48l32_get_group_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + + if (selector < priv->chip->n_pin_groups) + return priv->chip->pin_groups[selector].name; + + selector -= priv->chip->n_pin_groups; + + return cs48l32_pin_single_group_names[selector]; +} + +static int cs48l32_get_group_pins(struct pinctrl_dev *pctldev, + unsigned int selector, + const unsigned int **pins, + unsigned int *num_pins) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + + if (selector < priv->chip->n_pin_groups) { + *pins = priv->chip->pin_groups[selector].pins; + *num_pins = priv->chip->pin_groups[selector].n_pins; + } else { + /* return the dummy group for a single pin */ + selector -= priv->chip->n_pin_groups; + *pins = &cs48l32_pin_single_group_pins[selector]; + *num_pins = 1; + } + + return 0; +} + +static void cs48l32_pin_dbg_show_fn(struct cs48l32_pin_private *priv, + struct seq_file *s, + unsigned int pin, unsigned int fn) +{ + const struct cs48l32_pin_chip *chip = priv->chip; + int i, g_pin; + + if (fn != 0) { + for (i = 0; i < ARRAY_SIZE(cs48l32_mux_funcs); ++i) { + if (cs48l32_mux_funcs[i].func == fn) { + seq_printf(s, " FN=%s", cs48l32_mux_funcs[i].name); + return; + } + } + return; /* ignore unknown function values */ + } + + /* alt function */ + for (i = 0; i < chip->n_pin_groups; ++i) { + for (g_pin = 0; g_pin < chip->pin_groups[i].n_pins; ++g_pin) { + if (chip->pin_groups[i].pins[g_pin] == pin) { + seq_printf(s, " FN=%s", chip->pin_groups[i].name); + return; + } + } + } +} + +static void cs48l32_pin_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, unsigned int pin) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin); + unsigned int conf, fn; + int ret; + + ret = regmap_read(priv->mfd->regmap, reg, &conf); + if (ret) + return; + + seq_printf(s, "%08x", conf); + + fn = (conf & CS48L32_GP_FN_MASK) >> CS48L32_GP_FN_SHIFT; + cs48l32_pin_dbg_show_fn(priv, s, pin, fn); + + /* State of direction bit is only relevant if function==1 */ + if (fn == 1) { + if (conf & CS48L32_GP_DIR_MASK) + seq_puts(s, " IN"); + else + seq_puts(s, " OUT"); + } + + if (conf & CS48L32_GP_PU_MASK) + seq_puts(s, " PU"); + + if (conf & CS48L32_GP_PD_MASK) + seq_puts(s, " PD"); + + if (conf & CS48L32_GP_DB_MASK) + seq_puts(s, " DB"); + + if (conf & CS48L32_GP_OP_CFG_MASK) + seq_puts(s, " OD"); + else + seq_puts(s, " CMOS"); + + seq_printf(s, " DRV=%umA", cs48l32_pin_unmake_drv_str(priv, conf)); +} + + +static const struct pinctrl_ops cs48l32_pin_group_ops = { + .get_groups_count = &cs48l32_get_groups_count, + .get_group_name = &cs48l32_get_group_name, + .get_group_pins = &cs48l32_get_group_pins, + .pin_dbg_show = &cs48l32_pin_dbg_show, + .dt_node_to_map = &pinconf_generic_dt_node_to_map_all, + .dt_free_map = &pinctrl_utils_free_map, +}; + +static int cs48l32_mux_get_funcs_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(cs48l32_mux_funcs); +} + +static const char *cs48l32_mux_get_func_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + return cs48l32_mux_funcs[selector].name; +} + +static int cs48l32_mux_get_groups(struct pinctrl_dev *pctldev, + unsigned int selector, + const char * const **groups, + unsigned int * const num_groups) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + + *groups = cs48l32_mux_funcs[selector].group_names; + + if (cs48l32_mux_funcs[selector].func == 0) { + /* alt func always maps to a single group */ + *num_groups = 1; + } else { + /* other funcs map to all available gpio pins */ + *num_groups = priv->chip->n_pins; + } + + return 0; +} + +static int cs48l32_mux_set_mux(struct pinctrl_dev *pctldev, unsigned int selector, + unsigned int group) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + struct cs48l32_mfd *mfd = priv->mfd; + const struct cs48l32_pin_groups *pin_group = priv->chip->pin_groups; + unsigned int n_chip_groups = priv->chip->n_pin_groups; + const char *func_name = cs48l32_mux_funcs[selector].name; + unsigned int reg; + int i, ret; + + dev_dbg(priv->dev, "%s selecting %u (%s) for group %u (%s)\n", + __func__, selector, func_name, group, + cs48l32_get_group_name(pctldev, group)); + + if (cs48l32_mux_funcs[selector].func == 0) { + /* alt func pin assignments are codec-specific */ + for (i = 0; i < n_chip_groups; ++i) { + if (strcmp(func_name, pin_group->name) == 0) + break; + + ++pin_group; + } + + if (i == n_chip_groups) + return -EINVAL; + + for (i = 0; i < pin_group->n_pins; ++i) { + reg = CS48L32_GPIO1_CTRL1 + (4 * pin_group->pins[i]); + + dev_dbg(priv->dev, "%s setting 0x%x func bits to 0\n", __func__, reg); + + ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_FN_MASK, 0); + if (ret) + break; + + } + } else { + /* + * for other funcs the group will be the gpio number and will + * be offset by the number of chip-specific functions at the + * start of the group list + */ + group -= n_chip_groups; + reg = CS48L32_GPIO1_CTRL1 + (4 * group); + + dev_dbg(priv->dev, "%s setting 0x%x func bits to 0x%x\n", + __func__, reg, cs48l32_mux_funcs[selector].func); + + ret = regmap_update_bits(mfd->regmap, + reg, + CS48L32_GP_FN_MASK, + cs48l32_mux_funcs[selector].func); + } + + if (ret) + dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret); + + return ret; +} + +static int cs48l32_gpio_set_direction(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int pin, + bool input) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + struct cs48l32_mfd *mfd = priv->mfd; + unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin); + unsigned int val; + int ret; + + if (input) + val = CS48L32_GP_DIR_MASK; + else + val = 0; + + ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_DIR_MASK, val); + if (ret) + dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret); + + return ret; +} + +static int cs48l32_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int pin) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + struct cs48l32_mfd *mfd = priv->mfd; + unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin); + int ret; + + /* put the pin into GPIO mode */ + ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_FN_MASK, 1); + if (ret) + dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret); + + return ret; +} + +static void cs48l32_gpio_disable_free(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int pin) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + struct cs48l32_mfd *mfd = priv->mfd; + unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin); + int ret; + + /* disable GPIO by setting to GPIO IN */ + cs48l32_gpio_set_direction(pctldev, range, pin, true); + + ret = regmap_update_bits(mfd->regmap, reg, CS48L32_GP_FN_MASK, 1); + if (ret) + dev_err(priv->dev, "Failed to write to 0x%x (%d)\n", reg, ret); +} +static const struct pinmux_ops cs48l32_pin_mux_ops = { + .get_functions_count = &cs48l32_mux_get_funcs_count, + .get_function_name = &cs48l32_mux_get_func_name, + .get_function_groups = &cs48l32_mux_get_groups, + .set_mux = &cs48l32_mux_set_mux, + .gpio_request_enable = &cs48l32_gpio_request_enable, + .gpio_disable_free = &cs48l32_gpio_disable_free, + .gpio_set_direction = &cs48l32_gpio_set_direction, + .strict = true, /* GPIO and other functions are exclusive */ +}; + +static int cs48l32_pin_conf_get(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *config) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + unsigned int param = pinconf_to_config_param(*config); + unsigned int result = 0; + unsigned int conf; + int ret; + + ret = regmap_read(priv->mfd->regmap, CS48L32_GPIO1_CTRL1 + (4 * pin), &conf); + if (ret) { + dev_err(priv->dev, "Failed to read GP%d conf (%d)\n", pin + 1, ret); + return ret; + } + + switch (param) { + case PIN_CONFIG_BIAS_BUS_HOLD: + conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + if (conf == (CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK)) + result = 1; + break; + case PIN_CONFIG_BIAS_DISABLE: + conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + if (!conf) + result = 1; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + if (conf == CS48L32_GP_PD_MASK) + result = 1; + break; + case PIN_CONFIG_BIAS_PULL_UP: + conf &= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + if (conf == CS48L32_GP_PU_MASK) + result = 1; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + if (conf & CS48L32_GP_OP_CFG_MASK) + result = 1; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + if (!(conf & CS48L32_GP_OP_CFG_MASK)) + result = 1; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + result = cs48l32_pin_unmake_drv_str(priv, conf); + break; + case PIN_CONFIG_INPUT_DEBOUNCE: + dev_dbg(priv->dev, "Input debounce time not supported."); + break; + case PIN_CONFIG_INPUT_ENABLE: + if (conf & CS48L32_GP_DIR_MASK) + result = 1; + break; + case PIN_CONFIG_OUTPUT: + if ((conf & CS48L32_GP_DIR_MASK) && (conf & CS48L32_GP_LVL_MASK)) + result = 1; + break; + default: + return -EINVAL; + } + + *config = pinconf_to_config_packed(param, result); + + return 0; +} + +static int cs48l32_pin_conf_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *configs, unsigned int num_configs) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + unsigned int conf = 0; + unsigned int mask = 0; + unsigned int reg = CS48L32_GPIO1_CTRL1 + (4 * pin); + unsigned int val; + int ret; + + while (num_configs) { + dev_dbg(priv->dev, "%s config 0x%lx\n", __func__, *configs); + + switch (pinconf_to_config_param(*configs)) { + case PIN_CONFIG_BIAS_BUS_HOLD: + mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + conf |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + break; + case PIN_CONFIG_BIAS_DISABLE: + mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + conf &= ~(CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + conf |= CS48L32_GP_PD_MASK; + conf &= ~CS48L32_GP_PU_MASK; + break; + case PIN_CONFIG_BIAS_PULL_UP: + mask |= CS48L32_GP_PU_MASK | CS48L32_GP_PD_MASK; + conf |= CS48L32_GP_PU_MASK; + conf &= ~CS48L32_GP_PD_MASK; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + mask |= CS48L32_GP_OP_CFG_MASK; + conf |= CS48L32_GP_OP_CFG_MASK; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + mask |= CS48L32_GP_OP_CFG_MASK; + conf &= ~CS48L32_GP_OP_CFG_MASK; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + val = pinconf_to_config_argument(*configs); + mask |= CS48L32_GP_DRV_STR_MASK; + conf &= ~CS48L32_GP_DRV_STR_MASK; + conf |= cs48l32_pin_make_drv_str(priv, val); + break; + case PIN_CONFIG_INPUT_DEBOUNCE: + dev_dbg(priv->dev, "Input debounce time not supported."); + break; + case PIN_CONFIG_INPUT_ENABLE: + val = pinconf_to_config_argument(*configs); + mask |= CS48L32_GP_DIR_MASK; + if (val) + conf |= CS48L32_GP_DIR_MASK; + else + conf &= ~CS48L32_GP_DIR_MASK; + break; + case PIN_CONFIG_OUTPUT: + val = pinconf_to_config_argument(*configs); + mask |= CS48L32_GP_LVL_MASK; + if (val) + conf |= CS48L32_GP_LVL_MASK; + else + conf &= ~CS48L32_GP_LVL_MASK; + + mask |= CS48L32_GP_DIR_MASK; + conf &= ~CS48L32_GP_DIR_MASK; + break; + default: + return -EINVAL; + } + + ++configs; + --num_configs; + } + + dev_dbg(priv->dev, "%s gpio%d 0x%x:0x%x\n", __func__, pin + 1, reg, conf); + + ret = regmap_update_bits(priv->mfd->regmap, reg, mask, conf); + if (ret) + dev_err(priv->dev, "Failed to write GPIO%d conf (%d) reg 0x%x\n", + pin + 1, ret, reg); + + return ret; +} + +static int cs48l32_pin_conf_group_set(struct pinctrl_dev *pctldev, + unsigned int selector, + unsigned long *configs, + unsigned int num_configs) +{ + struct cs48l32_pin_private *priv = pinctrl_dev_get_drvdata(pctldev); + const struct cs48l32_pin_groups *pin_group; + unsigned int n_groups = priv->chip->n_pin_groups; + int i, ret; + + dev_dbg(priv->dev, "%s setting group %s\n", __func__, + cs48l32_get_group_name(pctldev, selector)); + + if (selector >= n_groups) { + /* group is a single pin, convert to pin number and set */ + return cs48l32_pin_conf_set(pctldev, + selector - n_groups, + configs, + num_configs); + } else { + pin_group = &priv->chip->pin_groups[selector]; + + for (i = 0; i < pin_group->n_pins; ++i) { + ret = cs48l32_pin_conf_set(pctldev, + pin_group->pins[i], + configs, + num_configs); + if (ret) + return ret; + } + } + + return 0; +} + +static const struct pinconf_ops cs48l32_pin_conf_ops = { + .is_generic = true, + .pin_config_get = &cs48l32_pin_conf_get, + .pin_config_set = &cs48l32_pin_conf_set, + .pin_config_group_set = &cs48l32_pin_conf_group_set, + +}; + +static struct pinctrl_desc cs48l32_pin_desc = { + .name = "cs48l32-pinctrl", + .pins = cs48l32_pins, + .pctlops = &cs48l32_pin_group_ops, + .pmxops = &cs48l32_pin_mux_ops, + .confops = &cs48l32_pin_conf_ops, + .owner = THIS_MODULE, +}; + +static int cs48l32_pin_probe(struct platform_device *pdev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(pdev->dev.parent); + struct cs48l32_pin_private *priv; + int ret; + + BUILD_BUG_ON(ARRAY_SIZE(cs48l32_pin_single_group_names) != + ARRAY_SIZE(cs48l32_pin_single_group_pins)); + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->mfd = mfd; + /* Composite MFD device so shares the parent OF node. */ + pdev->dev.of_node = mfd->dev->of_node; + + priv->chip = &cs48l32_pin_chip; + cs48l32_pin_desc.npins = priv->chip->n_pins; + + ret = devm_pinctrl_register_and_init(&pdev->dev, &cs48l32_pin_desc, priv, &priv->pctl); + if (ret) + return dev_err_probe(priv->dev, ret, "Failed pinctrl register\n"); + + ret = pinctrl_enable(priv->pctl); + if (ret) + return dev_err_probe(priv->dev, ret, "Failed to enable pinctrl\n"); + + platform_set_drvdata(pdev, priv); + + dev_dbg(priv->dev, "pinctrl registered\n"); + + return 0; +} + +static struct platform_driver cs48l32_pin_driver = { + .probe = &cs48l32_pin_probe, + .driver = { + .name = "cs48l32-pinctrl", + }, +}; + +module_platform_driver(cs48l32_pin_driver); + +MODULE_DESCRIPTION("CS48L32 pinctrl driver"); +MODULE_AUTHOR("Richard Fitzgerald rf@opensource.cirrus.com"); +MODULE_AUTHOR("Piotr Stankiewicz piotrs@opensource.cirrus.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pinctrl/cirrus/pinctrl-cs48l32.h b/drivers/pinctrl/cirrus/pinctrl-cs48l32.h new file mode 100644 index 000000000000..2193c7558dd3 --- /dev/null +++ b/drivers/pinctrl/cirrus/pinctrl-cs48l32.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Pinctrl for Cirrus Logic CS48L32 + * + * Copyright (C) 2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef PINCTRL_CS48L32_H +#define PINCTRL_CS48L32_H + +#include <linux/device.h> +#include <linux/mfd/cs48l32/core.h> + +struct pinctrl_dev; + +#define CS48L32_GP_DIR_MASK 0x80000000 +#define CS48L32_GP_DIR_SHIFT 31 +#define CS48L32_GP_PU_MASK 0x40000000 +#define CS48L32_GP_PU_SHIFT 30 +#define CS48L32_GP_PD_MASK 0x20000000 +#define CS48L32_GP_PD_SHIFT 29 +#define CS48L32_GP_DRV_STR_MASK 0x03000000 +#define CS48L32_GP_DRV_STR_SHIFT 24 +#define CS48L32_GP_DBTIME_MASK 0x000f0000 +#define CS48L32_GP_DBTIME_SHIFT 16 +#define CS48L32_GP_LVL_MASK 0x00008000 +#define CS48L32_GP_LVL_SHIFT 15 +#define CS48L32_GP_OP_CFG_MASK 0x00004000 +#define CS48L32_GP_OP_CFG_SHIFT 14 +#define CS48L32_GP_DB_MASK 0x00002000 +#define CS48L32_GP_DB_SHIFT 13 +#define CS48L32_GP_POL_MASK 0x00001000 +#define CS48L32_GP_POL_SHIFT 12 +#define CS48L32_GP_FN_MASK 0x000007ff +#define CS48L32_GP_FN_SHIFT 0 + +#define CS48L32_NUM_GPIOS 16 + +struct cs48l32_pin_groups { + const char *name; + const unsigned int *pins; + unsigned int n_pins; +}; + +struct cs48l32_pin_chip { + unsigned int n_pins; + + const struct cs48l32_pin_groups *pin_groups; + unsigned int n_pin_groups; +}; + +struct cs48l32_pin_private { + struct cs48l32_mfd *mfd; + + const struct cs48l32_pin_chip *chip; /* chip-specific groups */ + + struct device *dev; + struct pinctrl_dev *pctl; +}; + +#endif
On Wed, Nov 9, 2022 at 5:53 PM Richard Fitzgerald rf@opensource.cirrus.com wrote:
From: Piotr Stankiewicz piotrs@opensource.cirrus.com
Codecs in this family have multiple digital I/O functions for audio, DSP subsystem, GPIO and various special functions. All muxable pins are selectable as either a GPIO or an alternate function.
Signed-off-by: Piotr Stankiewicz piotrs@opensource.cirrus.com Signed-off-by: Qi Zhou qi.zhou@cirrus.com Signed-off-by: Stuart Henderson stuarth@opensource.cirrus.com Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
This looks OK. Acked-by: Linus Walleij linus.walleij@linaro.org
Does this patch have compile-time dependencies on the other patches or is it something I can just merge separately?
Yours, Linus Walleij
On 10/11/2022 10:02, Linus Walleij wrote:
On Wed, Nov 9, 2022 at 5:53 PM Richard Fitzgerald rf@opensource.cirrus.com wrote:
From: Piotr Stankiewicz piotrs@opensource.cirrus.com
Codecs in this family have multiple digital I/O functions for audio, DSP subsystem, GPIO and various special functions. All muxable pins are selectable as either a GPIO or an alternate function.
Signed-off-by: Piotr Stankiewicz piotrs@opensource.cirrus.com Signed-off-by: Qi Zhou qi.zhou@cirrus.com Signed-off-by: Stuart Henderson stuarth@opensource.cirrus.com Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
This looks OK. Acked-by: Linus Walleij linus.walleij@linaro.org
Does this patch have compile-time dependencies on the other patches or is it something I can just merge separately?
It has compile-time dependencies on the MFD at least. I should have said that in the cover letter.
Yours, Linus Walleij
Hi Richard,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on broonie-sound/for-next] [also build test ERROR on lee-mfd/for-mfd-next linusw-pinctrl/devel linusw-pinctrl/for-next broonie-regulator/for-next linus/master v6.1-rc4 next-20221111] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Richard-Fitzgerald/Add-suppor... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next patch link: https://lore.kernel.org/r/20221109165331.29332-6-rf%40opensource.cirrus.com patch subject: [PATCH 05/12] pinctrl: cirrus: Add support for CS48L31/32/33 codecs config: s390-randconfig-c44-20221113 compiler: s390-linux-gcc (GCC) 12.1.0 reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/23ae23e54f7ece974162bb1a195bd0... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Richard-Fitzgerald/Add-support-for-the-Cirrus-Logic-CS48L32-audio-codecs/20221110-005630 git checkout 23ae23e54f7ece974162bb1a195bd01addda9400 # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash
If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot lkp@intel.com
All errors (new ones prefixed by >>):
s390-linux-ld: drivers/pinctrl/cirrus/pinctrl-cs48l32.o: in function `pinconf_generic_dt_node_to_map_all':
pinctrl-cs48l32.c:(.text+0xa8c): undefined reference to `pinconf_generic_dt_node_to_map'
When Madera support was added to this driver the code was left using ARIZONA_* defines. This wasn't causing any problem because those defines just happened to have the same value as the equivalent MADERA_* defines. But it is not ideal to assume this, and future devices that can share this driver do not have the same register map.
Fix the code to refer to the register data in struct regulator_desc.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- drivers/regulator/arizona-micsupp.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/drivers/regulator/arizona-micsupp.c b/drivers/regulator/arizona-micsupp.c index f6cfd3f6f0dd..21c36972a8e9 100644 --- a/drivers/regulator/arizona-micsupp.c +++ b/drivers/regulator/arizona-micsupp.c @@ -34,7 +34,7 @@ struct arizona_micsupp { struct regulator_dev *regulator; struct regmap *regmap; struct snd_soc_dapm_context **dapm; - unsigned int enable_reg; + const struct regulator_desc *desc; struct device *dev;
struct regulator_consumer_supply supply; @@ -49,10 +49,11 @@ static void arizona_micsupp_check_cp(struct work_struct *work) container_of(work, struct arizona_micsupp, check_cp_work); struct snd_soc_dapm_context *dapm = *micsupp->dapm; struct snd_soc_component *component; + const struct regulator_desc *desc = micsupp->desc; unsigned int val; int ret;
- ret = regmap_read(micsupp->regmap, micsupp->enable_reg, &val); + ret = regmap_read(micsupp->regmap, desc->enable_reg, &val); if (ret != 0) { dev_err(micsupp->dev, "Failed to read CP state: %d\n", ret); @@ -62,8 +63,8 @@ static void arizona_micsupp_check_cp(struct work_struct *work) if (dapm) { component = snd_soc_dapm_to_component(dapm);
- if ((val & (ARIZONA_CPMIC_ENA | ARIZONA_CPMIC_BYPASS)) == - ARIZONA_CPMIC_ENA) + if ((val & (desc->enable_mask | desc->bypass_mask)) == + desc->enable_mask) snd_soc_component_force_enable_pin(component, "MICSUPP"); else @@ -209,7 +210,6 @@ static const struct regulator_desc madera_micsupp = { .type = REGULATOR_VOLTAGE, .n_voltages = 40, .ops = &arizona_micsupp_ops, - .vsel_reg = MADERA_LDO2_CONTROL_1, .vsel_mask = MADERA_LDO2_VSEL_MASK, .enable_reg = MADERA_MIC_CHARGE_PUMP_1, @@ -264,7 +264,7 @@ static int arizona_micsupp_common_init(struct platform_device *pdev, micsupp->init_data.consumer_supplies = &micsupp->supply; micsupp->supply.supply = "MICVDD"; micsupp->supply.dev_name = dev_name(micsupp->dev); - micsupp->enable_reg = desc->enable_reg; + micsupp->desc = desc;
config.dev = micsupp->dev; config.driver_data = micsupp; @@ -285,8 +285,7 @@ static int arizona_micsupp_common_init(struct platform_device *pdev, config.init_data = &micsupp->init_data;
/* Default to regulated mode */ - regmap_update_bits(micsupp->regmap, micsupp->enable_reg, - ARIZONA_CPMIC_BYPASS, 0); + regmap_update_bits(micsupp->regmap, desc->enable_reg, desc->bypass_mask, 0);
micsupp->regulator = devm_regulator_register(&pdev->dev, desc,
The Arizona and Madera codecs all have a datasheet name of "MICVDD" for the regulator output. But future codecs with a regulator that can be controlled by this driver have different naming convention for the output of the regulator.
Move the setting of the supply name from arizona_micsupp_common_init() to arizona_micsupp_probe() and madera_micsupp_probe().
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- drivers/regulator/arizona-micsupp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/regulator/arizona-micsupp.c b/drivers/regulator/arizona-micsupp.c index 21c36972a8e9..596ecd8041cd 100644 --- a/drivers/regulator/arizona-micsupp.c +++ b/drivers/regulator/arizona-micsupp.c @@ -262,7 +262,6 @@ static int arizona_micsupp_common_init(struct platform_device *pdev, INIT_WORK(&micsupp->check_cp_work, arizona_micsupp_check_cp);
micsupp->init_data.consumer_supplies = &micsupp->supply; - micsupp->supply.supply = "MICVDD"; micsupp->supply.dev_name = dev_name(micsupp->dev); micsupp->desc = desc;
@@ -319,6 +318,8 @@ static int arizona_micsupp_probe(struct platform_device *pdev) micsupp->dapm = &arizona->dapm; micsupp->dev = arizona->dev;
+ micsupp->supply.supply = "MICVDD"; + /* * Since the chip usually supplies itself we provide some * default init_data for it. This will be overridden with @@ -354,6 +355,8 @@ static int madera_micsupp_probe(struct platform_device *pdev) micsupp->dev = madera->dev; micsupp->init_data = arizona_micsupp_ext_default;
+ micsupp->supply.supply = "MICVDD"; + return arizona_micsupp_common_init(pdev, micsupp, &madera_micsupp, &madera->pdata.micvdd); }
This adds a new driver identity "cs48l32-micsupp" and probe function so that this driver can be used to control the micsupp regulator on Cirrus Logic CS48L31/32/33 audio codecs.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- drivers/regulator/Kconfig | 8 ++-- drivers/regulator/arizona-micsupp.c | 58 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 070e4403c6c2..1d6813b24f85 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -139,12 +139,12 @@ config REGULATOR_ARIZONA_LDO1
config REGULATOR_ARIZONA_MICSUPP tristate "Cirrus Madera and Wolfson Arizona class devices MICSUPP" - depends on MFD_ARIZONA || MFD_MADERA + depends on MFD_ARIZONA || MFD_MADERA || MFD_CS48L32 depends on SND_SOC help - Support for the MICSUPP regulators found on Cirrus Logic Madera codecs - and Wolfson Microelectronic Arizona codecs - devices. + Support for the MICSUPP regulators found on Cirrus Logic Madera, + Cirrus Logic CS48L31/32/33, and on Wolfson Microelectronic + Arizona codecs.
config REGULATOR_ARM_SCMI tristate "SCMI based regulator driver" diff --git a/drivers/regulator/arizona-micsupp.c b/drivers/regulator/arizona-micsupp.c index 596ecd8041cd..bf154067ed34 100644 --- a/drivers/regulator/arizona-micsupp.c +++ b/drivers/regulator/arizona-micsupp.c @@ -24,6 +24,9 @@ #include <linux/mfd/arizona/pdata.h> #include <linux/mfd/arizona/registers.h>
+#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> + #include <linux/mfd/madera/core.h> #include <linux/mfd/madera/pdata.h> #include <linux/mfd/madera/registers.h> @@ -225,6 +228,28 @@ static const struct regulator_desc madera_micsupp = { .owner = THIS_MODULE, };
+static const struct regulator_desc cs48l32_micsupp = { + .name = "VOUT_MIC", + .supply_name = "VDD_CP", + .type = REGULATOR_VOLTAGE, + .n_voltages = 40, + .ops = &arizona_micsupp_ops, + + .vsel_reg = CS48L32_LDO2_CTRL1, + .vsel_mask = CS48L32_LDO2_VSEL_MASK, + .enable_reg = CS48L32_CHARGE_PUMP1, + .enable_mask = CS48L32_CP2_EN_MASK, + .bypass_reg = CS48L32_CHARGE_PUMP1, + .bypass_mask = CS48L32_CP2_BYPASS_MASK, + + .linear_ranges = arizona_micsupp_ext_ranges, + .n_linear_ranges = ARRAY_SIZE(arizona_micsupp_ext_ranges), + + .enable_time = 3000, + + .owner = THIS_MODULE, +}; + static int arizona_micsupp_of_get_pdata(struct arizona_micsupp_pdata *pdata, struct regulator_config *config, const struct regulator_desc *desc) @@ -361,6 +386,29 @@ static int madera_micsupp_probe(struct platform_device *pdev) &madera->pdata.micvdd); }
+static int cs48l32_micsupp_probe(struct platform_device *pdev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(pdev->dev.parent); + struct arizona_micsupp *micsupp; + struct arizona_micsupp_pdata *pdata; + + micsupp = devm_kzalloc(&pdev->dev, sizeof(*micsupp), GFP_KERNEL); + if (!micsupp) + return -ENOMEM; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + micsupp->regmap = mfd->regmap; + micsupp->dapm = &mfd->dapm; + micsupp->dev = mfd->dev; + micsupp->init_data = arizona_micsupp_ext_default; + micsupp->supply.supply = "VOUT_MIC"; + + return arizona_micsupp_common_init(pdev, micsupp, &cs48l32_micsupp, pdata); +} + static struct platform_driver arizona_micsupp_driver = { .probe = arizona_micsupp_probe, .driver = { @@ -375,9 +423,17 @@ static struct platform_driver madera_micsupp_driver = { }, };
+static struct platform_driver cs48l32_micsupp_driver = { + .probe = cs48l32_micsupp_probe, + .driver = { + .name = "cs48l32-micsupp", + }, +}; + static struct platform_driver * const arizona_micsupp_drivers[] = { &arizona_micsupp_driver, &madera_micsupp_driver, + &cs48l32_micsupp_driver, };
static int __init arizona_micsupp_init(void) @@ -396,7 +452,9 @@ module_exit(arizona_micsupp_exit);
/* Module information */ MODULE_AUTHOR("Mark Brown broonie@opensource.wolfsonmicro.com"); +MODULE_AUTHOR("Richard Fitzgerald rf@opensource.cirrus.com"); MODULE_DESCRIPTION("Arizona microphone supply driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:arizona-micsupp"); +MODULE_ALIAS("platform:cs48l32-micsupp"); MODULE_ALIAS("platform:madera-micsupp");
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- MAINTAINERS | 2 + drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-cirrus-cs48l32.c | 281 +++++++++++++++++++++ drivers/irqchip/irq-cirrus-cs48l32.h | 74 ++++++ include/linux/irqchip/irq-cirrus-cs48l32.h | 101 ++++++++ 6 files changed, 462 insertions(+) create mode 100644 drivers/irqchip/irq-cirrus-cs48l32.c create mode 100644 drivers/irqchip/irq-cirrus-cs48l32.h create mode 100644 include/linux/irqchip/irq-cirrus-cs48l32.h
diff --git a/MAINTAINERS b/MAINTAINERS index cd1773d39dd8..f52e9a6e290c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5015,12 +5015,14 @@ F: Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/pinctrl/cirrus,madera.yaml F: Documentation/devicetree/bindings/sound/cirrus,madera.yaml F: drivers/gpio/gpio-madera* +F: drivers/irqchip/irq-cirrus-cs48l32* F: drivers/irqchip/irq-madera* F: drivers/mfd/cs47l* F: drivers/mfd/cs48l* F: drivers/mfd/madera* F: drivers/pinctrl/cirrus/* F: include/dt-bindings/sound/madera* +F: include/linux/irqchip/irq-cirrus-cs48l32* F: include/linux/irqchip/irq-madera* F: include/linux/mfd/cs48l32/* F: include/linux/mfd/madera/* diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 7ef9f5e696d3..d4521158849c 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -136,6 +136,9 @@ config BRCMSTB_L2_IRQ select GENERIC_IRQ_CHIP select IRQ_DOMAIN
+config CIRRUS_CS48L32_IRQ + tristate + config DAVINCI_AINTC bool select GENERIC_IRQ_CHIP diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 87b49a10962c..049796365232 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -121,3 +121,4 @@ obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o +obj-$(CONFIG_CIRRUS_CS48L32_IRQ) += irq-cirrus-cs48l32.o diff --git a/drivers/irqchip/irq-cirrus-cs48l32.c b/drivers/irqchip/irq-cirrus-cs48l32.c new file mode 100644 index 000000000000..3ca9f34a6289 --- /dev/null +++ b/drivers/irqchip/irq-cirrus-cs48l32.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Interrupt support for Cirrus Logic CS48L32 audio codec +// +// Copyright (C) 2020, 2022 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqchip/irq-cirrus-cs48l32.h> +#include <linux/irqdomain.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include "irq-cirrus-cs48l32.h" + +#define CS48L32_IRQ(_irq, _reg) \ + [CS48L32_IRQ_ ## _irq] = { \ + .reg_offset = (_reg) - CS48L32_IRQ1_EINT_1, \ + .mask = CS48L32_ ## _irq ## _EINT1_MASK \ + } + +static const struct regmap_irq cs48l32_irqs[] = { + CS48L32_IRQ(DSP1_IRQ0, CS48L32_IRQ1_EINT_9), + CS48L32_IRQ(DSP1_IRQ1, CS48L32_IRQ1_EINT_9), + CS48L32_IRQ(DSP1_IRQ2, CS48L32_IRQ1_EINT_9), + CS48L32_IRQ(DSP1_IRQ3, CS48L32_IRQ1_EINT_9), + + CS48L32_IRQ(US1_ACT_DET_RISE, CS48L32_IRQ1_EINT_5), + CS48L32_IRQ(US1_ACT_DET_FALL, CS48L32_IRQ1_EINT_5), + CS48L32_IRQ(US2_ACT_DET_RISE, CS48L32_IRQ1_EINT_5), + CS48L32_IRQ(US2_ACT_DET_FALL, CS48L32_IRQ1_EINT_5), + + CS48L32_IRQ(INPUTS_SIG_DET_RISE, CS48L32_IRQ1_EINT_5), + CS48L32_IRQ(INPUTS_SIG_DET_FALL, CS48L32_IRQ1_EINT_5), + + CS48L32_IRQ(GPIO1_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO1_FALL, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO2_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO2_FALL, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO3_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO3_FALL, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO4_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO4_FALL, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO5_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO5_FALL, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO6_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO6_FALL, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO7_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO7_FALL, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO8_RISE, CS48L32_IRQ1_EINT_11), + CS48L32_IRQ(GPIO8_FALL, CS48L32_IRQ1_EINT_11), + + CS48L32_IRQ(DRC1_SIG_DET_RISE, CS48L32_IRQ1_EINT_5), + CS48L32_IRQ(DRC1_SIG_DET_FALL, CS48L32_IRQ1_EINT_5), + CS48L32_IRQ(DRC2_SIG_DET_RISE, CS48L32_IRQ1_EINT_5), + CS48L32_IRQ(DRC2_SIG_DET_FALL, CS48L32_IRQ1_EINT_5), + + CS48L32_IRQ(FLL1_LOCK_RISE, CS48L32_IRQ1_EINT_6), + CS48L32_IRQ(FLL1_LOCK_FALL, CS48L32_IRQ1_EINT_6), + CS48L32_IRQ(FLL1_REF_LOST, CS48L32_IRQ1_EINT_6), + + CS48L32_IRQ(SYSCLK_FAIL, CS48L32_IRQ1_EINT_1), + CS48L32_IRQ(CTRLIF_ERR, CS48L32_IRQ1_EINT_1), + CS48L32_IRQ(SYSCLK_ERR, CS48L32_IRQ1_EINT_1), + CS48L32_IRQ(DSPCLK_ERR, CS48L32_IRQ1_EINT_1), + + CS48L32_IRQ(DSP1_NMI_ERR, CS48L32_IRQ1_EINT_7), + CS48L32_IRQ(DSP1_WDT_EXPIRE, CS48L32_IRQ1_EINT_7), + CS48L32_IRQ(DSP1_MPU_ERR, CS48L32_IRQ1_EINT_7), + + CS48L32_IRQ(BOOT_DONE, CS48L32_IRQ1_EINT_2), +}; + +static const struct regmap_irq_chip cs48l32_irqchip = { + .name = "CS48L32 IRQ", + .status_base = CS48L32_IRQ1_EINT_1, + .mask_base = CS48L32_IRQ1_MASK_1, + .ack_base = CS48L32_IRQ1_EINT_1, + .runtime_pm = true, + .num_regs = 11, + .irqs = cs48l32_irqs, + .num_irqs = ARRAY_SIZE(cs48l32_irqs), +}; + +static int __maybe_unused cs48l32_suspend(struct device *dev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(dev->parent); + + dev_dbg(mfd->irq_dev, "Suspend, disabling IRQ\n"); + + /* + * A runtime resume would be needed to access the chip interrupt + * controller but runtime pm doesn't function during suspend. + * Temporarily disable interrupts until we reach suspend_noirq state. + */ + disable_irq(mfd->irq); + + return 0; +} + +static int __maybe_unused cs48l32_suspend_noirq(struct device *dev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(dev->parent); + + dev_dbg(mfd->irq_dev, "No IRQ suspend, reenabling IRQ\n"); + + /* Re-enable interrupts to service wakeup interrupts from the chip */ + enable_irq(mfd->irq); + + return 0; +} + +static int __maybe_unused cs48l32_resume_noirq(struct device *dev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(dev->parent); + + dev_dbg(mfd->irq_dev, "No IRQ resume, disabling IRQ\n"); + + /* + * We can't handle interrupts until runtime pm is available again. + * Disable them temporarily. + */ + disable_irq(mfd->irq); + + return 0; +} + +static int __maybe_unused cs48l32_resume(struct device *dev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(dev->parent); + + dev_dbg(mfd->irq_dev, "Resume, enabling IRQ\n"); + + /* Interrupts can now be handled */ + enable_irq(mfd->irq); + + return 0; +} + +static const struct dev_pm_ops cs48l32_irq_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cs48l32_suspend, cs48l32_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs48l32_suspend_noirq, cs48l32_resume_noirq) +}; + +static irqreturn_t cs48l32_sysclk_fail(int irq, void *data) +{ + struct cs48l32_mfd *mfd = data; + + dev_warn(mfd->dev, "SYSCLK fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs48l32_sysclk_error(int irq, void *data) +{ + struct cs48l32_mfd *mfd = data; + + dev_warn(mfd->dev, "SYSCLK error\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs48l32_ctrlif_error(int irq, void *data) +{ + struct cs48l32_mfd *mfd = data; + + dev_warn(mfd->dev, "CTRLIF error\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs48l32_boot_done(int irq, void *data) +{ + struct cs48l32_mfd *mfd = data; + + dev_dbg(mfd->dev, "BOOT_DONE\n"); + + return IRQ_HANDLED; +} + +static int cs48l32_irq_probe(struct platform_device *pdev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(pdev->dev.parent); + struct irq_data *irq_data; + unsigned int irq_flags; + int ret; + + irq_data = irq_get_irq_data(mfd->irq); + if (!irq_data) + return dev_err_probe(&pdev->dev, -EINVAL, "Invalid IRQ: %d\n", mfd->irq); + + irq_flags = irqd_get_trigger_type(irq_data); + + /* Codec defaults to trigger low, use this if no flags given */ + if (irq_flags == IRQ_TYPE_NONE) + irq_flags = IRQF_TRIGGER_LOW; + + if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) + return dev_err_probe(&pdev->dev, -EINVAL, "Host interrupt not level-triggered\n"); + + /* + * The silicon always starts at active-low, check if we need to + * switch to active-high. + */ + if (irq_flags & IRQF_TRIGGER_HIGH) + ret = regmap_clear_bits(mfd->regmap, CS48L32_IRQ1_CTRL_AOD, + CS48L32_IRQ_POL_MASK); + else + ret = regmap_set_bits(mfd->regmap, CS48L32_IRQ1_CTRL_AOD, + CS48L32_IRQ_POL_MASK); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to set IRQ polarity\n"); + + /* + * NOTE: regmap registers this against the OF node of the parent of + * the regmap - that is, against the mfd driver + */ + ret = regmap_add_irq_chip(mfd->regmap, mfd->irq, IRQF_ONESHOT, 0, + &cs48l32_irqchip, &mfd->irq_data); + if (ret) + return dev_err_probe(&pdev->dev, ret, "add_irq_chip failed\n"); + + /* Save dev in parent MFD struct so it is accessible to siblings */ + mfd->irq_dev = &pdev->dev; + + /* + * Log global chip error conditions that aren't specific to + * any particular sibling driver. + */ + cs48l32_request_irq(mfd, CS48L32_IRQ_SYSCLK_FAIL, "SYSCLK fail", + cs48l32_sysclk_fail, mfd); + cs48l32_request_irq(mfd, CS48L32_IRQ_SYSCLK_ERR, "SYSCLK error", + cs48l32_sysclk_error, mfd); + cs48l32_request_irq(mfd, CS48L32_IRQ_CTRLIF_ERR, "CTRLIF error", + cs48l32_ctrlif_error, mfd); + cs48l32_request_irq(mfd, CS48L32_IRQ_BOOT_DONE, "BOOT_DONE", + cs48l32_boot_done, mfd); + + return 0; +} + +static int cs48l32_irq_remove(struct platform_device *pdev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(pdev->dev.parent); + + /* + * The IRQ is disabled by the parent MFD driver before + * it starts cleaning up all child drivers + */ + cs48l32_free_irq(mfd, CS48L32_IRQ_BOOT_DONE, mfd); + cs48l32_free_irq(mfd, CS48L32_IRQ_CTRLIF_ERR, mfd); + cs48l32_free_irq(mfd, CS48L32_IRQ_SYSCLK_ERR, mfd); + cs48l32_free_irq(mfd, CS48L32_IRQ_SYSCLK_FAIL, mfd); + + mfd->irq_dev = NULL; + regmap_del_irq_chip(mfd->irq, mfd->irq_data); + + return 0; +} + +static struct platform_driver cs48l32_irq_driver = { + .probe = &cs48l32_irq_probe, + .remove = &cs48l32_irq_remove, + .driver = { + .name = "cs48l32-irq", + .pm = &cs48l32_irq_pm_ops, + } +}; + +module_platform_driver(cs48l32_irq_driver); + +MODULE_DESCRIPTION("CS48L32 IRQ driver"); +MODULE_AUTHOR("Richard Fitzgerald rf@opensource.cirrus.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/irqchip/irq-cirrus-cs48l32.h b/drivers/irqchip/irq-cirrus-cs48l32.h new file mode 100644 index 000000000000..6dae6ddf724d --- /dev/null +++ b/drivers/irqchip/irq-cirrus-cs48l32.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Interrupt support for Cirrus Logic CS48L32 audio codec + * + * Copyright (C) 2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef IRQ_CIRRUS_CS48L32_H +#define IRQ_CIRRUS_CS48L32_H + +/* (0x18010) IRQ1_EINT_1 */ +#define CS48L32_DSPCLK_ERR_EINT1_MASK 0x00001000 +#define CS48L32_SYSCLK_ERR_EINT1_MASK 0x00000400 +#define CS48L32_CTRLIF_ERR_EINT1_MASK 0x00000200 +#define CS48L32_SYSCLK_FAIL_EINT1_MASK 0x00000100 + +/* (0x18014) IRQ1_EINT_2 */ +#define CS48L32_BOOT_DONE_EINT1_MASK 0x00000008 + +/* (0x18020) IRQ1_EINT_5 */ +#define CS48L32_US2_ACT_DET_FALL_EINT1_MASK 0x02000000 +#define CS48L32_US2_ACT_DET_RISE_EINT1_MASK 0x01000000 +#define CS48L32_US1_ACT_DET_FALL_EINT1_MASK 0x00800000 +#define CS48L32_US1_ACT_DET_RISE_EINT1_MASK 0x00400000 +#define CS48L32_INPUTS_SIG_DET_FALL_EINT1_MASK 0x00200000 +#define CS48L32_INPUTS_SIG_DET_RISE_EINT1_MASK 0x00100000 +#define CS48L32_DRC2_SIG_DET_FALL_EINT1_MASK 0x00080000 +#define CS48L32_DRC2_SIG_DET_RISE_EINT1_MASK 0x00040000 +#define CS48L32_DRC1_SIG_DET_FALL_EINT1_MASK 0x00020000 +#define CS48L32_DRC1_SIG_DET_RISE_EINT1_MASK 0x00010000 + +/* (0x18024) IRQ1_EINT_6 */ +#define CS48L32_FLL1_REF_LOST_EINT1_MASK 0x00000100 +#define CS48L32_FLL1_LOCK_FALL_EINT1_MASK 0x00000002 +#define CS48L32_FLL1_LOCK_RISE_EINT1_MASK 0x00000001 + +/* (0x18028) IRQ1_EINT_7 */ +#define CS48L32_DSP1_MPU_ERR_EINT1_MASK 0x00200000 +#define CS48L32_DSP1_WDT_EXPIRE_EINT1_MASK 0x00100000 +#define CS48L32_DSP1_IHB_ERR_EINT1_MASK 0x00080000 +#define CS48L32_DSP1_AHB_SYS_ERR_EINT1_MASK 0x00040000 +#define CS48L32_DSP1_AHB_PACK_ERR_EINT1_MASK 0x00020000 +#define CS48L32_DSP1_NMI_ERR_EINT1_MASK 0x00010000 + +/* (0x18030) IRQ1_EINT_9 */ +#define CS48L32_MCU_HWERR_IRQ_OUT_EINT1_MASK 0x80000000 +#define CS48L32_DSP1_IRQ3_EINT1_MASK 0x00000008 +#define CS48L32_DSP1_IRQ2_EINT1_MASK 0x00000004 +#define CS48L32_DSP1_IRQ1_EINT1_MASK 0x00000002 +#define CS48L32_DSP1_IRQ0_EINT1_MASK 0x00000001 + +/* (0x18034) IRQ1_EINT_10 */ +#define CS48L32_CLOCK_DETECT_EINT1_MASK 0x01000000 + +/* (0x18038) IRQ1_EINT_11 */ +#define CS48L32_GPIO8_FALL_EINT1_MASK 0x80000000 +#define CS48L32_GPIO8_RISE_EINT1_MASK 0x40000000 +#define CS48L32_GPIO7_FALL_EINT1_MASK 0x20000000 +#define CS48L32_GPIO7_RISE_EINT1_MASK 0x10000000 +#define CS48L32_GPIO6_FALL_EINT1_MASK 0x08000000 +#define CS48L32_GPIO6_RISE_EINT1_MASK 0x04000000 +#define CS48L32_GPIO5_FALL_EINT1_MASK 0x02000000 +#define CS48L32_GPIO5_RISE_EINT1_MASK 0x01000000 +#define CS48L32_GPIO4_FALL_EINT1_MASK 0x00800000 +#define CS48L32_GPIO4_RISE_EINT1_MASK 0x00400000 +#define CS48L32_GPIO3_FALL_EINT1_MASK 0x00200000 +#define CS48L32_GPIO3_RISE_EINT1_MASK 0x00100000 +#define CS48L32_GPIO2_FALL_EINT1_MASK 0x00080000 +#define CS48L32_GPIO2_RISE_EINT1_MASK 0x00040000 +#define CS48L32_GPIO1_FALL_EINT1_MASK 0x00020000 +#define CS48L32_GPIO1_RISE_EINT1_MASK 0x00010000 + +#endif diff --git a/include/linux/irqchip/irq-cirrus-cs48l32.h b/include/linux/irqchip/irq-cirrus-cs48l32.h new file mode 100644 index 000000000000..c94d31cef96f --- /dev/null +++ b/include/linux/irqchip/irq-cirrus-cs48l32.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Interrupt support for Cirrus Logic CS48L32 audio codec + * + * Copyright (C) 2017-2020, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef IRQCHIP_CIRRUS_CS48L32_H +#define IRQCHIP_CIRRUS_CS48L32_H + +#include <linux/interrupt.h> +#include <linux/mfd/cs48l32/core.h> + +/* Main interrupts, organized by priority order - highest first */ +#define CS48L32_IRQ_DSP1_IRQ0 0 +#define CS48L32_IRQ_DSP1_IRQ1 1 +#define CS48L32_IRQ_DSP1_IRQ2 2 +#define CS48L32_IRQ_DSP1_IRQ3 3 +#define CS48L32_IRQ_US1_ACT_DET_RISE 4 +#define CS48L32_IRQ_US1_ACT_DET_FALL 5 +#define CS48L32_IRQ_US2_ACT_DET_RISE 6 +#define CS48L32_IRQ_US2_ACT_DET_FALL 7 +#define CS48L32_IRQ_INPUTS_SIG_DET_RISE 8 +#define CS48L32_IRQ_INPUTS_SIG_DET_FALL 9 +#define CS48L32_IRQ_GPIO1_RISE 10 +#define CS48L32_IRQ_GPIO1_FALL 11 +#define CS48L32_IRQ_GPIO2_RISE 12 +#define CS48L32_IRQ_GPIO2_FALL 13 +#define CS48L32_IRQ_GPIO3_RISE 14 +#define CS48L32_IRQ_GPIO3_FALL 15 +#define CS48L32_IRQ_GPIO4_RISE 16 +#define CS48L32_IRQ_GPIO4_FALL 17 +#define CS48L32_IRQ_GPIO5_RISE 18 +#define CS48L32_IRQ_GPIO5_FALL 19 +#define CS48L32_IRQ_GPIO6_RISE 20 +#define CS48L32_IRQ_GPIO6_FALL 21 +#define CS48L32_IRQ_GPIO7_RISE 22 +#define CS48L32_IRQ_GPIO7_FALL 23 +#define CS48L32_IRQ_GPIO8_RISE 24 +#define CS48L32_IRQ_GPIO8_FALL 25 +#define CS48L32_IRQ_DRC1_SIG_DET_RISE 26 +#define CS48L32_IRQ_DRC1_SIG_DET_FALL 27 +#define CS48L32_IRQ_DRC2_SIG_DET_RISE 28 +#define CS48L32_IRQ_DRC2_SIG_DET_FALL 29 +#define CS48L32_IRQ_FLL1_LOCK_RISE 30 +#define CS48L32_IRQ_FLL1_LOCK_FALL 31 +#define CS48L32_IRQ_FLL1_REF_LOST 32 +#define CS48L32_IRQ_SYSCLK_FAIL 33 +#define CS48L32_IRQ_CTRLIF_ERR 34 +#define CS48L32_IRQ_SYSCLK_ERR 35 +#define CS48L32_IRQ_DSPCLK_ERR 36 +#define CS48L32_IRQ_DSP1_NMI_ERR 37 +#define CS48L32_IRQ_DSP1_WDT_EXPIRE 38 +#define CS48L32_IRQ_DSP1_MPU_ERR 39 +#define CS48L32_IRQ_BOOT_DONE 40 + +struct cs48l32_mfd; + +/* + * These wrapper functions are for use by other child drivers of the + * same parent MFD. + */ +static inline int cs48l32_get_irq_mapping(struct cs48l32_mfd *cs48l32, int irq) +{ + if (!cs48l32->irq_dev) + return -ENODEV; + + return regmap_irq_get_virq(cs48l32->irq_data, irq); +} + +static inline int cs48l32_request_irq(struct cs48l32_mfd *cs48l32, int irq, + const char *name, + irq_handler_t handler, void *data) +{ + irq = cs48l32_get_irq_mapping(cs48l32, irq); + if (irq < 0) + return irq; + + return request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, name, data); +} + +static inline void cs48l32_free_irq(struct cs48l32_mfd *cs48l32, int irq, void *data) +{ + irq = cs48l32_get_irq_mapping(cs48l32, irq); + if (irq < 0) + return; + + free_irq(irq, data); +} + +static inline int cs48l32_set_irq_wake(struct cs48l32_mfd *cs48l32, int irq, int on) +{ + irq = cs48l32_get_irq_mapping(cs48l32, irq); + if (irq < 0) + return irq; + + return irq_set_irq_wake(irq, on); +} + +#endif
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
I cannot spot a shred of interrupt controller code in there. This belongs IMO to the MFD code. It is also a direct copy of the existing irq-madera.c code, duplicated for no obvious reason.
M.
On 10/11/2022 08:02, Marc Zyngier wrote:
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
I cannot spot a shred of interrupt controller code in there. This
It is providing support for handling an interrupt controller so that other drivers can bind to those interrupts. It's just that regmap provides a lot of generic implementation for SPI-connected interrupt controllers so we don't need to open-code all that in the irqchip driver.
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
It is also a direct copy of the existing irq-madera.c code, duplicated for no obvious reason.
It's not a duplicate. The register map of this device is different (different addressing, 32-bit registers not 16-bit)
M.
On Thu, 10 Nov 2022 11:22:26 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 08:02, Marc Zyngier wrote:
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
I cannot spot a shred of interrupt controller code in there. This
It is providing support for handling an interrupt controller so that other drivers can bind to those interrupts. It's just that regmap provides a lot of generic implementation for SPI-connected interrupt controllers so we don't need to open-code all that in the irqchip driver.
And thus none of that code needs to live in drivers/irqchip.
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
I don't like this stuff either. All this code is a glorified set of interrupt handlers and #defines that only hide the lack of a proper DT binding to express the interrupt routing (it feels like looking at board files from 10 years ago).
None of that belongs in the irqchip code.
It is also a direct copy of the existing irq-madera.c code, duplicated for no obvious reason.
It's not a duplicate. The register map of this device is different (different addressing, 32-bit registers not 16-bit)
And? How hard is it to implement an indirection containing the register map and the relevant callbacks? /roll-eyes
M.
On 10/11/2022 12:01, Marc Zyngier wrote:
On Thu, 10 Nov 2022 11:22:26 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 08:02, Marc Zyngier wrote:
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
I cannot spot a shred of interrupt controller code in there. This
It is providing support for handling an interrupt controller so that other drivers can bind to those interrupts. It's just that regmap provides a lot of generic implementation for SPI-connected interrupt controllers so we don't need to open-code all that in the irqchip driver.
And thus none of that code needs to live in drivers/irqchip.
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
I don't like this stuff either. All this code is a glorified set of interrupt handlers and #defines that only hide the lack of a proper DT binding to express the interrupt routing (it feels like looking at board files from 10 years ago).
I didn't understand this. The whole purpose of this is to instantiate Linux interrupts for the PIC interrupt sources so that other drivers that want to use the interrupts from the CS48L32 PIC can use standard kernel APIs or DT to bind against them.
The four handlers registered within the driver are done here simply because they don't belong to any particular child driver. Since they are a fixed feature of the chip that we know we want to handle we may as well just register them. If we put them in the MFD with DT definitions it would make a circular dependency between MFD and its child, which is not a great situation. If it's these handlers that are bothering you, we could move them to the audio driver.
None of that belongs in the irqchip code.
I don't really understand here what the criteria is that makes this not a irqchip driver but it was ok for madera. We have a PIC and we need to handle that and export those interrupts so other drivers can bind against them. Is the problem that the PIC is on a SPI bus and irqchip is only for memory-mapped PICs? Or is it that we have re-used existing library code instead of open-coding it, so you aren't seeing the actual handling code?
As Lee has already objected in the past to having the interrupt controller implementation in MFD I don't want to move it there without Lee's agreement that it's ok to put the PIC IRQ implementation in MFD for CS48L32.
It is also a direct copy of the existing irq-madera.c code, duplicated for no obvious reason.
It's not a duplicate. The register map of this device is different (different addressing, 32-bit registers not 16-bit)
And? How hard is it to implement an indirection containing the register map and the relevant callbacks? /roll-eyes
M.
On Thu, 10 Nov 2022 13:00:50 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 12:01, Marc Zyngier wrote:
On Thu, 10 Nov 2022 11:22:26 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 08:02, Marc Zyngier wrote:
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
I cannot spot a shred of interrupt controller code in there. This
It is providing support for handling an interrupt controller so that other drivers can bind to those interrupts. It's just that regmap provides a lot of generic implementation for SPI-connected interrupt controllers so we don't need to open-code all that in the irqchip driver.
And thus none of that code needs to live in drivers/irqchip.
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
I don't like this stuff either. All this code is a glorified set of interrupt handlers and #defines that only hide the lack of a proper DT binding to express the interrupt routing (it feels like looking at board files from 10 years ago).
I didn't understand this. The whole purpose of this is to instantiate Linux interrupts for the PIC interrupt sources so that other drivers that want to use the interrupts from the CS48L32 PIC can use standard kernel APIs or DT to bind against them.
There is zero standard APIs in this patch. Does cs48l32_request_irq() look standard to you? This whole thing makes a mockery of the interrupt model and of firmware-based interrupt description which we spent years to build.
The four handlers registered within the driver are done here simply because they don't belong to any particular child driver. Since they are a fixed feature of the chip that we know we want to handle we may as well just register them.
Again, they have no purpose in an interrupt controller driver.
If we put them in the MFD with DT definitions it would make a circular dependency between MFD and its child, which is not a great situation. If it's these handlers that are bothering you, we could move them to the audio driver.
And what's left? Nothing.
None of that belongs in the irqchip code.
I don't really understand here what the criteria is that makes this not a irqchip driver but it was ok for madera. We have a PIC and we need to handle that and export those interrupts so other drivers can bind against them. Is the problem that the PIC is on a SPI bus and irqchip is only for memory-mapped PICs? Or is it that we have re-used existing library code instead of open-coding it, so you aren't seeing the actual handling code?
An irqchip driver uses the irq_chip structure, uses irq domains to abstract the device-specific interrupt numbering from clients, and doesn't force the use of an esoteric API on these clients.
What I see here is the exact opposite.
Was it OK for madera? No. A moment of weakness, I presume. Do I want to repeat the same mistake? Neither.
As Lee has already objected in the past to having the interrupt controller implementation in MFD I don't want to move it there without Lee's agreement that it's ok to put the PIC IRQ implementation in MFD for CS48L32.
If you were implementing an actual interrupt controller driver, I'd take it without any question. The fact that this code mandates the use of its own homegrown API rules it out.
Thanks,
M.
On 10/11/2022 15:13, Marc Zyngier wrote:
On Thu, 10 Nov 2022 13:00:50 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 12:01, Marc Zyngier wrote:
On Thu, 10 Nov 2022 11:22:26 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 08:02, Marc Zyngier wrote:
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
I cannot spot a shred of interrupt controller code in there. This
It is providing support for handling an interrupt controller so that other drivers can bind to those interrupts. It's just that regmap provides a lot of generic implementation for SPI-connected interrupt controllers so we don't need to open-code all that in the irqchip driver.
And thus none of that code needs to live in drivers/irqchip.
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
I don't like this stuff either. All this code is a glorified set of interrupt handlers and #defines that only hide the lack of a proper DT binding to express the interrupt routing (it feels like looking at board files from 10 years ago).
I didn't understand this. The whole purpose of this is to instantiate Linux interrupts for the PIC interrupt sources so that other drivers that want to use the interrupts from the CS48L32 PIC can use standard kernel APIs or DT to bind against them.
There is zero standard APIs in this patch. Does cs48l32_request_irq() look standard to you? This whole thing makes a mockery of the interrupt model and of firmware-based interrupt description which we spent years to build.
The four handlers registered within the driver are done here simply because they don't belong to any particular child driver. Since they are a fixed feature of the chip that we know we want to handle we may as well just register them.
Again, they have no purpose in an interrupt controller driver.
If we put them in the MFD with DT definitions it would make a circular dependency between MFD and its child, which is not a great situation. If it's these handlers that are bothering you, we could move them to the audio driver.
And what's left? Nothing.
Ah, I see. You've missed that the bulk of the implementation re-uses existing library code from regmap. It does say this in the commit message.
"the generic regmap_irq functionality is used to do most of the work."
and I've also said this in previous replies.
This is no way driver that does nothing. There's over 1000 lines of code handling the PIC and dispatching its interrupts to other drivers that want to bind to them. It's just that it makes no sense to duplicate 1300 lines of interrupt handling code from elsewhere when we can re-use that by calling regmap_add_irq_chip(). That gives us all the interrupt- controller-handling code in drivers/base/regmap/regmap-irq.c
Perhaps you could re-review this taking into account that regmap_add_irq_chip() is significant.
On Thu, Nov 10, 2022 at 04:31:06PM +0000, Richard Fitzgerald wrote:
On 10/11/2022 15:13, Marc Zyngier wrote:
If we put them in the MFD with DT definitions it would make a circular dependency between MFD and its child, which is not a great situation. If it's these handlers that are bothering you, we could move them to the audio driver.
And what's left? Nothing.
Ah, I see. You've missed that the bulk of the implementation re-uses existing library code from regmap. It does say this in the commit message.
"the generic regmap_irq functionality is used to do most of the work."
and I've also said this in previous replies.
The thread prompted me to have a look at regmap-irq earlier today and see what it's still doing that peers into the regmap core internals and it seems it's just getting the register stride which has had an external API added already and getting the device for the regmap. It should be straightforward to repaint it and move it into the irqchip subsystem which would be a much more sensible home for a library for implementing irqchips in this day and age. I started looking at the code changes for that a bit.
This is no way driver that does nothing. There's over 1000 lines of code handling the PIC and dispatching its interrupts to other drivers that want to bind to them. It's just that it makes no sense to duplicate 1300 lines of interrupt handling code from elsewhere when we can re-use that by calling regmap_add_irq_chip(). That gives us all the interrupt- controller-handling code in drivers/base/regmap/regmap-irq.c
TBF that's 1000 lines of overly generic code, a bunch of it is conditional stuff and it's unlikely that any individual driver would want all of it. Equally it does mean that all the users are just providing data rather than writing any code which generally makes things easier to maintain and was the main goal.
On Thu, 10 Nov 2022 16:31:06 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 15:13, Marc Zyngier wrote:
On Thu, 10 Nov 2022 13:00:50 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 12:01, Marc Zyngier wrote:
On Thu, 10 Nov 2022 11:22:26 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 08:02, Marc Zyngier wrote:
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote: > > The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable > interrupt controller with a variety of interrupt sources, including > GPIOs that can be used as interrupt inputs. > > This driver provides the handling for the interrupt controller. As the > codec is accessed via regmap, the generic regmap_irq functionality > is used to do most of the work. >
I cannot spot a shred of interrupt controller code in there. This
It is providing support for handling an interrupt controller so that other drivers can bind to those interrupts. It's just that regmap provides a lot of generic implementation for SPI-connected interrupt controllers so we don't need to open-code all that in the irqchip driver.
And thus none of that code needs to live in drivers/irqchip.
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
I don't like this stuff either. All this code is a glorified set of interrupt handlers and #defines that only hide the lack of a proper DT binding to express the interrupt routing (it feels like looking at board files from 10 years ago).
I didn't understand this. The whole purpose of this is to instantiate Linux interrupts for the PIC interrupt sources so that other drivers that want to use the interrupts from the CS48L32 PIC can use standard kernel APIs or DT to bind against them.
There is zero standard APIs in this patch. Does cs48l32_request_irq() look standard to you? This whole thing makes a mockery of the interrupt model and of firmware-based interrupt description which we spent years to build.
The four handlers registered within the driver are done here simply because they don't belong to any particular child driver. Since they are a fixed feature of the chip that we know we want to handle we may as well just register them.
Again, they have no purpose in an interrupt controller driver.
If we put them in the MFD with DT definitions it would make a circular dependency between MFD and its child, which is not a great situation. If it's these handlers that are bothering you, we could move them to the audio driver.
And what's left? Nothing.
Ah, I see. You've missed that the bulk of the implementation re-uses existing library code from regmap. It does say this in the commit message.
"the generic regmap_irq functionality is used to do most of the work."
and I've also said this in previous replies.
This is no way driver that does nothing. There's over 1000 lines of code handling the PIC and dispatching its interrupts to other drivers that want to bind to them. It's just that it makes no sense to duplicate 1300 lines of interrupt handling code from elsewhere when we can re-use that by calling regmap_add_irq_chip(). That gives us all the interrupt- controller-handling code in drivers/base/regmap/regmap-irq.c
Perhaps you could re-review this taking into account that regmap_add_irq_chip() is significant.
Read again what I have written. Having to expose a device-specific API for endpoint drivers to obtain their interrupts, and requiring them to know about some magic values that describe the interrupts source are not a acceptable constructs.
We have firmware descriptions to expose interrupt linkages, and your HW is not special enough to deserve its own top level API. Yes, we accepted such drivers in the past, but it has to stop.
Either you describe the internal structure of your device in DT or ACPI, and make all client drivers use the standard API, or you make this a codec library, purely specific to your device and only used by it. But the current shape is not something I'm prepared to accept.
M.
On Thu, Nov 10, 2022 at 06:47:20PM +0000, Marc Zyngier wrote:
Read again what I have written. Having to expose a device-specific API for endpoint drivers to obtain their interrupts, and requiring them to know about some magic values that describe the interrupts source are not a acceptable constructs.
We have firmware descriptions to expose interrupt linkages, and your HW is not special enough to deserve its own top level API. Yes, we accepted such drivers in the past, but it has to stop.
Either you describe the internal structure of your device in DT or ACPI, and make all client drivers use the standard API, or you make this a codec library, purely specific to your device and only used by it. But the current shape is not something I'm prepared to accept.
ACPI gets to be a lot of fun here, it's just not idiomatic to describe the internals of these devices in firmware there and a lot of the systems shipping this stuff are targeted at other OSs and system integrators are therefore not in the least worried about Linux preferences. You'd need to look at having the MFD add additional description via swnode or something to try to get things going. MFD does have support for that, though it's currently mainly used with devices that only have ACPI use (axp20x looks like the only potentially DT user, from the git history the swnode bits are apparently for use on ACPI systems). That might get fragile in the DT case since you could have multiple sources for description of the same thing unless you do something like suppress the swnode stuff on DT systems.
Given that swnode is basically DT written out in C code I'm not actually convinced it's that much of a win, unless someone writes some tooling to generate swnode data from DT files you're not getting the benefit of any of the schema validation work that's being done. We'd also need to do some work for regulators to make sure that if we are parsing DT properties on ACPI systems we don't do so from _DSD since ACPI has strong ideas about how power works and we don't want to end up with systems with firmware providing mixed ACPI/DT models without a clear understanding of what we're geting into.
I do also have other concerns in the purely DT case, especially with chip functions like the CODEC where there's a very poor mapping between physical IPs and how Linux is tending to describe things internally at the minute. In particular these devices often have a clock tree portions of which can be visible and useful off chip but which tends to get lumped in with the audio IPs in our current code. Ideally we'd describe that as a clock subdevice (or subdevices if that fits the hardware) using the clock bindings but then that has a bunch of knock on effects the way the code currently is which probably it's probably disproportionate to force an individual driver author to work through. OTOH the DT bindings should be OS neutral ABI so...
On Thu, 10 Nov 2022 20:36:16 +0000, Mark Brown broonie@kernel.org wrote:
On Thu, Nov 10, 2022 at 06:47:20PM +0000, Marc Zyngier wrote:
Read again what I have written. Having to expose a device-specific API for endpoint drivers to obtain their interrupts, and requiring them to know about some magic values that describe the interrupts source are not a acceptable constructs.
We have firmware descriptions to expose interrupt linkages, and your HW is not special enough to deserve its own top level API. Yes, we accepted such drivers in the past, but it has to stop.
Either you describe the internal structure of your device in DT or ACPI, and make all client drivers use the standard API, or you make this a codec library, purely specific to your device and only used by it. But the current shape is not something I'm prepared to accept.
ACPI gets to be a lot of fun here, it's just not idiomatic to describe the internals of these devices in firmware there and a lot of the systems shipping this stuff are targeted at other OSs and system integrators are therefore not in the least worried about Linux preferences.
Let me reassure the vendors that I do not care about them either. By this standard, we'd all run Windows on x86.
You'd need to look at having the MFD add additional description via swnode or something to try to get things going. MFD does have support for that, though it's currently mainly used with devices that only have ACPI use (axp20x looks like the only potentially DT user, from the git history the swnode bits are apparently for use on ACPI systems). That might get fragile in the DT case since you could have multiple sources for description of the same thing unless you do something like suppress the swnode stuff on DT systems.
Given that swnode is basically DT written out in C code I'm not actually convinced it's that much of a win, unless someone writes some tooling to generate swnode data from DT files you're not getting the benefit of any of the schema validation work that's being done. We'd also need to do some work for regulators to make sure that if we are parsing DT properties on ACPI systems we don't do so from _DSD since ACPI has strong ideas about how power works and we don't want to end up with systems with firmware providing mixed ACPI/DT models without a clear understanding of what we're geting into.
I do also have other concerns in the purely DT case, especially with chip functions like the CODEC where there's a very poor mapping between physical IPs and how Linux is tending to describe things internally at the minute. In particular these devices often have a clock tree portions of which can be visible and useful off chip but which tends to get lumped in with the audio IPs in our current code. Ideally we'd describe that as a clock subdevice (or subdevices if that fits the hardware) using the clock bindings but then that has a bunch of knock on effects the way the code currently is which probably it's probably disproportionate to force an individual driver author to work through. OTOH the DT bindings should be OS neutral ABI so...
I don't think this is a reason to continue on the current path that pretends to have something generic, but instead is literally a board file fragment with baked-in magic numbers.
An irqchip is supposed to offer services to arbitrary clients (endpoint drivers) that are oblivious of the irqchip itself, of the hwirq mapping, and use the standard APIs to obtain a virtual interrupt number. None of that here. This is a monolithic driver, only split across multiple subsystem to satisfy a "not in my backyard" requirement.
If the vendors/authors want to keep the shape of the code as is, they can do it outside of the irqchip code and have some library code with an internal API. At least they will stop pretending that this is a general purpose driver. And the existing madera code can also go in the process.
M.
On Fri, Nov 11, 2022 at 08:00:10AM +0000, Marc Zyngier wrote:
On Thu, 10 Nov 2022 20:36:16 +0000, Mark Brown broonie@kernel.org wrote:
On Thu, Nov 10, 2022 at 06:47:20PM +0000, Marc Zyngier wrote:
Apologies this ended up getting quite long. Cirrus has no trouble changing how the IRQ driver works I just think we are struggling a little to understand exactly what parts of the code need reworking in what way, we appreciate your patience in helping us through.
If you were implementing an actual interrupt controller driver, I'd take it without any question. The fact that this code mandates the use of its own homegrown API rules it out.
I think this is part of the crossed wires here, the code does not mandate the use of its own home grown API, although it does provide one. For example our CODECs often provide GPIO/IRQ services for other devices such as say speaker amps attached along side them.
Here is a DT example from one of my dev systems with GPIO1 on cs47l35 (a madera CODEC) handling the IRQ for cs35l35 (a speaker amp):
cs35l35_left: cs35l35@40 { compatible = "cirrus,cs35l35"; reg = <0x40>;
#sound-dai-cells = <1>;
reset-gpios = <&axi_gpio0 0 0>;
interrupt-parent = <&cs47l35>; interrupts = <57 0>; };
No special code is required in the cs35l35 driver (it is fully upstreamed sound/soc/codecs/cs35l35.c). So if we are missing some actual interrupt controller API we need to be supporting that we are not please point us at it and we will happily add support?
So I think your objections are mostly regarding the cs48l32_request_irq function (and friends) that are being used by the other parts of the MFD. I don't think it would be super hard to remove these functions and move the IRQ into DT if that is the preferred way.
ACPI gets to be a lot of fun here, it's just not idiomatic to describe the internals of these devices in firmware there and a lot of the systems shipping this stuff are targeted at other OSs and system integrators are therefore not in the least worried about Linux preferences.
I would echo Mark's statement that going the way of moving this into DT/ACPI will actually likely necessitate the addition of a lot of "board file" stuff in the future. If the part gets used in any ACPI systems (granted support is not in yet but this is not a super unlikely addition in the future for cs48l32) we will need to support the laptops containing the part in Linux and the vendors are extremely unlikely to put internal CODEC IRQs into the ACPI tables.
But that aside I guess my main question about this approach would be what the DT binding would look like for the CODEC. Currently our devices use a single DT node for the device. Again pulling a Madera example from my dev setup, this is what the DT binding for one of our CODECs currently looks vaguely like:
cs47l35: cs47l35@1 { compatible = "cirrus,cs47l35"; reg = <0x1>;
spi-max-frequency = <11000000>;
interrupt-controller; #interrupt-cells = <2>; interrupt-parent = <&gpio0>; interrupts = <56 8>;
gpio-controller; #gpio-cells = <2>;
#sound-dai-cells = <1>;
AVDD-supply = <&lochnagar_vdd1v8>; DBVDD1-supply = <&lochnagar_vdd1v8>; DBVDD2-supply = <&lochnagar_vdd1v8>; CPVDD1-supply = <&lochnagar_vdd1v8>; CPVDD2-supply = <&lochnagar_vddcore>; DCVDD-supply = <&lochnagar_vddcore>; SPKVDD-supply = <&wallvdd>;
reset-gpios = <&lochnagar_pin 0 0>;
clocks = <&lochnagar_clk LOCHNAGAR_CDC_MCLK1>, <&lochnagar_clk LOCHNAGAR_CDC_MCLK2>; clock-names = "mclk1", "mclk2";
pinctrl-names = "default"; pinctrl-0 = <&cs47l35_defaults>; };
The interrupt-parent points at who our IRQ is connected to, and we are an interrupt-controller so people can use our IRQs. I think it is not currently supported to have more than a single interrupt-parent for a device so with the current binding is it actually possible for the device to refer to its own IRQs in DT?
An alternative approach would be to actually represent the MFD in device tree, I think this would allow things to work and look something like (totally not tested just for discussion):
cs47l35: cs47l35@1 { compatible = "cirrus,cs47l35"; reg = <0x1>;
spi-max-frequency = <11000000>;
irq: madera-irq { compatible = "cirrus,madera-irq";
interrupt-controller; #interrupt-cells = <2>; interrupt-parent = <&gpio0>; interrupts = <56 8>; };
gpio: madera-gpio { compatible = "cirrus,madera-gpio"; gpio-controller; #gpio-cells = <2>; };
sound: madera-sound { compatible = "cirrus,cs47l35-sound";
interrupt-parent = <&madera-irq>; interrupts = <55 0>, <56 0>; #sound-dai-cells = <1>; }; };
Historically I believe we have been discouraged (by upstream, not from our customers) from explicitly representing the parts of the MFD in device tree separately, as it was viewed that this is just an external SPI CODEC and one node mapped much more logically to the hardware, which is what DT should be describing. However, are you saying this would be a preferrable approach from your side? Or am I missing some alternative solution?
Again thank you kindly for you time looking at this.
Thanks, Charles
On Fri, Nov 11, 2022 at 11:16:11AM +0000, Charles Keepax wrote:
On Fri, Nov 11, 2022 at 08:00:10AM +0000, Marc Zyngier wrote:
ACPI gets to be a lot of fun here, it's just not idiomatic to describe the internals of these devices in firmware there and a lot of the systems shipping this stuff are targeted at other OSs and system integrators are therefore not in the least worried about Linux preferences.
I would echo Mark's statement that going the way of moving this into DT/ACPI will actually likely necessitate the addition of a lot of "board file" stuff in the future. If the part gets used in any ACPI systems (granted support is not in yet but this is not a super unlikely addition in the future for cs48l32) we will need to support the laptops containing the part in Linux and the vendors are extremely unlikely to put internal CODEC IRQs into the ACPI tables.
It's a bit of a stronger issue than that in that it's not how ACPI is usually expected to work (it draws more from the PCI model where you just get a top level ID from the device and have to figure the rest out yourself).
An alternative approach would be to actually represent the MFD in device tree, I think this would allow things to work and look something like (totally not tested just for discussion):
That's what Marc's pushing for - there is an idea to do that which works well enough for cases (like this irqchip for the most part, modulo how to handle the top level interrupts for the chip) where the way Linux wants to model the device maps clearly onto the hardware but like I was mentioning with the audio/clocking split it gets tricky where things are more up in the air and potentially changable since it's much harder to define a suitable ABI.
On Fri, Nov 11, 2022 at 11:49:25AM +0000, Mark Brown wrote:
On Fri, Nov 11, 2022 at 11:16:11AM +0000, Charles Keepax wrote:
On Fri, Nov 11, 2022 at 08:00:10AM +0000, Marc Zyngier wrote:
ACPI gets to be a lot of fun here, it's just not idiomatic to describe the internals of these devices in firmware there and a lot of the systems shipping this stuff are targeted at other OSs and system integrators are therefore not in the least worried about Linux preferences.
I would echo Mark's statement that going the way of moving this into DT/ACPI will actually likely necessitate the addition of a lot of "board file" stuff in the future. If the part gets used in any ACPI systems (granted support is not in yet but this is not a super unlikely addition in the future for cs48l32) we will need to support the laptops containing the part in Linux and the vendors are extremely unlikely to put internal CODEC IRQs into the ACPI tables.
It's a bit of a stronger issue than that in that it's not how ACPI is usually expected to work (it draws more from the PCI model where you just get a top level ID from the device and have to figure the rest out yourself).
Hmm... yes ok true ACPI isn't going to put the elements of the MFD in either so we would then need something to bind all those in as well.
Thanks, Charles
On Fri, Nov 11, 2022 at 11:16:11AM +0000, Charles Keepax wrote:
On Fri, Nov 11, 2022 at 08:00:10AM +0000, Marc Zyngier wrote:
On Thu, 10 Nov 2022 20:36:16 +0000, Mark Brown broonie@kernel.org wrote:
On Thu, Nov 10, 2022 at 06:47:20PM +0000, Marc Zyngier wrote:
The interrupt-parent points at who our IRQ is connected to, and we are an interrupt-controller so people can use our IRQs. I think it is not currently supported to have more than a single interrupt-parent for a device so with the current binding is it actually possible for the device to refer to its own IRQs in DT?
I see there is actually interrupts-extended which would let us refer to ourselves although its a little unclear to be if that would actually work but might be worth a look.
Thanks, Charles
On Fri, Nov 11, 2022 at 08:00:10AM +0000, Marc Zyngier wrote:
Mark Brown broonie@kernel.org wrote:
On Thu, Nov 10, 2022 at 06:47:20PM +0000, Marc Zyngier wrote:
Either you describe the internal structure of your device in DT or ACPI, and make all client drivers use the standard API, or you make this a codec library, purely specific to your device and only used by it. But the current shape is not something I'm prepared to accept.
ACPI gets to be a lot of fun here, it's just not idiomatic to describe the internals of these devices in firmware there and a lot of the systems shipping this stuff are targeted at other OSs and system integrators are therefore not in the least worried about Linux preferences.
Let me reassure the vendors that I do not care about them either. By this standard, we'd all run Windows on x86.
It turns out a bunch of these systems are intended to be used with Linux, and even where the vendor does care about Linux we also have to consider what's tasteful for ACPI.
You'd need to look at having the MFD add additional description via swnode or something to try to get things going. MFD
...
Given that swnode is basically DT written out in C code I'm not actually convinced it's that much of a win, unless someone writes some tooling to generate swnode data from DT files you're not getting the benefit of any
...
I do also have other concerns in the purely DT case, especially with chip functions like the CODEC where there's a very poor mapping between physical IPs and how Linux is tending to describe things internally at the minute. In particular these devices often have a clock tree
I don't think this is a reason to continue on the current path that pretends to have something generic, but instead is literally a board file fragment with baked-in magic numbers.
An irqchip is supposed to offer services to arbitrary clients (endpoint drivers) that are oblivious of the irqchip itself, of the hwirq mapping, and use the standard APIs to obtain a virtual interrupt number. None of that here. This is a monolithic driver, only split across multiple subsystem to satisfy a "not in my backyard" requirement.
If the vendors/authors want to keep the shape of the code as is, they can do it outside of the irqchip code and have some library code with an internal API. At least they will stop pretending that this is a general purpose driver. And the existing madera code can also go in the process.
Yeah, I'm definitely not in the least bit convinced that the irqchip code is a good home for this sort of glue (especially the interrupt consumers) for the reasons you mention - my concern was more that the firmware interface also has issues, and that putting things into firmware is also putting them into ABI which is much harder to do a good job with later.
On 10/11/2022 12:01, Marc Zyngier wrote:
On Thu, 10 Nov 2022 11:22:26 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 10/11/2022 08:02, Marc Zyngier wrote:
On Wed, 09 Nov 2022 16:53:28 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
The Cirrus Logic CS48L31/32/33 audio codecs contain a programmable interrupt controller with a variety of interrupt sources, including GPIOs that can be used as interrupt inputs.
This driver provides the handling for the interrupt controller. As the codec is accessed via regmap, the generic regmap_irq functionality is used to do most of the work.
I cannot spot a shred of interrupt controller code in there. This
It is providing support for handling an interrupt controller so that other drivers can bind to those interrupts. It's just that regmap provides a lot of generic implementation for SPI-connected interrupt controllers so we don't need to open-code all that in the irqchip driver.
And thus none of that code needs to live in drivers/irqchip.
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
I don't like this stuff either. All this code is a glorified set of interrupt handlers and #defines that only hide the lack of a proper DT binding to express the interrupt routing (it feels like looking at board files from 10 years ago).
None of that belongs in the irqchip code.
It is also a direct copy of the existing irq-madera.c code, duplicated for no obvious reason.
It's not a duplicate. The register map of this device is different (different addressing, 32-bit registers not 16-bit)
And? How hard is it to implement an indirection containing the register map and the relevant callbacks? /roll-eyes
I note your accusation that we were too lazy (or too stupid?) to think of this.
M.
On Thu, 10 Nov 2022 13:14:30 +0000, Richard Fitzgerald rf@opensource.cirrus.com wrote:
I note your accusation that we were too lazy (or too stupid?) to think of this.
Take it the way you want. But I criticise the code, not the author. And I'm merely pointed out that there was significant room for improvement.
M.
On Thu, Nov 10, 2022 at 11:22:26AM +0000, Richard Fitzgerald wrote:
On 10/11/2022 08:02, Marc Zyngier wrote:
belongs IMO to the MFD code.
We did once put interrupt support in MFD for an older product line but the MFD maintainer doesn't like the MFD being a dumping-ground for random other functionality that have their own subsystems.
There's bits of this like logging the top level error interrupts that seem like they clearly fit in the driver for the top level chip (SYSCLK possibly in the audio driver, dunno if it gets used by other functions), they're users of the interrupt controller rather than part of the interrupt controller.
It is also a direct copy of the existing irq-madera.c code, duplicated for no obvious reason.
It's not a duplicate. The register map of this device is different (different addressing, 32-bit registers not 16-bit)
Isn't that just a data difference which could be parameterised?
Some HALO-based codecs need some additional custom setup in the pre_run stage of cs_dsp. Implement the callback in wm_adsp to call an optional codec driver callback.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- sound/soc/codecs/wm_adsp.c | 11 +++++++++++ sound/soc/codecs/wm_adsp.h | 1 + 2 files changed, 12 insertions(+)
diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 8a2e9771bb50..34a94b011518 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -1035,6 +1035,16 @@ int wm_adsp_early_event(struct snd_soc_dapm_widget *w, } EXPORT_SYMBOL_GPL(wm_adsp_early_event);
+static int wm_adsp_pre_run(struct cs_dsp *cs_dsp) +{ + struct wm_adsp *dsp = container_of(cs_dsp, struct wm_adsp, cs_dsp); + + if (!dsp->pre_run) + return 0; + + return (*dsp->pre_run)(dsp); +} + static int wm_adsp_event_post_run(struct cs_dsp *cs_dsp) { struct wm_adsp *dsp = container_of(cs_dsp, struct wm_adsp, cs_dsp); @@ -2043,6 +2053,7 @@ static const struct cs_dsp_client_ops wm_adsp1_client_ops = { static const struct cs_dsp_client_ops wm_adsp2_client_ops = { .control_add = wm_adsp_control_add, .control_remove = wm_adsp_control_remove, + .pre_run = wm_adsp_pre_run, .post_run = wm_adsp_event_post_run, .post_stop = wm_adsp_event_post_stop, .watchdog_expired = wm_adsp_fatal_error, diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 375009a65828..dc2f7a096e26 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -36,6 +36,7 @@ struct wm_adsp { int fw;
struct work_struct boot_work; + int (*pre_run)(struct wm_adsp *dsp);
bool preloaded; bool fatal_error;
Codecs in this family have multiple digital and analog audio I/O that support a variety of external hardware connections and configurations.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- .../bindings/sound/cirrus,cs48l32.yaml | 96 +++++++++++++++++++ include/dt-bindings/sound/cs48l32.h | 25 +++++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml create mode 100644 include/dt-bindings/sound/cs48l32.h
diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..70fb294c6dc1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cirrus Logic CS48L31/32/33 audio CODECs + +maintainers: + - patches@opensource.cirrus.com + +description: | + This describes audio configuration bindings for these codecs. + + See also the core bindings for the parent MFD driver: + + Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml + + and defines for values used in these bindings: + + include/dt-bindings/sound/cs48l32.h + + The properties are all contained in the parent MFD node. + +properties: + '#sound-dai-cells': + const: 1 + + cirrus,in-type: + description: + A list of input type settings for each input. A maximum of 8 cells, + with four cells per input in the order INnL_1, INnR_1 INnL_2 INnR_2. + (where _1 and _2 are the alternative mux selections for that INn). + If the array is shorter than the number of inputs the unspecified + inputs default to CS48L32_IN_TYPE_DIFF. + $ref: "/schemas/types.yaml#/definitions/uint32-matrix" + minItems: 1 + maxItems: 8 + items: + items: + - description: + The first cell is INnL_1 input type. One of the CS48L32_IN_TYPE_xxx. + For non-muxed inputs this sets the type of INnL. + minimum: 0 + maximum: 1 + - description: + The second cell is INnR_1 input type. One of the CS48L32_IN_TYPE_xxx. + For non-muxed inputs this sets the type of INnR. + minimum: 0 + maximum: 1 + - description: + The third cell is INnL_2 input type. One of the CS48L32_IN_TYPE_xxx. + For non-muxed inputs this cell must be 0. + minimum: 0 + maximum: 1 + - description: + The fourth cell is INnR_2 input type. One of the CS48L32_IN_TYPE_xxx. + For non-muxed inputs this cell must be 0. + minimum: 0 + maximum: 1 + + cirrus,max-channels-clocked: + description: + Maximum number of channels that clocks will be generated for. When using + multiple data lines, every sample slot can transfer multiple channels + (one per data line). This pdata sets the maximum number of slots. + One cell for each ASP, use a value of zero for ASPs that should be + handled normally. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 4 + items: + default: 0 + + cirrus,pdm-sup: + description: + Indicates how the MICBIAS pins have been externally connected to DMICs + on each input. One cell per input (IN1, IN2, ...). One of the + CS48L32_MICBIAS_xxx values. + See the INn_PDM_SUP field in the datasheet for a description. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 4 + +examples: + - | + cs48l32@0 { + compatible = "cirrus,cs48l32"; + + cirrus,in-type = < + CS48L32_IN_TYPE_DIFF CS48L32_IN_TYPE_DIFF /* IN1[LR]_1 differential */ + CS48L32_IN_TYPE_SE CS48L32_IN_TYPE_SE /* IN1[LR]_2 single-ended */ + CS48L32_IN_TYPE_DIFF CS48L32_IN_TYPE_DIFF /* IN2[LR]_1 differential */ + >; + cirrus,max-channels-clocked = <2 0 0>; + }; diff --git a/include/dt-bindings/sound/cs48l32.h b/include/dt-bindings/sound/cs48l32.h new file mode 100644 index 000000000000..0b774da0a6c8 --- /dev/null +++ b/include/dt-bindings/sound/cs48l32.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Device Tree defines for CS48L32 codec. + * + * Copyright (C) 2016-2018, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef DT_BINDINGS_SOUND_CS48L32_H +#define DT_BINDINGS_SOUND_CS48L32_H + +#define CS48L32_IN_TYPE_DIFF 0 +#define CS48L32_IN_TYPE_SE 1 + +#define CS48L32_PDM_SUP_VOUT_MIC 0 +#define CS48L32_PDM_SUP_MICBIAS1 1 +#define CS48L32_PDM_SUP_MICBIAS2 2 +#define CS48L32_PDM_SUP_MICBIAS3 3 + +#define CS48L32_PDM_FMT_MODE_A_LSB_FIRST 0x0000 +#define CS48L32_PDM_FMT_MODE_B_LSB_FIRST 0x4000 +#define CS48L32_PDM_FMT_MODE_A_MSB_FIRST 0x8000 +#define CS48L32_PDM_FMT_MODE_B_MSB_FIRST 0xc000 + +#endif
On Wed, 09 Nov 2022 16:53:30 +0000, Richard Fitzgerald wrote:
Codecs in this family have multiple digital and analog audio I/O that support a variety of external hardware connections and configurations.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/sound/cirrus,cs48l32.yaml | 96 +++++++++++++++++++ include/dt-bindings/sound/cs48l32.h | 25 +++++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml create mode 100644 include/dt-bindings/sound/cs48l32.h
My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check' on your patch (DT_CHECKER_FLAGS is new in v5.13):
yamllint warnings/errors: ./Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml:44:20: [error] syntax error: mapping values are not allowed here (syntax)
dtschema/dtc warnings/errors: make[1]: *** Deleting file 'Documentation/devicetree/bindings/sound/cirrus,cs48l32.example.dts' Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml:44:20: mapping values are not allowed here make[1]: *** [Documentation/devicetree/bindings/Makefile:26: Documentation/devicetree/bindings/sound/cirrus,cs48l32.example.dts] Error 1 make[1]: *** Waiting for unfinished jobs.... ./Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml:44:20: mapping values are not allowed here /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml: ignoring, error parsing file make: *** [Makefile:1492: dt_binding_check] Error 2
doc reference errors (make refcheckdocs): Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
See https://patchwork.ozlabs.org/patch/
This check can fail if there are any dependencies. The base for a patch series is generally the most recent rc1.
If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date:
pip3 install dtschema --upgrade
Please check and re-submit.
On 09/11/2022 17:53, Richard Fitzgerald wrote:
Codecs in this family have multiple digital and analog audio I/O that support a variety of external hardware connections and configurations.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/sound/cirrus,cs48l32.yaml | 96 +++++++++++++++++++ include/dt-bindings/sound/cs48l32.h | 25 +++++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml create mode 100644 include/dt-bindings/sound/cs48l32.h
diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..70fb294c6dc1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Cirrus Logic CS48L31/32/33 audio CODECs
+maintainers:
- patches@opensource.cirrus.com
+description: |
- This describes audio configuration bindings for these codecs.
Don't start with "This". Instead describe the hardware.
- See also the core bindings for the parent MFD driver:
- Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
Same comment as for pinctrl patch.
- and defines for values used in these bindings:
- include/dt-bindings/sound/cs48l32.h
- The properties are all contained in the parent MFD node.
+properties:
Missing compatible. What's the point to organize bindings like that? The schema on its own does nothing - does not match anything.
- '#sound-dai-cells':
- const: 1
- cirrus,in-type:
- description:
A list of input type settings for each input. A maximum of 8 cells,
with four cells per input in the order INnL_1, INnR_1 INnL_2 INnR_2.
(where _1 and _2 are the alternative mux selections for that INn).
If the array is shorter than the number of inputs the unspecified
inputs default to CS48L32_IN_TYPE_DIFF.
- $ref: "/schemas/types.yaml#/definitions/uint32-matrix"
Drop quotes.
- minItems: 1
- maxItems: 8
- items:
items:
- description:
The first cell is INnL_1 input type. One of the CS48L32_IN_TYPE_xxx.
For non-muxed inputs this sets the type of INnL.
What is the "input type"? Referring to constants is not enough, especially that they are not descriptive. Explain here the values.
minimum: 0
maximum: 1
- description:
The second cell is INnR_1 input type. One of the CS48L32_IN_TYPE_xxx.
For non-muxed inputs this sets the type of INnR.
minimum: 0
maximum: 1
- description:
The third cell is INnL_2 input type. One of the CS48L32_IN_TYPE_xxx.
For non-muxed inputs this cell must be 0.
minimum: 0
maximum: 1
- description:
The fourth cell is INnR_2 input type. One of the CS48L32_IN_TYPE_xxx.
For non-muxed inputs this cell must be 0.
minimum: 0
maximum: 1
- cirrus,max-channels-clocked:
- description:
Maximum number of channels that clocks will be generated for. When using
multiple data lines, every sample slot can transfer multiple channels
(one per data line). This pdata sets the maximum number of slots.
One cell for each ASP, use a value of zero for ASPs that should be
handled normally.
- $ref: /schemas/types.yaml#/definitions/uint32-array
- minItems: 1
- maxItems: 4
- items:
default: 0
- cirrus,pdm-sup:
- description:
Indicates how the MICBIAS pins have been externally connected to DMICs
on each input. One cell per input (IN1, IN2, ...). One of the
CS48L32_MICBIAS_xxx values.
See the INn_PDM_SUP field in the datasheet for a description.
No, explain here.
- $ref: /schemas/types.yaml#/definitions/uint32-array
- minItems: 1
- maxItems: 4
+examples:
- |
cs48l32@0 {
Node names should be generic. https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetre...
compatible = "cirrus,cs48l32";
Use 4 spaces for example indentation.
cirrus,in-type = <
CS48L32_IN_TYPE_DIFF CS48L32_IN_TYPE_DIFF /* IN1[LR]_1 differential */
CS48L32_IN_TYPE_SE CS48L32_IN_TYPE_SE /* IN1[LR]_2 single-ended */
CS48L32_IN_TYPE_DIFF CS48L32_IN_TYPE_DIFF /* IN2[LR]_1 differential */
>;
cirrus,max-channels-clocked = <2 0 0>;
};
diff --git a/include/dt-bindings/sound/cs48l32.h b/include/dt-bindings/sound/cs48l32.h new file mode 100644 index 000000000000..0b774da0a6c8 --- /dev/null +++ b/include/dt-bindings/sound/cs48l32.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */
Dual license.
+/*
- Device Tree defines for CS48L32 codec.
- Copyright (C) 2016-2018, 2022 Cirrus Logic, Inc. and
Cirrus Logic International Semiconductor Ltd.
- */
+#ifndef DT_BINDINGS_SOUND_CS48L32_H +#define DT_BINDINGS_SOUND_CS48L32_H
+#define CS48L32_IN_TYPE_DIFF 0 +#define CS48L32_IN_TYPE_SE 1
+#define CS48L32_PDM_SUP_VOUT_MIC 0 +#define CS48L32_PDM_SUP_MICBIAS1 1 +#define CS48L32_PDM_SUP_MICBIAS2 2 +#define CS48L32_PDM_SUP_MICBIAS3 3
+#define CS48L32_PDM_FMT_MODE_A_LSB_FIRST 0x0000 +#define CS48L32_PDM_FMT_MODE_B_LSB_FIRST 0x4000 +#define CS48L32_PDM_FMT_MODE_A_MSB_FIRST 0x8000 +#define CS48L32_PDM_FMT_MODE_B_MSB_FIRST 0xc000
Register values do not belong to bindings.
+#endif
Best regards, Krzysztof
On 14/11/2022 08:45, Krzysztof Kozlowski wrote:
On 09/11/2022 17:53, Richard Fitzgerald wrote:
Codecs in this family have multiple digital and analog audio I/O that support a variety of external hardware connections and configurations.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/sound/cirrus,cs48l32.yaml | 96 +++++++++++++++++++ include/dt-bindings/sound/cs48l32.h | 25 +++++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml create mode 100644 include/dt-bindings/sound/cs48l32.h
diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..70fb294c6dc1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Cirrus Logic CS48L31/32/33 audio CODECs
+maintainers:
- patches@opensource.cirrus.com
+description: |
- This describes audio configuration bindings for these codecs.
Don't start with "This". Instead describe the hardware.
- See also the core bindings for the parent MFD driver:
- Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
Same comment as for pinctrl patch.
- and defines for values used in these bindings:
- include/dt-bindings/sound/cs48l32.h
- The properties are all contained in the parent MFD node.
+properties:
Missing compatible. What's the point to organize bindings like that? The schema on its own does nothing - does not match anything.
Do you mean child drivers should not share the MFD node? Or do you mean that if they share the MFD node all the child driver bindings should be documented in the MFD schema instead of having a sub-schema for each class of hardware functionality?
I'm certainly willing to collapse all the bindings into a single MFD schema yaml. For this driver we followed the same structure that was accepted for madera (and there was some discussion when we upstreamed madera about how the bindings should be organized which resulted in them being changed). We pretty much assumed that the safe bet was to do the same that was accepted by the maintainer last time around.
On 14/11/2022 12:00, Richard Fitzgerald wrote:
On 14/11/2022 08:45, Krzysztof Kozlowski wrote:
On 09/11/2022 17:53, Richard Fitzgerald wrote:
Codecs in this family have multiple digital and analog audio I/O that support a variety of external hardware connections and configurations.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/sound/cirrus,cs48l32.yaml | 96 +++++++++++++++++++ include/dt-bindings/sound/cs48l32.h | 25 +++++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml create mode 100644 include/dt-bindings/sound/cs48l32.h
diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..70fb294c6dc1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Cirrus Logic CS48L31/32/33 audio CODECs
+maintainers:
- patches@opensource.cirrus.com
+description: |
- This describes audio configuration bindings for these codecs.
Don't start with "This". Instead describe the hardware.
- See also the core bindings for the parent MFD driver:
- Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
Same comment as for pinctrl patch.
- and defines for values used in these bindings:
- include/dt-bindings/sound/cs48l32.h
- The properties are all contained in the parent MFD node.
+properties:
Missing compatible. What's the point to organize bindings like that? The schema on its own does nothing - does not match anything.
Do you mean child drivers should not share the MFD node? Or do you mean that if they share the MFD node all the child driver bindings should be documented in the MFD schema instead of having a sub-schema for each class of hardware functionality?
I mean, that regular binding has a compatible which allows the schema to be matched.
Splitting parts from top-level properties is used only for re-usable shared/common schemas, which does not seem the case here.
I'm certainly willing to collapse all the bindings into a single MFD schema yaml. For this driver we followed the same structure that was accepted for madera (and there was some discussion when we upstreamed madera about how the bindings should be organized which resulted in them being changed). We pretty much assumed that the safe bet was to do the same that was accepted by the maintainer last time around.
Just merge it with MFD binding.
Best regards, Krzysztof
On 14/11/2022 11:03, Krzysztof Kozlowski wrote:
On 14/11/2022 12:00, Richard Fitzgerald wrote:
On 14/11/2022 08:45, Krzysztof Kozlowski wrote:
On 09/11/2022 17:53, Richard Fitzgerald wrote:
Codecs in this family have multiple digital and analog audio I/O that support a variety of external hardware connections and configurations.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
.../bindings/sound/cirrus,cs48l32.yaml | 96 +++++++++++++++++++ include/dt-bindings/sound/cs48l32.h | 25 +++++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml create mode 100644 include/dt-bindings/sound/cs48l32.h
diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml new file mode 100644 index 000000000000..70fb294c6dc1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cirrus,cs48l32.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Cirrus Logic CS48L31/32/33 audio CODECs
+maintainers:
- patches@opensource.cirrus.com
+description: |
- This describes audio configuration bindings for these codecs.
Don't start with "This". Instead describe the hardware.
- See also the core bindings for the parent MFD driver:
- Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml
Same comment as for pinctrl patch.
- and defines for values used in these bindings:
- include/dt-bindings/sound/cs48l32.h
- The properties are all contained in the parent MFD node.
+properties:
Missing compatible. What's the point to organize bindings like that? The schema on its own does nothing - does not match anything.
Do you mean child drivers should not share the MFD node? Or do you mean that if they share the MFD node all the child driver bindings should be documented in the MFD schema instead of having a sub-schema for each class of hardware functionality?
I mean, that regular binding has a compatible which allows the schema to be matched.
Splitting parts from top-level properties is used only for re-usable shared/common schemas, which does not seem the case here.
Ok, that's good. None of these drivers are re-useable standalone. I'll squash the bindings all into MFD schema for V2.
I'm certainly willing to collapse all the bindings into a single MFD schema yaml. For this driver we followed the same structure that was accepted for madera (and there was some discussion when we upstreamed madera about how the bindings should be organized which resulted in them being changed). We pretty much assumed that the safe bet was to do the same that was accepted by the maintainer last time around.
Just merge it with MFD binding.
Best regards, Krzysztof
From: Stuart Henderson stuarth@opensource.cirrus.com
The CS48L32 is a high-performance low-power audio DSP for smartphones and other portable audio devices. The CS48L32 combines a programmable Halo Core DSP with a variety of power-efficient fixed-function audio processors.
Signed-off-by: Piotr Stankiewicz piotrs@opensource.cirrus.com Signed-off-by: Stuart Henderson stuarth@opensource.cirrus.com Signed-off-by: Qi Zhou qi.zhou@cirrus.com Signed-off-by: Charles Keepax ckeepax@opensource.cirrus.com Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- MAINTAINERS | 4 + include/sound/cs48l32.h | 89 + sound/soc/codecs/Kconfig | 9 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cs48l32-core.c | 2782 +++++++++++++++++++++++++++++++ sound/soc/codecs/cs48l32.c | 1211 ++++++++++++++ sound/soc/codecs/cs48l32.h | 386 +++++ 7 files changed, 4483 insertions(+) create mode 100644 include/sound/cs48l32.h create mode 100644 sound/soc/codecs/cs48l32-core.c create mode 100644 sound/soc/codecs/cs48l32.c create mode 100644 sound/soc/codecs/cs48l32.h
diff --git a/MAINTAINERS b/MAINTAINERS index f52e9a6e290c..9d5ddb50f388 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5013,6 +5013,7 @@ F: Documentation/devicetree/bindings/mfd/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/mfd/cirrus,madera.yaml F: Documentation/devicetree/bindings/pinctrl/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/pinctrl/cirrus,madera.yaml +F: Documentation/devicetree/bindings/sound/cirrus,cs48l32.yaml F: Documentation/devicetree/bindings/sound/cirrus,madera.yaml F: drivers/gpio/gpio-madera* F: drivers/irqchip/irq-cirrus-cs48l32* @@ -5021,13 +5022,16 @@ F: drivers/mfd/cs47l* F: drivers/mfd/cs48l* F: drivers/mfd/madera* F: drivers/pinctrl/cirrus/* +F: include/dt-bindings/sound/cs48l32.h F: include/dt-bindings/sound/madera* F: include/linux/irqchip/irq-cirrus-cs48l32* F: include/linux/irqchip/irq-madera* F: include/linux/mfd/cs48l32/* F: include/linux/mfd/madera/* +F: include/sound/cs48l32.h F: include/sound/madera* F: sound/soc/codecs/cs47l* +F: sound/soc/codecs/cs48l32* F: sound/soc/codecs/madera*
CISCO FCOE HBA DRIVER diff --git a/include/sound/cs48l32.h b/include/sound/cs48l32.h new file mode 100644 index 000000000000..3078bb520dae --- /dev/null +++ b/include/sound/cs48l32.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Cirrus Logic CS48L32 codec + * + * Copyright (C) 2016-2018, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef SOUND_CS48L32_H +#define SOUND_CS48L32_H + +#include <linux/mfd/cs48l32/core.h> +#include <linux/notifier.h> +#include <sound/soc-component.h> + +/* pll_id for snd_soc_component_set_pll() */ +#define CS48L32_FLL1_REFCLK 1 + +/* source for snd_soc_component_set_pll() */ +#define CS48L32_FLL_SRC_NONE -1 +#define CS48L32_FLL_SRC_MCLK1 0 +#define CS48L32_FLL_SRC_PDMCLK 5 +#define CS48L32_FLL_SRC_ASP1_BCLK 8 +#define CS48L32_FLL_SRC_ASP2_BCLK 9 +#define CS48L32_FLL_SRC_ASP1_FSYNC 12 +#define CS48L32_FLL_SRC_ASP2_FSYNC 13 + +/* clk_id for snd_soc_component_set_sysclk() and snd_soc_dai_set_sysclk() */ +#define CS48L32_CLK_SYSCLK_1 1 +#define CS48L32_CLK_SYSCLK_2 2 +#define CS48L32_CLK_SYSCLK_3 3 +#define CS48L32_CLK_SYSCLK_4 4 +#define CS48L32_CLK_DSPCLK 7 +#define CS48L32_CLK_OPCLK 9 +#define CS48L32_CLK_PDM_FLLCLK 13 + +/* source for snd_soc_component_set_sysclk() */ +#define CS48L32_CLK_SRC_MCLK1 0x0 +#define CS48L32_CLK_SRC_FLL1 0x4 +#define CS48L32_CLK_SRC_ASP1_BCLK 0x8 +#define CS48L32_CLK_SRC_ASP2_BCLK 0x9 + +/* Notifier events */ +#define CS48L32_NOTIFY_ULTRASONIC 0x1 + +/** + * struct cs48l32_us_notify_data - Ultrasonic event notification data + * @us_no: Index of ultrasonic block that reported the event. + * + * Data passed in an ultrasonic notification event callback. + */ +struct cs48l32_us_notify_data { + unsigned int us_no; +}; + +/** + * cs48l32_register_notifier() - Register notification event callback + * @component: ASoC component of the cs48l32 driver. + * @nb: Client struct notifier_block. + * + * Return: 0 on success else standard negative error code. + */ +static inline int cs48l32_register_notifier(struct snd_soc_component *component, + struct notifier_block *nb) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(component->dev->parent); + + return blocking_notifier_chain_register(&mfd->notifier, nb); +} + +/** + * cs48l32_unregister_notifier() - Unregister notification event callback + * @component: ASoC component of the cs48l32 driver. + * @nb: Client struct notifier_block. + * + * Unregister an event notifier callback that was registered by + * cs48l32_register_notifier(). + * + * Return: 0 on success else standard negative error code. + */ +static inline int cs48l32_unregister_notifier(struct snd_soc_component *component, + struct notifier_block *nb) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(component->dev->parent); + + return blocking_notifier_chain_unregister(&mfd->notifier, nb); +} + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7a13e750751a..aadbdf33b373 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -87,6 +87,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_CS47L85 imply SND_SOC_CS47L90 imply SND_SOC_CS47L92 + imply SND_SOC_CS48L32 imply SND_SOC_CS53L30 imply SND_SOC_CX20442 imply SND_SOC_CX2072X @@ -351,6 +352,7 @@ config SND_SOC_WM_ADSP select CS_DSP select SND_SOC_COMPRESS default y if SND_SOC_MADERA=y + default y if SND_SOC_CS48L32=y default y if SND_SOC_CS47L24=y default y if SND_SOC_WM5102=y default y if SND_SOC_WM5110=y @@ -358,6 +360,7 @@ config SND_SOC_WM_ADSP default y if SND_SOC_CS35L41_SPI=y default y if SND_SOC_CS35L41_I2C=y default m if SND_SOC_MADERA=m + default m if SND_SOC_CS48L32=m default m if SND_SOC_CS47L24=m default m if SND_SOC_WM5102=m default m if SND_SOC_WM5110=m @@ -816,6 +819,12 @@ config SND_SOC_CS47L92 tristate depends on MFD_CS47L92
+config SND_SOC_CS48L32 + tristate "Cirrus Logic CS48L32 CODEC" + depends on MFD_CS48L32 + help + Enable support for the Cirrus Logic CS48L32 codec. + # Cirrus Logic Quad-Channel ADC config SND_SOC_CS53L30 tristate "Cirrus Logic CS53L30 CODEC" diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9170ee1447dd..2c59463d1085 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -89,6 +89,7 @@ snd-soc-cs47l35-objs := cs47l35.o snd-soc-cs47l85-objs := cs47l85.o snd-soc-cs47l90-objs := cs47l90.o snd-soc-cs47l92-objs := cs47l92.o +snd-soc-cs48l32-objs := cs48l32.o cs48l32-core.o snd-soc-cs53l30-objs := cs53l30.o snd-soc-cx20442-objs := cx20442.o snd-soc-cx2072x-objs := cx2072x.o @@ -449,6 +450,7 @@ obj-$(CONFIG_SND_SOC_CS47L35) += snd-soc-cs47l35.o obj-$(CONFIG_SND_SOC_CS47L85) += snd-soc-cs47l85.o obj-$(CONFIG_SND_SOC_CS47L90) += snd-soc-cs47l90.o obj-$(CONFIG_SND_SOC_CS47L92) += snd-soc-cs47l92.o +obj-$(CONFIG_SND_SOC_CS48L32) += snd-soc-cs48l32.o obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_CX2072X) += snd-soc-cx2072x.o diff --git a/sound/soc/codecs/cs48l32-core.c b/sound/soc/codecs/cs48l32-core.c new file mode 100644 index 000000000000..8daebbb0ea50 --- /dev/null +++ b/sound/soc/codecs/cs48l32-core.c @@ -0,0 +1,2782 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Cirrus Logic CS48L32 codec core support +// +// Copyright (C) 2016-2018, 2020, 2022 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. +// + +#include <dt-bindings/sound/cs48l32.h> +#include <linux/delay.h> +#include <linux/gcd.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "cs48l32.h" + +#define CS48L32_ASP_ENABLES1 0x00 +#define CS48L32_ASP_CONTROL1 0x04 +#define CS48L32_ASP_CONTROL2 0x08 +#define CS48L32_ASP_CONTROL3 0x0c +#define CS48L32_ASP_FRAME_CONTROL1 0x10 +#define CS48L32_ASP_FRAME_CONTROL2 0x14 +#define CS48L32_ASP_FRAME_CONTROL5 0x20 +#define CS48L32_ASP_FRAME_CONTROL6 0x24 +#define CS48L32_ASP_DATA_CONTROL1 0x30 +#define CS48L32_ASP_DATA_CONTROL5 0x40 + +#define CS48L32_SYSCLK_RATE_6MHZ 0 +#define CS48L32_SYSCLK_RATE_12MHZ 1 +#define CS48L32_SYSCLK_RATE_24MHZ 2 +#define CS48L32_SYSCLK_RATE_49MHZ 3 +#define CS48L32_SYSCLK_RATE_98MHZ 4 + +#define CS48L32_FLLHJ_INT_MAX_N 1023 +#define CS48L32_FLLHJ_INT_MIN_N 1 +#define CS48L32_FLLHJ_FRAC_MAX_N 255 +#define CS48L32_FLLHJ_FRAC_MIN_N 2 +#define CS48L32_FLLHJ_LP_INT_MODE_THRESH 100000 +#define CS48L32_FLLHJ_LOW_THRESH 192000 +#define CS48L32_FLLHJ_MID_THRESH 1152000 +#define CS48L32_FLLHJ_MAX_THRESH 13000000 +#define CS48L32_FLLHJ_LOW_GAINS 0x23f0 +#define CS48L32_FLLHJ_MID_GAINS 0x22f2 +#define CS48L32_FLLHJ_HIGH_GAINS 0x21f0 +#define CS48L32_FLL_MAX_FOUT 50000000 +#define CS48L32_FLL_MAX_REFDIV 8 + +#define CS48L32_FLL_CONTROL1_OFFS 0x00 +#define CS48L32_FLL_CONTROL2_OFFS 0x04 +#define CS48L32_FLL_CONTROL3_OFFS 0x08 +#define CS48L32_FLL_CONTROL4_OFFS 0x0c +#define CS48L32_FLL_CONTROL5_OFFS 0x10 +#define CS48L32_FLL_CONTROL6_OFFS 0x14 +#define CS48L32_FLL_DIGITAL_TEST2_OFFS 0x34 +#define CS48L32_FLL_GPIO_CLOCK_OFFS 0xa0 + +#define CS48L32_DSP_CLOCK_FREQ_OFFS 0x00000 + +#define CS48L32_ASP_FMT_DSP_MODE_A 0 +#define CS48L32_ASP_FMT_DSP_MODE_B 1 +#define CS48L32_ASP_FMT_I2S_MODE 2 +#define CS48L32_ASP_FMT_LEFT_JUSTIFIED_MODE 3 + +#define CS48L32_HALO_SAMPLE_RATE_RX1 0x00080 +#define CS48L32_HALO_SAMPLE_RATE_TX1 0x00280 +#define CS48L32_HALO_DSP_RATE_MASK 0x1f + +#define cs48l32_fll_err(_fll, fmt, ...) \ + dev_err(_fll->cs48l32->dev, "FLL%d: " fmt, _fll->id, ##__VA_ARGS__) +#define cs48l32_fll_warn(_fll, fmt, ...) \ + dev_warn(_fll->cs48l32->dev, "FLL%d: " fmt, _fll->id, ##__VA_ARGS__) +#define cs48l32_fll_dbg(_fll, fmt, ...) \ + dev_dbg(_fll->cs48l32->dev, "FLL%d: " fmt, _fll->id, ##__VA_ARGS__) + +#define cs48l32_asp_err(_dai, fmt, ...) \ + dev_err(_dai->component->dev, "ASP%d: " fmt, _dai->id, ##__VA_ARGS__) +#define cs48l32_asp_warn(_dai, fmt, ...) \ + dev_warn(_dai->component->dev, "ASP%d: " fmt, _dai->id, ##__VA_ARGS__) +#define cs48l32_asp_dbg(_dai, fmt, ...) \ + dev_dbg(_dai->component->dev, "ASP%d: " fmt, _dai->id, ##__VA_ARGS__) + +const char * const cs48l32_mixer_texts[] = { + "None", + "Tone Generator 1", + "Tone Generator 2", + "Noise Generator", + "IN1L", + "IN1R", + "IN2L", + "IN2R", + "ASP1RX1", + "ASP1RX2", + "ASP1RX3", + "ASP1RX4", + "ASP1RX5", + "ASP1RX6", + "ASP1RX7", + "ASP1RX8", + "ASP2RX1", + "ASP2RX2", + "ASP2RX3", + "ASP2RX4", + "ISRC1INT1", + "ISRC1INT2", + "ISRC1INT3", + "ISRC1INT4", + "ISRC1DEC1", + "ISRC1DEC2", + "ISRC1DEC3", + "ISRC1DEC4", + "ISRC2INT1", + "ISRC2INT2", + "ISRC2DEC1", + "ISRC2DEC2", + "ISRC3INT1", + "ISRC3INT2", + "ISRC3DEC1", + "ISRC3DEC2", + "EQ1", + "EQ2", + "EQ3", + "EQ4", + "DRC1L", + "DRC1R", + "DRC2L", + "DRC2R", + "LHPF1", + "LHPF2", + "LHPF3", + "LHPF4", + "Ultrasonic 1", + "Ultrasonic 2", + "DSP1.1", + "DSP1.2", + "DSP1.3", + "DSP1.4", + "DSP1.5", + "DSP1.6", + "DSP1.7", + "DSP1.8", +}; + +unsigned int cs48l32_mixer_values[] = { + 0x000, /* Silence (mute) */ + 0x004, /* Tone generator 1 */ + 0x005, /* Tone generator 2 */ + 0x00C, /* Noise Generator */ + 0x010, /* IN1L signal path */ + 0x011, /* IN1R signal path */ + 0x012, /* IN2L signal path */ + 0x013, /* IN2R signal path */ + 0x020, /* ASP1 RX1 */ + 0x021, /* ASP1 RX2 */ + 0x022, /* ASP1 RX3 */ + 0x023, /* ASP1 RX4 */ + 0x024, /* ASP1 RX5 */ + 0x025, /* ASP1 RX6 */ + 0x026, /* ASP1 RX7 */ + 0x027, /* ASP1 RX8 */ + 0x030, /* ASP2 RX1 */ + 0x031, /* ASP2 RX2 */ + 0x032, /* ASP2 RX3 */ + 0x033, /* ASP2 RX4 */ + 0x098, /* ISRC1 INT1 */ + 0x099, /* ISRC1 INT2 */ + 0x09a, /* ISRC1 INT3 */ + 0x09b, /* ISRC1 INT4 */ + 0x09C, /* ISRC1 DEC1 */ + 0x09D, /* ISRC1 DEC2 */ + 0x09e, /* ISRC1 DEC3 */ + 0x09f, /* ISRC1 DEC4 */ + 0x0A0, /* ISRC2 INT1 */ + 0x0A1, /* ISRC2 INT2 */ + 0x0A4, /* ISRC2 DEC1 */ + 0x0A5, /* ISRC2 DEC2 */ + 0x0A8, /* ISRC3 INT1 */ + 0x0A9, /* ISRC3 INT2 */ + 0x0AC, /* ISRC3 DEC1 */ + 0x0AD, /* ISRC3 DEC2 */ + 0x0B8, /* EQ1 */ + 0x0B9, /* EQ2 */ + 0x0BA, /* EQ3 */ + 0x0BB, /* EQ4 */ + 0x0C0, /* DRC1 Left */ + 0x0C1, /* DRC1 Right */ + 0x0C2, /* DRC2 Left */ + 0x0C3, /* DRC2 Right */ + 0x0C8, /* LHPF1 */ + 0x0C9, /* LHPF2 */ + 0x0CA, /* LHPF3 */ + 0x0CB, /* LHPF4 */ + 0x0D8, /* Ultrasonic 1 */ + 0x0D9, /* Ultrasonic 2 */ + 0x100, /* DSP1 channel 1 */ + 0x101, /* DSP1 channel 2 */ + 0x102, /* DSP1 channel 3 */ + 0x103, /* DSP1 channel 4 */ + 0x104, /* DSP1 channel 5 */ + 0x105, /* DSP1 channel 6 */ + 0x106, /* DSP1 channel 7 */ + 0x107, /* DSP1 channel 8 */ +}; + +const DECLARE_TLV_DB_SCALE(cs48l32_ana_tlv, 0, 100, 0); +const DECLARE_TLV_DB_SCALE(cs48l32_eq_tlv, -1200, 100, 0); +const DECLARE_TLV_DB_SCALE(cs48l32_digital_tlv, -6400, 50, 0); +const DECLARE_TLV_DB_SCALE(cs48l32_noise_tlv, -10800, 600, 0); +const DECLARE_TLV_DB_SCALE(cs48l32_mixer_tlv, -3200, 100, 0); +const DECLARE_TLV_DB_SCALE(cs48l32_us_tlv, 0, 600, 0); + +static void cs48l32_spin_sysclk(struct cs48l32 *cs48l32) +{ + struct cs48l32_mfd *mfd = cs48l32->mfd; + unsigned int val; + int ret, i; + + /* Skip this if the chip is down */ + if (pm_runtime_suspended(mfd->dev)) + return; + + /* + * Just read a register a few times to ensure the internal + * oscillator sends out a some clocks. + */ + for (i = 0; i < 4; i++) { + ret = regmap_read(mfd->regmap, CS48L32_DEVID, &val); + if (ret) + dev_err(cs48l32->dev, "%s Failed to read register: %d (%d)\n", + __func__, ret, i); + } + + udelay(300); +} + +static const char * const cs48l32_rate_text[] = { + "Sample Rate 1", + "Sample Rate 2", + "Sample Rate 3", + "Sample Rate 4", +}; + +static const unsigned int cs48l32_rate_val[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +int cs48l32_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + int ret; + + /* Prevent any mixer mux changes while we do this */ + mutex_lock(&cs48l32->rate_lock); + + /* The write must be guarded by a number of SYSCLK cycles */ + cs48l32_spin_sysclk(cs48l32); + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + cs48l32_spin_sysclk(cs48l32); + + mutex_unlock(&cs48l32->rate_lock); + + return ret; +} + +static const char * const cs48l32_sample_rate_text[] = { + "12kHz", + "24kHz", + "48kHz", + "96kHz", + "192kHz", + "384kHz", + "768kHz", + "11.025kHz", + "22.05kHz", + "44.1kHz", + "88.2kHz", + "176.4kHz", + "352.8kHz", + "705.6kHz", + "8kHz", + "16kHz", + "32kHz", +}; + +static const unsigned int cs48l32_sample_rate_val[] = { + 0x01, /* 12kHz */ + 0x02, /* 24kHz */ + 0x03, /* 48kHz */ + 0x04, /* 96kHz */ + 0x05, /* 192kHz */ + 0x06, /* 384kHz */ + 0x07, /* 768kHz */ + 0x09, /* 11.025kHz */ + 0x0a, /* 22.05kHz */ + 0x0b, /* 44.1kHz */ + 0x0c, /* 88.2kHz */ + 0x0d, /* 176.4kHz */ + 0x0e, /* 352.8kHz */ + 0x0f, /* 705.6kHz */ + 0x11, /* 8kHz */ + 0x12, /* 16kHz */ + 0x13, /* 32kHz */ +}; + +const struct soc_enum cs48l32_sample_rate[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_SAMPLE_RATE1, + CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_1_MASK >> CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_ENUM_SIZE, + cs48l32_sample_rate_text, + cs48l32_sample_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_SAMPLE_RATE2, + CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_1_MASK >> CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_ENUM_SIZE, + cs48l32_sample_rate_text, + cs48l32_sample_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_SAMPLE_RATE3, + CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_1_MASK >> CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_ENUM_SIZE, + cs48l32_sample_rate_text, + cs48l32_sample_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_SAMPLE_RATE4, + CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_1_MASK >> CS48L32_SAMPLE_RATE_1_SHIFT, + CS48L32_SAMPLE_RATE_ENUM_SIZE, + cs48l32_sample_rate_text, + cs48l32_sample_rate_val), +}; + +static int cs48l32_inmux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *) kcontrol->private_value; + unsigned int mux, src_val, in_type; + int ret; + + mux = ucontrol->value.enumerated.item[0]; + if (mux > 1) + return -EINVAL; + + switch (e->reg) { + case CS48L32_IN1L_CONTROL1: + in_type = cs48l32->in_type[0][2 * mux]; + break; + case CS48L32_IN1R_CONTROL1: + in_type = cs48l32->in_type[0][1 + (2 * mux)]; + break; + case CS48L32_IN2L_CONTROL1: + in_type = cs48l32->in_type[1][2 * mux]; + break; + case CS48L32_IN2R_CONTROL1: + in_type = cs48l32->in_type[1][1 + (2 * mux)]; + break; + default: + return -EINVAL; + } + + src_val = mux << e->shift_l; + + if (in_type == CS48L32_IN_TYPE_SE) + src_val |= 1 << CS48L32_INx_SRC_SHIFT; + + dev_dbg(cs48l32->dev, "mux=%u reg=0x%x in_type=0x%x val=0x%x\n", + mux, e->reg, in_type, src_val); + + ret = snd_soc_component_update_bits(dapm->component, + e->reg, + CS48L32_INx_SRC_MASK, + src_val); + if (ret > 0) + snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL); + + return ret; +} + +static const char * const cs48l32_inmux_texts[] = { + "Analog 1", "Analog 2", +}; + +static SOC_ENUM_SINGLE_DECL(cs48l32_in1muxl_enum, + CS48L32_IN1L_CONTROL1, + CS48L32_INx_SRC_SHIFT + 1, + cs48l32_inmux_texts); + +static SOC_ENUM_SINGLE_DECL(cs48l32_in1muxr_enum, + CS48L32_IN1R_CONTROL1, + CS48L32_INx_SRC_SHIFT + 1, + cs48l32_inmux_texts); + +static SOC_ENUM_SINGLE_DECL(cs48l32_in2muxl_enum, + CS48L32_IN2L_CONTROL1, + CS48L32_INx_SRC_SHIFT + 1, + cs48l32_inmux_texts); + +static SOC_ENUM_SINGLE_DECL(cs48l32_in2muxr_enum, + CS48L32_IN2R_CONTROL1, + CS48L32_INx_SRC_SHIFT + 1, + cs48l32_inmux_texts); + +const struct snd_kcontrol_new cs48l32_inmux[] = { + SOC_DAPM_ENUM_EXT("IN1L Mux", cs48l32_in1muxl_enum, + snd_soc_dapm_get_enum_double, cs48l32_inmux_put), + SOC_DAPM_ENUM_EXT("IN1R Mux", cs48l32_in1muxr_enum, + snd_soc_dapm_get_enum_double, cs48l32_inmux_put), + SOC_DAPM_ENUM_EXT("IN2L Mux", cs48l32_in2muxl_enum, + snd_soc_dapm_get_enum_double, cs48l32_inmux_put), + SOC_DAPM_ENUM_EXT("IN2R Mux", cs48l32_in2muxr_enum, + snd_soc_dapm_get_enum_double, cs48l32_inmux_put), +}; + +static const char * const cs48l32_dmode_texts[] = { + "Analog", "Digital", +}; + +static int cs48l32_dmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct soc_enum *e = (struct soc_enum *) kcontrol->private_value; + unsigned int mode; + int ret, result; + + mode = ucontrol->value.enumerated.item[0]; + switch (mode) { + case 0: + ret = snd_soc_component_update_bits(component, + CS48L32_ADC1L_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + CS48L32_ADC1x_INT_ENA_FRC_MASK); + if (ret < 0) { + dev_err(component->dev, + "Failed to set ADC1L_INT_ENA_FRC: %d\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, + CS48L32_ADC1R_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + CS48L32_ADC1x_INT_ENA_FRC_MASK); + if (ret < 0) { + dev_err(component->dev, + "Failed to set ADC1R_INT_ENA_FRC: %d\n", ret); + return ret; + } + + result = snd_soc_component_update_bits(component, + e->reg, + BIT(CS48L32_IN1_MODE_SHIFT), + 0); + if (result < 0) { + dev_err(component->dev, "Failed to set input mode: %d\n", result); + return result; + } + + usleep_range(200, 300); + + ret = snd_soc_component_update_bits(component, + CS48L32_ADC1L_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + 0); + if (ret < 0) { + dev_err(component->dev, + "Failed to clear ADC1L_INT_ENA_FRC: %d\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, + CS48L32_ADC1R_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + 0); + if (ret < 0) { + dev_err(component->dev, + "Failed to clear ADC1R_INT_ENA_FRC: %d\n", ret); + return ret; + } + + if (result > 0) + snd_soc_dapm_mux_update_power(dapm, kcontrol, mode, e, NULL); + + return result; + case 1: + return snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + default: + return -EINVAL; + } +} + +static SOC_ENUM_SINGLE_DECL(cs48l32_in1dmode_enum, + CS48L32_INPUT1_CONTROL1, + CS48L32_IN1_MODE_SHIFT, + cs48l32_dmode_texts); + +const struct snd_kcontrol_new cs48l32_dmode_mux[] = { + SOC_DAPM_ENUM_EXT("IN1 Mode", cs48l32_in1dmode_enum, + snd_soc_dapm_get_enum_double, cs48l32_dmode_put), +}; + +static const char * const cs48l32_in_texts[] = { + "IN1L", "IN1R", "IN2L", "IN2R", +}; + +static const char * const cs48l32_us_freq_texts[] = { + "24.5-40.5kHz", "18-22kHz", "16-24kHz", "20-28kHz", +}; + +static const unsigned int cs48l32_us_freq_val[] = { + 0x2, 0x3, +}; + +const struct soc_enum cs48l32_us_freq[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_US1_CONTROL, + CS48L32_US1_FREQ_SHIFT, + CS48L32_US1_FREQ_MASK >> CS48L32_US1_FREQ_SHIFT, + ARRAY_SIZE(cs48l32_us_freq_val), + &cs48l32_us_freq_texts[2], + cs48l32_us_freq_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_US2_CONTROL, + CS48L32_US1_FREQ_SHIFT, + CS48L32_US1_FREQ_MASK >> CS48L32_US1_FREQ_SHIFT, + ARRAY_SIZE(cs48l32_us_freq_val), + &cs48l32_us_freq_texts[2], + cs48l32_us_freq_val), +}; + +static const unsigned int cs48l32_us_in_val[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static const struct soc_enum cs48l32_us_inmux_enum[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_US1_CONTROL, + CS48L32_US1_SRC_SHIFT, + CS48L32_US1_SRC_MASK >> CS48L32_US1_SRC_SHIFT, + ARRAY_SIZE(cs48l32_us_in_val), + cs48l32_in_texts, + cs48l32_us_in_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_US2_CONTROL, + CS48L32_US1_SRC_SHIFT, + CS48L32_US1_SRC_MASK >> CS48L32_US1_SRC_SHIFT, + ARRAY_SIZE(cs48l32_us_in_val), + cs48l32_in_texts, + cs48l32_us_in_val), +}; + +const struct snd_kcontrol_new cs48l32_us_inmux[] = { + SOC_DAPM_ENUM("Ultrasonic 1 Input", cs48l32_us_inmux_enum[0]), + SOC_DAPM_ENUM("Ultrasonic 2 Input", cs48l32_us_inmux_enum[1]), +}; + +static const char * const cs48l32_us_det_thr_texts[] = { + "-6dB", "-9dB", "-12dB", "-15dB", "-18dB", "-21dB", "-24dB", "-27dB", +}; + +const struct soc_enum cs48l32_us_det_thr[] = { + SOC_ENUM_SINGLE(CS48L32_US1_DET_CONTROL, + CS48L32_US1_DET_THR_SHIFT, + ARRAY_SIZE(cs48l32_us_det_thr_texts), + cs48l32_us_det_thr_texts), + SOC_ENUM_SINGLE(CS48L32_US2_DET_CONTROL, + CS48L32_US1_DET_THR_SHIFT, + ARRAY_SIZE(cs48l32_us_det_thr_texts), + cs48l32_us_det_thr_texts), +}; + +static const char * const cs48l32_us_det_num_texts[] = { + "1 Sample", + "2 Samples", + "4 Samples", + "8 Samples", + "16 Samples", + "32 Samples", + "64 Samples", + "128 Samples", + "256 Samples", + "512 Samples", + "1024 Samples", + "2048 Samples", + "4096 Samples", + "8192 Samples", + "16384 Samples", + "32768 Samples", +}; + +const struct soc_enum cs48l32_us_det_num[] = { + SOC_ENUM_SINGLE(CS48L32_US1_DET_CONTROL, + CS48L32_US1_DET_NUM_SHIFT, + ARRAY_SIZE(cs48l32_us_det_num_texts), + cs48l32_us_det_num_texts), + SOC_ENUM_SINGLE(CS48L32_US2_DET_CONTROL, + CS48L32_US1_DET_NUM_SHIFT, + ARRAY_SIZE(cs48l32_us_det_num_texts), + cs48l32_us_det_num_texts), +}; + +static const char * const cs48l32_us_det_hold_texts[] = { + "0 Samples", + "31 Samples", + "63 Samples", + "127 Samples", + "255 Samples", + "511 Samples", + "1023 Samples", + "2047 Samples", + "4095 Samples", + "8191 Samples", + "16383 Samples", + "32767 Samples", + "65535 Samples", + "131071 Samples", + "262143 Samples", + "524287 Samples", +}; + +const struct soc_enum cs48l32_us_det_hold[] = { + SOC_ENUM_SINGLE(CS48L32_US1_DET_CONTROL, + CS48L32_US1_DET_HOLD_SHIFT, + ARRAY_SIZE(cs48l32_us_det_hold_texts), + cs48l32_us_det_hold_texts), + SOC_ENUM_SINGLE(CS48L32_US2_DET_CONTROL, + CS48L32_US1_DET_HOLD_SHIFT, + ARRAY_SIZE(cs48l32_us_det_hold_texts), + cs48l32_us_det_hold_texts), +}; + +const struct soc_enum cs48l32_us_output_rate[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_US1_CONTROL, + CS48L32_US1_RATE_SHIFT, + CS48L32_US1_RATE_MASK >> CS48L32_US1_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_US2_CONTROL, + CS48L32_US1_RATE_SHIFT, + CS48L32_US1_RATE_MASK >> CS48L32_US1_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), +}; + +static const char * const cs48l32_us_det_lpf_cut_texts[] = { + "1722Hz", "833Hz", "408Hz", "203Hz", +}; + +const struct soc_enum cs48l32_us_det_lpf_cut[] = { + SOC_ENUM_SINGLE(CS48L32_US1_DET_CONTROL, + CS48L32_US1_DET_LPF_CUT_SHIFT, + ARRAY_SIZE(cs48l32_us_det_lpf_cut_texts), + cs48l32_us_det_lpf_cut_texts), + SOC_ENUM_SINGLE(CS48L32_US2_DET_CONTROL, + CS48L32_US1_DET_LPF_CUT_SHIFT, + ARRAY_SIZE(cs48l32_us_det_lpf_cut_texts), + cs48l32_us_det_lpf_cut_texts), +}; + +static const char * const cs48l32_us_det_dcy_texts[] = { + "0 ms", "0.79 ms", "1.58 ms", "3.16 ms", "6.33 ms", "12.67 ms", + "25.34 ms", "50.69 ms", +}; + +const struct soc_enum cs48l32_us_det_dcy[] = { + SOC_ENUM_SINGLE(CS48L32_US1_DET_CONTROL, + CS48L32_US1_DET_DCY_SHIFT, + ARRAY_SIZE(cs48l32_us_det_dcy_texts), + cs48l32_us_det_dcy_texts), + SOC_ENUM_SINGLE(CS48L32_US2_DET_CONTROL, + CS48L32_US1_DET_DCY_SHIFT, + ARRAY_SIZE(cs48l32_us_det_dcy_texts), + cs48l32_us_det_dcy_texts), +}; + +const struct snd_kcontrol_new cs48l32_us_switch[] = { + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), +}; + +static const char * const cs48l32_vol_ramp_text[] = { + "0ms/6dB", "0.5ms/6dB", "1ms/6dB", "2ms/6dB", "4ms/6dB", "8ms/6dB", + "16ms/6dB", "32ms/6dB", +}; + +irqreturn_t cs48l32_us1_activity(int irq, void *data) +{ + struct cs48l32 *cs48l32 = data; + struct cs48l32_us_notify_data us_data; + + us_data.us_no = 1; + cs48l32_call_notifiers(cs48l32->mfd, CS48L32_NOTIFY_ULTRASONIC, &us_data); + + return IRQ_HANDLED; +} + +irqreturn_t cs48l32_us2_activity(int irq, void *data) +{ + struct cs48l32 *cs48l32 = data; + struct cs48l32_us_notify_data us_data; + + us_data.us_no = 2; + cs48l32_call_notifiers(cs48l32->mfd, CS48L32_NOTIFY_ULTRASONIC, &us_data); + + return IRQ_HANDLED; +} + +SOC_ENUM_SINGLE_DECL(cs48l32_in_vd_ramp, + CS48L32_INPUT_VOL_CONTROL, + CS48L32_IN_VD_RAMP_SHIFT, + cs48l32_vol_ramp_text); + +SOC_ENUM_SINGLE_DECL(cs48l32_in_vi_ramp, + CS48L32_INPUT_VOL_CONTROL, + CS48L32_IN_VI_RAMP_SHIFT, + cs48l32_vol_ramp_text); + +static const char * const cs48l32_in_hpf_cut_text[] = { + "2.5Hz", "5Hz", "10Hz", "20Hz", "40Hz" +}; + +SOC_ENUM_SINGLE_DECL(cs48l32_in_hpf_cut_enum, + CS48L32_INPUT_HPF_CONTROL, + CS48L32_IN_HPF_CUT_SHIFT, + cs48l32_in_hpf_cut_text); + +static const char * const cs48l32_in_dmic_osr_text[] = { + "384kHz", "768kHz", "1.536MHz", "2.048MHz", "2.4576MHz", "3.072MHz", + "6.144MHz", +}; + +const struct soc_enum cs48l32_in_dmic_osr[] = { + SOC_ENUM_SINGLE(CS48L32_INPUT1_CONTROL1, + CS48L32_IN1_OSR_SHIFT, + ARRAY_SIZE(cs48l32_in_dmic_osr_text), + cs48l32_in_dmic_osr_text), + SOC_ENUM_SINGLE(CS48L32_INPUT2_CONTROL1, + CS48L32_IN1_OSR_SHIFT, + ARRAY_SIZE(cs48l32_in_dmic_osr_text), + cs48l32_in_dmic_osr_text), +}; + +int cs48l32_in_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg, shift; + int ret = 0; + + snd_soc_dapm_mutex_lock(dapm); + + /* Cannot change rate on an active input */ + reg = snd_soc_component_read(component, CS48L32_INPUT_CONTROL); + shift = (e->reg - CS48L32_IN1L_CONTROL1) / 0x20; + shift ^= 0x1; /* Flip bottom bit for channel order */ + + if ((reg) & (1 << shift)) { + ret = -EBUSY; + goto exit; + } + + ret = snd_soc_put_enum_double(kcontrol, ucontrol); +exit: + snd_soc_dapm_mutex_unlock(dapm); + return ret; +} + +const struct soc_enum cs48l32_input_rate[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_IN1L_CONTROL1, + CS48L32_INx_RATE_SHIFT, + CS48L32_INx_RATE_MASK >> CS48L32_INx_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_IN1R_CONTROL1, + CS48L32_INx_RATE_SHIFT, + CS48L32_INx_RATE_MASK >> CS48L32_INx_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_IN2L_CONTROL1, + CS48L32_INx_RATE_SHIFT, + CS48L32_INx_RATE_MASK >> CS48L32_INx_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_IN2R_CONTROL1, + CS48L32_INx_RATE_SHIFT, + CS48L32_INx_RATE_MASK >> CS48L32_INx_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), +}; + +int cs48l32_low_power_mode_put(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 *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + unsigned int reg, mask; + int ret; + + snd_soc_dapm_mutex_lock(dapm); + + /* Cannot change low power mode on an active input */ + reg = snd_soc_component_read(component, CS48L32_INPUT_CONTROL); + mask = (mc->reg - CS48L32_IN1L_CONTROL1) / 0x20; + mask ^= 0x1; /* Flip bottom bit for channel order */ + + if ((reg) & (1 << mask)) { + ret = -EBUSY; + dev_err(component->dev, "Can't change lp mode on an active input\n"); + goto exit; + } + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + +exit: + snd_soc_dapm_mutex_unlock(dapm); + return ret; +} + +const struct soc_enum noise_gen_rate = + SOC_VALUE_ENUM_SINGLE(CS48L32_COMFORT_NOISE_GENERATOR, + CS48L32_NOISE_GEN_RATE_SHIFT, + CS48L32_NOISE_GEN_RATE_MASK >> CS48L32_NOISE_GEN_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val); + +static const char * const cs48l32_auxpdm_freq_texts[] = { + "3.072MHz", "2.048MHz", "1.536MHz", "768kHz", +}; + +SOC_ENUM_SINGLE_DECL(cs48l32_auxpdm1_freq, + CS48L32_AUXPDM1_CONTROL1, + CS48L32_AUXPDM1_FREQ_SHIFT, + cs48l32_auxpdm_freq_texts); + +SOC_ENUM_SINGLE_DECL(cs48l32_auxpdm2_freq, + CS48L32_AUXPDM2_CONTROL1, + CS48L32_AUXPDM1_FREQ_SHIFT, + cs48l32_auxpdm_freq_texts); + +static const char * const cs48l32_auxpdm_src_texts[] = { + "Analog", "IN1 Digital", "IN2 Digital", +}; + +static SOC_ENUM_SINGLE_DECL(cs48l32_auxpdm1_in, + CS48L32_AUXPDM_CTRL2, + CS48L32_AUXPDMDAT1_SRC_SHIFT, + cs48l32_auxpdm_src_texts); + +static SOC_ENUM_SINGLE_DECL(cs48l32_auxpdm2_in, + CS48L32_AUXPDM_CTRL2, + CS48L32_AUXPDMDAT2_SRC_SHIFT, + cs48l32_auxpdm_src_texts); + +const struct snd_kcontrol_new cs48l32_auxpdm_inmux[] = { + SOC_DAPM_ENUM("AUXPDM1 Input", cs48l32_auxpdm1_in), + SOC_DAPM_ENUM("AUXPDM2 Input", cs48l32_auxpdm2_in), +}; + +static const unsigned int cs48l32_auxpdm_analog_in_val[] = { + 0x0, 0x1, +}; + +static const struct soc_enum cs48l32_auxpdm_analog_inmux_enum[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_AUXPDM1_CONTROL1, + CS48L32_AUXPDM1_SRC_SHIFT, + CS48L32_AUXPDM1_SRC_MASK >> CS48L32_AUXPDM1_SRC_SHIFT, + ARRAY_SIZE(cs48l32_auxpdm_analog_in_val), + cs48l32_in_texts, + cs48l32_auxpdm_analog_in_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_AUXPDM2_CONTROL1, + CS48L32_AUXPDM1_SRC_SHIFT, + CS48L32_AUXPDM1_SRC_MASK >> CS48L32_AUXPDM1_SRC_SHIFT, + ARRAY_SIZE(cs48l32_auxpdm_analog_in_val), + cs48l32_in_texts, + cs48l32_auxpdm_analog_in_val), +}; + +const struct snd_kcontrol_new cs48l32_auxpdm_analog_inmux[] = { + SOC_DAPM_ENUM("AUXPDM1 Analog Input", cs48l32_auxpdm_analog_inmux_enum[0]), + SOC_DAPM_ENUM("AUXPDM2 Analog Input", cs48l32_auxpdm_analog_inmux_enum[1]), +}; + +const struct snd_kcontrol_new cs48l32_auxpdm_switch[] = { + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), +}; + +const struct soc_enum cs48l32_isrc_fsh[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_ISRC1_CONTROL1, + CS48L32_ISRC1_FSH_SHIFT, + CS48L32_ISRC1_FSH_MASK >> CS48L32_ISRC1_FSH_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_ISRC2_CONTROL1, + CS48L32_ISRC1_FSH_SHIFT, + CS48L32_ISRC1_FSH_MASK >> CS48L32_ISRC1_FSH_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_ISRC3_CONTROL1, + CS48L32_ISRC1_FSH_SHIFT, + CS48L32_ISRC1_FSH_MASK >> CS48L32_ISRC1_FSH_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), +}; + +const struct soc_enum cs48l32_isrc_fsl[] = { + SOC_VALUE_ENUM_SINGLE(CS48L32_ISRC1_CONTROL1, + CS48L32_ISRC1_FSL_SHIFT, + CS48L32_ISRC1_FSL_MASK >> CS48L32_ISRC1_FSL_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_ISRC2_CONTROL1, + CS48L32_ISRC1_FSL_SHIFT, + CS48L32_ISRC1_FSL_MASK >> CS48L32_ISRC1_FSL_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(CS48L32_ISRC3_CONTROL1, + CS48L32_ISRC1_FSL_SHIFT, + CS48L32_ISRC1_FSL_MASK >> CS48L32_ISRC1_FSL_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val), +}; + +const struct soc_enum cs48l32_fx_rate = + SOC_VALUE_ENUM_SINGLE(CS48L32_FX_SAMPLE_RATE, + CS48L32_FX_RATE_SHIFT, + CS48L32_FX_RATE_MASK >> CS48L32_FX_RATE_SHIFT, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, + cs48l32_rate_val); + +static const char * const cs48l32_lhpf_mode_text[] = { + "Low-pass", "High-pass" +}; + +const struct soc_enum cs48l32_lhpf_mode[] = { + SOC_ENUM_SINGLE(CS48L32_LHPF_CONTROL2, 0, + ARRAY_SIZE(cs48l32_lhpf_mode_text), cs48l32_lhpf_mode_text), + SOC_ENUM_SINGLE(CS48L32_LHPF_CONTROL2, 1, + ARRAY_SIZE(cs48l32_lhpf_mode_text), cs48l32_lhpf_mode_text), + SOC_ENUM_SINGLE(CS48L32_LHPF_CONTROL2, 2, + ARRAY_SIZE(cs48l32_lhpf_mode_text), cs48l32_lhpf_mode_text), + SOC_ENUM_SINGLE(CS48L32_LHPF_CONTROL2, 3, + ARRAY_SIZE(cs48l32_lhpf_mode_text), cs48l32_lhpf_mode_text), +}; + +int cs48l32_lhpf_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + __be32 *data = (__be32 *)ucontrol->value.bytes.data; + s16 val = (s16)be32_to_cpu(*data); + + if (abs(val) >= 4096) { + dev_err(cs48l32->dev, "Rejecting unstable LHPF coefficients\n"); + return -EINVAL; + } + + return snd_soc_bytes_put(kcontrol, ucontrol); +} + +static const char * const cs48l32_eq_mode_text[] = { + "Low-pass", "High-pass", +}; + +const struct soc_enum cs48l32_eq_mode[] = { + SOC_ENUM_SINGLE(CS48L32_EQ_CONTROL2, 0, + ARRAY_SIZE(cs48l32_eq_mode_text), + cs48l32_eq_mode_text), + SOC_ENUM_SINGLE(CS48L32_EQ_CONTROL2, 1, + ARRAY_SIZE(cs48l32_eq_mode_text), + cs48l32_eq_mode_text), + SOC_ENUM_SINGLE(CS48L32_EQ_CONTROL2, 2, + ARRAY_SIZE(cs48l32_eq_mode_text), + cs48l32_eq_mode_text), + SOC_ENUM_SINGLE(CS48L32_EQ_CONTROL2, 3, + ARRAY_SIZE(cs48l32_eq_mode_text), + cs48l32_eq_mode_text), +}; + +int cs48l32_eq_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *) kcontrol->private_value; + unsigned int item; + + item = snd_soc_enum_val_to_item(e, cs48l32->eq_mode[e->shift_l]); + ucontrol->value.enumerated.item[0] = item; + + return 0; +} + +int cs48l32_eq_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *) kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val; + bool changed = false; + + if (item[0] >= e->items) + return -EINVAL; + + val = snd_soc_enum_item_to_val(e, item[0]); + + snd_soc_dapm_mutex_lock(dapm); + if (cs48l32->eq_mode[e->shift_l] != val) { + cs48l32->eq_mode[e->shift_l] = val; + changed = true; + } + snd_soc_dapm_mutex_unlock(dapm); + + return changed; +} + +int cs48l32_eq_coeff_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct cs48l32_eq_control *ctl = (void *) kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = ctl->max; + + return 0; +} + +int cs48l32_eq_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_eq_control *params = (void *)kcontrol->private_value; + __be16 *coeffs; + unsigned int coeff_idx; + int block_idx; + + block_idx = ((int) params->block_base - (int) CS48L32_EQ1_BAND1_COEFF1); + block_idx /= 68; + + coeffs = &cs48l32->eq_coefficients[block_idx][0]; + + coeff_idx = (params->reg - params->block_base) / 2; + coeff_idx += ((params->shift == 0) ? 1 : 0); + + ucontrol->value.integer.value[0] = be16_to_cpu(coeffs[coeff_idx]); + + return 0; +} + +int cs48l32_eq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_eq_control *params = (void *)kcontrol->private_value; + __be16 *coeffs; + unsigned int coeff_idx; + int block_idx; + + block_idx = ((int) params->block_base - (int) CS48L32_EQ1_BAND1_COEFF1); + block_idx /= 68; + + coeffs = &cs48l32->eq_coefficients[block_idx][0]; + + coeff_idx = (params->reg - params->block_base) / 2; + coeff_idx += ((params->shift == 0) ? 1 : 0); + + snd_soc_dapm_mutex_lock(dapm); + coeffs[coeff_idx] = cpu_to_be16(ucontrol->value.integer.value[0]); + snd_soc_dapm_mutex_unlock(dapm); + + return 0; +} + +const struct snd_kcontrol_new cs48l32_drc_activity_output_mux[] = { + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), +}; + +const struct snd_kcontrol_new cs48l32_dsp_trigger_output_mux[] = { + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), +}; + +int cs48l32_dsp_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *) kcontrol->private_value; + unsigned int cached_rate; + const unsigned int rate_num = e->mask; + int item; + + if (rate_num >= ARRAY_SIZE(cs48l32->dsp_dma_rates)) + return -EINVAL; + + cached_rate = cs48l32->dsp_dma_rates[rate_num]; + item = snd_soc_enum_val_to_item(e, cached_rate); + ucontrol->value.enumerated.item[0] = item; + + return 0; +} + +int cs48l32_dsp_rate_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *) kcontrol->private_value; + const unsigned int rate_num = e->mask; + const unsigned int item = ucontrol->value.enumerated.item[0]; + unsigned int val; + bool changed = false; + + if (item >= e->items) + return -EINVAL; + + if (rate_num >= ARRAY_SIZE(cs48l32->dsp_dma_rates)) + return -EINVAL; + + val = snd_soc_enum_item_to_val(e, item); + + snd_soc_dapm_mutex_lock(dapm); + if (cs48l32->dsp_dma_rates[rate_num] != val) { + cs48l32->dsp_dma_rates[rate_num] = val; + changed = true; + } + snd_soc_dapm_mutex_unlock(dapm); + + return changed; +} + +const struct soc_enum cs48l32_dsp_rate_enum[] = { + /* RX rates */ + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 0, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 1, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 2, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 3, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 4, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 5, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 6, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 7, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + /* TX rates */ + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 8, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 9, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 10, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 11, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 12, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 13, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 14, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), + SOC_VALUE_ENUM_SINGLE(SND_SOC_NOPM, 0, + 15, + ARRAY_SIZE(cs48l32_rate_text), + cs48l32_rate_text, cs48l32_rate_val), +}; + +int cs48l32_dsp_pre_run(struct wm_adsp *dsp) +{ + struct cs48l32 *cs48l32 = container_of(dsp, struct cs48l32, dsp); + unsigned int reg; + const u8 *rate = cs48l32->dsp_dma_rates; + int i; + + reg = dsp->cs_dsp.base + CS48L32_HALO_SAMPLE_RATE_RX1; + for (i = 0; i < CS48L32_DSP_N_RX_CHANNELS; ++i) { + regmap_update_bits(dsp->cs_dsp.regmap, reg, CS48L32_HALO_DSP_RATE_MASK, *rate); + reg += 8; + rate++; + } + + reg = dsp->cs_dsp.base + CS48L32_HALO_SAMPLE_RATE_TX1; + for (i = 0; i < CS48L32_DSP_N_TX_CHANNELS; ++i) { + regmap_update_bits(dsp->cs_dsp.regmap, reg, CS48L32_HALO_DSP_RATE_MASK, *rate); + reg += 8; + rate++; + } + + usleep_range(300, 600); + + return 0; +} + +int cs48l32_dsp_memory_enable(struct cs48l32 *cs48l32, + const struct cs48l32_dsp_power_regs *regs) +{ + struct regmap *regmap = cs48l32->mfd->regmap; + int i, j, ret; + + /* disable power-off */ + for (i = 0; i < regs->n_ext; ++i) { + for (j = regs->ext[i].start; j <= regs->ext[i].end; j += 4) { + ret = regmap_write(regmap, j, 0x3); + if (ret) + goto err; + } + } + + /* power-up the banks in sequence */ + for (i = 0; i < regs->n_pwd; ++i) { + ret = regmap_write(regmap, regs->pwd[i], 0x1); + if (ret) + goto err; + + udelay(1); /* allow bank to power-up */ + + ret = regmap_write(regmap, regs->pwd[i], 0x3); + if (ret) + goto err; + + udelay(1); /* allow bank to power-up */ + } + + return 0; + +err: + dev_err(cs48l32->dev, "Failed to write SRAM enables (%d)\n", ret); + cs48l32_dsp_memory_disable(cs48l32, regs); + + return ret; +} + +void cs48l32_dsp_memory_disable(struct cs48l32 *cs48l32, + const struct cs48l32_dsp_power_regs *regs) +{ + struct regmap *regmap = cs48l32->mfd->regmap; + int i, j, ret; + + for (i = 0; i < regs->n_pwd; ++i) { + ret = regmap_write(regmap, regs->pwd[i], 0); + if (ret) + dev_warn(cs48l32->dev, "Failed to write SRAM enables (%d)\n", ret); + } + + for (i = 0; i < regs->n_ext; ++i) { + for (j = regs->ext[i].start; j <= regs->ext[i].end; j += 4) { + ret = regmap_write(regmap, j, 0); + if (ret) + dev_warn(cs48l32->dev, "Failed to write SRAM enables (%d)\n", ret); + } + } +} + +static int cs48l32_dsp_freq_update(struct snd_soc_dapm_widget *w, unsigned int freq_reg, + unsigned int freqsel_reg) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + struct wm_adsp *dsp = &cs48l32->dsp; + int ret; + unsigned int freq, freq_sel, freq_sts; + + if (!freq_reg) + return -EINVAL; + + ret = regmap_read(mfd->regmap, freq_reg, &freq); + if (ret) { + dev_err(component->dev, "Failed to read 0x%x: %d\n", freq_reg, ret); + return ret; + } + + if (freqsel_reg) { + freq_sts = (freq & CS48L32_SYSCLK_FREQ_STS_MASK) >> CS48L32_SYSCLK_FREQ_STS_SHIFT; + + ret = regmap_read(mfd->regmap, freqsel_reg, &freq_sel); + if (ret) { + dev_err(component->dev, "Failed to read 0x%x: %d\n", freqsel_reg, ret); + return ret; + } + freq_sel = (freq_sel & CS48L32_SYSCLK_FREQ_MASK) >> CS48L32_SYSCLK_FREQ_SHIFT; + + if (freq_sts != freq_sel) { + dev_err(component->dev, "SYSCLK FREQ (0x%x) != FREQ STS (0x%x)\n", + freq_sel, freq_sts); + return -ETIMEDOUT; + } + } + + freq &= CS48L32_DSP_CLK_FREQ_MASK; + freq >>= CS48L32_DSP_CLK_FREQ_SHIFT; + + ret = regmap_write(dsp->cs_dsp.regmap, + dsp->cs_dsp.base + CS48L32_DSP_CLOCK_FREQ_OFFS, freq); + if (ret) { + dev_err(component->dev, "Failed to set HALO clock freq: %d\n", ret); + return ret; + } + + return 0; +} + +int cs48l32_dsp_freq_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + return cs48l32_dsp_freq_update(w, CS48L32_SYSTEM_CLOCK2, CS48L32_SYSTEM_CLOCK1); + default: + return 0; + } +} + +irqreturn_t cs48l32_dsp1_irq(int irq, void *data) +{ + struct cs48l32 *cs48l32 = data; + int ret; + + ret = wm_adsp_compr_handle_irq(&cs48l32->dsp); + if (ret == -ENODEV) { + dev_err(cs48l32->dev, "Spurious compressed data IRQ\n"); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static const unsigned int cs48l32_opclk_ref_48k_rates[] = { + 6144000, + 12288000, + 24576000, + 49152000, +}; + +static const unsigned int cs48l32_opclk_ref_44k1_rates[] = { + 5644800, + 11289600, + 22579200, + 45158400, +}; + +static int cs48l32_set_opclk(struct snd_soc_component *component, unsigned int clk, + unsigned int freq) +{ + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + unsigned int reg; + const unsigned int *rates; + int ref, div, refclk; + + BUILD_BUG_ON(ARRAY_SIZE(cs48l32_opclk_ref_48k_rates) != + ARRAY_SIZE(cs48l32_opclk_ref_44k1_rates)); + + switch (clk) { + case CS48L32_CLK_OPCLK: + reg = CS48L32_OUTPUT_SYS_CLK; + refclk = cs48l32->sysclk; + break; + default: + return -EINVAL; + } + + if (refclk % 4000) + rates = cs48l32_opclk_ref_44k1_rates; + else + rates = cs48l32_opclk_ref_48k_rates; + + for (ref = 0; ref < ARRAY_SIZE(cs48l32_opclk_ref_48k_rates); ++ref) { + if (rates[ref] > refclk) + continue; + + div = 2; + while ((rates[ref] / div >= freq) && (div <= 30)) { + if (rates[ref] / div == freq) { + dev_dbg(component->dev, "Configured %dHz OPCLK\n", freq); + snd_soc_component_update_bits(component, reg, + CS48L32_OPCLK_DIV_MASK | + CS48L32_OPCLK_SEL_MASK, + (div << CS48L32_OPCLK_DIV_SHIFT) | + ref); + return 0; + } + div += 2; + } + } + + dev_err(component->dev, "Unable to generate %dHz OPCLK\n", freq); + return -EINVAL; +} + +static int cs48l32_get_dspclk_setting(struct cs48l32 *cs48l32, unsigned int freq, + int src, unsigned int *val) +{ + freq /= 15625; /* convert to 1/64ths of 1MHz */ + *val |= freq << CS48L32_DSP_CLK_FREQ_SHIFT; + + return 0; +} + +static int cs48l32_get_sysclk_setting(unsigned int freq) +{ + switch (freq) { + case 0: + case 5644800: + case 6144000: + return CS48L32_SYSCLK_RATE_6MHZ; + case 11289600: + case 12288000: + return CS48L32_SYSCLK_RATE_12MHZ << CS48L32_SYSCLK_FREQ_SHIFT; + case 22579200: + case 24576000: + return CS48L32_SYSCLK_RATE_24MHZ << CS48L32_SYSCLK_FREQ_SHIFT; + case 45158400: + case 49152000: + return CS48L32_SYSCLK_RATE_49MHZ << CS48L32_SYSCLK_FREQ_SHIFT; + case 90316800: + case 98304000: + return CS48L32_SYSCLK_RATE_98MHZ << CS48L32_SYSCLK_FREQ_SHIFT; + default: + return -EINVAL; + } +} + +static int cs48l32_set_pdm_fllclk(struct snd_soc_component *component, int source) +{ + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + unsigned int val; + + switch (source) { + case CS48L32_PDMCLK_SRC_IN1_PDMCLK: + case CS48L32_PDMCLK_SRC_IN2_PDMCLK: + case CS48L32_PDMCLK_SRC_IN3_PDMCLK: + case CS48L32_PDMCLK_SRC_IN4_PDMCLK: + case CS48L32_PDMCLK_SRC_AUXPDM1_CLK: + case CS48L32_PDMCLK_SRC_AUXPDM2_CLK: + val = source << CS48L32_PDM_FLLCLK_SRC_SHIFT; + break; + default: + dev_err(cs48l32->dev, "Invalid PDM FLLCLK src %d\n", source); + return -EINVAL; + } + + return regmap_update_bits(mfd->regmap, CS48L32_INPUT_CONTROL2, + CS48L32_PDM_FLLCLK_SRC_MASK, val); +} + +int cs48l32_set_sysclk(struct snd_soc_component *component, int clk_id, int source, + unsigned int freq, int dir) +{ + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + char *name; + unsigned int reg; + unsigned int mask = CS48L32_SYSCLK_SRC_MASK; + unsigned int val = source << CS48L32_SYSCLK_SRC_SHIFT; + int clk_freq_sel, *clk; + + switch (clk_id) { + case CS48L32_CLK_SYSCLK_1: + name = "SYSCLK"; + reg = CS48L32_SYSTEM_CLOCK1; + clk = &cs48l32->sysclk; + clk_freq_sel = cs48l32_get_sysclk_setting(freq); + mask |= CS48L32_SYSCLK_FREQ_MASK | CS48L32_SYSCLK_FRAC_MASK; + break; + case CS48L32_CLK_DSPCLK: + name = "DSPCLK"; + reg = CS48L32_DSP_CLOCK1; + clk = &cs48l32->dspclk; + clk_freq_sel = cs48l32_get_dspclk_setting(cs48l32, freq, source, &val); + mask |= CS48L32_DSP_CLK_FREQ_MASK; + break; + case CS48L32_CLK_OPCLK: + return cs48l32_set_opclk(component, clk_id, freq); + case CS48L32_CLK_PDM_FLLCLK: + return cs48l32_set_pdm_fllclk(component, source); + default: + return -EINVAL; + } + + if (clk_freq_sel < 0) { + dev_err(cs48l32->dev, "Failed to get %s setting for %dHZ\n", name, freq); + return clk_freq_sel; + } + + *clk = freq; + + if (freq == 0) { + dev_dbg(cs48l32->dev, "%s cleared\n", name); + return 0; + } + + val |= clk_freq_sel; + + if (freq % 6144000) + val |= CS48L32_SYSCLK_FRAC_MASK; + + dev_dbg(cs48l32->dev, "%s set to %uHz", name, freq); + + return regmap_update_bits(mfd->regmap, reg, mask, val); +} + +static int cs48l32_is_enabled_fll(struct cs48l32_fll *fll, int base) +{ + struct cs48l32_mfd *mfd = fll->cs48l32->mfd; + unsigned int reg; + int ret; + + ret = regmap_read(mfd->regmap, base + CS48L32_FLL_CONTROL1_OFFS, ®); + if (ret != 0) { + cs48l32_fll_err(fll, "Failed to read current state: %d\n", ret); + return ret; + } + + return reg & CS48L32_FLL_EN_MASK; +} + +static int cs48l32_wait_for_fll(struct cs48l32_fll *fll, bool requested) +{ + struct cs48l32_mfd *mfd = fll->cs48l32->mfd; + unsigned int val = 0; + int i; + + cs48l32_fll_dbg(fll, "Waiting for FLL...\n"); + + for (i = 0; i < 30; i++) { + regmap_read(mfd->regmap, fll->sts_addr, &val); + if (!!(val & fll->sts_mask) == requested) + return 0; + + switch (i) { + case 0 ... 5: + usleep_range(75, 125); + break; + case 11 ... 20: + usleep_range(750, 1250); + break; + default: + msleep(20); + break; + } + } + + cs48l32_fll_warn(fll, "Timed out waiting for %s\n", requested ? "lock" : "unlock"); + + return -ETIMEDOUT; +} + +static int cs48l32_fllhj_disable(struct cs48l32_fll *fll) +{ + struct cs48l32_mfd *mfd = fll->cs48l32->mfd; + bool change; + + cs48l32_fll_dbg(fll, "Disabling FLL\n"); + + /* + * Disable lockdet, but don't set ctrl_upd update bit. This allows the + * lock status bit to clear as normal, but should the FLL be enabled + * again due to a control clock being required, the lock won't re-assert + * as the FLL config registers are automatically applied when the FLL + * enables. + */ + regmap_set_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL1_OFFS, + CS48L32_FLL_HOLD_MASK); + regmap_clear_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL2_OFFS, + CS48L32_FLL_LOCKDET_MASK); + regmap_set_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL5_OFFS, + CS48L32_FLL_FRC_INTEG_UPD_MASK); + regmap_update_bits_check(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL1_OFFS, + CS48L32_FLL_EN_MASK, + 0, + &change); + + cs48l32_wait_for_fll(fll, false); + + /* + * ctrl_up gates the writes to all the fll's registers, setting it to 0 + * here ensures that after a runtime suspend/resume cycle when one + * enables the fll then ctrl_up is the last bit that is configured + * by the fll enable code rather than the cache sync operation which + * would have updated it much earlier before writing out all fll + * registers + */ + regmap_clear_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL1_OFFS, + CS48L32_FLL_CTRL_UPD_MASK); + + if (change) + pm_runtime_put_autosuspend(mfd->dev); + + return 0; +} + +static int cs48l32_fllhj_apply(struct cs48l32_fll *fll, int fin) +{ + struct cs48l32_mfd *mfd = fll->cs48l32->mfd; + int refdiv, fref, fout, lockdet_thr, fbdiv, fllgcd; + bool frac = false; + unsigned int fll_n, min_n, max_n, ratio, theta, lambda, hp; + unsigned int gains, num; + + cs48l32_fll_dbg(fll, "fin=%d, fout=%d\n", fin, fll->fout); + + for (refdiv = 0; refdiv < 4; refdiv++) { + if ((fin / (1 << refdiv)) <= CS48L32_FLLHJ_MAX_THRESH) + break; + } + + fref = fin / (1 << refdiv); + fout = fll->fout; + frac = fout % fref; + + if (frac && fll->integer_only) { + cs48l32_fll_err(fll, "%u:%u not an integer ratio\n", fin, fout); + return -EINVAL; + } + + if (fll->max_fref && (fref > fll->max_fref)) { + cs48l32_fll_err(fll, "fref=%u too high (max %u)\n", + fref, fll->max_fref); + return -EINVAL; + } + + /* + * Use simple heuristic approach to find a configuration that + * should work for most input clocks. + */ + if (fref < CS48L32_FLLHJ_LOW_THRESH) { + lockdet_thr = 2; + gains = CS48L32_FLLHJ_LOW_GAINS; + + if (frac) + fbdiv = 256; + else + fbdiv = 4; + } else if (fref < CS48L32_FLLHJ_MID_THRESH) { + lockdet_thr = 8; + gains = CS48L32_FLLHJ_MID_GAINS; + fbdiv = (frac) ? 16 : 2; + } else { + lockdet_thr = 8; + gains = CS48L32_FLLHJ_HIGH_GAINS; + fbdiv = 1; + } + /* Use high performance mode for fractional configurations. */ + if (frac) { + hp = 0x3; + min_n = CS48L32_FLLHJ_FRAC_MIN_N; + max_n = CS48L32_FLLHJ_FRAC_MAX_N; + } else { + if (fll->has_lp && (fref < CS48L32_FLLHJ_LP_INT_MODE_THRESH)) + hp = 0x0; + else + hp = 0x1; + + min_n = CS48L32_FLLHJ_INT_MIN_N; + max_n = CS48L32_FLLHJ_INT_MAX_N; + } + + ratio = fout / fref; + + cs48l32_fll_dbg(fll, "refdiv=%d, fref=%d, frac:%d\n", refdiv, fref, frac); + + while (ratio / fbdiv < min_n) { + fbdiv /= 2; + if (fbdiv < min_n) { + cs48l32_fll_err(fll, "FBDIV (%u) < minimum N (%u)\n", fbdiv, min_n); + return -EINVAL; + } + } + while (frac && (ratio / fbdiv > max_n)) { + fbdiv *= 2; + if (fbdiv >= 1024) { + cs48l32_fll_err(fll, "FBDIV (%u) >= 1024\n", fbdiv); + return -EINVAL; + } + } + + cs48l32_fll_dbg(fll, "lockdet=%d, hp=0x%x, fbdiv:%d\n", lockdet_thr, hp, fbdiv); + + /* Calculate N.K values */ + fllgcd = gcd(fout, fbdiv * fref); + num = fout / fllgcd; + lambda = (fref * fbdiv) / fllgcd; + fll_n = num / lambda; + theta = num % lambda; + + cs48l32_fll_dbg(fll, "fll_n=%d, gcd=%d, theta=%d, lambda=%d\n", + fll_n, fllgcd, theta, lambda); + + /* Some sanity checks before any registers are written. */ + if (fll_n < min_n || fll_n > max_n) { + cs48l32_fll_err(fll, "N not in valid %s mode range %d-%d: %d\n", + frac ? "fractional" : "integer", min_n, max_n, fll_n); + return -EINVAL; + } + if (fbdiv < 1 || (frac && fbdiv >= 1024) || (!frac && fbdiv >= 256)) { + cs48l32_fll_err(fll, "Invalid fbdiv for %s mode (%u)\n", + frac ? "fractional" : "integer", fbdiv); + return -EINVAL; + } + + /* clear the ctrl_upd bit to guarantee we write to it later. */ + regmap_update_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL2_OFFS, + CS48L32_FLL_LOCKDET_THR_MASK | + CS48L32_FLL_PHASEDET_MASK | + CS48L32_FLL_REFCLK_DIV_MASK | + CS48L32_FLL_N_MASK | + CS48L32_FLL_CTRL_UPD_MASK, + (lockdet_thr << CS48L32_FLL_LOCKDET_THR_SHIFT) | + (1 << CS48L32_FLL_PHASEDET_SHIFT) | + (refdiv << CS48L32_FLL_REFCLK_DIV_SHIFT) | + (fll_n << CS48L32_FLL_N_SHIFT)); + + regmap_update_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL3_OFFS, + CS48L32_FLL_LAMBDA_MASK | + CS48L32_FLL_THETA_MASK, + (lambda << CS48L32_FLL_LAMBDA_SHIFT) | + (theta << CS48L32_FLL_THETA_SHIFT)); + + regmap_update_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL4_OFFS, + (0xffff << CS48L32_FLL_FD_GAIN_COARSE_SHIFT) | + CS48L32_FLL_HP_MASK | + CS48L32_FLL_FB_DIV_MASK, + (gains << CS48L32_FLL_FD_GAIN_COARSE_SHIFT) | + (hp << CS48L32_FLL_HP_SHIFT) | + (fbdiv << CS48L32_FLL_FB_DIV_SHIFT)); + + return 0; +} + +static int cs48l32_fllhj_enable(struct cs48l32_fll *fll) +{ + struct cs48l32_mfd *mfd = fll->cs48l32->mfd; + int already_enabled = cs48l32_is_enabled_fll(fll, fll->base); + int ret; + + if (already_enabled < 0) + return already_enabled; + + if (!already_enabled) + pm_runtime_get_sync(mfd->dev); + + cs48l32_fll_dbg(fll, "Enabling FLL, initially %s\n", + already_enabled ? "enabled" : "disabled"); + + /* FLLn_HOLD must be set before configuring any registers */ + regmap_set_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL1_OFFS, + CS48L32_FLL_HOLD_MASK); + + /* Apply refclk */ + ret = cs48l32_fllhj_apply(fll, fll->ref_freq); + if (ret) { + cs48l32_fll_err(fll, "Failed to set FLL: %d\n", ret); + goto out; + } + regmap_update_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL2_OFFS, + CS48L32_FLL_REFCLK_SRC_MASK, + fll->ref_src << CS48L32_FLL_REFCLK_SRC_SHIFT); + + regmap_set_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL1_OFFS, + CS48L32_FLL_EN_MASK); + +out: + regmap_set_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL2_OFFS, + CS48L32_FLL_LOCKDET_MASK); + + regmap_set_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL1_OFFS, + CS48L32_FLL_CTRL_UPD_MASK); + + /* Release the hold so that flln locks to external frequency */ + regmap_clear_bits(mfd->regmap, + fll->base + CS48L32_FLL_CONTROL1_OFFS, + CS48L32_FLL_HOLD_MASK); + + if (!already_enabled) + cs48l32_wait_for_fll(fll, true); + + return 0; +} + +static int cs48l32_fllhj_validate(struct cs48l32_fll *fll, + unsigned int ref_in, + unsigned int fout) +{ + if (fout && !ref_in) { + cs48l32_fll_err(fll, "fllout set without valid input clk\n"); + return -EINVAL; + } + + if (fll->fout && fout != fll->fout) { + cs48l32_fll_err(fll, "Can't change output on active FLL\n"); + return -EINVAL; + } + + if (ref_in / CS48L32_FLL_MAX_REFDIV > CS48L32_FLLHJ_MAX_THRESH) { + cs48l32_fll_err(fll, "Can't scale %dMHz to <=13MHz\n", ref_in); + return -EINVAL; + } + + if (fout > CS48L32_FLL_MAX_FOUT) { + cs48l32_fll_err(fll, "Fout=%dMHz exceeeds maximum %dMHz\n", + fout, CS48L32_FLL_MAX_FOUT); + return -EINVAL; + } + + return 0; +} + +int cs48l32_fllhj_set_refclk(struct cs48l32_fll *fll, int source, + unsigned int fin, unsigned int fout) +{ + int ret = 0; + + if (fll->ref_src == source && fll->ref_freq == fin && fll->fout == fout) + return 0; + + if (fin && fout && cs48l32_fllhj_validate(fll, fin, fout)) + return -EINVAL; + + fll->ref_src = source; + fll->ref_freq = fin; + fll->fout = fout; + + if (fout) + ret = cs48l32_fllhj_enable(fll); + else + cs48l32_fllhj_disable(fll); + + return ret; +} + +int cs48l32_init_fll(struct cs48l32_fll *fll) +{ + fll->ref_src = CS48L32_FLL_SRC_NONE; + + return 0; +} + +static int cs48l32_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + unsigned int val = 0U; + unsigned int base = dai->driver->base; + unsigned int mask = CS48L32_ASP_FMT_MASK | CS48L32_ASP_BCLK_INV_MASK | + CS48L32_ASP_BCLK_MSTR_MASK | + CS48L32_ASP_FSYNC_INV_MASK | + CS48L32_ASP_FSYNC_MSTR_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= (CS48L32_ASP_FMT_DSP_MODE_A << CS48L32_ASP_FMT_SHIFT); + break; + case SND_SOC_DAIFMT_DSP_B: + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BP_FP) { + cs48l32_asp_err(dai, "DSP_B cannot be clock consumer\n"); + return -EINVAL; + } + val |= (CS48L32_ASP_FMT_DSP_MODE_B << CS48L32_ASP_FMT_SHIFT); + break; + case SND_SOC_DAIFMT_I2S: + val |= (CS48L32_ASP_FMT_I2S_MODE << CS48L32_ASP_FMT_SHIFT); + break; + case SND_SOC_DAIFMT_LEFT_J: + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BP_FP) { + cs48l32_asp_err(dai, "LEFT_J cannot be clock consumer\n"); + return -EINVAL; + } + val |= (CS48L32_ASP_FMT_LEFT_JUSTIFIED_MODE << CS48L32_ASP_FMT_SHIFT); + break; + default: + cs48l32_asp_err(dai, "Unsupported DAI format %d\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BC_FC: + break; + case SND_SOC_DAIFMT_BC_FP: + val |= CS48L32_ASP_FSYNC_MSTR_MASK; + break; + case SND_SOC_DAIFMT_BP_FC: + val |= CS48L32_ASP_BCLK_MSTR_MASK; + break; + case SND_SOC_DAIFMT_BP_FP: + val |= CS48L32_ASP_BCLK_MSTR_MASK; + val |= CS48L32_ASP_FSYNC_MSTR_MASK; + break; + default: + cs48l32_asp_err(dai, "Unsupported clock direction %d\n", + fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + val |= CS48L32_ASP_BCLK_INV_MASK; + val |= CS48L32_ASP_FSYNC_INV_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + val |= CS48L32_ASP_BCLK_INV_MASK; + break; + case SND_SOC_DAIFMT_NB_IF: + val |= CS48L32_ASP_FSYNC_INV_MASK; + break; + default: + return -EINVAL; + } + + regmap_update_bits(mfd->regmap, base + CS48L32_ASP_CONTROL2, mask, val); + + return 0; +} + +static const struct { + u32 freq; + u32 id; +} cs48l32_sclk_rates[] = { + { 128000, 12 }, + { 176400, 13 }, + { 192000, 14 }, + { 256000, 15 }, + { 352800, 16 }, + { 384000, 17 }, + { 512000, 18 }, + { 705600, 19 }, + { 768000, 21 }, + { 1024000, 23 }, + { 1411200, 25 }, + { 1536000, 27 }, + { 2048000, 29 }, + { 2822400, 31 }, + { 3072000, 33 }, + { 4096000, 36 }, + { 5644800, 38 }, + { 6144000, 40 }, + { 8192000, 47 }, + { 11289600, 49 }, + { 12288000, 51 }, + { 22579200, 57 }, + { 24576000, 59 }, +}; + +#define CS48L32_48K_RATE_MASK 0x0e00fe +#define CS48L32_44K1_RATE_MASK 0x00fe00 +#define CS48L32_RATE_MASK (CS48L32_48K_RATE_MASK | CS48L32_44K1_RATE_MASK) + +static const unsigned int cs48l32_sr_vals[] = { + 0, + 12000, /* CS48L32_48K_RATE_MASK */ + 24000, /* CS48L32_48K_RATE_MASK */ + 48000, /* CS48L32_48K_RATE_MASK */ + 96000, /* CS48L32_48K_RATE_MASK */ + 192000, /* CS48L32_48K_RATE_MASK */ + 384000, /* CS48L32_48K_RATE_MASK */ + 768000, /* CS48L32_48K_RATE_MASK */ + 0, + 11025, /* CS48L32_44K1_RATE_MASK */ + 22050, /* CS48L32_44K1_RATE_MASK */ + 44100, /* CS48L32_44K1_RATE_MASK */ + 88200, /* CS48L32_44K1_RATE_MASK */ + 176400, /* CS48L32_44K1_RATE_MASK */ + 352800, /* CS48L32_44K1_RATE_MASK */ + 705600, /* CS48L32_44K1_RATE_MASK */ + 0, + 8000, /* CS48L32_48K_RATE_MASK */ + 16000, /* CS48L32_48K_RATE_MASK */ + 32000, /* CS48L32_48K_RATE_MASK */ +}; + +static const struct snd_pcm_hw_constraint_list cs48l32_constraint = { + .count = ARRAY_SIZE(cs48l32_sr_vals), + .list = cs48l32_sr_vals, +}; + +static int cs48l32_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_dai_priv *dai_priv = &cs48l32->dai[dai->id - 1]; + unsigned int base_rate; + + if (!substream->runtime) + return 0; + + switch (dai_priv->clk) { + case CS48L32_CLK_SYSCLK_1: + case CS48L32_CLK_SYSCLK_2: + case CS48L32_CLK_SYSCLK_3: + case CS48L32_CLK_SYSCLK_4: + base_rate = cs48l32->sysclk; + break; + default: + return 0; + } + + if (base_rate == 0) + dai_priv->constraint.mask = CS48L32_RATE_MASK; + else if (base_rate % 4000) + dai_priv->constraint.mask = CS48L32_44K1_RATE_MASK; + else + dai_priv->constraint.mask = CS48L32_48K_RATE_MASK; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &dai_priv->constraint); +} + +static int cs48l32_hw_params_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_dai_priv *dai_priv = &cs48l32->dai[dai->id - 1]; + unsigned int sr_val, sr_reg, rate; + + rate = params_rate(params); + for (sr_val = 0; sr_val < ARRAY_SIZE(cs48l32_sr_vals); sr_val++) + if (cs48l32_sr_vals[sr_val] == rate) + break; + + if (sr_val == ARRAY_SIZE(cs48l32_sr_vals)) { + cs48l32_asp_err(dai, "Unsupported sample rate %dHz\n", rate); + return -EINVAL; + } + + switch (dai_priv->clk) { + case CS48L32_CLK_SYSCLK_1: + sr_reg = CS48L32_SAMPLE_RATE1; + break; + case CS48L32_CLK_SYSCLK_2: + sr_reg = CS48L32_SAMPLE_RATE2; + break; + case CS48L32_CLK_SYSCLK_3: + sr_reg = CS48L32_SAMPLE_RATE3; + break; + case CS48L32_CLK_SYSCLK_4: + sr_reg = CS48L32_SAMPLE_RATE3; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, sr_reg, CS48L32_SAMPLE_RATE_1_MASK, sr_val); + + return 0; +} + +static bool cs48l32_asp_cfg_changed(struct snd_soc_component *component, + unsigned int base, unsigned int sclk, + unsigned int slotws, unsigned int dataw) +{ + unsigned int val; + + val = snd_soc_component_read(component, base + CS48L32_ASP_CONTROL1); + if (sclk != (val & CS48L32_ASP_BCLK_FREQ_MASK)) + return true; + + val = snd_soc_component_read(component, base + CS48L32_ASP_CONTROL2); + if (slotws != (val & (CS48L32_ASP_RX_WIDTH_MASK | CS48L32_ASP_TX_WIDTH_MASK))) + return true; + + val = snd_soc_component_read(component, base + CS48L32_ASP_DATA_CONTROL1); + if (dataw != (val & (CS48L32_ASP_TX_WL_MASK))) + return true; + + val = snd_soc_component_read(component, base + CS48L32_ASP_DATA_CONTROL5); + if (dataw != (val & (CS48L32_ASP_RX_WL_MASK))) + return true; + + return false; +} + +static int cs48l32_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 cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + int base = dai->driver->base; + int dai_id = dai->id - 1; + unsigned int rate = params_rate(params); + unsigned int dataw = snd_pcm_format_width(params_format(params)); + unsigned int chan_limit = cs48l32->max_channels_clocked[dai_id]; + unsigned int asp_state = 0; + int sclk, sclk_target; + unsigned int slotw, n_slots, n_slots_multiple, val; + int i, ret; + + cs48l32_asp_dbg(dai, "hwparams in: ch:%u dataw:%u rate:%u\n", + params_channels(params), dataw, rate); + /* + * The following calculations hold only under the assumption that + * symmetric_[rates|channels|samplebits] are set to 1 + */ + if (cs48l32->tdm_slots[dai_id]) { + n_slots = cs48l32->tdm_slots[dai_id]; + slotw = cs48l32->tdm_width[dai_id]; + } else { + n_slots = params_channels(params); + slotw = dataw; + } + + if (chan_limit) + n_slots = min(n_slots, chan_limit); + + val = snd_soc_component_read(component, base + CS48L32_ASP_CONTROL2); + val = (val & CS48L32_ASP_FMT_MASK) >> CS48L32_ASP_FMT_SHIFT; + if (val == CS48L32_ASP_FMT_I2S_MODE) + n_slots_multiple = 2; + else + n_slots_multiple = 1; + + sclk_target = snd_soc_tdm_params_to_bclk(params, slotw, n_slots, n_slots_multiple); + + for (i = 0; i < ARRAY_SIZE(cs48l32_sclk_rates); i++) { + if ((cs48l32_sclk_rates[i].freq >= sclk_target) && + (cs48l32_sclk_rates[i].freq % rate == 0)) { + sclk = cs48l32_sclk_rates[i].id; + break; + } + } + if (i == ARRAY_SIZE(cs48l32_sclk_rates)) { + cs48l32_asp_err(dai, "Unsupported sample rate %dHz\n", rate); + return -EINVAL; + } + + cs48l32_asp_dbg(dai, "hwparams out: n_slots:%u dataw:%u slotw:%u bclk:%u bclkid:%u\n", + n_slots, dataw, slotw, sclk_target, sclk); + + slotw = (slotw << CS48L32_ASP_TX_WIDTH_SHIFT) | + (slotw << CS48L32_ASP_RX_WIDTH_SHIFT); + + if (!cs48l32_asp_cfg_changed(component, base, sclk, slotw, dataw)) + return cs48l32_hw_params_rate(substream, params, dai); + + /* ASP must be disabled while changing configuration */ + asp_state = snd_soc_component_read(component, base + CS48L32_ASP_ENABLES1); + regmap_clear_bits(mfd->regmap, base + CS48L32_ASP_ENABLES1, 0xff00ff); + + ret = cs48l32_hw_params_rate(substream, params, dai); + if (ret != 0) + goto restore_asp; + + regmap_update_bits_async(mfd->regmap, + base + CS48L32_ASP_CONTROL1, + CS48L32_ASP_BCLK_FREQ_MASK, + sclk); + regmap_update_bits_async(mfd->regmap, + base + CS48L32_ASP_CONTROL2, + CS48L32_ASP_RX_WIDTH_MASK | CS48L32_ASP_TX_WIDTH_MASK, + slotw); + regmap_update_bits_async(mfd->regmap, + base + CS48L32_ASP_DATA_CONTROL1, + CS48L32_ASP_TX_WL_MASK, + dataw); + regmap_update_bits(mfd->regmap, + base + CS48L32_ASP_DATA_CONTROL5, + CS48L32_ASP_RX_WL_MASK, + dataw); + +restore_asp: + /* Restore ASP TX/RX enable state */ + regmap_update_bits(mfd->regmap, + base + CS48L32_ASP_ENABLES1, + 0xff00ff, + asp_state); + return ret; +} + +static const char * const cs48l32_dai_clk_str(int clk_id) +{ + switch (clk_id) { + case CS48L32_CLK_SYSCLK_1: + case CS48L32_CLK_SYSCLK_2: + case CS48L32_CLK_SYSCLK_3: + case CS48L32_CLK_SYSCLK_4: + return "SYSCLK"; + default: + return "Unknown clock"; + } +} + +static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_dai_priv *dai_priv = &cs48l32->dai[dai->id - 1]; + unsigned int base = dai->driver->base; + unsigned int current_asp_rate, target_asp_rate; + bool change_rate_domain = false; + int ret; + + if (clk_id == dai_priv->clk) + return 0; + + if (snd_soc_dai_active(dai)) { + cs48l32_asp_err(dai, "Can't change clock on active DAI\n"); + return -EBUSY; + } + + switch (clk_id) { + case CS48L32_CLK_SYSCLK_1: + target_asp_rate = 0U << CS48L32_ASP_RATE_SHIFT; + break; + case CS48L32_CLK_SYSCLK_2: + target_asp_rate = 1U << CS48L32_ASP_RATE_SHIFT; + break; + case CS48L32_CLK_SYSCLK_3: + target_asp_rate = 2U << CS48L32_ASP_RATE_SHIFT; + break; + case CS48L32_CLK_SYSCLK_4: + target_asp_rate = 3U << CS48L32_ASP_RATE_SHIFT; + break; + default: + return -EINVAL; + } + + dai_priv->clk = clk_id; + cs48l32_asp_dbg(dai, "Setting to %s\n", cs48l32_dai_clk_str(clk_id)); + + if (base) { + ret = regmap_read(cs48l32->mfd->regmap, + base + CS48L32_ASP_CONTROL1, + ¤t_asp_rate); + if (ret != 0) { + cs48l32_asp_err(dai, "Failed to check rate: %d\n", ret); + return ret; + } + + if ((current_asp_rate & CS48L32_ASP_RATE_MASK) != + (target_asp_rate & CS48L32_ASP_RATE_MASK)) { + change_rate_domain = true; + + mutex_lock(&cs48l32->rate_lock); + /* Guard the rate change with SYSCLK cycles */ + cs48l32_spin_sysclk(cs48l32); + } + + snd_soc_component_update_bits(component, base + CS48L32_ASP_CONTROL1, + CS48L32_ASP_RATE_MASK, target_asp_rate); + + if (change_rate_domain) { + cs48l32_spin_sysclk(cs48l32); + mutex_unlock(&cs48l32->rate_lock); + } + } + + return 0; +} + +static void cs48l32_set_channels_to_mask(struct snd_soc_dai *dai, + unsigned int base, + int channels, unsigned int mask) +{ + struct snd_soc_component *component = dai->component; + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + int slot, i, j = 0, shift; + unsigned int frame_ctls[2] = {0, 0}; + + for (i = 0; i < channels; ++i) { + slot = ffs(mask) - 1; + if (slot < 0) + return; + + if (i - (j * 4) >= 4) { + ++j; + if (j >= 2) + break; + } + + shift = (8 * (i - j * 4)); + + frame_ctls[j] |= slot << shift; + + mask &= ~(1 << slot); /* ? mask ^= 1 << slot ? */ + } + + regmap_write(mfd->regmap, base, frame_ctls[0]); + regmap_write(mfd->regmap, base + 0x4, frame_ctls[1]); + + if (mask) + cs48l32_asp_warn(dai, "Too many channels in TDM mask\n"); +} + +static int cs48l32_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; + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + int base = dai->driver->base; + int rx_max_chan = dai->driver->playback.channels_max; + int tx_max_chan = dai->driver->capture.channels_max; + + /* Only support TDM for the physical ASPs */ + if (dai->id > CS48L32_MAX_ASP) + return -EINVAL; + + if (slots == 0) { + tx_mask = (1 << tx_max_chan) - 1; + rx_mask = (1 << rx_max_chan) - 1; + } + + cs48l32_set_channels_to_mask(dai, base + CS48L32_ASP_FRAME_CONTROL1, + tx_max_chan, tx_mask); + cs48l32_set_channels_to_mask(dai, base + CS48L32_ASP_FRAME_CONTROL5, + rx_max_chan, rx_mask); + + cs48l32->tdm_width[dai->id - 1] = slot_width; + cs48l32->tdm_slots[dai->id - 1] = slots; + + return 0; +} + +const struct snd_soc_dai_ops cs48l32_dai_ops = { + .startup = &cs48l32_startup, + .set_fmt = &cs48l32_set_fmt, + .set_tdm_slot = &cs48l32_set_tdm_slot, + .hw_params = &cs48l32_hw_params, + .set_sysclk = &cs48l32_dai_set_sysclk, +}; + +int cs48l32_sysclk_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 cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + + cs48l32_spin_sysclk(cs48l32); + + return 0; +} + +int cs48l32_in_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 cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + unsigned int reg; + + if (w->shift % 2) + reg = CS48L32_IN1L_CONTROL2 + ((w->shift / 2) * 0x40); + else + reg = CS48L32_IN1R_CONTROL2 + ((w->shift / 2) * 0x40); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (w->shift) { + case CS48L32_IN1L_EN_SHIFT: + snd_soc_component_update_bits(component, + CS48L32_ADC1L_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + CS48L32_ADC1x_INT_ENA_FRC_MASK); + break; + case CS48L32_IN1R_EN_SHIFT: + snd_soc_component_update_bits(component, + CS48L32_ADC1R_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + CS48L32_ADC1x_INT_ENA_FRC_MASK); + break; + default: + break; + } + cs48l32->in_up_pending++; + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(200, 300); + + switch (w->shift) { + case CS48L32_IN1L_EN_SHIFT: + snd_soc_component_update_bits(component, + CS48L32_ADC1L_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + 0); + break; + case CS48L32_IN1R_EN_SHIFT: + snd_soc_component_update_bits(component, + CS48L32_ADC1R_ANA_CONTROL1, + CS48L32_ADC1x_INT_ENA_FRC_MASK, + 0); + break; + + default: + break; + } + cs48l32->in_up_pending--; + snd_soc_component_update_bits(component, reg, CS48L32_INx_MUTE_MASK, 0); + + /* Uncached write-only register, no need for update_bits */ + if (!cs48l32->in_up_pending) + snd_soc_component_write(component, cs48l32->in_vu_reg, CS48L32_IN_VU_MASK); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, reg, + CS48L32_INx_MUTE_MASK, CS48L32_INx_MUTE_MASK); + snd_soc_component_write(component, cs48l32->in_vu_reg, CS48L32_IN_VU_MASK); + break; + default: + break; + } + + return 0; +} + +int cs48l32_in_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* + * Uncached write-only register, no need for update_bits. + * Will fail if codec is off but that will be handled by cs48l32_in_ev + */ + snd_soc_component_write(component, cs48l32->in_vu_reg, CS48L32_IN_VU); + + return ret; +} + +static bool cs48l32_eq_filter_unstable(bool mode, __be16 in_a, __be16 in_b) +{ + s16 a = be16_to_cpu(in_a); + s16 b = be16_to_cpu(in_b); + + if (!mode) + return abs(a) >= 4096; + + if (abs(b) >= 4096) + return true; + + return (abs((a << 16) / (4096 - b)) >= 4096 << 4); +} + +int cs48l32_eq_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 cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + unsigned int mode = cs48l32->eq_mode[w->shift]; + unsigned int reg = CS48L32_EQ1_BAND1_COEFF1 + (68 * w->shift); + __be16 *data = &cs48l32->eq_coefficients[w->shift][0]; + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (cs48l32_eq_filter_unstable(!!mode, data[1], data[0]) || + cs48l32_eq_filter_unstable(true, data[7], data[6]) || + cs48l32_eq_filter_unstable(true, data[13], data[12]) || + cs48l32_eq_filter_unstable(true, data[19], data[18]) || + cs48l32_eq_filter_unstable(false, data[25], data[24])) { + dev_err(cs48l32->dev, "Rejecting unstable EQ coefficients.\n"); + ret = -EINVAL; + } else { + ret = regmap_raw_write(mfd->regmap, reg, data, + CS48L32_EQ_BLOCK_SZ); + if (ret < 0) { + dev_err(cs48l32->dev, "Error writing EQ coefficients: %d\n", ret); + goto out; + } + + ret = snd_soc_component_update_bits(component, + CS48L32_EQ_CONTROL2, + w->mask, + mode << w->shift); + if (ret < 0) + dev_err(cs48l32->dev, "Error writing EQ mode: %d\n", ret); + } + break; + default: + break; + } + +out: + return ret; +} + +static int cs48l32_get_variable_u32_array(struct cs48l32 *cs48l32, + const char *propname, + u32 *dest, + int n_max, + int multiple) +{ + struct cs48l32_mfd *mfd = cs48l32->mfd; + int n, ret; + + n = device_property_read_u32_array(mfd->dev, propname, NULL, 0); + if (n == -EINVAL) { + return 0; /* missing, ignore */ + } else if (n < 0) { + dev_warn(cs48l32->dev, "%s malformed (%d)\n", propname, n); + return -EINVAL; + } else if ((n % multiple) != 0) { + dev_warn(cs48l32->dev, "%s not a multiple of %d entries\n", propname, multiple); + return -EINVAL; + } + + if (n > n_max) + n = n_max; + + ret = device_property_read_u32_array(mfd->dev, propname, dest, n); + + if (ret < 0) + return ret; + else + return n; +} + +static void cs48l32_prop_get_in_type(struct cs48l32 *cs48l32) +{ + u32 tmp[CS48L32_MAX_INPUT * CS48L32_MAX_MUXED_IN_CHANNELS]; + int n, i, in_idx, ch_idx; + + BUILD_BUG_ON(ARRAY_SIZE(cs48l32->in_type) != CS48L32_MAX_INPUT); + BUILD_BUG_ON(ARRAY_SIZE(cs48l32->in_type[0]) != + CS48L32_MAX_MUXED_IN_CHANNELS); + + n = cs48l32_get_variable_u32_array(cs48l32, + "cirrus,in-type", + tmp, + ARRAY_SIZE(tmp), + CS48L32_MAX_MUXED_IN_CHANNELS); + if (n < 0) + return; + + in_idx = 0; + ch_idx = 0; + for (i = 0; i < n; ++i) { + cs48l32->in_type[in_idx][ch_idx] = tmp[i]; + + if (++ch_idx == CS48L32_MAX_MUXED_IN_CHANNELS) { + ch_idx = 0; + ++in_idx; + } + } +} + +static void cs48l32_prop_get(struct cs48l32 *cs48l32) +{ + int ret; + + ret = cs48l32_get_variable_u32_array(cs48l32, + "cirrus,max-channels-clocked", + cs48l32->max_channels_clocked, + ARRAY_SIZE(cs48l32->max_channels_clocked), + 1); + if (ret < 0) + return; + + cs48l32_prop_get_in_type(cs48l32); + + cs48l32_get_variable_u32_array(cs48l32, + "cirrus,pdm-sup", + cs48l32->pdm_sup, + ARRAY_SIZE(cs48l32->pdm_sup), + 1); +} + +int cs48l32_init_inputs(struct snd_soc_component *component) +{ + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + struct cs48l32_mfd *mfd = cs48l32->mfd; + unsigned int ana_mode_l, ana_mode_r, dig_mode; + int i; + + /* + * Initialize input modes from the A settings. For muxed inputs the + * B settings will be applied if the mux is changed + */ + for (i = 0; i < cs48l32->max_analogue_inputs; i++) { + dev_dbg(cs48l32->dev, "IN%d type %u:%u:%u:%u\n", i + 1, + cs48l32->in_type[i][0], + cs48l32->in_type[i][1], + cs48l32->in_type[i][2], + cs48l32->in_type[i][3]); + + switch (cs48l32->in_type[i][0]) { + case CS48L32_IN_TYPE_DIFF: + ana_mode_l = 0; + break; + case CS48L32_IN_TYPE_SE: + ana_mode_l = 1 << CS48L32_INx_SRC_SHIFT; + break; + default: + dev_warn(cs48l32->dev, "IN%dL_1 Illegal in_type %u ignored\n", + i + 1, cs48l32->in_type[i][0]); + continue; + } + + switch (cs48l32->in_type[i][1]) { + case CS48L32_IN_TYPE_DIFF: + ana_mode_r = 0; + break; + case CS48L32_IN_TYPE_SE: + ana_mode_r = 1 << CS48L32_INx_SRC_SHIFT; + break; + default: + dev_warn(cs48l32->dev, "IN%dR_1 Illegal in_type %u ignored\n", + i + 1, cs48l32->in_type[i][1]); + continue; + } + + dev_dbg(cs48l32->dev, "IN%d_1 Analogue mode=0x%x,0x%x\n", + i + 1, ana_mode_l, ana_mode_r); + + regmap_update_bits(mfd->regmap, + CS48L32_IN1L_CONTROL1 + (i * 0x40), + CS48L32_INx_SRC_MASK, + ana_mode_l); + + regmap_update_bits(mfd->regmap, + CS48L32_IN1R_CONTROL1 + (i * 0x40), + CS48L32_INx_SRC_MASK, + ana_mode_r); + } + + for (i = 0; i < cs48l32->max_pdm_sup; i++) { + dig_mode = cs48l32->pdm_sup[i] << CS48L32_IN1_PDM_SUP_SHIFT; + + dev_dbg(cs48l32->dev, "IN%d PDM_SUP=0x%x\n", i + 1, dig_mode); + + regmap_update_bits(mfd->regmap, + CS48L32_INPUT1_CONTROL1 + (i * 0x40), + CS48L32_IN1_PDM_SUP_MASK, dig_mode); + } + + return 0; +} + +int cs48l32_init_dai(struct cs48l32 *cs48l32, int id) +{ + struct cs48l32_dai_priv *dai_priv = &cs48l32->dai[id]; + + dai_priv->clk = CS48L32_CLK_SYSCLK_1; + dai_priv->constraint = cs48l32_constraint; + + return 0; +} + +int cs48l32_init_eq(struct cs48l32 *cs48l32) +{ + struct cs48l32_mfd *mfd = cs48l32->mfd; + unsigned int reg = CS48L32_EQ1_BAND1_COEFF1, mode; + __be16 *data; + int i, ret; + + ret = regmap_read(mfd->regmap, CS48L32_EQ_CONTROL2, &mode); + if (ret < 0) { + dev_err(cs48l32->dev, "Error reading EQ mode: %d\n", ret); + goto out; + } + + for (i = 0; i < 4; ++i) { + cs48l32->eq_mode[i] = (mode >> i) & 0x1; + + data = &cs48l32->eq_coefficients[i][0]; + ret = regmap_raw_read(mfd->regmap, reg + (i * 68), data, + CS48L32_EQ_BLOCK_SZ); + if (ret < 0) { + dev_err(cs48l32->dev, "Error reading EQ coefficients: %d\n", ret); + goto out; + } + } + +out: + return ret; +} + +int cs48l32_core_init(struct cs48l32 *cs48l32) +{ + BUILD_BUG_ON(ARRAY_SIZE(cs48l32_mixer_texts) != CS48L32_NUM_MIXER_INPUTS); + BUILD_BUG_ON(ARRAY_SIZE(cs48l32_mixer_values) != CS48L32_NUM_MIXER_INPUTS); + BUILD_BUG_ON(cs48l32_sample_rate_text[CS48L32_SAMPLE_RATE_ENUM_SIZE - 1] == NULL); + BUILD_BUG_ON(cs48l32_sample_rate_val[CS48L32_SAMPLE_RATE_ENUM_SIZE - 1] == 0); + BUILD_BUG_ON(ARRAY_SIZE(cs48l32_in_texts) != CS48L32_MAX_INPUT); + + cs48l32_prop_get(cs48l32); + + mutex_init(&cs48l32->rate_lock); + + return 0; +} + +int cs48l32_core_destroy(struct cs48l32 *cs48l32) +{ + mutex_destroy(&cs48l32->rate_lock); + + return 0; +} diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c new file mode 100644 index 000000000000..64763772b881 --- /dev/null +++ b/sound/soc/codecs/cs48l32.c @@ -0,0 +1,1211 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// ALSA SoC Audio driver for CS48L32 codec. +// +// Copyright (C) 2018-2020, 2022 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/irqchip/irq-cirrus-cs48l32.h> +#include <linux/mfd/cs48l32/core.h> +#include <linux/mfd/cs48l32/registers.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pm.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 <sound/tlv.h> + +#include "cs48l32.h" +#include "wm_adsp.h" + +static const struct cs_dsp_region cs48l32_dsp1_regions[] = { + { .type = WMFW_HALO_PM_PACKED, .base = 0x3800000 }, + { .type = WMFW_HALO_XM_PACKED, .base = 0x2000000 }, + { .type = WMFW_ADSP2_XM, .base = 0x2800000 }, + { .type = WMFW_HALO_YM_PACKED, .base = 0x2C00000 }, + { .type = WMFW_ADSP2_YM, .base = 0x3400000 }, +}; + +static const struct cs48l32_dsp_power_reg_block cs48l32_dsp1_sram_ext_regs[] = { + { CS48L32_DSP1_XM_SRAM_IBUS_SETUP_1, CS48L32_DSP1_XM_SRAM_IBUS_SETUP_24 }, + { CS48L32_DSP1_YM_SRAM_IBUS_SETUP_1, CS48L32_DSP1_YM_SRAM_IBUS_SETUP_8 }, + { CS48L32_DSP1_PM_SRAM_IBUS_SETUP_1, CS48L32_DSP1_PM_SRAM_IBUS_SETUP_7 }, +}; + +static const unsigned int cs48l32_dsp1_sram_pwd_regs[] = { + CS48L32_DSP1_XM_SRAM_IBUS_SETUP_0, + CS48L32_DSP1_YM_SRAM_IBUS_SETUP_0, + CS48L32_DSP1_PM_SRAM_IBUS_SETUP_0, +}; + +static const struct cs48l32_dsp_power_regs cs48l32_dsp_sram_regs = { + .ext = cs48l32_dsp1_sram_ext_regs, + .n_ext = ARRAY_SIZE(cs48l32_dsp1_sram_ext_regs), + .pwd = cs48l32_dsp1_sram_pwd_regs, + .n_pwd = ARRAY_SIZE(cs48l32_dsp1_sram_pwd_regs), +}; + +static const struct snd_kcontrol_new cs48l32_snd_controls[] = { +SOC_ENUM("IN1 OSR", cs48l32_in_dmic_osr[0]), +SOC_ENUM("IN2 OSR", cs48l32_in_dmic_osr[1]), + +SOC_SINGLE_RANGE_TLV("IN1L Volume", CS48L32_IN1L_CONTROL2, + CS48L32_INx_PGA_VOL_SHIFT, 0x40, 0x5f, 0, cs48l32_ana_tlv), +SOC_SINGLE_RANGE_TLV("IN1R Volume", CS48L32_IN1R_CONTROL2, + CS48L32_INx_PGA_VOL_SHIFT, 0x40, 0x5f, 0, cs48l32_ana_tlv), + +SOC_ENUM("IN HPF Cutoff Frequency", cs48l32_in_hpf_cut_enum), + +SOC_SINGLE_EXT("IN1L LP Switch", CS48L32_IN1L_CONTROL1, CS48L32_INx_LP_MODE_SHIFT, + 1, 0, snd_soc_get_volsw, cs48l32_low_power_mode_put), +SOC_SINGLE_EXT("IN1R LP Switch", CS48L32_IN1R_CONTROL1, CS48L32_INx_LP_MODE_SHIFT, + 1, 0, snd_soc_get_volsw, cs48l32_low_power_mode_put), + +SOC_SINGLE("IN1L HPF Switch", CS48L32_IN1L_CONTROL1, CS48L32_INx_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN1R HPF Switch", CS48L32_IN1R_CONTROL1, CS48L32_INx_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN2L HPF Switch", CS48L32_IN2L_CONTROL1, CS48L32_INx_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN2R HPF Switch", CS48L32_IN2R_CONTROL1, CS48L32_INx_HPF_SHIFT, 1, 0), + +SOC_SINGLE_EXT_TLV("IN1L Digital Volume", CS48L32_IN1L_CONTROL2, + CS48L32_INx_VOL_SHIFT, 0xbf, 0, snd_soc_get_volsw, + cs48l32_in_put_volsw, cs48l32_digital_tlv), +SOC_SINGLE_EXT_TLV("IN1R Digital Volume", CS48L32_IN1R_CONTROL2, + CS48L32_INx_VOL_SHIFT, 0xbf, 0, snd_soc_get_volsw, + cs48l32_in_put_volsw, cs48l32_digital_tlv), +SOC_SINGLE_EXT_TLV("IN2L Digital Volume", CS48L32_IN2L_CONTROL2, + CS48L32_INx_VOL_SHIFT, 0xbf, 0, snd_soc_get_volsw, + cs48l32_in_put_volsw, cs48l32_digital_tlv), +SOC_SINGLE_EXT_TLV("IN2R Digital Volume", CS48L32_IN2R_CONTROL2, + CS48L32_INx_VOL_SHIFT, 0xbf, 0, snd_soc_get_volsw, + cs48l32_in_put_volsw, cs48l32_digital_tlv), + +SOC_ENUM("Input Ramp Up", cs48l32_in_vi_ramp), +SOC_ENUM("Input Ramp Down", cs48l32_in_vd_ramp), + +CS48L32_RATE_ENUM("Ultrasonic 1 Rate", cs48l32_us_output_rate[0]), +CS48L32_RATE_ENUM("Ultrasonic 2 Rate", cs48l32_us_output_rate[1]), + +SOC_ENUM("Ultrasonic 1 Freq", cs48l32_us_freq[0]), +SOC_ENUM("Ultrasonic 2 Freq", cs48l32_us_freq[1]), + +SOC_SINGLE_TLV("Ultrasonic 1 Volume", CS48L32_US1_CONTROL, CS48L32_US1_GAIN_SHIFT, + 3, 0, cs48l32_us_tlv), +SOC_SINGLE_TLV("Ultrasonic 2 Volume", CS48L32_US2_CONTROL, CS48L32_US1_GAIN_SHIFT, + 3, 0, cs48l32_us_tlv), + +SOC_ENUM("Ultrasonic 1 Activity Detect Threshold", cs48l32_us_det_thr[0]), +SOC_ENUM("Ultrasonic 2 Activity Detect Threshold", cs48l32_us_det_thr[1]), + +SOC_ENUM("Ultrasonic 1 Activity Detect Pulse Length", cs48l32_us_det_num[0]), +SOC_ENUM("Ultrasonic 2 Activity Detect Pulse Length", cs48l32_us_det_num[1]), + +SOC_ENUM("Ultrasonic 1 Activity Detect Hold", cs48l32_us_det_hold[0]), +SOC_ENUM("Ultrasonic 2 Activity Detect Hold", cs48l32_us_det_hold[1]), + +SOC_ENUM("Ultrasonic 1 Activity Detect Decay", cs48l32_us_det_dcy[0]), +SOC_ENUM("Ultrasonic 2 Activity Detect Decay", cs48l32_us_det_dcy[1]), + +SOC_SINGLE("Ultrasonic 1 Activity Detect LPF Switch", + CS48L32_US1_DET_CONTROL, CS48L32_US1_DET_LPF_SHIFT, 1, 0), +SOC_SINGLE("Ultrasonic 2 Activity Detect LPF Switch", + CS48L32_US2_DET_CONTROL, CS48L32_US1_DET_LPF_SHIFT, 1, 0), + +SOC_ENUM("Ultrasonic 1 Activity Detect LPF Cut-off", cs48l32_us_det_lpf_cut[0]), +SOC_ENUM("Ultrasonic 2 Activity Detect LPF Cut-off", cs48l32_us_det_lpf_cut[1]), + +CS48L32_MIXER_CONTROLS("EQ1", CS48L32_EQ1_INPUT1), +CS48L32_MIXER_CONTROLS("EQ2", CS48L32_EQ2_INPUT1), +CS48L32_MIXER_CONTROLS("EQ3", CS48L32_EQ3_INPUT1), +CS48L32_MIXER_CONTROLS("EQ4", CS48L32_EQ4_INPUT1), + +SOC_ENUM_EXT("EQ1 Mode", cs48l32_eq_mode[0], cs48l32_eq_mode_get, cs48l32_eq_mode_put), + +CS48L32_EQ_COEFF_CONTROLS(EQ1), + +SOC_SINGLE_TLV("EQ1 B1 Volume", CS48L32_EQ1_GAIN1, 0, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ1 B2 Volume", CS48L32_EQ1_GAIN1, 8, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ1 B3 Volume", CS48L32_EQ1_GAIN1, 16, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ1 B4 Volume", CS48L32_EQ1_GAIN1, 24, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ1 B5 Volume", CS48L32_EQ1_GAIN2, 0, 24, 0, cs48l32_eq_tlv), + +SOC_ENUM_EXT("EQ2 Mode", cs48l32_eq_mode[1], cs48l32_eq_mode_get, cs48l32_eq_mode_put), +CS48L32_EQ_COEFF_CONTROLS(EQ2), +SOC_SINGLE_TLV("EQ2 B1 Volume", CS48L32_EQ2_GAIN1, 0, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ2 B2 Volume", CS48L32_EQ2_GAIN1, 8, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ2 B3 Volume", CS48L32_EQ2_GAIN1, 16, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ2 B4 Volume", CS48L32_EQ2_GAIN1, 24, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ2 B5 Volume", CS48L32_EQ2_GAIN2, 0, 24, 0, cs48l32_eq_tlv), + +SOC_ENUM_EXT("EQ3 Mode", cs48l32_eq_mode[2], cs48l32_eq_mode_get, cs48l32_eq_mode_put), +CS48L32_EQ_COEFF_CONTROLS(EQ3), +SOC_SINGLE_TLV("EQ3 B1 Volume", CS48L32_EQ3_GAIN1, 0, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ3 B2 Volume", CS48L32_EQ3_GAIN1, 8, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ3 B3 Volume", CS48L32_EQ3_GAIN1, 16, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ3 B4 Volume", CS48L32_EQ3_GAIN1, 24, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ3 B5 Volume", CS48L32_EQ3_GAIN2, 0, 24, 0, cs48l32_eq_tlv), + +SOC_ENUM_EXT("EQ4 Mode", cs48l32_eq_mode[3], cs48l32_eq_mode_get, cs48l32_eq_mode_put), +CS48L32_EQ_COEFF_CONTROLS(EQ4), +SOC_SINGLE_TLV("EQ4 B1 Volume", CS48L32_EQ4_GAIN1, 0, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ4 B2 Volume", CS48L32_EQ4_GAIN1, 8, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ4 B3 Volume", CS48L32_EQ4_GAIN1, 16, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ4 B4 Volume", CS48L32_EQ4_GAIN1, 24, 24, 0, cs48l32_eq_tlv), +SOC_SINGLE_TLV("EQ4 B5 Volume", CS48L32_EQ4_GAIN2, 0, 24, 0, cs48l32_eq_tlv), + +CS48L32_MIXER_CONTROLS("DRC1L", CS48L32_DRC1L_INPUT1), +CS48L32_MIXER_CONTROLS("DRC1R", CS48L32_DRC1R_INPUT1), +CS48L32_MIXER_CONTROLS("DRC2L", CS48L32_DRC2L_INPUT1), +CS48L32_MIXER_CONTROLS("DRC2R", CS48L32_DRC2R_INPUT1), + +SND_SOC_BYTES_MASK("DRC1 Coefficients", CS48L32_DRC1_CONTROL1, 4, + BIT(CS48L32_DRC1R_EN_SHIFT) | BIT(CS48L32_DRC1L_EN_SHIFT)), +SND_SOC_BYTES_MASK("DRC2 Coefficients", CS48L32_DRC2_CONTROL1, 4, + BIT(CS48L32_DRC1R_EN_SHIFT) | BIT(CS48L32_DRC1L_EN_SHIFT)), + +CS48L32_MIXER_CONTROLS("LHPF1", CS48L32_LHPF1_INPUT1), +CS48L32_MIXER_CONTROLS("LHPF2", CS48L32_LHPF2_INPUT1), +CS48L32_MIXER_CONTROLS("LHPF3", CS48L32_LHPF3_INPUT1), +CS48L32_MIXER_CONTROLS("LHPF4", CS48L32_LHPF4_INPUT1), + +CS48L32_LHPF_CONTROL("LHPF1 Coefficients", CS48L32_LHPF1_COEFF), +CS48L32_LHPF_CONTROL("LHPF2 Coefficients", CS48L32_LHPF2_COEFF), +CS48L32_LHPF_CONTROL("LHPF3 Coefficients", CS48L32_LHPF3_COEFF), +CS48L32_LHPF_CONTROL("LHPF4 Coefficients", CS48L32_LHPF4_COEFF), + +SOC_ENUM("LHPF1 Mode", cs48l32_lhpf_mode[0]), +SOC_ENUM("LHPF2 Mode", cs48l32_lhpf_mode[1]), +SOC_ENUM("LHPF3 Mode", cs48l32_lhpf_mode[2]), +SOC_ENUM("LHPF4 Mode", cs48l32_lhpf_mode[3]), + +CS48L32_RATE_CONTROL("Sample Rate 1", 1), +CS48L32_RATE_CONTROL("Sample Rate 2", 2), +CS48L32_RATE_CONTROL("Sample Rate 3", 3), +CS48L32_RATE_CONTROL("Sample Rate 4", 4), + +CS48L32_RATE_ENUM("FX Rate", cs48l32_fx_rate), + +CS48L32_RATE_ENUM("ISRC1 FSL", cs48l32_isrc_fsl[0]), +CS48L32_RATE_ENUM("ISRC2 FSL", cs48l32_isrc_fsl[1]), +CS48L32_RATE_ENUM("ISRC3 FSL", cs48l32_isrc_fsl[2]), +CS48L32_RATE_ENUM("ISRC1 FSH", cs48l32_isrc_fsh[0]), +CS48L32_RATE_ENUM("ISRC2 FSH", cs48l32_isrc_fsh[1]), +CS48L32_RATE_ENUM("ISRC3 FSH", cs48l32_isrc_fsh[2]), + +SOC_ENUM("AUXPDM1 Rate", cs48l32_auxpdm1_freq), +SOC_ENUM("AUXPDM2 Rate", cs48l32_auxpdm2_freq), + +SOC_ENUM_EXT("IN1L Rate", cs48l32_input_rate[0], snd_soc_get_enum_double, cs48l32_in_rate_put), +SOC_ENUM_EXT("IN1R Rate", cs48l32_input_rate[1], snd_soc_get_enum_double, cs48l32_in_rate_put), +SOC_ENUM_EXT("IN2L Rate", cs48l32_input_rate[2], snd_soc_get_enum_double, cs48l32_in_rate_put), +SOC_ENUM_EXT("IN2R Rate", cs48l32_input_rate[3], snd_soc_get_enum_double, cs48l32_in_rate_put), + +CS48L32_RATE_ENUM("Noise Generator Rate", noise_gen_rate), + +SOC_SINGLE_TLV("Noise Generator Volume", CS48L32_COMFORT_NOISE_GENERATOR, + CS48L32_NOISE_GEN_GAIN_SHIFT, 0x12, 0, cs48l32_noise_tlv), + +CS48L32_MIXER_CONTROLS("ASP1TX1", CS48L32_ASP1TX1_INPUT1), +CS48L32_MIXER_CONTROLS("ASP1TX2", CS48L32_ASP1TX2_INPUT1), +CS48L32_MIXER_CONTROLS("ASP1TX3", CS48L32_ASP1TX3_INPUT1), +CS48L32_MIXER_CONTROLS("ASP1TX4", CS48L32_ASP1TX4_INPUT1), +CS48L32_MIXER_CONTROLS("ASP1TX5", CS48L32_ASP1TX5_INPUT1), +CS48L32_MIXER_CONTROLS("ASP1TX6", CS48L32_ASP1TX6_INPUT1), +CS48L32_MIXER_CONTROLS("ASP1TX7", CS48L32_ASP1TX7_INPUT1), +CS48L32_MIXER_CONTROLS("ASP1TX8", CS48L32_ASP1TX8_INPUT1), + +CS48L32_MIXER_CONTROLS("ASP2TX1", CS48L32_ASP2TX1_INPUT1), +CS48L32_MIXER_CONTROLS("ASP2TX2", CS48L32_ASP2TX2_INPUT1), +CS48L32_MIXER_CONTROLS("ASP2TX3", CS48L32_ASP2TX3_INPUT1), +CS48L32_MIXER_CONTROLS("ASP2TX4", CS48L32_ASP2TX4_INPUT1), + +WM_ADSP2_PRELOAD_SWITCH("DSP1", 1), + +CS48L32_MIXER_CONTROLS("DSP1RX1", CS48L32_DSP1RX1_INPUT1), +CS48L32_MIXER_CONTROLS("DSP1RX2", CS48L32_DSP1RX2_INPUT1), +CS48L32_MIXER_CONTROLS("DSP1RX3", CS48L32_DSP1RX3_INPUT1), +CS48L32_MIXER_CONTROLS("DSP1RX4", CS48L32_DSP1RX4_INPUT1), +CS48L32_MIXER_CONTROLS("DSP1RX5", CS48L32_DSP1RX5_INPUT1), +CS48L32_MIXER_CONTROLS("DSP1RX6", CS48L32_DSP1RX6_INPUT1), +CS48L32_MIXER_CONTROLS("DSP1RX7", CS48L32_DSP1RX7_INPUT1), +CS48L32_MIXER_CONTROLS("DSP1RX8", CS48L32_DSP1RX8_INPUT1), + +WM_ADSP_FW_CONTROL("DSP1", 0), + +CS48L32_DSP_RATE_CONTROL("DSP1RX1", 0), +CS48L32_DSP_RATE_CONTROL("DSP1RX2", 1), +CS48L32_DSP_RATE_CONTROL("DSP1RX3", 2), +CS48L32_DSP_RATE_CONTROL("DSP1RX4", 3), +CS48L32_DSP_RATE_CONTROL("DSP1RX5", 4), +CS48L32_DSP_RATE_CONTROL("DSP1RX6", 5), +CS48L32_DSP_RATE_CONTROL("DSP1RX7", 6), +CS48L32_DSP_RATE_CONTROL("DSP1RX8", 7), +CS48L32_DSP_RATE_CONTROL("DSP1TX1", 8), +CS48L32_DSP_RATE_CONTROL("DSP1TX2", 9), +CS48L32_DSP_RATE_CONTROL("DSP1TX3", 10), +CS48L32_DSP_RATE_CONTROL("DSP1TX4", 11), +CS48L32_DSP_RATE_CONTROL("DSP1TX5", 12), +CS48L32_DSP_RATE_CONTROL("DSP1TX6", 13), +CS48L32_DSP_RATE_CONTROL("DSP1TX7", 14), +CS48L32_DSP_RATE_CONTROL("DSP1TX8", 15), +}; + +CS48L32_MIXER_ENUMS(EQ1, CS48L32_EQ1_INPUT1); +CS48L32_MIXER_ENUMS(EQ2, CS48L32_EQ2_INPUT1); +CS48L32_MIXER_ENUMS(EQ3, CS48L32_EQ3_INPUT1); +CS48L32_MIXER_ENUMS(EQ4, CS48L32_EQ4_INPUT1); + +CS48L32_MIXER_ENUMS(DRC1L, CS48L32_DRC1L_INPUT1); +CS48L32_MIXER_ENUMS(DRC1R, CS48L32_DRC1R_INPUT1); +CS48L32_MIXER_ENUMS(DRC2L, CS48L32_DRC2L_INPUT1); +CS48L32_MIXER_ENUMS(DRC2R, CS48L32_DRC2R_INPUT1); + +CS48L32_MIXER_ENUMS(LHPF1, CS48L32_LHPF1_INPUT1); +CS48L32_MIXER_ENUMS(LHPF2, CS48L32_LHPF2_INPUT1); +CS48L32_MIXER_ENUMS(LHPF3, CS48L32_LHPF3_INPUT1); +CS48L32_MIXER_ENUMS(LHPF4, CS48L32_LHPF4_INPUT1); + +CS48L32_MIXER_ENUMS(ASP1TX1, CS48L32_ASP1TX1_INPUT1); +CS48L32_MIXER_ENUMS(ASP1TX2, CS48L32_ASP1TX2_INPUT1); +CS48L32_MIXER_ENUMS(ASP1TX3, CS48L32_ASP1TX3_INPUT1); +CS48L32_MIXER_ENUMS(ASP1TX4, CS48L32_ASP1TX4_INPUT1); +CS48L32_MIXER_ENUMS(ASP1TX5, CS48L32_ASP1TX5_INPUT1); +CS48L32_MIXER_ENUMS(ASP1TX6, CS48L32_ASP1TX6_INPUT1); +CS48L32_MIXER_ENUMS(ASP1TX7, CS48L32_ASP1TX7_INPUT1); +CS48L32_MIXER_ENUMS(ASP1TX8, CS48L32_ASP1TX8_INPUT1); + +CS48L32_MIXER_ENUMS(ASP2TX1, CS48L32_ASP2TX1_INPUT1); +CS48L32_MIXER_ENUMS(ASP2TX2, CS48L32_ASP2TX2_INPUT1); +CS48L32_MIXER_ENUMS(ASP2TX3, CS48L32_ASP2TX3_INPUT1); +CS48L32_MIXER_ENUMS(ASP2TX4, CS48L32_ASP2TX4_INPUT1); + +CS48L32_MUX_ENUMS(ISRC1INT1, CS48L32_ISRC1INT1_INPUT1); +CS48L32_MUX_ENUMS(ISRC1INT2, CS48L32_ISRC1INT2_INPUT1); +CS48L32_MUX_ENUMS(ISRC1INT3, CS48L32_ISRC1INT3_INPUT1); +CS48L32_MUX_ENUMS(ISRC1INT4, CS48L32_ISRC1INT4_INPUT1); + +CS48L32_MUX_ENUMS(ISRC1DEC1, CS48L32_ISRC1DEC1_INPUT1); +CS48L32_MUX_ENUMS(ISRC1DEC2, CS48L32_ISRC1DEC2_INPUT1); +CS48L32_MUX_ENUMS(ISRC1DEC3, CS48L32_ISRC1DEC3_INPUT1); +CS48L32_MUX_ENUMS(ISRC1DEC4, CS48L32_ISRC1DEC4_INPUT1); + +CS48L32_MUX_ENUMS(ISRC2INT1, CS48L32_ISRC2INT1_INPUT1); +CS48L32_MUX_ENUMS(ISRC2INT2, CS48L32_ISRC2INT2_INPUT1); + +CS48L32_MUX_ENUMS(ISRC2DEC1, CS48L32_ISRC2DEC1_INPUT1); +CS48L32_MUX_ENUMS(ISRC2DEC2, CS48L32_ISRC2DEC2_INPUT1); + +CS48L32_MUX_ENUMS(ISRC3INT1, CS48L32_ISRC3INT1_INPUT1); +CS48L32_MUX_ENUMS(ISRC3INT2, CS48L32_ISRC3INT2_INPUT1); + +CS48L32_MUX_ENUMS(ISRC3DEC1, CS48L32_ISRC3DEC1_INPUT1); +CS48L32_MUX_ENUMS(ISRC3DEC2, CS48L32_ISRC3DEC2_INPUT1); + +CS48L32_MIXER_ENUMS(DSP1RX1, CS48L32_DSP1RX1_INPUT1); +CS48L32_MIXER_ENUMS(DSP1RX2, CS48L32_DSP1RX2_INPUT1); +CS48L32_MIXER_ENUMS(DSP1RX3, CS48L32_DSP1RX3_INPUT1); +CS48L32_MIXER_ENUMS(DSP1RX4, CS48L32_DSP1RX4_INPUT1); +CS48L32_MIXER_ENUMS(DSP1RX5, CS48L32_DSP1RX5_INPUT1); +CS48L32_MIXER_ENUMS(DSP1RX6, CS48L32_DSP1RX6_INPUT1); +CS48L32_MIXER_ENUMS(DSP1RX7, CS48L32_DSP1RX7_INPUT1); +CS48L32_MIXER_ENUMS(DSP1RX8, CS48L32_DSP1RX8_INPUT1); + +static int cs48l32_dsp_mem_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 cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + return cs48l32_dsp_memory_enable(cs48l32, &cs48l32_dsp_sram_regs); + case SND_SOC_DAPM_PRE_PMD: + cs48l32_dsp_memory_disable(cs48l32, &cs48l32_dsp_sram_regs); + return 0; + default: + return 0; + } +} + +static const struct snd_soc_dapm_widget cs48l32_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", CS48L32_SYSTEM_CLOCK1, CS48L32_SYSCLK_EN_SHIFT, 0, + cs48l32_sysclk_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_SUPPLY("OPCLK", CS48L32_OUTPUT_SYS_CLK, CS48L32_OPCLK_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_REGULATOR_SUPPLY("VDD_CP", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("VOUT_MIC", 0, SND_SOC_DAPM_REGULATOR_BYPASS), + +SND_SOC_DAPM_SUPPLY("MICBIAS1", CS48L32_MICBIAS_CTRL1, CS48L32_MICB1_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("MICBIAS1A", CS48L32_MICBIAS_CTRL5, CS48L32_MICB1A_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS1B", CS48L32_MICBIAS_CTRL5, CS48L32_MICB1B_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS1C", CS48L32_MICBIAS_CTRL5, CS48L32_MICB1C_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("DSP1MEM", SND_SOC_NOPM, 0, 0, cs48l32_dsp_mem_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +CS48L32_DSP_FREQ_WIDGET_EV("DSP1", 0, cs48l32_dsp_freq_ev), + +SND_SOC_DAPM_SIGGEN("TONE"), +SND_SOC_DAPM_SIGGEN("NOISE"), + +SND_SOC_DAPM_INPUT("IN1LN_1"), +SND_SOC_DAPM_INPUT("IN1LN_2"), +SND_SOC_DAPM_INPUT("IN1LP_1"), +SND_SOC_DAPM_INPUT("IN1LP_2"), +SND_SOC_DAPM_INPUT("IN1RN_1"), +SND_SOC_DAPM_INPUT("IN1RN_2"), +SND_SOC_DAPM_INPUT("IN1RP_1"), +SND_SOC_DAPM_INPUT("IN1RP_2"), +SND_SOC_DAPM_INPUT("IN1_PDMCLK"), +SND_SOC_DAPM_INPUT("IN1_PDMDATA"), + +SND_SOC_DAPM_INPUT("IN2_PDMCLK"), +SND_SOC_DAPM_INPUT("IN2_PDMDATA"), + +SND_SOC_DAPM_MUX("Ultrasonic 1 Input", SND_SOC_NOPM, 0, 0, &cs48l32_us_inmux[0]), +SND_SOC_DAPM_MUX("Ultrasonic 2 Input", SND_SOC_NOPM, 0, 0, &cs48l32_us_inmux[1]), + +SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), +SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"), + +SND_SOC_DAPM_OUTPUT("DSP Trigger Out"), + +SND_SOC_DAPM_MUX("IN1L Mux", SND_SOC_NOPM, 0, 0, &cs48l32_inmux[0]), +SND_SOC_DAPM_MUX("IN1R Mux", SND_SOC_NOPM, 0, 0, &cs48l32_inmux[1]), + +SND_SOC_DAPM_MUX("IN1L Mode", SND_SOC_NOPM, 0, 0, &cs48l32_dmode_mux[0]), +SND_SOC_DAPM_MUX("IN1R Mode", SND_SOC_NOPM, 0, 0, &cs48l32_dmode_mux[0]), + +SND_SOC_DAPM_AIF_OUT("ASP1TX1", NULL, 0, CS48L32_ASP1_ENABLES1, 0, 0), +SND_SOC_DAPM_AIF_OUT("ASP1TX2", NULL, 1, CS48L32_ASP1_ENABLES1, 1, 0), +SND_SOC_DAPM_AIF_OUT("ASP1TX3", NULL, 2, CS48L32_ASP1_ENABLES1, 2, 0), +SND_SOC_DAPM_AIF_OUT("ASP1TX4", NULL, 3, CS48L32_ASP1_ENABLES1, 3, 0), +SND_SOC_DAPM_AIF_OUT("ASP1TX5", NULL, 4, CS48L32_ASP1_ENABLES1, 4, 0), +SND_SOC_DAPM_AIF_OUT("ASP1TX6", NULL, 5, CS48L32_ASP1_ENABLES1, 5, 0), +SND_SOC_DAPM_AIF_OUT("ASP1TX7", NULL, 6, CS48L32_ASP1_ENABLES1, 6, 0), +SND_SOC_DAPM_AIF_OUT("ASP1TX8", NULL, 7, CS48L32_ASP1_ENABLES1, 7, 0), + +SND_SOC_DAPM_AIF_OUT("ASP2TX1", NULL, 0, CS48L32_ASP2_ENABLES1, 0, 0), +SND_SOC_DAPM_AIF_OUT("ASP2TX2", NULL, 1, CS48L32_ASP2_ENABLES1, 1, 0), +SND_SOC_DAPM_AIF_OUT("ASP2TX3", NULL, 2, CS48L32_ASP2_ENABLES1, 2, 0), +SND_SOC_DAPM_AIF_OUT("ASP2TX4", NULL, 3, CS48L32_ASP2_ENABLES1, 3, 0), + +SND_SOC_DAPM_SWITCH("AUXPDM1 Output", CS48L32_AUXPDM_CONTROL1, 0, 0, &cs48l32_auxpdm_switch[0]), +SND_SOC_DAPM_SWITCH("AUXPDM2 Output", CS48L32_AUXPDM_CONTROL1, 1, 0, &cs48l32_auxpdm_switch[1]), + +SND_SOC_DAPM_MUX("AUXPDM1 Input", SND_SOC_NOPM, 0, 0, &cs48l32_auxpdm_inmux[0]), +SND_SOC_DAPM_MUX("AUXPDM2 Input", SND_SOC_NOPM, 0, 0, &cs48l32_auxpdm_inmux[1]), + +SND_SOC_DAPM_MUX("AUXPDM1 Analog Input", SND_SOC_NOPM, 0, 0, + &cs48l32_auxpdm_analog_inmux[0]), +SND_SOC_DAPM_MUX("AUXPDM2 Analog Input", SND_SOC_NOPM, 0, 0, + &cs48l32_auxpdm_analog_inmux[1]), + +SND_SOC_DAPM_SWITCH("Ultrasonic 1 Activity Detect", CS48L32_US_CONTROL, + CS48L32_US1_DET_EN_SHIFT, 0, &cs48l32_us_switch[0]), +SND_SOC_DAPM_SWITCH("Ultrasonic 2 Activity Detect", CS48L32_US_CONTROL, + CS48L32_US1_DET_EN_SHIFT, 0, &cs48l32_us_switch[1]), + +/* + * mux_in widgets : arranged in the order of sources + * specified in CS48L32_MIXER_INPUT_ROUTES + */ +SND_SOC_DAPM_PGA("Tone Generator 1", CS48L32_TONE_GENERATOR1, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("Tone Generator 2", CS48L32_TONE_GENERATOR1, 1, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Noise Generator", CS48L32_COMFORT_NOISE_GENERATOR, + CS48L32_NOISE_GEN_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("IN1L PGA", CS48L32_INPUT_CONTROL, CS48L32_IN1L_EN_SHIFT, + 0, NULL, 0, cs48l32_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN1R PGA", CS48L32_INPUT_CONTROL, CS48L32_IN1R_EN_SHIFT, + 0, NULL, 0, cs48l32_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2L PGA", CS48L32_INPUT_CONTROL, CS48L32_IN2L_EN_SHIFT, + 0, NULL, 0, cs48l32_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2R PGA", CS48L32_INPUT_CONTROL, CS48L32_IN2R_EN_SHIFT, + 0, NULL, 0, cs48l32_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_AIF_IN("ASP1RX1", NULL, 0, CS48L32_ASP1_ENABLES1, 16, 0), +SND_SOC_DAPM_AIF_IN("ASP1RX2", NULL, 1, CS48L32_ASP1_ENABLES1, 17, 0), +SND_SOC_DAPM_AIF_IN("ASP1RX3", NULL, 2, CS48L32_ASP1_ENABLES1, 18, 0), +SND_SOC_DAPM_AIF_IN("ASP1RX4", NULL, 3, CS48L32_ASP1_ENABLES1, 19, 0), +SND_SOC_DAPM_AIF_IN("ASP1RX5", NULL, 4, CS48L32_ASP1_ENABLES1, 20, 0), +SND_SOC_DAPM_AIF_IN("ASP1RX6", NULL, 5, CS48L32_ASP1_ENABLES1, 21, 0), +SND_SOC_DAPM_AIF_IN("ASP1RX7", NULL, 6, CS48L32_ASP1_ENABLES1, 22, 0), +SND_SOC_DAPM_AIF_IN("ASP1RX8", NULL, 7, CS48L32_ASP1_ENABLES1, 23, 0), + +SND_SOC_DAPM_AIF_IN("ASP2RX1", NULL, 0, CS48L32_ASP2_ENABLES1, 16, 0), +SND_SOC_DAPM_AIF_IN("ASP2RX2", NULL, 1, CS48L32_ASP2_ENABLES1, 17, 0), +SND_SOC_DAPM_AIF_IN("ASP2RX3", NULL, 2, CS48L32_ASP2_ENABLES1, 18, 0), +SND_SOC_DAPM_AIF_IN("ASP2RX4", NULL, 3, CS48L32_ASP2_ENABLES1, 19, 0), + +SND_SOC_DAPM_PGA("ISRC1DEC1", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_DEC1_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC2", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_DEC2_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC3", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_DEC3_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC4", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_DEC4_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1INT1", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_INT1_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT2", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_INT2_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT3", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_INT3_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT4", CS48L32_ISRC1_CONTROL2, CS48L32_ISRC1_INT4_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2DEC1", CS48L32_ISRC2_CONTROL2, CS48L32_ISRC1_DEC1_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC2", CS48L32_ISRC2_CONTROL2, CS48L32_ISRC1_DEC2_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2INT1", CS48L32_ISRC2_CONTROL2, CS48L32_ISRC1_INT1_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT2", CS48L32_ISRC2_CONTROL2, CS48L32_ISRC1_INT2_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC3DEC1", CS48L32_ISRC3_CONTROL2, CS48L32_ISRC1_DEC1_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3DEC2", CS48L32_ISRC3_CONTROL2, CS48L32_ISRC1_DEC2_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC3INT1", CS48L32_ISRC3_CONTROL2, CS48L32_ISRC1_INT1_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3INT2", CS48L32_ISRC3_CONTROL2, CS48L32_ISRC1_INT2_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("EQ1", CS48L32_EQ_CONTROL1, 0, 0, NULL, 0, cs48l32_eq_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("EQ2", CS48L32_EQ_CONTROL1, 1, 0, NULL, 0, cs48l32_eq_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("EQ3", CS48L32_EQ_CONTROL1, 2, 0, NULL, 0, cs48l32_eq_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("EQ4", CS48L32_EQ_CONTROL1, 3, 0, NULL, 0, cs48l32_eq_ev, SND_SOC_DAPM_PRE_PMU), + +SND_SOC_DAPM_PGA("DRC1L", CS48L32_DRC1_CONTROL1, CS48L32_DRC1L_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("DRC1R", CS48L32_DRC1_CONTROL1, CS48L32_DRC1R_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("DRC2L", CS48L32_DRC2_CONTROL1, CS48L32_DRC1L_EN_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("DRC2R", CS48L32_DRC2_CONTROL1, CS48L32_DRC1R_EN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", CS48L32_LHPF_CONTROL1, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", CS48L32_LHPF_CONTROL1, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("LHPF3", CS48L32_LHPF_CONTROL1, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("LHPF4", CS48L32_LHPF_CONTROL1, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Ultrasonic 1", CS48L32_US_CONTROL, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("Ultrasonic 2", CS48L32_US_CONTROL, 1, 0, NULL, 0), + +WM_ADSP2("DSP1", 0, wm_adsp_early_event), + +/* end of ordered widget list */ + +CS48L32_MIXER_WIDGETS(EQ1, "EQ1"), +CS48L32_MIXER_WIDGETS(EQ2, "EQ2"), +CS48L32_MIXER_WIDGETS(EQ3, "EQ3"), +CS48L32_MIXER_WIDGETS(EQ4, "EQ4"), + +CS48L32_MIXER_WIDGETS(DRC1L, "DRC1L"), +CS48L32_MIXER_WIDGETS(DRC1R, "DRC1R"), +CS48L32_MIXER_WIDGETS(DRC2L, "DRC2L"), +CS48L32_MIXER_WIDGETS(DRC2R, "DRC2R"), + +SND_SOC_DAPM_SWITCH("DRC1 Activity Output", SND_SOC_NOPM, 0, 0, + &cs48l32_drc_activity_output_mux[0]), +SND_SOC_DAPM_SWITCH("DRC2 Activity Output", SND_SOC_NOPM, 0, 0, + &cs48l32_drc_activity_output_mux[1]), + +CS48L32_MIXER_WIDGETS(LHPF1, "LHPF1"), +CS48L32_MIXER_WIDGETS(LHPF2, "LHPF2"), +CS48L32_MIXER_WIDGETS(LHPF3, "LHPF3"), +CS48L32_MIXER_WIDGETS(LHPF4, "LHPF4"), + +CS48L32_MIXER_WIDGETS(ASP1TX1, "ASP1TX1"), +CS48L32_MIXER_WIDGETS(ASP1TX2, "ASP1TX2"), +CS48L32_MIXER_WIDGETS(ASP1TX3, "ASP1TX3"), +CS48L32_MIXER_WIDGETS(ASP1TX4, "ASP1TX4"), +CS48L32_MIXER_WIDGETS(ASP1TX5, "ASP1TX5"), +CS48L32_MIXER_WIDGETS(ASP1TX6, "ASP1TX6"), +CS48L32_MIXER_WIDGETS(ASP1TX7, "ASP1TX7"), +CS48L32_MIXER_WIDGETS(ASP1TX8, "ASP1TX8"), + +CS48L32_MIXER_WIDGETS(ASP2TX1, "ASP2TX1"), +CS48L32_MIXER_WIDGETS(ASP2TX2, "ASP2TX2"), +CS48L32_MIXER_WIDGETS(ASP2TX3, "ASP2TX3"), +CS48L32_MIXER_WIDGETS(ASP2TX4, "ASP2TX4"), + +CS48L32_MUX_WIDGETS(ISRC1DEC1, "ISRC1DEC1"), +CS48L32_MUX_WIDGETS(ISRC1DEC2, "ISRC1DEC2"), +CS48L32_MUX_WIDGETS(ISRC1DEC3, "ISRC1DEC3"), +CS48L32_MUX_WIDGETS(ISRC1DEC4, "ISRC1DEC4"), + +CS48L32_MUX_WIDGETS(ISRC1INT1, "ISRC1INT1"), +CS48L32_MUX_WIDGETS(ISRC1INT2, "ISRC1INT2"), +CS48L32_MUX_WIDGETS(ISRC1INT3, "ISRC1INT3"), +CS48L32_MUX_WIDGETS(ISRC1INT4, "ISRC1INT4"), + +CS48L32_MUX_WIDGETS(ISRC2DEC1, "ISRC2DEC1"), +CS48L32_MUX_WIDGETS(ISRC2DEC2, "ISRC2DEC2"), + +CS48L32_MUX_WIDGETS(ISRC2INT1, "ISRC2INT1"), +CS48L32_MUX_WIDGETS(ISRC2INT2, "ISRC2INT2"), + +CS48L32_MUX_WIDGETS(ISRC3DEC1, "ISRC3DEC1"), +CS48L32_MUX_WIDGETS(ISRC3DEC2, "ISRC3DEC2"), + +CS48L32_MUX_WIDGETS(ISRC3INT1, "ISRC3INT1"), +CS48L32_MUX_WIDGETS(ISRC3INT2, "ISRC3INT2"), + +CS48L32_MIXER_WIDGETS(DSP1RX1, "DSP1RX1"), +CS48L32_MIXER_WIDGETS(DSP1RX2, "DSP1RX2"), +CS48L32_MIXER_WIDGETS(DSP1RX3, "DSP1RX3"), +CS48L32_MIXER_WIDGETS(DSP1RX4, "DSP1RX4"), +CS48L32_MIXER_WIDGETS(DSP1RX5, "DSP1RX5"), +CS48L32_MIXER_WIDGETS(DSP1RX6, "DSP1RX6"), +CS48L32_MIXER_WIDGETS(DSP1RX7, "DSP1RX7"), +CS48L32_MIXER_WIDGETS(DSP1RX8, "DSP1RX8"), + +SND_SOC_DAPM_SWITCH("DSP1 Trigger Output", SND_SOC_NOPM, 0, 0, + &cs48l32_dsp_trigger_output_mux[0]), + +SND_SOC_DAPM_OUTPUT("AUXPDM1_CLK"), +SND_SOC_DAPM_OUTPUT("AUXPDM1_DOUT"), +SND_SOC_DAPM_OUTPUT("AUXPDM2_CLK"), +SND_SOC_DAPM_OUTPUT("AUXPDM2_DOUT"), + +SND_SOC_DAPM_OUTPUT("MICSUPP"), + +SND_SOC_DAPM_OUTPUT("Ultrasonic Dummy Output"), +}; + +#define CS48L32_MIXER_INPUT_ROUTES(name) \ + { name, "Tone Generator 1", "Tone Generator 1" }, \ + { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "Noise Generator", "Noise Generator" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2L PGA" }, \ + { name, "IN2R", "IN2R PGA" }, \ + { name, "ASP1RX1", "ASP1RX1" }, \ + { name, "ASP1RX2", "ASP1RX2" }, \ + { name, "ASP1RX3", "ASP1RX3" }, \ + { name, "ASP1RX4", "ASP1RX4" }, \ + { name, "ASP1RX5", "ASP1RX5" }, \ + { name, "ASP1RX6", "ASP1RX6" }, \ + { name, "ASP1RX7", "ASP1RX7" }, \ + { name, "ASP1RX8", "ASP1RX8" }, \ + { name, "ASP2RX1", "ASP2RX1" }, \ + { name, "ASP2RX2", "ASP2RX2" }, \ + { name, "ASP2RX3", "ASP2RX3" }, \ + { name, "ASP2RX4", "ASP2RX4" }, \ + { name, "ISRC1DEC1", "ISRC1DEC1" }, \ + { name, "ISRC1DEC2", "ISRC1DEC2" }, \ + { name, "ISRC1DEC3", "ISRC1DEC3" }, \ + { name, "ISRC1DEC4", "ISRC1DEC4" }, \ + { name, "ISRC1INT1", "ISRC1INT1" }, \ + { name, "ISRC1INT2", "ISRC1INT2" }, \ + { name, "ISRC1INT3", "ISRC1INT3" }, \ + { name, "ISRC1INT4", "ISRC1INT4" }, \ + { name, "ISRC2DEC1", "ISRC2DEC1" }, \ + { name, "ISRC2DEC2", "ISRC2DEC2" }, \ + { name, "ISRC2INT1", "ISRC2INT1" }, \ + { name, "ISRC2INT2", "ISRC2INT2" }, \ + { name, "ISRC3DEC1", "ISRC3DEC1" }, \ + { name, "ISRC3DEC2", "ISRC3DEC2" }, \ + { name, "ISRC3INT1", "ISRC3INT1" }, \ + { name, "ISRC3INT2", "ISRC3INT2" }, \ + { name, "EQ1", "EQ1" }, \ + { name, "EQ2", "EQ2" }, \ + { name, "EQ3", "EQ3" }, \ + { name, "EQ4", "EQ4" }, \ + { name, "DRC1L", "DRC1L" }, \ + { name, "DRC1R", "DRC1R" }, \ + { name, "DRC2L", "DRC2L" }, \ + { name, "DRC2R", "DRC2R" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" }, \ + { name, "LHPF3", "LHPF3" }, \ + { name, "LHPF4", "LHPF4" }, \ + { name, "Ultrasonic 1", "Ultrasonic 1" }, \ + { name, "Ultrasonic 2", "Ultrasonic 2" }, \ + { name, "DSP1.1", "DSP1" }, \ + { name, "DSP1.2", "DSP1" }, \ + { name, "DSP1.3", "DSP1" }, \ + { name, "DSP1.4", "DSP1" }, \ + { name, "DSP1.5", "DSP1" }, \ + { name, "DSP1.6", "DSP1" }, \ + { name, "DSP1.7", "DSP1" }, \ + { name, "DSP1.8", "DSP1" } + +static const struct snd_soc_dapm_route cs48l32_dapm_routes[] = { + { "OPCLK", NULL, "SYSCLK" }, + + { "IN1LN_1", NULL, "SYSCLK" }, + { "IN1LN_2", NULL, "SYSCLK" }, + { "IN1LP_1", NULL, "SYSCLK" }, + { "IN1LP_2", NULL, "SYSCLK" }, + { "IN1RN_1", NULL, "SYSCLK" }, + { "IN1RN_2", NULL, "SYSCLK" }, + { "IN1RP_1", NULL, "SYSCLK" }, + { "IN1RP_2", NULL, "SYSCLK" }, + + { "IN1_PDMCLK", NULL, "SYSCLK" }, + { "IN1_PDMDATA", NULL, "SYSCLK" }, + { "IN2_PDMCLK", NULL, "SYSCLK" }, + { "IN2_PDMDATA", NULL, "SYSCLK" }, + + { "DSP1 Preloader", NULL, "DSP1MEM" }, + { "DSP1", NULL, "DSP1FREQ" }, + + { "Audio Trace DSP", NULL, "DSP1" }, + { "Voice Ctrl DSP", NULL, "DSP1" }, + + { "MICBIAS1", NULL, "VOUT_MIC" }, + + { "MICBIAS1A", NULL, "MICBIAS1" }, + { "MICBIAS1B", NULL, "MICBIAS1" }, + { "MICBIAS1C", NULL, "MICBIAS1" }, + + { "Tone Generator 1", NULL, "SYSCLK" }, + { "Tone Generator 2", NULL, "SYSCLK" }, + { "Noise Generator", NULL, "SYSCLK" }, + + { "Tone Generator 1", NULL, "TONE" }, + { "Tone Generator 2", NULL, "TONE" }, + { "Noise Generator", NULL, "NOISE" }, + + { "ASP1 Capture", NULL, "ASP1TX1" }, + { "ASP1 Capture", NULL, "ASP1TX2" }, + { "ASP1 Capture", NULL, "ASP1TX3" }, + { "ASP1 Capture", NULL, "ASP1TX4" }, + { "ASP1 Capture", NULL, "ASP1TX5" }, + { "ASP1 Capture", NULL, "ASP1TX6" }, + { "ASP1 Capture", NULL, "ASP1TX7" }, + { "ASP1 Capture", NULL, "ASP1TX8" }, + + { "ASP1RX1", NULL, "ASP1 Playback" }, + { "ASP1RX2", NULL, "ASP1 Playback" }, + { "ASP1RX3", NULL, "ASP1 Playback" }, + { "ASP1RX4", NULL, "ASP1 Playback" }, + { "ASP1RX5", NULL, "ASP1 Playback" }, + { "ASP1RX6", NULL, "ASP1 Playback" }, + { "ASP1RX7", NULL, "ASP1 Playback" }, + { "ASP1RX8", NULL, "ASP1 Playback" }, + + { "ASP2 Capture", NULL, "ASP2TX1" }, + { "ASP2 Capture", NULL, "ASP2TX2" }, + { "ASP2 Capture", NULL, "ASP2TX3" }, + { "ASP2 Capture", NULL, "ASP2TX4" }, + + { "ASP2RX1", NULL, "ASP2 Playback" }, + { "ASP2RX2", NULL, "ASP2 Playback" }, + { "ASP2RX3", NULL, "ASP2 Playback" }, + { "ASP2RX4", NULL, "ASP2 Playback" }, + + { "ASP1 Playback", NULL, "SYSCLK" }, + { "ASP2 Playback", NULL, "SYSCLK" }, + + { "ASP1 Capture", NULL, "SYSCLK" }, + { "ASP2 Capture", NULL, "SYSCLK" }, + + { "IN1L Mux", "Analog 1", "IN1LN_1" }, + { "IN1L Mux", "Analog 2", "IN1LN_2" }, + { "IN1L Mux", "Analog 1", "IN1LP_1" }, + { "IN1L Mux", "Analog 2", "IN1LP_2" }, + { "IN1R Mux", "Analog 1", "IN1RN_1" }, + { "IN1R Mux", "Analog 2", "IN1RN_2" }, + { "IN1R Mux", "Analog 1", "IN1RP_1" }, + { "IN1R Mux", "Analog 2", "IN1RP_2" }, + + { "IN1L PGA", NULL, "IN1L Mode" }, + { "IN1R PGA", NULL, "IN1R Mode" }, + + { "IN1L Mode", "Analog", "IN1L Mux" }, + { "IN1R Mode", "Analog", "IN1R Mux" }, + + { "IN1L Mode", "Digital", "IN1_PDMCLK" }, + { "IN1L Mode", "Digital", "IN1_PDMDATA" }, + { "IN1R Mode", "Digital", "IN1_PDMCLK" }, + { "IN1R Mode", "Digital", "IN1_PDMDATA" }, + + { "IN1L PGA", NULL, "VOUT_MIC" }, + { "IN1R PGA", NULL, "VOUT_MIC" }, + + { "IN2L PGA", NULL, "VOUT_MIC" }, + { "IN2R PGA", NULL, "VOUT_MIC" }, + + { "IN2L PGA", NULL, "IN2_PDMCLK" }, + { "IN2R PGA", NULL, "IN2_PDMCLK" }, + { "IN2L PGA", NULL, "IN2_PDMDATA" }, + { "IN2R PGA", NULL, "IN2_PDMDATA" }, + + { "Ultrasonic 1", NULL, "Ultrasonic 1 Input" }, + { "Ultrasonic 2", NULL, "Ultrasonic 2 Input" }, + + { "Ultrasonic 1 Input", "IN1L", "IN1L PGA" }, + { "Ultrasonic 1 Input", "IN1R", "IN1R PGA" }, + { "Ultrasonic 1 Input", "IN2L", "IN2L PGA" }, + { "Ultrasonic 1 Input", "IN2R", "IN2R PGA" }, + + { "Ultrasonic 2 Input", "IN1L", "IN1L PGA" }, + { "Ultrasonic 2 Input", "IN1R", "IN1R PGA" }, + { "Ultrasonic 2 Input", "IN2L", "IN2L PGA" }, + { "Ultrasonic 2 Input", "IN2R", "IN2R PGA" }, + + { "Ultrasonic 1 Activity Detect", "Switch", "Ultrasonic 1 Input" }, + { "Ultrasonic 2 Activity Detect", "Switch", "Ultrasonic 2 Input" }, + + { "Ultrasonic Dummy Output", NULL, "Ultrasonic 1 Activity Detect" }, + { "Ultrasonic Dummy Output", NULL, "Ultrasonic 2 Activity Detect" }, + + CS48L32_MIXER_ROUTES("ASP1TX1", "ASP1TX1"), + CS48L32_MIXER_ROUTES("ASP1TX2", "ASP1TX2"), + CS48L32_MIXER_ROUTES("ASP1TX3", "ASP1TX3"), + CS48L32_MIXER_ROUTES("ASP1TX4", "ASP1TX4"), + CS48L32_MIXER_ROUTES("ASP1TX5", "ASP1TX5"), + CS48L32_MIXER_ROUTES("ASP1TX6", "ASP1TX6"), + CS48L32_MIXER_ROUTES("ASP1TX7", "ASP1TX7"), + CS48L32_MIXER_ROUTES("ASP1TX8", "ASP1TX8"), + + CS48L32_MIXER_ROUTES("ASP2TX1", "ASP2TX1"), + CS48L32_MIXER_ROUTES("ASP2TX2", "ASP2TX2"), + CS48L32_MIXER_ROUTES("ASP2TX3", "ASP2TX3"), + CS48L32_MIXER_ROUTES("ASP2TX4", "ASP2TX4"), + + CS48L32_MIXER_ROUTES("EQ1", "EQ1"), + CS48L32_MIXER_ROUTES("EQ2", "EQ2"), + CS48L32_MIXER_ROUTES("EQ3", "EQ3"), + CS48L32_MIXER_ROUTES("EQ4", "EQ4"), + + CS48L32_MIXER_ROUTES("DRC1L", "DRC1L"), + CS48L32_MIXER_ROUTES("DRC1R", "DRC1R"), + CS48L32_MIXER_ROUTES("DRC2L", "DRC2L"), + CS48L32_MIXER_ROUTES("DRC2R", "DRC2R"), + + CS48L32_MIXER_ROUTES("LHPF1", "LHPF1"), + CS48L32_MIXER_ROUTES("LHPF2", "LHPF2"), + CS48L32_MIXER_ROUTES("LHPF3", "LHPF3"), + CS48L32_MIXER_ROUTES("LHPF4", "LHPF4"), + + CS48L32_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), + CS48L32_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), + CS48L32_MUX_ROUTES("ISRC1INT3", "ISRC1INT3"), + CS48L32_MUX_ROUTES("ISRC1INT4", "ISRC1INT4"), + + CS48L32_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), + CS48L32_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), + CS48L32_MUX_ROUTES("ISRC1DEC3", "ISRC1DEC3"), + CS48L32_MUX_ROUTES("ISRC1DEC4", "ISRC1DEC4"), + + CS48L32_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), + CS48L32_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), + + CS48L32_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), + CS48L32_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), + + CS48L32_MUX_ROUTES("ISRC3INT1", "ISRC3INT1"), + CS48L32_MUX_ROUTES("ISRC3INT2", "ISRC3INT2"), + + CS48L32_MUX_ROUTES("ISRC3DEC1", "ISRC3DEC1"), + CS48L32_MUX_ROUTES("ISRC3DEC2", "ISRC3DEC2"), + + CS48L32_DSP_ROUTES_1_8_SYSCLK("DSP1"), + + { "DSP Trigger Out", NULL, "DSP1 Trigger Output" }, + + { "DSP1 Trigger Output", "Switch", "DSP1" }, + + { "AUXPDM1 Analog Input", "IN1L", "IN1L PGA" }, + { "AUXPDM1 Analog Input", "IN1R", "IN1R PGA" }, + + { "AUXPDM2 Analog Input", "IN1L", "IN1L PGA" }, + { "AUXPDM2 Analog Input", "IN1R", "IN1R PGA" }, + + { "AUXPDM1 Input", "Analog", "AUXPDM1 Analog Input" }, + { "AUXPDM1 Input", "IN1 Digital", "IN1L PGA" }, + { "AUXPDM1 Input", "IN1 Digital", "IN1R PGA" }, + { "AUXPDM1 Input", "IN2 Digital", "IN2L PGA" }, + { "AUXPDM1 Input", "IN2 Digital", "IN2R PGA" }, + + { "AUXPDM2 Input", "Analog", "AUXPDM2 Analog Input" }, + { "AUXPDM2 Input", "IN1 Digital", "IN1L PGA" }, + { "AUXPDM2 Input", "IN1 Digital", "IN1R PGA" }, + { "AUXPDM2 Input", "IN2 Digital", "IN2L PGA" }, + { "AUXPDM2 Input", "IN2 Digital", "IN2R PGA" }, + + { "AUXPDM1 Output", "Switch", "AUXPDM1 Input" }, + { "AUXPDM1_CLK", NULL, "AUXPDM1 Output" }, + { "AUXPDM1_DOUT", NULL, "AUXPDM1 Output" }, + + { "AUXPDM2 Output", "Switch", "AUXPDM2 Input" }, + { "AUXPDM2_CLK", NULL, "AUXPDM2 Output" }, + { "AUXPDM2_DOUT", NULL, "AUXPDM2 Output" }, + + { "MICSUPP", NULL, "SYSCLK" }, + + { "DRC1 Signal Activity", NULL, "DRC1 Activity Output" }, + { "DRC2 Signal Activity", NULL, "DRC2 Activity Output" }, + { "DRC1 Activity Output", "Switch", "DRC1L" }, + { "DRC1 Activity Output", "Switch", "DRC1R" }, + { "DRC2 Activity Output", "Switch", "DRC2L" }, + { "DRC2 Activity Output", "Switch", "DRC2R" }, +}; + +static struct snd_soc_dai_driver cs48l32_dai[] = { + { + .name = "cs48l32-asp1", + .id = 1, + .base = CS48L32_ASP1_ENABLES1, + .playback = { + .stream_name = "ASP1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + .capture = { + .stream_name = "ASP1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + .ops = &cs48l32_dai_ops, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, + }, + { + .name = "cs48l32-asp2", + .id = 2, + .base = CS48L32_ASP2_ENABLES1, + .playback = { + .stream_name = "ASP2 Playback", + .channels_min = 1, + .channels_max = 4, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + .capture = { + .stream_name = "ASP2 Capture", + .channels_min = 1, + .channels_max = 4, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + .ops = &cs48l32_dai_ops, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, + }, + { + .name = "cs48l32-cpu-trace", + .id = 3, + .capture = { + .stream_name = "Audio Trace CPU", + .channels_min = 1, + .channels_max = 8, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + .compress_new = &snd_soc_new_compress, + }, + { + .name = "cs48l32-dsp-trace", + .id = 4, + .capture = { + .stream_name = "Audio Trace DSP", + .channels_min = 1, + .channels_max = 8, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + }, + { + .name = "cs48l32-cpu-voicectrl", + .id = 5, + .capture = { + .stream_name = "Voice Ctrl CPU", + .channels_min = 1, + .channels_max = 8, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + .compress_new = &snd_soc_new_compress, + }, + { + .name = "cs48l32-dsp-voicectrl", + .id = 6, + .capture = { + .stream_name = "Voice Ctrl DSP", + .channels_min = 1, + .channels_max = 8, + .rates = CS48L32_RATES, + .formats = CS48L32_FORMATS, + }, + }, +}; + +static int cs48l32_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + + if (strcmp(asoc_rtd_to_codec(rtd, 0)->name, "cs48l32-dsp-trace") && + strcmp(asoc_rtd_to_codec(rtd, 0)->name, "cs48l32-dsp-voicectrl")) { + dev_err(cs48l32->dev, "No suitable compressed stream for DAI '%s'\n", + asoc_rtd_to_codec(rtd, 0)->name); + return -EINVAL; + } + + return wm_adsp_compr_open(&cs48l32->dsp, stream); +} + +static int cs48l32_component_probe(struct snd_soc_component *component) +{ + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + int ret; + + snd_soc_component_init_regmap(component, cs48l32->mfd->regmap); + cs48l32->mfd->dapm = snd_soc_component_get_dapm(component); + + ret = cs48l32_init_inputs(component); + if (ret) + return ret; + + ret = cs48l32_init_eq(cs48l32); + if (ret) + return ret; + + wm_adsp2_component_probe(&cs48l32->dsp, component); + + return 0; +} + +static void cs48l32_component_remove(struct snd_soc_component *component) +{ + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + + wm_adsp2_component_remove(&cs48l32->dsp, component); + cs48l32->mfd->dapm = NULL; +} + +static int cs48l32_set_fll(struct snd_soc_component *component, int fll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct cs48l32 *cs48l32 = snd_soc_component_get_drvdata(component); + + switch (fll_id) { + case CS48L32_FLL1_REFCLK: + break; + default: + return -EINVAL; + } + + return cs48l32_fllhj_set_refclk(&cs48l32->fll, source, fref, fout); +} + +static const struct snd_compress_ops cs48l32_compress_ops = { + .open = &cs48l32_compr_open, + .free = &wm_adsp_compr_free, + .set_params = &wm_adsp_compr_set_params, + .get_caps = &wm_adsp_compr_get_caps, + .trigger = &wm_adsp_compr_trigger, + .pointer = &wm_adsp_compr_pointer, + .copy = &wm_adsp_compr_copy, +}; + +static const struct snd_soc_component_driver soc_component_dev_cs48l32 = { + .probe = &cs48l32_component_probe, + .remove = &cs48l32_component_remove, + .set_sysclk = &cs48l32_set_sysclk, + .set_pll = &cs48l32_set_fll, + .name = "cs48l32-codec", + .compress_ops = &cs48l32_compress_ops, + .controls = cs48l32_snd_controls, + .num_controls = ARRAY_SIZE(cs48l32_snd_controls), + .dapm_widgets = cs48l32_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs48l32_dapm_widgets), + .dapm_routes = cs48l32_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs48l32_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct { + int id; + bool wake_source; + const char * const name; + irq_handler_t handler; +} cs48l32_interrupts[] = { + { CS48L32_IRQ_US1_ACT_DET_RISE, false, "US1 activity", cs48l32_us1_activity }, + { CS48L32_IRQ_US2_ACT_DET_RISE, false, "US2 activity", cs48l32_us2_activity }, + { CS48L32_IRQ_DSP1_IRQ0, true, "DSP1 Buffer IRQ", cs48l32_dsp1_irq }, + { CS48L32_IRQ_DSP1_MPU_ERR, false, "DSP1 MPU", wm_halo_bus_error }, + { CS48L32_IRQ_DSP1_WDT_EXPIRE, false, "DSP1 WDT", wm_halo_wdt_expire }, +}; + +static void cs48l32_free_interrupts(struct cs48l32 *cs48l32, int num) +{ + for (--num; num >= 0; --num) { + cs48l32_free_irq(cs48l32->mfd, cs48l32_interrupts[num].id, cs48l32); + if (cs48l32_interrupts[num].wake_source) + cs48l32_set_irq_wake(cs48l32->mfd, cs48l32_interrupts[num].id, 0); + } +} + +static int cs48l32_request_interrupts(struct cs48l32 *cs48l32) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(cs48l32_interrupts); ++i) { + ret = cs48l32_request_irq(cs48l32->mfd, + cs48l32_interrupts[i].id, + cs48l32_interrupts[i].name, + cs48l32_interrupts[i].handler, + cs48l32); + if (ret != 0) + goto err; + + if (cs48l32_interrupts[i].wake_source) { + ret = cs48l32_set_irq_wake(cs48l32->mfd, cs48l32_interrupts[i].id, 1); + if (ret) + dev_warn(cs48l32->dev, "Failed to set %s wake: %d\n", + cs48l32_interrupts[i].name, ret); + } + } + + return 0; +err: + dev_err_probe(cs48l32->dev, ret, "Failed to get %s IRQ\n", cs48l32_interrupts[i].name); + cs48l32_free_interrupts(cs48l32, i); + + return ret; +} + +static int cs48l32_probe(struct platform_device *pdev) +{ + struct cs48l32_mfd *mfd = dev_get_drvdata(pdev->dev.parent); + struct cs48l32 *cs48l32; + struct wm_adsp *dsp; + int i, ret; + + BUILD_BUG_ON(offsetof(struct cs48l32, dsp) != 0); + BUILD_BUG_ON(ARRAY_SIZE(cs48l32_dai) > CS48L32_MAX_DAI); + + /* quick exit if irqchip driver hasn't completed probe */ + if (!mfd->irq_dev) { + dev_dbg(&pdev->dev, "irqchip driver not ready\n"); + return -EPROBE_DEFER; + } + + cs48l32 = devm_kzalloc(&pdev->dev, sizeof(struct cs48l32), GFP_KERNEL); + if (!cs48l32) + return -ENOMEM; + + platform_set_drvdata(pdev, cs48l32); + pdev->dev.of_node = of_node_get(mfd->dev->of_node); + + cs48l32->mfd = mfd; + cs48l32->dev = &pdev->dev; + cs48l32->max_analogue_inputs = 1; + cs48l32->max_pdm_sup = 2; + cs48l32->in_vu_reg = CS48L32_INPUT_CONTROL3; + + ret = cs48l32_core_init(cs48l32); + if (ret) + return ret; + + ret = cs48l32_request_interrupts(cs48l32); + if (ret) + goto err; + + dsp = &cs48l32->dsp; + + switch (mfd->part) { + case 0x31: + dsp->part = "cs48l31"; + break; + case 0x32: + dsp->part = "cs48l32"; + break; + case 0x33: + dsp->part = "cs48l33"; + break; + default: + ret = -EINVAL; + goto err; + } + + dsp->cs_dsp.num = 1; + dsp->cs_dsp.type = WMFW_HALO; + dsp->cs_dsp.rev = 0; + dsp->cs_dsp.dev = mfd->dev; + dsp->cs_dsp.regmap = mfd->regmap; + + dsp->cs_dsp.base = CS48L32_DSP1_CLOCK_FREQ; + dsp->cs_dsp.base_sysinfo = CS48L32_DSP1_SYS_INFO_ID; + + dsp->cs_dsp.mem = cs48l32_dsp1_regions; + dsp->cs_dsp.num_mems = ARRAY_SIZE(cs48l32_dsp1_regions); + dsp->pre_run = cs48l32_dsp_pre_run; + + ret = wm_halo_init(dsp); + if (ret != 0) + goto err; + + cs48l32->fll.cs48l32 = cs48l32; + cs48l32->fll.id = 1; + cs48l32->fll.base = CS48L32_FLL1_CONTROL1; + cs48l32->fll.sts_addr = CS48L32_IRQ1_STS_6; + cs48l32->fll.sts_mask = CS48L32_FLL1_LOCK_STS1_MASK; + cs48l32->fll.has_lp = 1; + cs48l32_init_fll(&cs48l32->fll); + + for (i = 0; i < ARRAY_SIZE(cs48l32_dai); i++) + cs48l32_init_dai(cs48l32, i); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_cs48l32, + cs48l32_dai, + ARRAY_SIZE(cs48l32_dai)); + if (ret < 0) { + dev_err_probe(&pdev->dev, ret, "Failed to register component\n"); + goto err_dsp; + } + + return ret; +err_dsp: + wm_adsp2_remove(&cs48l32->dsp); +err: + cs48l32_core_destroy(cs48l32); + + return ret; +} + +static int cs48l32_remove(struct platform_device *pdev) +{ + struct cs48l32 *cs48l32 = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + cs48l32_free_interrupts(cs48l32, ARRAY_SIZE(cs48l32_interrupts)); + wm_adsp2_remove(&cs48l32->dsp); + + cs48l32_core_destroy(cs48l32); + + return 0; +} + +static struct platform_driver cs48l32_codec_driver = { + .driver = { + .name = "cs48l32-codec", + }, + .probe = &cs48l32_probe, + .remove = &cs48l32_remove, +}; + +module_platform_driver(cs48l32_codec_driver); + +MODULE_SOFTDEP("pre: cs48l32 irq-cirrus-cs48l32 arizona-micsupp"); +MODULE_DESCRIPTION("ASoC CS48L32 driver"); +MODULE_AUTHOR("Stuart Henderson stuarth@opensource.cirrus.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cs48l32-codec"); diff --git a/sound/soc/codecs/cs48l32.h b/sound/soc/codecs/cs48l32.h new file mode 100644 index 000000000000..7cbd85077902 --- /dev/null +++ b/sound/soc/codecs/cs48l32.h @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Cirrus Logic CS48L32 codec core support + * + * Copyright (C) 2016-2018, 2022 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef ASOC_CS48L32_H +#define ASOC_CS48L32_H + +#include <linux/mfd/cs48l32/core.h> +#include <sound/cs48l32.h> +#include <sound/soc.h> + +#include "wm_adsp.h" + +#define CS48L32_PDMCLK_SRC_IN1_PDMCLK 0x0 +#define CS48L32_PDMCLK_SRC_IN2_PDMCLK 0x1 +#define CS48L32_PDMCLK_SRC_IN3_PDMCLK 0x2 +#define CS48L32_PDMCLK_SRC_IN4_PDMCLK 0x3 +#define CS48L32_PDMCLK_SRC_AUXPDM1_CLK 0x8 +#define CS48L32_PDMCLK_SRC_AUXPDM2_CLK 0x9 + +#define CS48L32_MAX_DAI 12 +#define CS48L32_MAX_INPUT 4 +#define CS48L32_MAX_MUXED_IN_CHANNELS 4 +#define CS48L32_MAX_ASP 2 + +#define CS48L32_NUM_MIXER_INPUTS 58 + +#define CS48L32_EQ_BLOCK_SZ 60 +#define CS48L32_N_EQ_BLOCKS 4 + +#define CS48L32_DSP_N_RX_CHANNELS 8 +#define CS48L32_DSP_N_TX_CHANNELS 8 + +#define CS48L32_SAMPLE_RATE_ENUM_SIZE 17 + +#define CS48L32_MIXER_CONTROLS(name, base) \ + SOC_SINGLE_RANGE_TLV(name " Input 1 Volume", base, \ + CS48L32_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + cs48l32_mixer_tlv), \ + SOC_SINGLE_RANGE_TLV(name " Input 2 Volume", base + 4, \ + CS48L32_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + cs48l32_mixer_tlv), \ + SOC_SINGLE_RANGE_TLV(name " Input 3 Volume", base + 8, \ + CS48L32_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + cs48l32_mixer_tlv), \ + SOC_SINGLE_RANGE_TLV(name " Input 4 Volume", base + 12, \ + CS48L32_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + cs48l32_mixer_tlv) + +#define CS48L32_MUX_ENUM_DECL(name, reg) \ + SOC_VALUE_ENUM_SINGLE_DECL( \ + name, reg, 0, CS48L32_MIXER_SRC_MASK, \ + cs48l32_mixer_texts, cs48l32_mixer_values) + +#define CS48L32_MUX_CTL_DECL(name) \ + const struct snd_kcontrol_new name##_mux = SOC_DAPM_ENUM("Route", name##_enum) + +#define CS48L32_MUX_ENUMS(name, base_reg) \ + static CS48L32_MUX_ENUM_DECL(name##_enum, base_reg); \ + static CS48L32_MUX_CTL_DECL(name) + +#define CS48L32_MIXER_ENUMS(name, base_reg) \ + CS48L32_MUX_ENUMS(name##_in1, base_reg); \ + CS48L32_MUX_ENUMS(name##_in2, base_reg + 4); \ + CS48L32_MUX_ENUMS(name##_in3, base_reg + 8); \ + CS48L32_MUX_ENUMS(name##_in4, base_reg + 12) + +#define CS48L32_MUX(name, ctrl) SND_SOC_DAPM_MUX(name, SND_SOC_NOPM, 0, 0, ctrl) + +#define CS48L32_MUX_WIDGETS(name, name_str) CS48L32_MUX(name_str " Input 1", &name##_mux) + +#define CS48L32_MIXER_WIDGETS(name, name_str) \ + CS48L32_MUX(name_str " Input 1", &name##_in1_mux), \ + CS48L32_MUX(name_str " Input 2", &name##_in2_mux), \ + CS48L32_MUX(name_str " Input 3", &name##_in3_mux), \ + CS48L32_MUX(name_str " Input 4", &name##_in4_mux), \ + SND_SOC_DAPM_MIXER(name_str " Mixer", SND_SOC_NOPM, 0, 0, NULL, 0) + +#define CS48L32_MUX_ROUTES(widget, name) \ + { widget, NULL, name " Input 1" }, \ + CS48L32_MIXER_INPUT_ROUTES(name " Input 1") + +#define CS48L32_MIXER_ROUTES(widget, name) \ + { widget, NULL, name " Mixer" }, \ + { name " Mixer", NULL, name " Input 1" }, \ + { name " Mixer", NULL, name " Input 2" }, \ + { name " Mixer", NULL, name " Input 3" }, \ + { name " Mixer", NULL, name " Input 4" }, \ + CS48L32_MIXER_INPUT_ROUTES(name " Input 1"), \ + CS48L32_MIXER_INPUT_ROUTES(name " Input 2"), \ + CS48L32_MIXER_INPUT_ROUTES(name " Input 3"), \ + CS48L32_MIXER_INPUT_ROUTES(name " Input 4") + +#define CS48L32_DSP_ROUTES_1_8_SYSCLK(name) \ + { name, NULL, name " Preloader" }, \ + { name, NULL, "SYSCLK" }, \ + { name " Preload", NULL, name " Preloader" }, \ + CS48L32_MIXER_ROUTES(name, name "RX1"), \ + CS48L32_MIXER_ROUTES(name, name "RX2"), \ + CS48L32_MIXER_ROUTES(name, name "RX3"), \ + CS48L32_MIXER_ROUTES(name, name "RX4"), \ + CS48L32_MIXER_ROUTES(name, name "RX5"), \ + CS48L32_MIXER_ROUTES(name, name "RX6"), \ + CS48L32_MIXER_ROUTES(name, name "RX7"), \ + CS48L32_MIXER_ROUTES(name, name "RX8") \ + +#define CS48L32_DSP_ROUTES_1_8(name) \ + { name, NULL, "DSPCLK" }, \ + CS48L32_DSP_ROUTES_1_8_SYSCLK(name) \ + +#define CS48L32_RATE_CONTROL(name, domain) SOC_ENUM(name, cs48l32_sample_rate[(domain) - 1]) + +#define CS48L32_RATE_ENUM(name, enum) \ + SOC_ENUM_EXT(name, enum, snd_soc_get_enum_double, cs48l32_rate_put) + +#define CS48L32_DSP_RATE_CONTROL(name, num) \ + SOC_ENUM_EXT(name " Rate", cs48l32_dsp_rate_enum[num], \ + cs48l32_dsp_rate_get, cs48l32_dsp_rate_put) + +#define CS48L32_EQ_COEFF_CONTROL(xname, xreg, xbase, xshift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = cs48l32_eq_coeff_info, .get = cs48l32_eq_coeff_get, \ + .put = cs48l32_eq_coeff_put, .private_value = \ + (unsigned long)&(struct cs48l32_eq_control) { .reg = xreg,\ + .shift = xshift, .block_base = xbase, .max = 65535 } } + +#define CS48L32_EQ_REG_NAME_PASTER(eq, band, type) \ + CS48L32_ ## eq ## _ ## band ## _ ## type + +#define CS48L32_EQ_BAND_COEFF_CONTROLS(name, band) \ + CS48L32_EQ_COEFF_CONTROL(#name " " #band " A", \ + CS48L32_EQ_REG_NAME_PASTER(name, band, COEFF1), \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND1, COEFF1), \ + 0), \ + CS48L32_EQ_COEFF_CONTROL(#name " " #band " B", \ + CS48L32_EQ_REG_NAME_PASTER(name, band, COEFF1), \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND1, COEFF1), \ + 16), \ + CS48L32_EQ_COEFF_CONTROL(#name " " #band " C", \ + CS48L32_EQ_REG_NAME_PASTER(name, band, COEFF2), \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND1, COEFF1), \ + 0), \ + CS48L32_EQ_COEFF_CONTROL(#name " " #band " PG", \ + CS48L32_EQ_REG_NAME_PASTER(name, band, PG), \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND1, COEFF1), \ + 0) + +#define CS48L32_EQ_COEFF_CONTROLS(name) \ + CS48L32_EQ_BAND_COEFF_CONTROLS(name, BAND1), \ + CS48L32_EQ_BAND_COEFF_CONTROLS(name, BAND2), \ + CS48L32_EQ_BAND_COEFF_CONTROLS(name, BAND3), \ + CS48L32_EQ_BAND_COEFF_CONTROLS(name, BAND4), \ + CS48L32_EQ_COEFF_CONTROL(#name " BAND5 A", \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND5, COEFF1), \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND1, COEFF1), \ + 0), \ + CS48L32_EQ_COEFF_CONTROL(#name " BAND5 B", \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND5, COEFF1), \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND1, COEFF1), \ + 16), \ + CS48L32_EQ_COEFF_CONTROL(#name " BAND5 PG", \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND5, PG), \ + CS48L32_EQ_REG_NAME_PASTER(name, BAND1, COEFF1), \ + 0) + +#define CS48L32_LHPF_CONTROL(xname, xbase) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ + .put = cs48l32_lhpf_coeff_put, .private_value = \ + ((unsigned long)&(struct soc_bytes) { .base = xbase, \ + .num_regs = 1 }) } + +/* these have a subseq number so they run after SYSCLK and DSPCLK widgets */ +#define CS48L32_DSP_FREQ_WIDGET_EV(name, num, event) \ + SND_SOC_DAPM_SUPPLY_S(name "FREQ", 100, SND_SOC_NOPM, num, 0, \ + event, SND_SOC_DAPM_POST_PMU) + +#define CS48L32_RATES SNDRV_PCM_RATE_KNOT + +#define CS48L32_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct cs48l32_dai_priv { + int clk; + struct snd_pcm_hw_constraint_list constraint; +}; + +struct cs48l32_dsp_power_reg_block { + unsigned int start; + unsigned int end; +}; + +struct cs48l32_dsp_power_regs { + const unsigned int *pwd; + unsigned int n_pwd; + const struct cs48l32_dsp_power_reg_block *ext; + unsigned int n_ext; +}; + +struct cs48l32; + +struct cs48l32_fll_cfg { + int n; + unsigned int theta; + unsigned int lambda; + int refdiv; + int fratio; + int gain; + int alt_gain; +}; + +struct cs48l32_fll { + struct cs48l32 *cs48l32; + int id; + unsigned int base; + + unsigned int sts_addr; + unsigned int sts_mask; + + unsigned int fout; + + int ref_src; + unsigned int ref_freq; + struct cs48l32_fll_cfg ref_cfg; + + unsigned int max_fref; + + unsigned int integer_only:1; + unsigned int has_lp:1; +}; + +struct cs48l32 { + struct wm_adsp dsp; + struct cs48l32_mfd *mfd; + struct device *dev; + int sysclk; + int dspclk; + struct cs48l32_dai_priv dai[CS48L32_MAX_DAI]; + struct cs48l32_fll fll; + + int max_analogue_inputs; + int max_pdm_sup; + int num_dmic_clksrc; + u32 max_channels_clocked[CS48L32_MAX_ASP]; + u32 pdm_sup[CS48L32_MAX_INPUT]; + u32 in_type[CS48L32_MAX_INPUT][CS48L32_MAX_MUXED_IN_CHANNELS]; + + unsigned int in_up_pending; + unsigned int in_vu_reg; + + struct mutex rate_lock; + + u8 dsp_dma_rates[CS48L32_DSP_N_RX_CHANNELS + CS48L32_DSP_N_TX_CHANNELS]; + + int tdm_width[CS48L32_MAX_ASP]; + int tdm_slots[CS48L32_MAX_ASP]; + + unsigned int eq_mode[CS48L32_N_EQ_BLOCKS]; + __be16 eq_coefficients[CS48L32_N_EQ_BLOCKS][CS48L32_EQ_BLOCK_SZ / 2]; + + const struct cs48l32_dsp_power_regs *dsp_power_regs; +}; + +struct cs48l32_enum { + struct soc_enum mixer_enum; + int val; +}; + +struct cs48l32_eq_control { + unsigned int reg; + unsigned int shift; + unsigned int block_base; + unsigned int max; +}; + +extern const char * const cs48l32_mixer_texts[CS48L32_NUM_MIXER_INPUTS]; +extern unsigned int cs48l32_mixer_values[CS48L32_NUM_MIXER_INPUTS]; + +extern const unsigned int cs48l32_ana_tlv[]; +extern const unsigned int cs48l32_eq_tlv[]; +extern const unsigned int cs48l32_digital_tlv[]; +extern const unsigned int cs48l32_noise_tlv[]; +extern const unsigned int cs48l32_mixer_tlv[]; +extern const unsigned int cs48l32_us_tlv[]; + +int cs48l32_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); + +extern const struct soc_enum cs48l32_sample_rate[]; + +extern const struct snd_kcontrol_new cs48l32_inmux[]; +extern const struct snd_kcontrol_new cs48l32_dmode_mux[]; + +extern const struct soc_enum cs48l32_us_freq[]; +extern const struct snd_kcontrol_new cs48l32_us_inmux[]; +extern const struct soc_enum cs48l32_us_output_rate[]; +extern const struct soc_enum cs48l32_us_det_lpf_cut[]; +extern const struct soc_enum cs48l32_us_det_dcy[]; +extern const struct snd_kcontrol_new cs48l32_us_switch[]; +extern const struct soc_enum cs48l32_us_det_thr[]; +extern const struct soc_enum cs48l32_us_det_num[]; +extern const struct soc_enum cs48l32_us_det_hold[]; + +extern const struct soc_enum cs48l32_in_vi_ramp; +extern const struct soc_enum cs48l32_in_vd_ramp; +extern const struct soc_enum cs48l32_in_hpf_cut_enum; +extern const struct soc_enum cs48l32_in_dmic_osr[]; + +irqreturn_t cs48l32_us1_activity(int irq, void *data); +irqreturn_t cs48l32_us2_activity(int irq, void *data); + +int cs48l32_in_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +extern const struct soc_enum cs48l32_input_rate[]; +int cs48l32_low_power_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +extern const struct soc_enum noise_gen_rate; + +extern const struct soc_enum cs48l32_auxpdm1_freq; +extern const struct soc_enum cs48l32_auxpdm2_freq; +extern const struct snd_kcontrol_new cs48l32_auxpdm_inmux[]; +extern const struct snd_kcontrol_new cs48l32_auxpdm_analog_inmux[]; +extern const struct snd_kcontrol_new cs48l32_auxpdm_switch[]; + +extern const struct soc_enum cs48l32_isrc_fsl[]; +extern const struct soc_enum cs48l32_isrc_fsh[]; + +extern const struct soc_enum cs48l32_fx_rate; + +extern const struct soc_enum cs48l32_lhpf_mode[]; + +int cs48l32_lhpf_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +extern const struct soc_enum cs48l32_eq_mode[]; +int cs48l32_eq_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int cs48l32_eq_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int cs48l32_eq_coeff_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); +int cs48l32_eq_coeff_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int cs48l32_eq_coeff_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); + +extern const struct snd_kcontrol_new cs48l32_drc_activity_output_mux[]; +extern const struct snd_kcontrol_new cs48l32_dsp_trigger_output_mux[]; +int cs48l32_dsp_rate_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int cs48l32_dsp_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +extern const struct soc_enum cs48l32_dsp_rate_enum[]; + +int cs48l32_dsp_pre_run(struct wm_adsp *dsp); +int cs48l32_dsp_memory_enable(struct cs48l32 *priv, + const struct cs48l32_dsp_power_regs *regs); +void cs48l32_dsp_memory_disable(struct cs48l32 *priv, + const struct cs48l32_dsp_power_regs *regs); +int cs48l32_dsp_freq_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +irqreturn_t cs48l32_dsp1_irq(int irq, void *data); + +int cs48l32_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir); + +extern const struct snd_soc_dai_ops cs48l32_dai_ops; + +int cs48l32_sysclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event); +int cs48l32_in_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event); +int cs48l32_in_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int cs48l32_eq_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event); + +int cs48l32_fllhj_set_refclk(struct cs48l32_fll *fll, int source, + unsigned int fin, unsigned int fout); +int cs48l32_init_fll(struct cs48l32_fll *fll); +int cs48l32_init_inputs(struct snd_soc_component *component); +int cs48l32_init_dai(struct cs48l32 *priv, int dai); +int cs48l32_init_eq(struct cs48l32 *priv); +int cs48l32_core_init(struct cs48l32 *priv); +int cs48l32_core_destroy(struct cs48l32 *priv); + +#endif
Hi Richard,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on broonie-sound/for-next] [also build test WARNING on lee-mfd/for-mfd-next linusw-pinctrl/devel linusw-pinctrl/for-next broonie-regulator/for-next linus/master] [cannot apply to lee-mfd/for-mfd-fixes] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Richard-Fitzgerald/Add-suppor... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next patch link: https://lore.kernel.org/r/20221109165331.29332-13-rf%40opensource.cirrus.com patch subject: [PATCH 12/12] ASoC: cs48l32: Add codec driver for Cirrus Logic CS48L31/32/33 config: i386-allyesconfig compiler: gcc-11 (Debian 11.3.0-8) 11.3.0 reproduce (this is a W=1 build): # https://github.com/intel-lab-lkp/linux/commit/66bc34007a7eabb0b819d76e13de3c... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Richard-Fitzgerald/Add-support-for-the-Cirrus-Logic-CS48L32-audio-codecs/20221110-005630 git checkout 66bc34007a7eabb0b819d76e13de3c33562b4525 # save the config file mkdir build_dir && cp config build_dir/.config make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash drivers/pci/controller/cadence/ sound/soc/codecs/ sound/soc/intel/boards/
If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot lkp@intel.com
All warnings (new ones prefixed by >>):
sound/soc/codecs/cs48l32-core.c:2257:8: warning: type qualifiers ignored on function return type [-Wignored-qualifiers]
2257 | static const char * const cs48l32_dai_clk_str(int clk_id) | ^~~~~
vim +2257 sound/soc/codecs/cs48l32-core.c
2256
2257 static const char * const cs48l32_dai_clk_str(int clk_id)
2258 { 2259 switch (clk_id) { 2260 case CS48L32_CLK_SYSCLK_1: 2261 case CS48L32_CLK_SYSCLK_2: 2262 case CS48L32_CLK_SYSCLK_3: 2263 case CS48L32_CLK_SYSCLK_4: 2264 return "SYSCLK"; 2265 default: 2266 return "Unknown clock"; 2267 } 2268 } 2269
On Wed, Nov 09, 2022 at 04:53:19PM +0000, Richard Fitzgerald wrote:
regulator: arizona-micsupp: Don't hardcode use of ARIZONA defines regulator: arizona-micsupp: Don't use a common regulator name ASoC: wm_adsp: Allow client to hook into pre_run callback
The regulator and ASoC bits look good. It looks like this is going to need another spin for at least the DT and irqchip bits, I think it'd make sense to take the above cleanup patches for this release even if everything else misses it to cut down on future patch volume. I'll leave it for a bit and do that unless someone has concerns, it's going to be easier than applying and sending pull requests.
On 10/11/2022 20:53, Mark Brown wrote:
On Wed, Nov 09, 2022 at 04:53:19PM +0000, Richard Fitzgerald wrote:
regulator: arizona-micsupp: Don't hardcode use of ARIZONA defines regulator: arizona-micsupp: Don't use a common regulator name ASoC: wm_adsp: Allow client to hook into pre_run callback
The regulator and ASoC bits look good. It looks like this is going to need another spin for at least the DT and irqchip bits, I think it'd make sense to take the above cleanup patches for this release even if everything else misses it to cut down on future patch volume. I'll leave it for a bit and do that unless someone has concerns, it's going to be easier than applying and sending pull requests.
Yes, I have tested that these 3 patches can apply and build on their own and don't break the older chips.
I should have put these at the start of the chain and mentioned in the cover letter that they can be taken independently.
On Wed, 9 Nov 2022 16:53:19 +0000, Richard Fitzgerald wrote:
The CS48L32 is a high-performance low-power audio DSP for smartphones and other portable audio devices. It has various digital audio I/O, a programmable Halo Core DSP, fixed-function audio processors, configurable GPIO and microphone bias regulators.
The CS48L31 and CS48L33 were derivatives of the CS48L32.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[10/12] ASoC: wm_adsp: Allow client to hook into pre_run callback commit: fe07130870c8540bc0cddbaa8d4521ecdba6b560
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (9)
-
Charles Keepax
-
kernel test robot
-
Krzysztof Kozlowski
-
Lee Jones
-
Linus Walleij
-
Marc Zyngier
-
Mark Brown
-
Richard Fitzgerald
-
Rob Herring