--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/fsl/cpm_qe/fsl,cpm1-scc-qmc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: PowerQUICC CPM QUICC Multichannel Controller (QMC)
+
+maintainers:
+ - Herve Codina <herve.codina@bootlin.com>
+
+description:
+ The QMC (QUICC Multichannel Controller) emulates up to 64 channels within one
+ serial controller using the same TDM physical interface routed from TSA.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - fsl,mpc885-scc-qmc
+ - fsl,mpc866-scc-qmc
+ - const: fsl,cpm1-scc-qmc
+
+ reg:
+ items:
+ - description: SCC (Serial communication controller) register base
+ - description: SCC parameter ram base
+ - description: Dual port ram base
+
+ reg-names:
+ items:
+ - const: scc_regs
+ - const: scc_pram
+ - const: dpram
+
+ interrupts:
+ maxItems: 1
+ description: SCC interrupt line in the CPM interrupt controller
+
+ fsl,tsa-serial:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle to TSA node
+ - enum: [1, 2, 3]
+ description: |
+ TSA serial interface (dt-bindings/soc/cpm1-fsl,tsa.h defines these
+ values)
+ - 1: SCC2
+ - 2: SCC3
+ - 3: SCC4
+ description:
+ Should be a phandle/number pair. The phandle to TSA node and the TSA
+ serial interface to use.
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+patternProperties:
+ '^channel@([0-9]|[1-5][0-9]|6[0-3])$':
+ description:
+ A channel managed by this controller
+ type: object
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 63
+ description:
+ The channel number
+
+ fsl,operational-mode:
+ $ref: /schemas/types.yaml#/definitions/string
+ enum: [transparent, hdlc]
+ default: transparent
+ description: |
+ The channel operational mode
+ - hdlc: The channel handles HDLC frames
+ - transparent: The channel handles raw data without any processing
+
+ fsl,reverse-data:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ The bit order as seen on the channels is reversed,
+ transmitting/receiving the MSB of each octet first.
+ This flag is used only in 'transparent' mode.
+
+ fsl,tx-ts-mask:
+ $ref: /schemas/types.yaml#/definitions/uint64
+ description:
+ Channel assigned Tx time-slots within the Tx time-slots routed by the
+ TSA to this cell.
+
+ fsl,rx-ts-mask:
+ $ref: /schemas/types.yaml#/definitions/uint64
+ description:
+ Channel assigned Rx time-slots within the Rx time-slots routed by the
+ TSA to this cell.
+
+ required:
+ - reg
+ - fsl,tx-ts-mask
+ - fsl,rx-ts-mask
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - fsl,tsa-serial
+ - '#address-cells'
+ - '#size-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/soc/cpm1-fsl,tsa.h>
+
+ qmc@a60 {
+ compatible = "fsl,mpc885-scc-qmc", "fsl,cpm1-scc-qmc";
+ reg = <0xa60 0x20>,
+ <0x3f00 0xc0>,
+ <0x2000 0x1000>;
+ reg-names = "scc_regs", "scc_pram", "dpram";
+ interrupts = <27>;
+ interrupt-parent = <&CPM_PIC>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ fsl,tsa-serial = <&tsa FSL_CPM_TSA_SCC4>;
+
+ channel@16 {
+ /* Ch16 : First 4 even TS from all routed from TSA */
+ reg = <16>;
+ fsl,mode = "transparent";
+ fsl,reverse-data;
+ fsl,tx-ts-mask = <0x00000000 0x000000aa>;
+ fsl,rx-ts-mask = <0x00000000 0x000000aa>;
+ };
+
+ channel@17 {
+ /* Ch17 : First 4 odd TS from all routed from TSA */
+ reg = <17>;
+ fsl,mode = "transparent";
+ fsl,reverse-data;
+ fsl,tx-ts-mask = <0x00000000 0x00000055>;
+ fsl,rx-ts-mask = <0x00000000 0x00000055>;
+ };
+
+ channel@19 {
+ /* Ch19 : 8 TS (TS 8..15) from all routed from TSA */
+ reg = <19>;
+ fsl,mode = "hdlc";
+ fsl,tx-ts-mask = <0x00000000 0x0000ff00>;
+ fsl,rx-ts-mask = <0x00000000 0x0000ff00>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/fsl/cpm_qe/fsl,cpm1-tsa.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: PowerQUICC CPM Time-slot assigner (TSA) controller
+
+maintainers:
+ - Herve Codina <herve.codina@bootlin.com>
+
+description:
+ The TSA is the time-slot assigner that can be found on some PowerQUICC SoC.
+ Its purpose is to route some TDM time-slots to other internal serial
+ controllers.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - fsl,mpc885-tsa
+ - fsl,mpc866-tsa
+ - const: fsl,cpm1-tsa
+
+ reg:
+ items:
+ - description: SI (Serial Interface) register base
+ - description: SI RAM base
+
+ reg-names:
+ items:
+ - const: si_regs
+ - const: si_ram
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+patternProperties:
+ '^tdm@[0-1]$':
+ description:
+ The TDM managed by this controller
+ type: object
+
+ additionalProperties: false
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 1
+ description:
+ The TDM number for this TDM, 0 for TDMa and 1 for TDMb
+
+ fsl,common-rxtx-pins:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ The hardware can use four dedicated pins for Tx clock, Tx sync, Rx
+ clock and Rx sync or use only two pins, Tx/Rx clock and Tx/Rx sync.
+ Without the 'fsl,common-rxtx-pins' property, the four pins are used.
+ With the 'fsl,common-rxtx-pins' property, two pins are used.
+
+ clocks:
+ minItems: 2
+ items:
+ - description: External clock connected to L1RSYNC pin
+ - description: External clock connected to L1RCLK pin
+ - description: External clock connected to L1TSYNC pin
+ - description: External clock connected to L1TCLK pin
+
+ clock-names:
+ minItems: 2
+ items:
+ - const: l1rsync
+ - const: l1rclk
+ - const: l1tsync
+ - const: l1tclk
+
+ fsl,rx-frame-sync-delay-bits:
+ enum: [0, 1, 2, 3]
+ default: 0
+ description: |
+ Receive frame sync delay in number of bits.
+ Indicates the delay between the Rx sync and the first bit of the Rx
+ frame. 0 for no bit delay. 1, 2 or 3 for 1, 2 or 3 bits delay.
+
+ fsl,tx-frame-sync-delay-bits:
+ enum: [0, 1, 2, 3]
+ default: 0
+ description: |
+ Transmit frame sync delay in number of bits.
+ Indicates the delay between the Tx sync and the first bit of the Tx
+ frame. 0 for no bit delay. 1, 2 or 3 for 1, 2 or 3 bits delay.
+
+ fsl,clock-falling-edge:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Data is sent on falling edge of the clock (and received on the rising
+ edge). If 'clock-falling-edge' is not present, data is sent on the
+ rising edge (and received on the falling edge).
+
+ fsl,fsync-rising-edge:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Frame sync pulses are sampled with the rising edge of the channel
+ clock. If 'fsync-rising-edge' is not present, pulses are sampled with
+ the falling edge.
+
+ fsl,double-speed-clock:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ The channel clock is twice the data rate.
+
+ patternProperties:
+ '^fsl,[rt]x-ts-routes$':
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ description: |
+ A list of tuple that indicates the Tx or Rx time-slots routes.
+ items:
+ items:
+ - description:
+ The number of time-slots
+ minimum: 1
+ maximum: 64
+ - description: |
+ The source (Tx) or destination (Rx) serial interface
+ (dt-bindings/soc/cpm1-fsl,tsa.h defines these values)
+ - 0: No destination
+ - 1: SCC2
+ - 2: SCC3
+ - 3: SCC4
+ - 4: SMC1
+ - 5: SMC2
+ enum: [0, 1, 2, 3, 4, 5]
+ minItems: 1
+ maxItems: 64
+
+ allOf:
+ # If fsl,common-rxtx-pins is present, only 2 clocks are needed.
+ # Else, the 4 clocks must be present.
+ - if:
+ required:
+ - fsl,common-rxtx-pins
+ then:
+ properties:
+ clocks:
+ maxItems: 2
+ clock-names:
+ maxItems: 2
+ else:
+ properties:
+ clocks:
+ minItems: 4
+ clock-names:
+ minItems: 4
+
+ required:
+ - reg
+ - clocks
+ - clock-names
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - '#address-cells'
+ - '#size-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/soc/cpm1-fsl,tsa.h>
+
+ tsa@ae0 {
+ compatible = "fsl,mpc885-tsa", "fsl,cpm1-tsa";
+ reg = <0xae0 0x10>,
+ <0xc00 0x200>;
+ reg-names = "si_regs", "si_ram";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ tdm@0 {
+ /* TDMa */
+ reg = <0>;
+
+ clocks = <&clk_l1rsynca>, <&clk_l1rclka>;
+ clock-names = "l1rsync", "l1rclk";
+
+ fsl,common-rxtx-pins;
+ fsl,fsync-rising-edge;
+
+ fsl,tx-ts-routes = <2 0>, /* TS 0..1 */
+ <24 FSL_CPM_TSA_SCC4>, /* TS 2..25 */
+ <1 0>, /* TS 26 */
+ <5 FSL_CPM_TSA_SCC3>; /* TS 27..31 */
+
+ fsl,rx-ts-routes = <2 0>, /* TS 0..1 */
+ <24 FSL_CPM_TSA_SCC4>, /* 2..25 */
+ <1 0>, /* TS 26 */
+ <5 FSL_CPM_TSA_SCC3>; /* TS 27..31 */
+ };
+ };
maxItems: 1
clock-names:
- const: "mclk"
+ const: mclk
powerdown-gpios:
description: GPIO used for hardware power-down.
+++ /dev/null
-Analog Devices ADAU1361/ADAU1461/ADAU1761/ADAU1961/ADAU1381/ADAU1781
-
-Required properties:
-
- - compatible: Should contain one of the following:
- "adi,adau1361"
- "adi,adau1461"
- "adi,adau1761"
- "adi,adau1961"
- "adi,adau1381"
- "adi,adau1781"
-
- - reg: The i2c address. Value depends on the state of ADDR0
- and ADDR1, as wired in hardware.
-
-Optional properties:
- - clock-names: If provided must be "mclk".
- - clocks: phandle + clock-specifiers for the clock that provides
- the audio master clock for the device.
-
-Examples:
-#include <dt-bindings/sound/adau17x1.h>
-
- i2c_bus {
- adau1361@38 {
- compatible = "adi,adau1761";
- reg = <0x38>;
-
- clock-names = "mclk";
- clocks = <&audio_clock>;
- };
- };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/adi,adau17x1.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADAU1361/ADAU1461/ADAU1761/ADAU1961/ADAU1381/ADAU1781 Codec
+
+maintainers:
+ - Lars-Peter Clausen <lars@metafoo.de>
+
+properties:
+ compatible:
+ enum:
+ - adi,adau1361
+ - adi,adau1381
+ - adi,adau1461
+ - adi,adau1761
+ - adi,adau1781
+ - adi,adau1961
+
+ reg:
+ maxItems: 1
+ description:
+ The i2c address. Value depends on the state of ADDR0 and ADDR1,
+ as wired in hardware.
+
+ clock-names:
+ const: mclk
+
+ clocks:
+ items:
+ - description: provides the audio master clock for the device.
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ audio-codec@38 {
+ compatible = "adi,adau1761";
+ reg = <0x38>;
+ clock-names = "mclk";
+ clocks = <&audio_clock>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/adi,max98363.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices MAX98363 SoundWire Amplifier
+
+maintainers:
+ - Ryan Lee <ryans.lee@analog.com>
+
+description:
+ The MAX98363 is a SoundWire input Class D mono amplifier that
+ supports MIPI SoundWire v1.2-compatible digital interface for
+ audio and control data.
+ SoundWire peripheral device ID of MAX98363 is 0x3*019f836300
+ where * is the peripheral device unique ID decoded from pin.
+ It supports up to 10 peripheral devices(0x0 to 0x9).
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: sdw3019f836300
+
+ reg:
+ maxItems: 1
+
+ '#sound-dai-cells':
+ const: 0
+
+required:
+ - compatible
+ - reg
+ - "#sound-dai-cells"
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ soundwire-controller@3250000 {
+ #address-cells = <2>;
+ #size-cells = <0>;
+ reg = <0x3250000 0x2000>;
+
+ speaker@0,0 {
+ compatible = "sdw3019f836300";
+ reg = <0 0>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "Speaker Left";
+ };
+
+ speaker@0,1 {
+ compatible = "sdw3019f836300";
+ reg = <0 1>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "Speaker Right";
+ };
+ };
adi,vmon-slot-no:
description: slot number of the voltage sense monitor
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 15
default: 0
adi,imon-slot-no:
description: slot number of the current sense monitor
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 15
default: 1
adi,spkfb-slot-no:
description: slot number of speaker DSP monitor
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 15
default: 2
description:
Selects the PCM data input channel that is routed to the speaker
audio processing bypass path.
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 15
default: 0
+++ /dev/null
-AK4458 audio DAC
-
-This device supports I2C mode.
-
-Required properties:
-
-- compatible : "asahi-kasei,ak4458" or "asahi-kasei,ak4497"
-- reg : The I2C address of the device for I2C
-
-Optional properties:
-- reset-gpios: A GPIO specifier for the power down & reset pin
-- mute-gpios: A GPIO specifier for the soft mute pin
-- AVDD-supply: Analog power supply
-- DVDD-supply: Digital power supply
-- dsd-path: Select DSD input pins for ak4497
- 0: select #16, #17, #19 pins
- 1: select #3, #4, #5 pins
-
-Example:
-
-&i2c {
- ak4458: dac@10 {
- compatible = "asahi-kasei,ak4458";
- reg = <0x10>;
- reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>
- mute-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>
- };
-};
+++ /dev/null
-AK5558 8 channel differential 32-bit delta-sigma ADC
-
-This device supports I2C mode only.
-
-Required properties:
-
-- compatible : "asahi-kasei,ak5558" or "asahi-kasei,ak5552".
-- reg : The I2C address of the device.
-
-Optional properties:
-
-- reset-gpios: A GPIO specifier for the power down & reset pin.
-- AVDD-supply: Analog power supply
-- DVDD-supply: Digital power supply
-
-Example:
-
-&i2c {
- ak5558: adc@10 {
- compatible = "asahi-kasei,ak5558";
- reg = <0x10>;
- reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
- };
-};
+++ /dev/null
-ALC5632 audio CODEC
-
-This device supports I2C only.
-
-Required properties:
-
- - compatible : "realtek,alc5632"
-
- - reg : the I2C address of the device.
-
- - gpio-controller : Indicates this device is a GPIO controller.
-
- - #gpio-cells : Should be two. The first cell is the pin number and the
- second cell is used to specify optional parameters (currently unused).
-
-Pins on the device (for linking into audio routes):
-
- * SPK_OUTP
- * SPK_OUTN
- * HP_OUT_L
- * HP_OUT_R
- * AUX_OUT_P
- * AUX_OUT_N
- * LINE_IN_L
- * LINE_IN_R
- * PHONE_P
- * PHONE_N
- * MIC1_P
- * MIC1_N
- * MIC2_P
- * MIC2_N
- * MICBIAS1
- * DMICDAT
-
-Example:
-
-alc5632: alc5632@1e {
- compatible = "realtek,alc5632";
- reg = <0x1a>;
-
- gpio-controller;
- #gpio-cells = <2>;
-};
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/asahi-kasei,ak4458.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AK4458 audio DAC
+
+maintainers:
+ - Shengjiu Wang <shengjiu.wang@nxp.com>
+
+properties:
+ compatible:
+ enum:
+ - asahi-kasei,ak4458
+ - asahi-kasei,ak4497
+
+ reg:
+ maxItems: 1
+
+ avdd-supply:
+ description: Analog power supply
+
+ dvdd-supply:
+ description: Digital power supply
+
+ reset-gpios:
+ maxItems: 1
+
+ mute-gpios:
+ maxItems: 1
+ description:
+ GPIO used to mute all the outputs
+
+ dsd-path:
+ description: Select DSD input pins for ak4497
+ $ref: /schemas/types.yaml#/definitions/uint32
+ oneOf:
+ - const: 0
+ description: "select #16, #17, #19 pins"
+ - const: 1
+ description: "select #3, #4, #5 pins"
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: asahi-kasei,ak4458
+
+ then:
+ properties:
+ dsd-path: false
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@10 {
+ compatible = "asahi-kasei,ak4458";
+ reg = <0x10>;
+ reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
+ mute-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/asahi-kasei,ak5558.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AK5558 8 channel differential 32-bit delta-sigma ADC
+
+maintainers:
+ - Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>
+ - Mihai Serban <mihai.serban@nxp.com>
+
+properties:
+ compatible:
+ enum:
+ - asahi-kasei,ak5552
+ - asahi-kasei,ak5558
+
+ reg:
+ maxItems: 1
+
+ avdd-supply:
+ description: A 1.8V supply that powers up the AVDD pin.
+
+ dvdd-supply:
+ description: A 1.2V supply that powers up the DVDD pin.
+
+ reset-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ ak5558: codec@10 {
+ compatible = "asahi-kasei,ak5558";
+ reg = <0x10>;
+ reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
+ };
+ };
$ref: /schemas/graph.yaml#/$defs/port-base
properties:
convert-rate:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-sample-rate"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-sample-rate
convert-channels:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-channels"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-channels
convert-sample-format:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-sample-format"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-sample-format
mclk-fs:
- $ref: "simple-card.yaml#/definitions/mclk-fs"
+ $ref: simple-card.yaml#/definitions/mclk-fs
endpoint-base:
$ref: /schemas/graph.yaml#/$defs/endpoint-base
properties:
mclk-fs:
- $ref: "simple-card.yaml#/definitions/mclk-fs"
+ $ref: simple-card.yaml#/definitions/mclk-fs
frame-inversion:
description: dai-link uses frame clock inversion
$ref: /schemas/types.yaml#/definitions/flag
description: Indicates system clock
$ref: /schemas/types.yaml#/definitions/phandle
system-clock-frequency:
- $ref: "simple-card.yaml#/definitions/system-clock-frequency"
+ $ref: simple-card.yaml#/definitions/system-clock-frequency
system-clock-direction-out:
- $ref: "simple-card.yaml#/definitions/system-clock-direction-out"
+ $ref: simple-card.yaml#/definitions/system-clock-direction-out
system-clock-fixed:
- $ref: "simple-card.yaml#/definitions/system-clock-fixed"
+ $ref: simple-card.yaml#/definitions/system-clock-fixed
dai-format:
description: audio format.
- msb
- lsb
convert-rate:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-sample-rate"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-sample-rate
convert-channels:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-channels"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-channels
convert-sample-format:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-sample-format"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-sample-format
dai-tdm-slot-num:
description: Number of slots in use.
label:
maxItems: 1
prefix:
- description: "device name prefix"
+ description: device name prefix
$ref: /schemas/types.yaml#/definitions/string
routing:
description: |
description: User specified audio sound widgets.
$ref: /schemas/types.yaml#/definitions/non-unique-string-array
convert-rate:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-sample-rate"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-sample-rate
convert-channels:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-channels"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-channels
convert-sample-format:
- $ref: "/schemas/sound/dai-params.yaml#/$defs/dai-sample-format"
+ $ref: /schemas/sound/dai-params.yaml#/$defs/dai-sample-format
pa-gpios:
maxItems: 1
boost-cap-microfarad.
External Boost must have GPIO1 as GPIO output. GPIO1 will be set high to
enable boost voltage.
+ Shared boost allows two amplifiers to share a single boost circuit by
+ communicating on the MDSYNC bus. The active amplifier controls the boost
+ circuit using combined data from both amplifiers. GPIO1 should be
+ configured for Sync when shared boost is used. Shared boost is not
+ compatible with External boost. Active amplifier requires
+ boost-peak-milliamp, boost-ind-nanohenry and boost-cap-microfarad.
0 = Internal Boost
1 = External Boost
+ 2 = Shared Boost Active
+ 3 = Shared Boost Passive
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
- maximum: 1
+ maximum: 3
cirrus,gpio1-polarity-invert:
description:
Audio serial port SDOUT Hi-Z control. Sets the Hi-Z
configuration for SDOUT pin of amplifier. Logical OR of
CS35L45_ASP_TX_HIZ_xxx values.
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 3
default: 2
+patternProperties:
+ "^cirrus,gpio-ctrl[1-3]$":
+ description:
+ GPIO pins configuration.
+ type: object
+ additionalProperties: false
+ properties:
+ gpio-dir:
+ description:
+ GPIO pin direction. Valid only when 'gpio-ctrl' is 1
+ 0 = Output
+ 1 = Input
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 1
+ default: 1
+ gpio-lvl:
+ description:
+ GPIO level. Valid only when 'gpio-ctrl' is 1 and 'gpio-dir' is 0
+ 0 = Low
+ 1 = High
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 1
+ default: 0
+ gpio-op-cfg:
+ description:
+ GPIO level. Valid only when 'gpio-ctrl' is 1 and 'gpio-dir' is 0
+ 0 = CMOS
+ 1 = Open Drain
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 1
+ default: 0
+ gpio-pol:
+ description:
+ GPIO output polarity select. Valid only when 'gpio-ctrl' is 1
+ and 'gpio-dir' is 0
+ 0 = Non-inverted, Active High
+ 1 = Inverted, Active Low
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 1
+ default: 0
+ gpio-ctrl:
+ description:
+ Defines the function of the GPIO pin.
+ GPIO1
+ 0 = High impedance input
+ 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir'
+ 2 = Pin acts as MDSYNC, direction controlled by MDSYNC
+ 3-7 = Reserved
+ GPIO2
+ 0 = High impedance input
+ 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir'
+ 2 = Pin acts as open drain INT
+ 3 = Reserved
+ 4 = Pin acts as push-pull output INT. Active low.
+ 5 = Pin acts as push-pull output INT. Active high.
+ 6,7 = Reserved
+ GPIO3
+ 0 = High impedance input
+ 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir'
+ 2-7 = Reserved
+ $ref: "/schemas/types.yaml#/definitions/uint32"
+ minimum: 0
+ maximum: 7
+ default: 0
required:
- compatible
- reg
reset-gpios = <&gpio 110 0>;
cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED |
CS35L45_ASP_TX_HIZ_DISABLED)>;
+ cirrus,gpio-ctrl1 {
+ gpio-ctrl = <0x2>;
+ };
+ cirrus,gpio-ctrl2 {
+ gpio-ctrl = <0x2>;
+ };
+ cirrus,gpio-ctrl3 {
+ gpio-ctrl = <0x1>;
+ gpio-dir = <0x1>;
+ };
};
};
This is "normal tip sense (TS)" in the datasheet.
The CS42L42_TS_INV_* defines are available for this.
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 1
7 - 1.5s
The CS42L42_TS_DBNCE_* defines are available for this.
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 7
7 - 1.5s
The CS42L42_TS_DBNCE_* defines are available for this.
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 7
0ms - 200ms,
Default = 100ms
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 200
0ms - 20ms,
Default = 10ms
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 20
3 - Slowest
The CS42L42_HSBIAS_RAMP_* defines are available for this.
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0
maximum: 3
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/cirrus,ep9301-i2s.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus EP93xx I2S Controller
+
+description: |
+ The I2S controller is used to stream serial audio data between the external
+ I2S CODECs’, ADCs/DACs, and the ARM Core. The controller supports I2S, Left-
+ and Right-Justified DSP formats.
+
+maintainers:
+ - Alexander Sverdlin <alexander.sverdlin@gmail.com>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: cirrus,ep9301-i2s
+
+ '#sound-dai-cells':
+ const: 0
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ minItems: 3
+ maxItems: 3
+
+ clock-names:
+ items:
+ - const: mclk
+ - const: sclk
+ - const: lrclk
+
+required:
+ - compatible
+ - '#sound-dai-cells'
+ - reg
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ i2s: i2s@80820000 {
+ compatible = "cirrus,ep9301-i2s";
+ #sound-dai-cells = <0>;
+ reg = <0x80820000 0x100>;
+ interrupt-parent = <&vic1>;
+ interrupts = <28>;
+ clocks = <&syscon 29>,
+ <&syscon 30>,
+ <&syscon 31>;
+ clock-names = "mclk", "sclk", "lrclk";
+ };
+
+...
items:
- const: mclk
+ port:
+ $ref: audio-graph-port.yaml#
+ unevaluatedProperties: false
+
"#sound-dai-cells":
const: 0
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/fsl,qmc-audio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: QMC audio
+
+maintainers:
+ - Herve Codina <herve.codina@bootlin.com>
+
+description: |
+ The QMC audio is an ASoC component which uses QMC (QUICC Multichannel
+ Controller) channels to transfer the audio data.
+ It provides as many DAI as the number of QMC channel used.
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: fsl,qmc-audio
+
+ '#address-cells':
+ const: 1
+ '#size-cells':
+ const: 0
+ '#sound-dai-cells':
+ const: 1
+
+patternProperties:
+ '^dai@([0-9]|[1-5][0-9]|6[0-3])$':
+ description:
+ A DAI managed by this controller
+ type: object
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 63
+ description:
+ The DAI number
+
+ fsl,qmc-chan:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle to QMC node
+ - description: Channel number
+ description:
+ Should be a phandle/number pair. The phandle to QMC node and the QMC
+ channel to use for this DAI.
+
+ required:
+ - reg
+ - fsl,qmc-chan
+
+required:
+ - compatible
+ - '#address-cells'
+ - '#size-cells'
+ - '#sound-dai-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ audio_controller: audio-controller {
+ compatible = "fsl,qmc-audio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #sound-dai-cells = <1>;
+ dai@16 {
+ reg = <16>;
+ fsl,qmc-chan = <&qmc 16>;
+ };
+ dai@17 {
+ reg = <17>;
+ fsl,qmc-chan = <&qmc 17>;
+ };
+ };
+
+ sound {
+ compatible = "simple-audio-card";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ simple-audio-card,dai-link@0 {
+ reg = <0>;
+ format = "dsp_b";
+ cpu {
+ sound-dai = <&audio_controller 16>;
+ };
+ codec {
+ sound-dai = <&codec1>;
+ dai-tdm-slot-num = <4>;
+ dai-tdm-slot-width = <8>;
+ /* TS 3, 5, 7, 9 */
+ dai-tdm-slot-tx-mask = <0 0 0 1 0 1 0 1 0 1>;
+ dai-tdm-slot-rx-mask = <0 0 0 1 0 1 0 1 0 1>;
+ };
+ };
+ simple-audio-card,dai-link@1 {
+ reg = <1>;
+ format = "dsp_b";
+ cpu {
+ sound-dai = <&audio_controller 17>;
+ };
+ codec {
+ sound-dai = <&codec2>;
+ dai-tdm-slot-num = <4>;
+ dai-tdm-slot-width = <8>;
+ /* TS 2, 4, 6, 8 */
+ dai-tdm-slot-tx-mask = <0 0 1 0 1 0 1 0 1>;
+ dai-tdm-slot-rx-mask = <0 0 1 0 1 0 1 0 1>;
+ };
+ };
+ };
+++ /dev/null
-max98371 codec
-
-This device supports I2C mode only.
-
-Required properties:
-
-- compatible : "maxim,max98371"
-- reg : The chip select number on the I2C bus
-
-Example:
-
-&i2c {
- max98371: max98371@31 {
- compatible = "maxim,max98371";
- reg = <0x31>;
- };
-};
+++ /dev/null
-max9867 codec
-
-This device supports I2C mode only.
-
-Required properties:
-
-- compatible : "maxim,max9867"
-- reg : The chip select number on the I2C bus
-
-Example:
-
-&i2c {
- max9867: max9867@18 {
- compatible = "maxim,max9867";
- reg = <0x18>;
- };
-};
+++ /dev/null
-Maxim MAX9759 Speaker Amplifier
-===============================
-
-Required properties:
-- compatible : "maxim,max9759"
-- shutdown-gpios : the gpio connected to the shutdown pin
-- mute-gpios : the gpio connected to the mute pin
-- gain-gpios : the 2 gpios connected to the g1 and g2 pins
-
-Example:
-
-max9759: analog-amplifier {
- compatible = "maxim,max9759";
- shutdown-gpios = <&gpio3 20 GPIO_ACTIVE_LOW>;
- mute-gpios = <&gpio3 19 GPIO_ACTIVE_LOW>;
- gain-gpios = <&gpio3 23 GPIO_ACTIVE_LOW>,
- <&gpio3 25 GPIO_ACTIVE_LOW>;
-};
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/maxim,max9759.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX9759 Speaker Amplifier
+
+maintainers:
+ - Otabek Nazrullaev <otabeknazrullaev1998@gmail.com>
+
+properties:
+ compatible:
+ const: maxim,max9759
+
+ shutdown-gpios:
+ maxItems: 1
+ description: the gpio connected to the shutdown pin
+
+ mute-gpios:
+ maxItems: 1
+ description: the gpio connected to the mute pin
+
+ gain-gpios:
+ maxItems: 2
+ description: the 2 gpios connected to the g1 and g2 pins
+
+required:
+ - compatible
+ - shutdown-gpios
+ - mute-gpios
+ - gain-gpios
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ amplifier {
+ compatible = "maxim,max9759";
+ shutdown-gpios = <&gpio3 20 GPIO_ACTIVE_LOW>;
+ mute-gpios = <&gpio3 19 GPIO_ACTIVE_LOW>;
+ gain-gpios = <&gpio3 23 GPIO_ACTIVE_LOW>,
+ <&gpio3 25 GPIO_ACTIVE_LOW>;
+ };
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/maxim,max98371.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX98371 audio codec
+
+maintainers:
+ - anish kumar <yesanishhere@gmail.com>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: maxim,max98371
+
+ '#sound-dai-cells':
+ const: 0
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ codec@31 {
+ compatible = "maxim,max98371";
+ reg = <0x31>;
+ #sound-dai-cells = <0>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/maxim,max9867.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim Integrated MAX9867 CODEC
+
+description: |
+ This device supports I2C only.
+ Pins on the device (for linking into audio routes):
+ * LOUT
+ * ROUT
+ * LINL
+ * LINR
+ * MICL
+ * MICR
+ * DMICL
+ * DMICR
+
+maintainers:
+ - Ladislav Michl <ladis@linux-mips.org>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - maxim,max9867
+
+ '#sound-dai-cells':
+ const: 0
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@18 {
+ compatible = "maxim,max9867";
+ #sound-dai-cells = <0>;
+ reg = <0x18>;
+ clocks = <&codec_clk>;
+ };
+ };
+...
const: audiosys
mediatek,apmixedsys:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of the mediatek apmixedsys controller
mediatek,infracfg:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of the mediatek infracfg controller
mediatek,topckgen:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of the mediatek topckgen controller
clocks:
- mediatek,mt8186-mt6366-da7219-max98357-sound
mediatek,platform:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of MT8186 ASoC platform.
headset-codec:
- mediatek,mt8186-mt6366-rt5682s-max98360-sound
mediatek,platform:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of MT8186 ASoC platform.
dmic-gpios:
const: audiosys
mediatek,apmixedsys:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of the mediatek apmixedsys controller
mediatek,infracfg:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of the mediatek infracfg controller
mediatek,topckgen:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of the mediatek topckgen controller
power-domains:
- mediatek,mt8192_mt6359_rt1015p_rt5682s
mediatek,platform:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of MT8192 ASoC platform.
mediatek,hdmi-codec:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of HDMI codec.
headset-codec:
See ../reserved-memory/reserved-memory.txt for details.
mediatek,topckgen:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of the mediatek topckgen controller
power-domains:
description: User specified audio sound card name
mediatek,platform:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of MT8195 ASoC platform.
mediatek,dptx-codec:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of MT8195 Display Port Tx codec node.
mediatek,hdmi-codec:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of MT8195 HDMI codec node.
mediatek,adsp:
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
description: The phandle of MT8195 ADSP platform.
mediatek,dai-link:
- nuvoton,adcout-drive-strong: make the drive strength of ADCOUT IO PIN strong if set.
Otherwise, the drive keeps normal strength.
+ - nuvoton,adc-delay-ms: Delay (in ms) to make input path stable and avoid pop noise. The
+ default value is 125 and range between 125 to 500 ms.
+
- clocks: list of phandle and clock specifier pairs according to common clock bindings for the
clocks described in clock-names
- clock-names: should include "mclk" for the MCLK master clock
items:
enum:
# Board Connectors
- - "Headset Stereophone"
- - "Int Spk"
- - "Headset Mic"
- - "Digital Mic"
+ - Headset Stereophone
+ - Int Spk
+ - Headset Mic
+ - Digital Mic
# CODEC Pins
- SPKOUT
type: boolean
description: The Mic Jack represents state of the headset microphone pin
+ nvidia,coupled-mic-hp-det:
+ type: boolean
+ description: The Mic detect GPIO is viable only if HP detect GPIO is active
+
additionalProperties: true
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-max9808x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra audio complex with MAX9808x CODEC
+
+maintainers:
+ - Jon Hunter <jonathanh@nvidia.com>
+ - Thierry Reding <thierry.reding@gmail.com>
+
+allOf:
+ - $ref: nvidia,tegra-audio-common.yaml#
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - pattern: '^[a-z0-9]+,tegra-audio-max98088(-[a-z0-9]+)+$'
+ - const: nvidia,tegra-audio-max98088
+ - items:
+ - pattern: '^[a-z0-9]+,tegra-audio-max98089(-[a-z0-9]+)+$'
+ - const: nvidia,tegra-audio-max98089
+
+ nvidia,audio-routing:
+ $ref: /schemas/types.yaml#/definitions/non-unique-string-array
+ description: |
+ A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the connection's sink,
+ the second being the connection's source. Valid names for sources and
+ sinks are the pins (documented in the binding document),
+ and the jacks on the board.
+ minItems: 2
+ items:
+ enum:
+ # Board Connectors
+ - "Int Spk"
+ - "Headphone Jack"
+ - "Earpiece"
+ - "Headset Mic"
+ - "Internal Mic 1"
+ - "Internal Mic 2"
+
+ # CODEC Pins
+ - HPL
+ - HPR
+ - SPKL
+ - SPKR
+ - RECL
+ - RECR
+ - INA1
+ - INA2
+ - INB1
+ - INB2
+ - MIC1
+ - MIC2
+ - MICBIAS
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/tegra30-car.h>
+ #include <dt-bindings/soc/tegra-pmc.h>
+ sound {
+ compatible = "lge,tegra-audio-max98089-p895",
+ "nvidia,tegra-audio-max98089";
+ nvidia,model = "LG Optimus Vu MAX98089";
+
+ nvidia,audio-routing =
+ "Headphone Jack", "HPL",
+ "Headphone Jack", "HPR",
+ "Int Spk", "SPKL",
+ "Int Spk", "SPKR",
+ "Earpiece", "RECL",
+ "Earpiece", "RECR",
+ "INA1", "Headset Mic",
+ "MIC1", "MICBIAS",
+ "MICBIAS", "Internal Mic 1",
+ "MIC2", "Internal Mic 2";
+
+ nvidia,i2s-controller = <&tegra_i2s0>;
+ nvidia,audio-codec = <&codec>;
+
+ clocks = <&tegra_car TEGRA30_CLK_PLL_A>,
+ <&tegra_car TEGRA30_CLK_PLL_A_OUT0>,
+ <&tegra_pmc TEGRA_PMC_CLK_OUT_1>;
+ clock-names = "pll_a", "pll_a_out0", "mclk";
+ };
items:
enum:
# Board Connectors
- - "Headphones"
- - "Speakers"
- - "Mic Jack"
- - "Int Mic"
+ - Headphones
+ - Speakers
+ - Mic Jack
+ - Int Mic
# CODEC Pins
- MIC1
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-rt5631.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra audio complex with RT5631 CODEC
+
+maintainers:
+ - Jon Hunter <jonathanh@nvidia.com>
+ - Thierry Reding <thierry.reding@gmail.com>
+
+allOf:
+ - $ref: nvidia,tegra-audio-common.yaml#
+
+properties:
+ compatible:
+ items:
+ - pattern: '^[a-z0-9]+,tegra-audio-rt5631(-[a-z0-9]+)+$'
+ - const: nvidia,tegra-audio-rt5631
+
+ nvidia,audio-routing:
+ $ref: /schemas/types.yaml#/definitions/non-unique-string-array
+ description: |
+ A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the connection's sink,
+ the second being the connection's source. Valid names for sources and
+ sinks are the pins (documented in the binding document),
+ and the jacks on the board.
+ minItems: 2
+ items:
+ enum:
+ # Board Connectors
+ - "Int Spk"
+ - "Headphone Jack"
+ - "Mic Jack"
+ - "Int Mic"
+
+ # CODEC Pins
+ - MIC1
+ - MIC2
+ - AXIL
+ - AXIR
+ - MONOIN_RXN
+ - MONOIN_RXP
+ - DMIC
+ - MIC Bias1
+ - MIC Bias2
+ - MONO_IN
+ - AUXO1
+ - AUXO2
+ - SPOL
+ - SPOR
+ - HPOL
+ - HPOR
+ - MONO
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/tegra30-car.h>
+ #include <dt-bindings/soc/tegra-pmc.h>
+ sound {
+ compatible = "asus,tegra-audio-rt5631-tf700t",
+ "nvidia,tegra-audio-rt5631";
+ nvidia,model = "Asus Transformer Infinity TF700T RT5631";
+
+ nvidia,audio-routing =
+ "Headphone Jack", "HPOL",
+ "Headphone Jack", "HPOR",
+ "Int Spk", "SPOL",
+ "Int Spk", "SPOR",
+ "MIC1", "MIC Bias1",
+ "MIC Bias1", "Mic Jack",
+ "DMIC", "Int Mic";
+
+ nvidia,i2s-controller = <&tegra_i2s1>;
+ nvidia,audio-codec = <&rt5631>;
+
+ clocks = <&tegra_car TEGRA30_CLK_PLL_A>,
+ <&tegra_car TEGRA30_CLK_PLL_A_OUT0>,
+ <&tegra_pmc TEGRA_PMC_CLK_OUT_1>;
+ clock-names = "pll_a", "pll_a_out0", "mclk";
+ };
items:
enum:
# Board Connectors
- - "Headphones"
- - "Speakers"
- - "Mic Jack"
+ - Headphones
+ - Speakers
+ - Mic Jack
# CODEC Pins
- DMIC1
items:
enum:
# Board Connectors
- - "Headphone"
- - "Speaker"
- - "Headset Mic"
- - "Internal Mic 1"
- - "Internal Mic 2"
+ - Headphone
+ - Speaker
+ - Headset Mic
+ - Internal Mic 1
+ - Internal Mic 2
# CODEC Pins
- IN1P
- DMIC2
- DMIC3
- DMIC4
- - "DMIC L1"
- - "DMIC L2"
- - "DMIC L3"
- - "DMIC L4"
- - "DMIC R1"
- - "DMIC R2"
- - "DMIC R3"
- - "DMIC R4"
+ - DMIC L1
+ - DMIC L2
+ - DMIC L3
+ - DMIC L4
+ - DMIC R1
+ - DMIC R2
+ - DMIC R3
+ - DMIC R4
- LOUT1
- LOUT2
- LOUT3
items:
enum:
# Board Connectors
- - "Headphone Jack"
- - "Line In Jack"
- - "Mic Jack"
+ - Headphone Jack
+ - Line In Jack
+ - Mic Jack
# CODEC Pins
- HP_OUT
items:
enum:
# Board Connectors
- - "Headphone Jack"
- - "Mic Jack"
+ - Headphone Jack
+ - Mic Jack
# CODEC Pins
- LOUT1
- MIC1
- MIC2N
- MIC2
- - "Mic Bias"
+ - Mic Bias
required:
- nvidia,i2s-controller
items:
enum:
# Board Connectors
- - "Headphone Jack"
- - "Int Spk"
- - "Mic Jack"
- - "Int Mic"
+ - Headphone Jack
+ - Int Spk
+ - Mic Jack
+ - Int Mic
# CODEC Pins
- IN1L
items:
enum:
# Board Connectors
- - "Headphone"
- - "LineIn"
- - "Mic"
+ - Headphone
+ - LineIn
+ - Mic
# CODEC Pins
- MONOOUT
- PCBEEP
- MIC1
- MIC2
- - "Mic Bias"
+ - Mic Bias
required:
- nvidia,ac97-controller
maintainers:
- Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
-allOf:
- - $ref: dai-common.yaml#
-
properties:
compatible:
enum:
- qcom,sc7280-lpass-rx-macro
- qcom,sm8250-lpass-rx-macro
- qcom,sm8450-lpass-rx-macro
+ - qcom,sm8550-lpass-rx-macro
- qcom,sc8280xp-lpass-rx-macro
reg:
const: 0
clocks:
+ minItems: 3
maxItems: 5
clock-names:
- oneOf:
- - items: # for ADSP based platforms
- - const: mclk
- - const: npl
- - const: macro
- - const: dcodec
- - const: fsgen
- - items: # for ADSP bypass based platforms
- - const: mclk
- - const: npl
- - const: fsgen
+ minItems: 3
+ maxItems: 5
clock-output-names:
maxItems: 1
- reg
- "#sound-dai-cells"
+allOf:
+ - $ref: dai-common.yaml#
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,sc7280-lpass-rx-macro
+ then:
+ properties:
+ clock-names:
+ oneOf:
+ - items: # for ADSP based platforms
+ - const: mclk
+ - const: npl
+ - const: macro
+ - const: dcodec
+ - const: fsgen
+ - items: # for ADSP bypass based platforms
+ - const: mclk
+ - const: npl
+ - const: fsgen
+
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,sc8280xp-lpass-rx-macro
+ - qcom,sm8250-lpass-rx-macro
+ - qcom,sm8450-lpass-rx-macro
+ then:
+ properties:
+ clocks:
+ minItems: 5
+ maxItems: 5
+ clock-names:
+ items:
+ - const: mclk
+ - const: npl
+ - const: macro
+ - const: dcodec
+ - const: fsgen
+
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,sm8550-lpass-rx-macro
+ then:
+ properties:
+ clocks:
+ minItems: 4
+ maxItems: 4
+ clock-names:
+ items:
+ - const: mclk
+ - const: macro
+ - const: dcodec
+ - const: fsgen
+
unevaluatedProperties: false
examples:
maintainers:
- Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
-allOf:
- - $ref: dai-common.yaml#
-
properties:
compatible:
enum:
- qcom,sc7280-lpass-tx-macro
- qcom,sm8250-lpass-tx-macro
- qcom,sm8450-lpass-tx-macro
+ - qcom,sm8550-lpass-tx-macro
- qcom,sc8280xp-lpass-tx-macro
reg:
const: 0
clocks:
- oneOf:
- - maxItems: 3
- - maxItems: 5
+ minItems: 3
+ maxItems: 5
clock-names:
- oneOf:
- - items: # for ADSP based platforms
- - const: mclk
- - const: npl
- - const: macro
- - const: dcodec
- - const: fsgen
- - items: # for ADSP bypass based platforms
- - const: mclk
- - const: npl
- - const: fsgen
+ minItems: 3
+ maxItems: 5
clock-output-names:
maxItems: 1
- reg
- "#sound-dai-cells"
+allOf:
+ - $ref: dai-common.yaml#
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,sc7280-lpass-tx-macro
+ then:
+ properties:
+ clock-names:
+ oneOf:
+ - items: # for ADSP based platforms
+ - const: mclk
+ - const: npl
+ - const: macro
+ - const: dcodec
+ - const: fsgen
+ - items: # for ADSP bypass based platforms
+ - const: mclk
+ - const: npl
+ - const: fsgen
+
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,sc8280xp-lpass-tx-macro
+ - qcom,sm8250-lpass-tx-macro
+ - qcom,sm8450-lpass-tx-macro
+ then:
+ properties:
+ clocks:
+ minItems: 5
+ maxItems: 5
+ clock-names:
+ items:
+ - const: mclk
+ - const: npl
+ - const: macro
+ - const: dcodec
+ - const: fsgen
+
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,sm8550-lpass-tx-macro
+ then:
+ properties:
+ clocks:
+ minItems: 4
+ maxItems: 4
+ clock-names:
+ items:
+ - const: mclk
+ - const: macro
+ - const: dcodec
+ - const: fsgen
+
unevaluatedProperties: false
examples:
maintainers:
- Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
-allOf:
- - $ref: dai-common.yaml#
-
properties:
compatible:
enum:
- qcom,sc7280-lpass-va-macro
- qcom,sm8250-lpass-va-macro
- qcom,sm8450-lpass-va-macro
+ - qcom,sm8550-lpass-va-macro
- qcom,sc8280xp-lpass-va-macro
reg:
const: 0
clocks:
- maxItems: 3
+ minItems: 1
+ maxItems: 4
clock-names:
- oneOf:
- - items: # for ADSP based platforms
- - const: mclk
- - const: macro
- - const: dcodec
- - items: # for ADSP bypass based platforms
- - const: mclk
+ minItems: 1
+ maxItems: 4
clock-output-names:
maxItems: 1
- compatible
- reg
- "#sound-dai-cells"
+ - clock-names
+ - clocks
+
+allOf:
+ - $ref: dai-common.yaml#
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,sc7280-lpass-va-macro
+ then:
+ properties:
+ clocks:
+ maxItems: 1
+ clock-names:
+ items:
+ - const: mclk
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,sm8250-lpass-va-macro
+ then:
+ properties:
+ clocks:
+ minItems: 3
+ maxItems: 3
+ clock-names:
+ items:
+ - const: mclk
+ - const: macro
+ - const: dcodec
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - qcom,sc8280xp-lpass-va-macro
+ - qcom,sm8450-lpass-va-macro
+ then:
+ properties:
+ clocks:
+ minItems: 4
+ maxItems: 4
+ clock-names:
+ items:
+ - const: mclk
+ - const: macro
+ - const: dcodec
+ - const: npl
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - qcom,sm8550-lpass-va-macro
+ then:
+ properties:
+ clocks:
+ minItems: 3
+ maxItems: 3
+ clock-names:
+ items:
+ - const: mclk
+ - const: macro
+ - const: dcodec
unevaluatedProperties: false
- qcom,sc7280-lpass-wsa-macro
- qcom,sm8250-lpass-wsa-macro
- qcom,sm8450-lpass-wsa-macro
+ - qcom,sm8550-lpass-wsa-macro
- qcom,sc8280xp-lpass-wsa-macro
reg:
const: 0
clocks:
- minItems: 5
+ minItems: 4
maxItems: 6
clock-names:
- minItems: 5
+ minItems: 4
maxItems: 6
clock-output-names:
then:
properties:
clocks:
+ minItems: 5
maxItems: 5
clock-names:
items:
- const: va
- const: fsgen
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,sm8550-lpass-wsa-macro
+ then:
+ properties:
+ clocks:
+ minItems: 4
+ maxItems: 4
+ clock-names:
+ items:
+ - const: mclk
+ - const: macro
+ - const: dcodec
+ - const: fsgen
+
unevaluatedProperties: false
examples:
Compress offload dai.
dependencies:
- is-compress-dai: ["direction"]
+ is-compress-dai: [ direction ]
required:
- reg
+++ /dev/null
-QCOM WCD9335 Codec
-
-Qualcomm WCD9335 Codec is a standalone Hi-Fi audio codec IC, supports
-Qualcomm Technologies, Inc. (QTI) multimedia solutions, including
-the MSM8996, MSM8976, and MSM8956 chipsets. It has in-built
-Soundwire controller, interrupt mux. It supports both I2S/I2C and
-SLIMbus audio interfaces.
-
-Required properties with SLIMbus Interface:
-
-- compatible:
- Usage: required
- Value type: <stringlist>
- Definition: For SLIMbus interface it should be "slimMID,PID",
- textual representation of Manufacturer ID, Product Code,
- shall be in lower case hexadecimal with leading zeroes
- suppressed. Refer to slimbus/bus.txt for details.
- Should be:
- "slim217,1a0" for MSM8996 and APQ8096 SoCs with SLIMbus.
-
-- reg
- Usage: required
- Value type: <u32 u32>
- Definition: Should be ('Device index', 'Instance ID')
-
-- interrupts
- Usage: required
- Value type: <prop-encoded-array>
- Definition: Interrupts via WCD INTR1 and INTR2 pins
-
-- interrupt-names:
- Usage: required
- Value type: <String array>
- Definition: Interrupt names of WCD INTR1 and INTR2
- Should be: "intr1", "intr2"
-
-- reset-gpios:
- Usage: required
- Value type: <String Array>
- Definition: Reset gpio line
-
-- slim-ifc-dev:
- Usage: required
- Value type: <phandle>
- Definition: SLIM interface device
-
-- clocks:
- Usage: required
- Value type: <prop-encoded-array>
- Definition: See clock-bindings.txt section "consumers". List of
- three clock specifiers for mclk, mclk2 and slimbus clock.
-
-- clock-names:
- Usage: required
- Value type: <string>
- Definition: Must contain "mclk", "mclk2" and "slimbus" strings.
-
-- vdd-buck-supply:
- Usage: required
- Value type: <phandle>
- Definition: Should contain a reference to the 1.8V buck supply
-
-- vdd-buck-sido-supply:
- Usage: required
- Value type: <phandle>
- Definition: Should contain a reference to the 1.8V SIDO buck supply
-
-- vdd-rx-supply:
- Usage: required
- Value type: <phandle>
- Definition: Should contain a reference to the 1.8V rx supply
-
-- vdd-tx-supply:
- Usage: required
- Value type: <phandle>
- Definition: Should contain a reference to the 1.8V tx supply
-
-- vdd-vbat-supply:
- Usage: Optional
- Value type: <phandle>
- Definition: Should contain a reference to the vbat supply
-
-- vdd-micbias-supply:
- Usage: required
- Value type: <phandle>
- Definition: Should contain a reference to the micbias supply
-
-- vdd-io-supply:
- Usage: required
- Value type: <phandle>
- Definition: Should contain a reference to the 1.8V io supply
-
-- interrupt-controller:
- Usage: required
- Definition: Indicating that this is a interrupt controller
-
-- #interrupt-cells:
- Usage: required
- Value type: <int>
- Definition: should be 1
-
-#sound-dai-cells
- Usage: required
- Value type: <u32>
- Definition: Must be 1
-
-audio-codec@1{
- compatible = "slim217,1a0";
- reg = <1 0>;
- interrupts = <&msmgpio 54 IRQ_TYPE_LEVEL_HIGH>;
- interrupt-names = "intr2"
- reset-gpios = <&msmgpio 64 GPIO_ACTIVE_LOW>;
- slim-ifc-dev = <&wc9335_ifd>;
- clock-names = "mclk", "native";
- clocks = <&rpmcc RPM_SMD_DIV_CLK1>,
- <&rpmcc RPM_SMD_BB_CLK1>;
- vdd-buck-supply = <&pm8994_s4>;
- vdd-rx-supply = <&pm8994_s4>;
- vdd-buck-sido-supply = <&pm8994_s4>;
- vdd-tx-supply = <&pm8994_s4>;
- vdd-io-supply = <&pm8994_s4>;
- #sound-dai-cells = <1>;
-}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/qcom,wcd9335.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm WCD9335 Audio Codec
+
+maintainers:
+ - Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
+
+description:
+ Qualcomm WCD9335 Codec is a standalone Hi-Fi audio codec IC with in-built
+ Soundwire controller and interrupt mux. It supports both I2S/I2C and SLIMbus
+ audio interfaces.
+
+properties:
+ compatible:
+ const: slim217,1a0
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 2
+
+ clock-names:
+ items:
+ - const: mclk
+ - const: slimbus
+
+ interrupts:
+ maxItems: 2
+
+ interrupt-names:
+ items:
+ - const: intr1
+ - const: intr2
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ slim-ifc-dev:
+ description: SLIM IFC device interface
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+ '#sound-dai-cells':
+ const: 1
+
+ vdd-buck-supply:
+ description: 1.8V buck supply
+
+ vdd-buck-sido-supply:
+ description: 1.8V SIDO buck supply
+
+ vdd-io-supply:
+ description: 1.8V I/O supply
+
+ vdd-micbias-supply:
+ description: micbias supply
+
+ vdd-rx-supply:
+ description: 1.8V rx supply
+
+ vdd-tx-supply:
+ description: 1.8V tx supply
+
+ vdd-vbat-supply:
+ description: vbat supply
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: dai-common.yaml#
+ - if:
+ required:
+ - slim-ifc-dev
+ then:
+ required:
+ - clocks
+ - clock-names
+ - interrupts
+ - interrupt-names
+ - interrupt-controller
+ - '#interrupt-cells'
+ - reset-gpios
+ - slim-ifc-dev
+ - '#sound-dai-cells'
+ - vdd-buck-supply
+ - vdd-buck-sido-supply
+ - vdd-io-supply
+ - vdd-rx-supply
+ - vdd-tx-supply
+ else:
+ properties:
+ clocks: false
+ clock-names: false
+ interrupts: false
+ interrupt-names: false
+ interrupt-controller: false
+ '#interrupt-cells': false
+ reset-gpios: false
+ slim-ifc-dev: false
+ '#sound-dai-cells': false
+ vdd-buck-supply: false
+ vdd-buck-sido-supply: false
+ vdd-io-supply: false
+ vdd-micbias-supply: false
+ vdd-rx-supply: false
+ vdd-tx-supply: false
+ vdd-vbat-supply: false
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,rpmcc.h>
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ tasha_ifd: codec@0,0 {
+ compatible = "slim217,1a0";
+ reg = <0 0>;
+ };
+
+ codec@1,0 {
+ compatible = "slim217,1a0";
+ reg = <1 0>;
+
+ clock-names = "mclk", "slimbus";
+ clocks = <&div1_mclk>, <&rpmcc RPM_SMD_BB_CLK1>;
+
+ interrupt-parent = <&tlmm>;
+ interrupts = <54 IRQ_TYPE_LEVEL_HIGH>,
+ <53 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "intr1", "intr2";
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ reset-gpios = <&tlmm 64 GPIO_ACTIVE_LOW>;
+ slim-ifc-dev = <&tasha_ifd>;
+ #sound-dai-cells = <1>;
+
+ vdd-buck-supply = <&vreg_s4a_1p8>;
+ vdd-buck-sido-supply = <&vreg_s4a_1p8>;
+ vdd-tx-supply = <&vreg_s4a_1p8>;
+ vdd-rx-supply = <&vreg_s4a_1p8>;
+ vdd-io-supply = <&vreg_s4a_1p8>;
+ };
- reg
allOf:
+ - $ref: dai-common.yaml#
- if:
required:
- slim-ifc-dev
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/realtek,alc5632.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ALC5632 audio CODEC
+
+description: |
+ Pins on the device (for linking into audio routes):
+ * SPK_OUTP
+ * SPK_OUTN
+ * HP_OUT_L
+ * HP_OUT_R
+ * AUX_OUT_P
+ * AUX_OUT_N
+ * LINE_IN_L
+ * LINE_IN_R
+ * PHONE_P
+ * PHONE_N
+ * MIC1_P
+ * MIC1_N
+ * MIC2_P
+ * MIC2_N
+ * MICBIAS1
+ * DMICDAT
+
+maintainers:
+ - Leon Romanovsky <leon@leon.nu>
+
+properties:
+ compatible:
+ const: realtek,alc5632
+
+ reg:
+ maxItems: 1
+
+ '#gpio-cells':
+ const: 2
+
+ gpio-controller: true
+
+required:
+ - compatible
+ - reg
+ - '#gpio-cells'
+ - gpio-controller
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@1a {
+ compatible = "realtek,alc5632";
+ reg = <0x1a>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ };
+ };
clock-names:
description: List of necessary clock names.
- minItems: 1
- maxItems: 31
- items:
- oneOf:
- - const: ssi-all
- - pattern: '^ssi\.[0-9]$'
- - pattern: '^src\.[0-9]$'
- - pattern: '^mix\.[0-1]$'
- - pattern: '^ctu\.[0-1]$'
- - pattern: '^dvc\.[0-1]$'
- - pattern: '^clk_(a|b|c|i)$'
+ # details are defined below
ports:
$ref: audio-graph-port.yaml#/definitions/port-base
dmas:
maxItems: 1
dma-names:
- const: "tx"
+ const: tx
required:
- dmas
- dma-names
allOf:
- $ref: dai-common.yaml#
+
+ # --------------------
+ # reg/reg-names
+ # --------------------
+ # for Gen1
- if:
properties:
compatible:
- scu
- ssi
- adg
- else:
+ # for Gen2/Gen3
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - renesas,rcar_sound-gen2
+ - renesas,rcar_sound-gen3
+ then:
properties:
reg:
minItems: 5
- ssiu
- ssi
- audmapp
+ # for Gen4
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: renesas,rcar_sound-gen4
+ then:
+ properties:
+ reg:
+ maxItems: 4
+ reg-names:
+ items:
+ enum:
+ - adg
+ - ssiu
+ - ssi
+ - sdmc
+
+ # --------------------
+ # clock-names
+ # --------------------
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: renesas,rcar_sound-gen4
+ then:
+ properties:
+ clock-names:
+ maxItems: 3
+ items:
+ enum:
+ - ssi.0
+ - ssiu.0
+ - clkin
+ else:
+ properties:
+ clock-names:
+ minItems: 1
+ maxItems: 31
+ items:
+ oneOf:
+ - const: ssi-all
+ - pattern: '^ssi\.[0-9]$'
+ - pattern: '^src\.[0-9]$'
+ - pattern: '^mix\.[0-1]$'
+ - pattern: '^ctu\.[0-1]$'
+ - pattern: '^dvc\.[0-1]$'
+ - pattern: '^clk_(a|b|c|i)$'
unevaluatedProperties: false
examples:
- |
+ #include <dt-bindings/clock/r8a7790-cpg-mssr.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/power/r8a7790-sysc.h>
rcar_sound: sound@ec500000 {
#sound-dai-cells = <1>;
compatible = "renesas,rcar_sound-r8a7790", "renesas,rcar_sound-gen2";
reg = <0xec500000 0x1000>, /* SCU */
<0xec5a0000 0x100>, /* ADG */
<0xec540000 0x1000>, /* SSIU */
- <0xec541000 0x1280>, /* SSI */
+ <0xec541000 0x280>, /* SSI */
<0xec740000 0x200>; /* Audio DMAC peri peri*/
reg-names = "scu", "adg", "ssiu", "ssi", "audmapp";
- clocks = <&mstp10_clks 1005>, /* SSI-ALL */
- <&mstp10_clks 1006>, <&mstp10_clks 1007>, /* SSI9, SSI8 */
- <&mstp10_clks 1008>, <&mstp10_clks 1009>, /* SSI7, SSI6 */
- <&mstp10_clks 1010>, <&mstp10_clks 1011>, /* SSI5, SSI4 */
- <&mstp10_clks 1012>, <&mstp10_clks 1013>, /* SSI3, SSI2 */
- <&mstp10_clks 1014>, <&mstp10_clks 1015>, /* SSI1, SSI0 */
- <&mstp10_clks 1022>, <&mstp10_clks 1023>, /* SRC9, SRC8 */
- <&mstp10_clks 1024>, <&mstp10_clks 1025>, /* SRC7, SRC6 */
- <&mstp10_clks 1026>, <&mstp10_clks 1027>, /* SRC5, SRC4 */
- <&mstp10_clks 1028>, <&mstp10_clks 1029>, /* SRC3, SRC2 */
- <&mstp10_clks 1030>, <&mstp10_clks 1031>, /* SRC1, SRC0 */
- <&mstp10_clks 1020>, <&mstp10_clks 1021>, /* MIX1, MIX0 */
- <&mstp10_clks 1020>, <&mstp10_clks 1021>, /* CTU1, CTU0 */
- <&mstp10_clks 1019>, <&mstp10_clks 1018>, /* DVC0, DVC1 */
+ clocks = <&cpg CPG_MOD 1005>, /* SSI-ALL */
+ <&cpg CPG_MOD 1006>, <&cpg CPG_MOD 1007>, /* SSI9, SSI8 */
+ <&cpg CPG_MOD 1008>, <&cpg CPG_MOD 1009>, /* SSI7, SSI6 */
+ <&cpg CPG_MOD 1010>, <&cpg CPG_MOD 1011>, /* SSI5, SSI4 */
+ <&cpg CPG_MOD 1012>, <&cpg CPG_MOD 1013>, /* SSI3, SSI2 */
+ <&cpg CPG_MOD 1014>, <&cpg CPG_MOD 1015>, /* SSI1, SSI0 */
+ <&cpg CPG_MOD 1022>, <&cpg CPG_MOD 1023>, /* SRC9, SRC8 */
+ <&cpg CPG_MOD 1024>, <&cpg CPG_MOD 1025>, /* SRC7, SRC6 */
+ <&cpg CPG_MOD 1026>, <&cpg CPG_MOD 1027>, /* SRC5, SRC4 */
+ <&cpg CPG_MOD 1028>, <&cpg CPG_MOD 1029>, /* SRC3, SRC2 */
+ <&cpg CPG_MOD 1030>, <&cpg CPG_MOD 1031>, /* SRC1, SRC0 */
+ <&cpg CPG_MOD 1020>, <&cpg CPG_MOD 1021>, /* MIX1, MIX0 */
+ <&cpg CPG_MOD 1020>, <&cpg CPG_MOD 1021>, /* CTU1, CTU0 */
+ <&cpg CPG_MOD 1019>, <&cpg CPG_MOD 1018>, /* DVC0, DVC1 */
<&audio_clk_a>, <&audio_clk_b>, /* CLKA, CLKB */
<&audio_clk_c>, <&audio_clk_i>; /* CLKC, CLKI */
"clk_a", "clk_b",
"clk_c", "clk_i";
+ power-domains = <&sysc R8A7790_PD_ALWAYS_ON>;
+
+ resets = <&cpg 1005>,
+ <&cpg 1006>, <&cpg 1007>, <&cpg 1008>, <&cpg 1009>,
+ <&cpg 1010>, <&cpg 1011>, <&cpg 1012>, <&cpg 1013>,
+ <&cpg 1014>, <&cpg 1015>;
+ reset-names = "ssi-all",
+ "ssi.9", "ssi.8", "ssi.7", "ssi.6",
+ "ssi.5", "ssi.4", "ssi.3", "ssi.2",
+ "ssi.1", "ssi.0";
+
rcar_sound,dvc {
dvc0: dvc-0 {
dmas = <&audma0 0xbc>;
status = "disabled";
};
src1: src-1 {
- interrupts = <0 353 0>;
+ interrupts = <GIC_SPI 353 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&audma0 0x87>, <&audma1 0x9c>;
dma-names = "rx", "tx";
};
rcar_sound,ssi {
ssi0: ssi-0 {
- interrupts = <0 370 1>;
+ interrupts = <GIC_SPI 370 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&audma0 0x01>, <&audma1 0x02>;
dma-names = "rx", "tx";
};
ssi1: ssi-1 {
- interrupts = <0 371 1>;
+ interrupts = <GIC_SPI 371 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&audma0 0x03>, <&audma1 0x04>;
dma-names = "rx", "tx";
};
};
};
-
/* assume audio-graph */
codec {
port {
maxItems: 1
interrupts:
- maxItems: 4
+ minItems: 2
+ maxItems: 3
interrupt-names:
- items:
- - const: int_req
- - const: dma_rx
- - const: dma_tx
- - const: dma_rt
+ oneOf:
+ - items:
+ - const: int_req
+ - const: dma_rx
+ - const: dma_tx
+ - items:
+ - const: int_req
+ - const: dma_rt
clocks:
maxItems: 4
reg = <0x10049c00 0x400>;
interrupts = <GIC_SPI 326 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 327 IRQ_TYPE_EDGE_RISING>,
- <GIC_SPI 328 IRQ_TYPE_EDGE_RISING>,
- <GIC_SPI 329 IRQ_TYPE_EDGE_RISING>;
- interrupt-names = "int_req", "dma_rx", "dma_tx", "dma_rt";
+ <GIC_SPI 328 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "int_req", "dma_rx", "dma_tx";
clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>,
<&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>,
<&audio_clk1>,
- tx-m
- rx-m
+ port:
+ $ref: audio-graph-port.yaml#
+ unevaluatedProperties: false
+
+ power-domains:
+ maxItems: 1
+
rockchip,grf:
$ref: /schemas/types.yaml#/definitions/phandle
description:
- rockchip,rk3366-i2s
- rockchip,rk3368-i2s
- rockchip,rk3399-i2s
+ - rockchip,rk3588-i2s
- rockchip,rv1126-i2s
- const: rockchip,rk3066-i2s
resets:
maxItems: 2
+ port:
+ $ref: audio-graph-port.yaml#
+ unevaluatedProperties: false
+
rockchip,capture-channels:
$ref: /schemas/types.yaml#/definitions/uint32
default: 2
description: The bias voltage to be used in mVolts. The voltage can take
values from 1.25V to 3V by 250mV steps. If this node is not mentioned
or the value is unknown, then the value is set to 1.25V.
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
enum: [ 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000 ]
lrclk-strength:
1 = 1.66 mA 2.87 mA 4.02 mA
2 = 3.33 mA 5.74 mA 8.03 mA
3 = 4.99 mA 8.61 mA 12.05 mA
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
enum: [ 0, 1, 2, 3 ]
sclk-strength:
1 = 1.66 mA 2.87 mA 4.02 mA
2 = 3.33 mA 5.74 mA 8.03 mA
3 = 4.99 mA 8.61 mA 12.05 mA
- $ref: "/schemas/types.yaml#/definitions/uint32"
+ $ref: /schemas/types.yaml#/definitions/uint32
enum: [ 0, 1, 2, 3 ]
port:
$ref: /schemas/types.yaml#/definitions/uint32
prefix:
- description: "device name prefix"
+ description: device name prefix
$ref: /schemas/types.yaml#/definitions/string
label:
Specifies a phandle to soc-glue, which is used for changing mode of S/PDIF
signal pin to output from Hi-Z. This property is optional if you use I2S
signal pins only.
- $ref: "/schemas/types.yaml#/definitions/phandle"
+ $ref: /schemas/types.yaml#/definitions/phandle
"#sound-dai-cells":
const: 1
- "ti,tas5717",
- "ti,tas5719",
- "ti,tas5721"
+ - "ti,tas5733"
- reg: The I2C address of the device
- #sound-dai-cells: must be equal to 0
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8510.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WM8510 audio CODEC
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: wlf,wm8510
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ codec@1a {
+ compatible = "wlf,wm8510";
+ reg = <0x1a>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8523.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WM8523 audio CODEC
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: wlf,wm8523
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@1a {
+ compatible = "wlf,wm8523";
+ reg = <0x1a>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8524.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Wolfson WM8524 24-bit 192KHz Stereo DAC
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: wlf,wm8524
+
+ "#sound-dai-cells":
+ const: 0
+
+ wlf,mute-gpios:
+ maxItems: 1
+ description:
+ a GPIO spec for the MUTE pin.
+
+required:
+ - compatible
+ - wlf,mute-gpios
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ wm8524: codec {
+ compatible = "wlf,wm8524";
+ wlf,mute-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8580.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WM8580 and WM8581 audio CODEC
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - wlf,wm8580
+ - wlf,wm8581
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@1a {
+ compatible = "wlf,wm8580";
+ reg = <0x1a>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8711.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WM8711 audio CODEC
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: wlf,wm8711
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@1a {
+ compatible = "wlf,wm8711";
+ reg = <0x1a>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8728.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WM8728 audio CODEC
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: wlf,wm8728
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@1a {
+ compatible = "wlf,wm8728";
+ reg = <0x1a>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8737.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WM8737 audio CODEC
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: wlf,wm8737
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@1a {
+ compatible = "wlf,wm8737";
+ reg = <0x1a>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8753.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WM8753 audio CODEC
+
+description: |
+ Pins on the device (for linking into audio routes):
+ * LOUT1
+ * LOUT2
+ * ROUT1
+ * ROUT2
+ * MONO1
+ * MONO2
+ * OUT3
+ * OUT4
+ * LINE1
+ * LINE2
+ * RXP
+ * RXN
+ * ACIN
+ * ACOP
+ * MIC1N
+ * MIC1
+ * MIC2N
+ * MIC2
+ * Mic Bias
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: wlf,wm8753
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@1a {
+ compatible = "wlf,wm8753";
+ reg = <0x1a>;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8960.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Wolfson WM8960 audio codec
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+properties:
+ compatible:
+ const: wlf,wm8960
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: mclk
+
+ '#sound-dai-cells':
+ const: 0
+
+ wlf,capless:
+ type: boolean
+ description:
+ If present, OUT3 pin will be enabled and disabled together with HP_L and
+ HP_R pins in response to jack detect events.
+
+ wlf,gpio-cfg:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ maxItems: 2
+ description: |
+ A list of GPIO configuration register values.
+ - gpio-cfg[0]: ALRCGPIO of R9 (Audio interface)
+ - gpio-cfg[1]: {GPIOPOL:GPIOSEL[2:0]} of R48 (Additional Control 4).
+
+ wlf,hp-cfg:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ maxItems: 3
+ description: |
+ A list of headphone jack detect configuration register values:
+ - hp-cfg[0]: HPSEL[1:0] of R48 (Additional Control 4).
+ - hp-cfg[1]: {HPSWEN:HPSWPOL} of R24 (Additional Control 2).
+ - hp-cfg[2]: {TOCLKSEL:TOEN} of R23 (Additional Control 1).
+
+ wlf,shared-lrclk:
+ type: boolean
+ description:
+ If present, the LRCM bit of R24 (Additional control 2) gets set,
+ indicating that ADCLRC and DACLRC pins will be disabled only when ADC
+ (Left and Right) and DAC (Left and Right) are disabled.
+ When WM8960 works on synchronize mode and DACLRC pin is used to supply
+ frame clock, it will no frame clock for captrue unless enable DAC to
+ enable DACLRC pin. If shared-lrclk is present, no need to enable DAC for
+ captrue.
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: dai-common.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ audio-codec@1a {
+ compatible = "wlf,wm8960";
+ reg = <0x1a>;
+ clocks = <&clks 0>;
+ clock-names = "mclk";
+ #sound-dai-cells = <0>;
+ wlf,hp-cfg = <3 2 3>;
+ wlf,gpio-cfg = <1 3>;
+ wlf,shared-lrclk;
+ };
+ };
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/wlf,wm8994.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Wolfson WM1811/WM8994/WM8958 audio codecs
+
+maintainers:
+ - Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+ - patches@opensource.cirrus.com
+
+description: |
+ These devices support both I2C and SPI (configured with pin strapping on the
+ board).
+
+ Pins on the device (for linking into audio routes):
+ IN1LN, IN1LP, IN2LN, IN2LP:VXRN, IN1RN, IN1RP, IN2RN, IN2RP:VXRP, SPKOUTLP,
+ SPKOUTLN, SPKOUTRP, SPKOUTRN, HPOUT1L, HPOUT1R, HPOUT2P, HPOUT2N, LINEOUT1P,
+ LINEOUT1N, LINEOUT2P, LINEOUT2N.
+
+properties:
+ compatible:
+ enum:
+ - wlf,wm1811
+ - wlf,wm8994
+ - wlf,wm8958
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ minItems: 1
+ maxItems: 2
+
+ clock-names:
+ minItems: 1
+ items:
+ - const: MCLK1
+ - const: MCLK2
+
+ gpio-controller: true
+
+ '#gpio-cells':
+ const: 2
+
+ interrupts:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+ description:
+ The first cell is the IRQ number. The second cell is the flags, encoded
+ as the trigger masks.
+
+ AVDD1-supply: true
+ AVDD2-supply: true
+ CPVDD-supply: true
+ DBVDD-supply: true
+ DBVDD1-supply: true
+ DBVDD2-supply: true
+ DBVDD3-supply: true
+ DCVDD-supply: true
+ LDO1VDD-supply: true
+ LDO2VDD-supply: true
+ SPKVDD1-supply: true
+ SPKVDD2-supply: true
+
+ '#sound-dai-cells':
+ const: 0
+
+ wlf,gpio-cfg:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ maxItems: 11
+ description:
+ A list of GPIO configuration register values. If absent, no configuration
+ of these registers is performed. If any value is over 0xffff then the
+ register will be left as default. If present 11 values must be supplied.
+
+ wlf,micbias-cfg:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ maxItems: 2
+ description:
+ Two MICBIAS register values for WM1811 or WM8958. If absent the register
+ defaults will be used.
+
+ wlf,ldo1ena-gpios:
+ maxItems: 1
+ description:
+ Control of LDO1ENA input to device.
+
+ wlf,ldo2ena-gpios:
+ maxItems: 1
+ description:
+ Control of LDO2ENA input to device.
+
+ wlf,lineout1-se:
+ type: boolean
+ description:
+ LINEOUT1 is in single ended mode.
+
+ wlf,lineout2-se:
+ type: boolean
+ description:
+ INEOUT2 is in single ended mode.
+
+ wlf,lineout1-feedback:
+ type: boolean
+ description:
+ LINEOUT1 has common mode feedback connected.
+
+ wlf,lineout2-feedback:
+ type: boolean
+ description:
+ LINEOUT2 has common mode feedback connected.
+
+ wlf,ldoena-always-driven:
+ type: boolean
+ description:
+ LDOENA is always driven.
+
+ wlf,spkmode-pu:
+ type: boolean
+ description:
+ Enable the internal pull-up resistor on the SPKMODE pin.
+
+ wlf,csnaddr-pd:
+ type: boolean
+ description:
+ Enable the internal pull-down resistor on the CS/ADDR pin.
+
+required:
+ - compatible
+ - reg
+ - AVDD2-supply
+ - CPVDD-supply
+ - SPKVDD1-supply
+ - SPKVDD2-supply
+
+allOf:
+ - $ref: dai-common.yaml#
+ - if:
+ properties:
+ compatible:
+ enum:
+ - wlf,wm1811
+ - wlf,wm8958
+ then:
+ properties:
+ DBVDD-supply: false
+ LDO2VDD-supply: false
+ required:
+ - DBVDD1-supply
+ - DBVDD2-supply
+ - DBVDD3-supply
+ else:
+ properties:
+ DBVDD1-supply: false
+ DBVDD2-supply: false
+ DBVDD3-supply: false
+ required:
+ - DBVDD-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ audio-codec@1a {
+ compatible = "wlf,wm1811";
+ reg = <0x1a>;
+ clocks = <&i2s0 0>;
+ clock-names = "MCLK1";
+
+ AVDD2-supply = <&main_dc_reg>;
+ CPVDD-supply = <&main_dc_reg>;
+ DBVDD1-supply = <&main_dc_reg>;
+ DBVDD2-supply = <&main_dc_reg>;
+ DBVDD3-supply = <&main_dc_reg>;
+ LDO1VDD-supply = <&main_dc_reg>;
+ SPKVDD1-supply = <&main_dc_reg>;
+ SPKVDD2-supply = <&main_dc_reg>;
+
+ wlf,ldo1ena-gpios = <&gpb0 0 GPIO_ACTIVE_HIGH>;
+ wlf,ldo2ena-gpios = <&gpb0 1 GPIO_ACTIVE_HIGH>;
+ };
+ };
+++ /dev/null
-WM8510 audio CODEC
-
-This device supports both I2C and SPI (configured with pin strapping
-on the board).
-
-Required properties:
-
- - compatible : "wlf,wm8510"
-
- - reg : the I2C address of the device for I2C, the chip select
- number for SPI.
-
-Example:
-
-wm8510: codec@1a {
- compatible = "wlf,wm8510";
- reg = <0x1a>;
-};
+++ /dev/null
-WM8523 audio CODEC
-
-This device supports I2C only.
-
-Required properties:
-
- - compatible : "wlf,wm8523"
-
- - reg : the I2C address of the device.
-
-Example:
-
-wm8523: codec@1a {
- compatible = "wlf,wm8523";
- reg = <0x1a>;
-};
+++ /dev/null
-WM8524 audio CODEC
-
-This device does not use I2C or SPI but a simple Hardware Control Interface.
-
-Required properties:
-
- - compatible : "wlf,wm8524"
-
- - wlf,mute-gpios: a GPIO spec for the MUTE pin.
-
-Example:
-
-wm8524: codec {
- compatible = "wlf,wm8524";
- wlf,mute-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
-};
+++ /dev/null
-WM8580 and WM8581 audio CODEC
-
-This device supports I2C only.
-
-Required properties:
-
- - compatible : "wlf,wm8580", "wlf,wm8581"
-
- - reg : the I2C address of the device.
-
-Example:
-
-wm8580: codec@1a {
- compatible = "wlf,wm8580";
- reg = <0x1a>;
-};
+++ /dev/null
-WM8711 audio CODEC
-
-This device supports both I2C and SPI (configured with pin strapping
-on the board).
-
-Required properties:
-
- - compatible : "wlf,wm8711"
-
- - reg : the I2C address of the device for I2C, the chip select
- number for SPI.
-
-Example:
-
-wm8711: codec@1a {
- compatible = "wlf,wm8711";
- reg = <0x1a>;
-};
+++ /dev/null
-WM8728 audio CODEC
-
-This device supports both I2C and SPI (configured with pin strapping
-on the board).
-
-Required properties:
-
- - compatible : "wlf,wm8728"
-
- - reg : the I2C address of the device for I2C, the chip select
- number for SPI.
-
-Example:
-
-wm8728: codec@1a {
- compatible = "wlf,wm8728";
- reg = <0x1a>;
-};
+++ /dev/null
-WM8737 audio CODEC
-
-This device supports both I2C and SPI (configured with pin strapping
-on the board).
-
-Required properties:
-
- - compatible : "wlf,wm8737"
-
- - reg : the I2C address of the device for I2C, the chip select
- number for SPI.
-
-Example:
-
-wm8737: codec@1a {
- compatible = "wlf,wm8737";
- reg = <0x1a>;
-};
+++ /dev/null
-WM8753 audio CODEC
-
-This device supports both I2C and SPI (configured with pin strapping
-on the board).
-
-Required properties:
-
- - compatible : "wlf,wm8753"
-
- - reg : the I2C address of the device for I2C, the chip select
- number for SPI.
-
-Pins on the device (for linking into audio routes):
-
- * LOUT1
- * LOUT2
- * ROUT1
- * ROUT2
- * MONO1
- * MONO2
- * OUT3
- * OUT4
- * LINE1
- * LINE2
- * RXP
- * RXN
- * ACIN
- * ACOP
- * MIC1N
- * MIC1
- * MIC2N
- * MIC2
- * Mic Bias
-
-Example:
-
-wm8753: codec@1a {
- compatible = "wlf,wm8753";
- reg = <0x1a>;
-};
+++ /dev/null
-WM8960 audio CODEC
-
-This device supports I2C only.
-
-Required properties:
-
- - compatible : "wlf,wm8960"
-
- - reg : the I2C address of the device.
-
-Optional properties:
- - wlf,shared-lrclk: This is a boolean property. If present, the LRCM bit of
- R24 (Additional control 2) gets set, indicating that ADCLRC and DACLRC pins
- will be disabled only when ADC (Left and Right) and DAC (Left and Right)
- are disabled.
- When wm8960 works on synchronize mode and DACLRC pin is used to supply
- frame clock, it will no frame clock for captrue unless enable DAC to enable
- DACLRC pin. If shared-lrclk is present, no need to enable DAC for captrue.
-
- - wlf,capless: This is a boolean property. If present, OUT3 pin will be
- enabled and disabled together with HP_L and HP_R pins in response to jack
- detect events.
-
- - wlf,hp-cfg: A list of headphone jack detect configuration register values.
- The list must be 3 entries long.
- hp-cfg[0]: HPSEL[1:0] of R48 (Additional Control 4).
- hp-cfg[1]: {HPSWEN:HPSWPOL} of R24 (Additional Control 2).
- hp-cfg[2]: {TOCLKSEL:TOEN} of R23 (Additional Control 1).
-
- - wlf,gpio-cfg: A list of GPIO configuration register values.
- The list must be 2 entries long.
- gpio-cfg[0]: ALRCGPIO of R9 (Audio interface)
- gpio-cfg[1]: {GPIOPOL:GPIOSEL[2:0]} of R48 (Additional Control 4).
-
-Example:
-
-wm8960: codec@1a {
- compatible = "wlf,wm8960";
- reg = <0x1a>;
-
- wlf,shared-lrclk;
-};
+++ /dev/null
-WM1811/WM8994/WM8958 audio CODEC
-
-These devices support both I2C and SPI (configured with pin strapping
-on the board).
-
-Required properties:
-
- - compatible : One of "wlf,wm1811", "wlf,wm8994" or "wlf,wm8958".
-
- - reg : the I2C address of the device for I2C, the chip select
- number for SPI.
-
- - gpio-controller : Indicates this device is a GPIO controller.
- - #gpio-cells : Must be 2. The first cell is the pin number and the
- second cell is used to specify optional parameters (currently unused).
-
- - power supplies for the device, as covered in
- Documentation/devicetree/bindings/regulator/regulator.txt, depending
- on compatible:
- - for wlf,wm1811 and wlf,wm8958:
- AVDD1-supply, AVDD2-supply, DBVDD1-supply, DBVDD2-supply, DBVDD3-supply,
- DCVDD-supply, CPVDD-supply, SPKVDD1-supply, SPKVDD2-supply
- - for wlf,wm8994:
- AVDD1-supply, AVDD2-supply, DBVDD-supply, DCVDD-supply, CPVDD-supply,
- SPKVDD1-supply, SPKVDD2-supply
-
-Optional properties:
-
- - interrupts : The interrupt line the IRQ signal for the device is
- connected to. This is optional, if it is not connected then none
- of the interrupt related properties should be specified.
- - interrupt-controller : These devices contain interrupt controllers
- and may provide interrupt services to other devices if they have an
- interrupt line connected.
- - #interrupt-cells: the number of cells to describe an IRQ, this should be 2.
- The first cell is the IRQ number.
- The second cell is the flags, encoded as the trigger masks from
- Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
-
- - clocks : A list of up to two phandle and clock specifier pairs
- - clock-names : A list of clock names sorted in the same order as clocks.
- Valid clock names are "MCLK1" and "MCLK2".
-
- - wlf,gpio-cfg : A list of GPIO configuration register values. If absent,
- no configuration of these registers is performed. If any value is
- over 0xffff then the register will be left as default. If present 11
- values must be supplied.
-
- - wlf,micbias-cfg : Two MICBIAS register values for WM1811 or
- WM8958. If absent the register defaults will be used.
-
- - wlf,ldo1ena : GPIO specifier for control of LDO1ENA input to device.
- - wlf,ldo2ena : GPIO specifier for control of LDO2ENA input to device.
-
- - wlf,lineout1-se : If present LINEOUT1 is in single ended mode.
- - wlf,lineout2-se : If present LINEOUT2 is in single ended mode.
-
- - wlf,lineout1-feedback : If present LINEOUT1 has common mode feedback
- connected.
- - wlf,lineout2-feedback : If present LINEOUT2 has common mode feedback
- connected.
-
- - wlf,ldoena-always-driven : If present LDOENA is always driven.
-
- - wlf,spkmode-pu : If present enable the internal pull-up resistor on
- the SPKMODE pin.
-
- - wlf,csnaddr-pd : If present enable the internal pull-down resistor on
- the CS/ADDR pin.
-
-Pins on the device (for linking into audio routes):
-
- * IN1LN
- * IN1LP
- * IN2LN
- * IN2LP:VXRN
- * IN1RN
- * IN1RP
- * IN2RN
- * IN2RP:VXRP
- * SPKOUTLP
- * SPKOUTLN
- * SPKOUTRP
- * SPKOUTRN
- * HPOUT1L
- * HPOUT1R
- * HPOUT2P
- * HPOUT2N
- * LINEOUT1P
- * LINEOUT1N
- * LINEOUT2P
- * LINEOUT2N
-
-Example:
-
-wm8994: codec@1a {
- compatible = "wlf,wm8994";
- reg = <0x1a>;
-
- gpio-controller;
- #gpio-cells = <2>;
-
- lineout1-se;
-
- AVDD1-supply = <®ulator>;
- AVDD2-supply = <®ulator>;
- CPVDD-supply = <®ulator>;
- DBVDD-supply = <®ulator>;
- DCVDD-supply = <®ulator>;
- SPKVDD1-supply = <®ulator>;
- SPKVDD2-supply = <®ulator>;
-};
* Sound Blaster Live!
* Sound Blaster PCI 512
-* Emu APS (partially supported)
* Sound Blaster Audigy
-
+* E-MU APS (partially supported)
+* E-MU DAS
+
extin
bitmap of available external inputs for FX8010 (see below)
extout
These controls are built using the DSP instructions. They offer extended
functionality. Only the default built-in code in the ALSA driver is described
here. Note that the controls work as attenuators: the maximum value is the
-neutral position leaving the signal unchanged. Note that if the same destination
-is mentioned in multiple controls, the signal is accumulated and can be wrapped
-(set to maximal or minimal value without checking of overflow).
+neutral position leaving the signal unchanged. Note that if the same destination
+is mentioned in multiple controls, the signal is accumulated and can be clipped
+(set to maximal or minimal value without checking for overflow).
Explanation of used abbreviations:
analog to digital converter
I2S
one-way three wire serial bus for digital sound by Philips Semiconductors
- (this standard is used for connecting standalone DAC and ADC converters)
+ (this standard is used for connecting standalone D/A and A/D converters)
LFE
- low frequency effects (subwoofer signal)
+ low frequency effects (used as subwoofer signal)
AC97
- a chip containing an analog mixer, DAC and ADC converters
+ a chip containing an analog mixer, D/A and A/D converters
IEC958
S/PDIF
FX-bus
the EMU10K2 chip has an effect bus containing 64 accumulators.
- Each of the synthesizer voices can feed its output to these accumulators
- and the DSP microcontroller can operate with the resulting sum.
+ Each of the synthesizer voices can feed its output to these accumulators
+ and the DSP microcontroller can operate with the resulting sum.
name='PCM Front Playback Volume',index=0
----------------------------------------
name='IEC958 Optical Raw Playback Switch',index=0
-------------------------------------------------
If this switch is on, then the samples for the IEC958 (S/PDIF) digital
-output are taken only from the raw FX8010 PCM, otherwise standard front
-PCM samples are taken.
+output are taken only from the raw iec958 ALSA PCM device (which uses
+accumulators 20 and 21 for left and right PCM by default).
PCM stream related controls
name='EMU10K1 PCM Send Routing',index 0-31
------------------------------------------
-This control specifies the destination - FX-bus accumulators. There 24
-values with this mapping:
+This control specifies the destination - FX-bus accumulators. There are 24
+values in this mapping:
* 0 - mono, A destination (FX-bus 0-63), default 0
* 1 - mono, B destination (FX-bus 0-63), default 1
ftp://opensource.creative.com/pub/doc
-------------------------------------
+Note that the site is defunct, but the documents are available
+from various other locations.
+
LM4545.pdf
AC97 Codec
IEC958 (S/PDIF) raw PCM
=======================
-This PCM device (it's the 4th PCM device (index 3!) and first subdevice
+This PCM device (it's the 3rd PCM device (index 2!) and first subdevice
(index 0) for a given card) allows to forward 48kHz, stereo, 16-bit
little endian streams without any modifications to the digital output
(coaxial or optical). The universal interface allows the creation of up
These controls are built using the DSP instructions. They offer extended
functionality. Only the default built-in code in the ALSA driver is described
here. Note that the controls work as attenuators: the maximum value is the
-neutral position leaving the signal unchanged. Note that if the same destination
-is mentioned in multiple controls, the signal is accumulated and can be wrapped
-(set to maximal or minimal value without checking of overflow).
+neutral position leaving the signal unchanged. Note that if the same destination
+is mentioned in multiple controls, the signal is accumulated and can be clipped
+(set to maximal or minimal value without checking for overflow).
Explanation of used abbreviations:
analog to digital converter
I2S
one-way three wire serial bus for digital sound by Philips Semiconductors
- (this standard is used for connecting standalone DAC and ADC converters)
+ (this standard is used for connecting standalone D/A and A/D converters)
LFE
- low frequency effects (subwoofer signal)
+ low frequency effects (used as subwoofer signal)
AC97
- a chip containing an analog mixer, DAC and ADC converters
+ a chip containing an analog mixer, D/A and A/D converters
IEC958
S/PDIF
FX-bus
ftp://opensource.creative.com/pub/doc
-------------------------------------
+Note that the site is defunct, but the documents are available
+from various other locations.
+
LM4545.pdf
AC97 Codec
m2049.pdf
controls
dp-mst
realtek-pc-beep
+ intel-multi-link
--- /dev/null
+.. SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+.. include:: <isonum.txt>
+
+================================================
+HDAudio multi-link extensions on Intel platforms
+================================================
+
+:Copyright: |copy| 2023 Intel Corporation
+
+This file documents the 'multi-link structure' introduced in 2015 with
+the Skylake processor and recently extended in newer Intel platforms
+
+HDaudio existing link mapping (2015 addition in SkyLake)
+========================================================
+
+External HDAudio codecs are handled with link #0, while iDISP codec
+for HDMI/DisplayPort is handled with link #1.
+
+The only change to the 2015 definitions is the declaration of the
+LCAP.ALT=0x0 - since the ALT bit was previously reserved, this is a
+backwards-compatible change.
+
+LCTL.SPA and LCTL.CPA are automatically set when exiting reset. They
+are only used in existing drivers when the SCF value needs to be
+corrected.
+
+Basic structure for HDaudio codecs
+----------------------------------
+
+::
+
+ +-----------+
+ | ML cap #0 |
+ +-----------+
+ | ML cap #1 |---+
+ +-----------+ |
+ |
+ +--> 0x0 +---------------+ LCAP
+ | ALT=0 |
+ +---------------+
+ | S192 |
+ +---------------+
+ | S96 |
+ +---------------+
+ | S48 |
+ +---------------+
+ | S24 |
+ +---------------+
+ | S12 |
+ +---------------+
+ | S6 |
+ +---------------+
+
+ 0x4 +---------------+ LCTL
+ | INTSTS |
+ +---------------+
+ | CPA |
+ +---------------+
+ | SPA |
+ +---------------+
+ | SCF |
+ +---------------+
+
+ 0x8 +---------------+ LOSIDV
+ | L1OSIVD15 |
+ +---------------+
+ | L1OSIDV.. |
+ +---------------+
+ | L1OSIDV1 |
+ +---------------+
+
+ 0xC +---------------+ LSDIID
+ | SDIID14 |
+ +---------------+
+ | SDIID... |
+ +---------------+
+ | SDIID0 |
+ +---------------+
+
+SoundWire HDaudio extended link mapping
+=======================================
+
+A SoundWire extended link is identified when LCAP.ALT=1 and
+LEPTR.ID=0.
+
+DMA control uses the existing LOSIDV register.
+
+Changes include additional descriptions for enumeration that were not
+present in earlier generations.
+
+- multi-link synchronization: capabilities in LCAP.LSS and control in LSYNC
+- number of sublinks (manager IP) in LCAP.LSCOUNT
+- power management moved from SHIM to LCTL.SPA bits
+- hand-over to the DSP for access to multi-link registers, SHIM/IP with LCTL.OFLEN
+- mapping of SoundWire codecs to SDI ID bits
+- move of SHIM and Cadence registers to different offsets, with no
+ change in functionality. The LEPTR.PTR value is an offset from the
+ ML address, with a default value of 0x30000.
+
+Extended structure for SoundWire (assuming 4 Manager IP)
+--------------------------------------------------------
+
+::
+
+ +-----------+
+ | ML cap #0 |
+ +-----------+
+ | ML cap #1 |
+ +-----------+
+ | ML cap #2 |---+
+ +-----------+ |
+ |
+ +--> 0x0 +---------------+ LCAP
+ | ALT=1 |
+ +---------------+
+ | INTC |
+ +---------------+
+ | OFLS |
+ +---------------+
+ | LSS |
+ +---------------+
+ | SLCOUNT=4 |-----------+
+ +---------------+ |
+ |
+ 0x4 +---------------+ LCTL |
+ | INTSTS | |
+ +---------------+ |
+ | CPA (x bits) | |
+ +---------------+ |
+ | SPA (x bits) | |
+ +---------------+ for each sublink x
+ | INTEN | |
+ +---------------+ |
+ | OFLEN | |
+ +---------------+ |
+ |
+ 0x8 +---------------+ LOSIDV |
+ | L1OSIVD15 | |
+ +---------------+ |
+ | L1OSIDV.. | |
+ +---------------+ |
+ | L1OSIDV1 | +---+----------------------------------------------------------+
+ +---------------+ | |
+ v |
+ 0xC + 0x2 * x +---------------+ LSDIIDx +---> 0x30000 +-----------------+ 0x00030000 |
+ | SDIID14 | | | SoundWire SHIM | |
+ +---------------+ | | generic | |
+ | SDIID... | | +-----------------+ 0x00030100 |
+ +---------------+ | | SoundWire IP | |
+ | SDIID0 | | +-----------------+ 0x00036000 |
+ +---------------+ | | SoundWire SHIM | |
+ | | vendor-specific | |
+ 0x1C +---------------+ LSYNC | +-----------------+ |
+ | CMDSYNC | | v
+ +---------------+ | +-----------------+ 0x00030000 + 0x8000 * x
+ | SYNCGO | | | SoundWire SHIM |
+ +---------------+ | | generic |
+ | SYNCPU | | +-----------------+ 0x00030100 + 0x8000 * x
+ +---------------+ | | SoundWire IP |
+ | SYNPRD | | +-----------------+ 0x00036000 + 0x8000 * x
+ +---------------+ | | SoundWire SHIM |
+ | | vendor-specific |
+ 0x20 +---------------+ LEPTR | +-----------------+
+ | ID = 0 | |
+ +---------------+ |
+ | VER | |
+ +---------------+ |
+ | PTR |------------+
+ +---------------+
+
+
+DMIC HDaudio extended link mapping
+==================================
+
+A DMIC extended link is identified when LCAP.ALT=1 and
+LEPTR.ID=0xC1 are set.
+
+DMA control uses the existing LOSIDV register
+
+Changes include additional descriptions for enumeration that were not
+present in earlier generations.
+
+- multi-link synchronization: capabilities in LCAP.LSS and control in LSYNC
+- power management with LCTL.SPA bits
+- hand-over to the DSP for access to multi-link registers, SHIM/IP with LCTL.OFLEN
+
+- move of DMIC registers to different offsets, with no change in
+ functionality. The LEPTR.PTR value is an offset from the ML
+ address, with a default value of 0x10000.
+
+Extended structure for DMIC
+---------------------------
+
+::
+
+ +-----------+
+ | ML cap #0 |
+ +-----------+
+ | ML cap #1 |
+ +-----------+
+ | ML cap #2 |---+
+ +-----------+ |
+ |
+ +--> 0x0 +---------------+ LCAP
+ | ALT=1 |
+ +---------------+
+ | INTC |
+ +---------------+
+ | OFLS |
+ +---------------+
+ | SLCOUNT=1 |
+ +---------------+
+
+ 0x4 +---------------+ LCTL
+ | INTSTS |
+ +---------------+
+ | CPA |
+ +---------------+
+ | SPA |
+ +---------------+
+ | INTEN |
+ +---------------+
+ | OFLEN |
+ +---------------+ +---> 0x10000 +-----------------+ 0x00010000
+ | | DMIC SHIM |
+ 0x8 +---------------+ LOSIDV | | generic |
+ | L1OSIVD15 | | +-----------------+ 0x00010100
+ +---------------+ | | DMIC IP |
+ | L1OSIDV.. | | +-----------------+ 0x00016000
+ +---------------+ | | DMIC SHIM |
+ | L1OSIDV1 | | | vendor-specific |
+ +---------------+ | +-----------------+
+ |
+ 0x20 +---------------+ LEPTR |
+ | ID = 0xC1 | |
+ +---------------+ |
+ | VER | |
+ +---------------+ |
+ | PTR |-----------+
+ +---------------+
+
+
+SSP HDaudio extended link mapping
+=================================
+
+A DMIC extended link is identified when LCAP.ALT=1 and
+LEPTR.ID=0xC0 are set.
+
+DMA control uses the existing LOSIDV register
+
+Changes include additional descriptions for enumeration and control that were not
+present in earlier generations:
+- number of sublinks (SSP IP instances) in LCAP.LSCOUNT
+- power management moved from SHIM to LCTL.SPA bits
+- hand-over to the DSP for access to multi-link registers, SHIM/IP
+with LCTL.OFLEN
+- move of SHIM and SSP IP registers to different offsets, with no
+change in functionality. The LEPTR.PTR value is an offset from the ML
+address, with a default value of 0x28000.
+
+Extended structure for SSP (assuming 3 instances of the IP)
+-----------------------------------------------------------
+
+::
+
+ +-----------+
+ | ML cap #0 |
+ +-----------+
+ | ML cap #1 |
+ +-----------+
+ | ML cap #2 |---+
+ +-----------+ |
+ |
+ +--> 0x0 +---------------+ LCAP
+ | ALT=1 |
+ +---------------+
+ | INTC |
+ +---------------+
+ | OFLS |
+ +---------------+
+ | SLCOUNT=3 |-------------------------for each sublink x -------------------------+
+ +---------------+ |
+ |
+ 0x4 +---------------+ LCTL |
+ | INTSTS | |
+ +---------------+ |
+ | CPA (x bits) | |
+ +---------------+ |
+ | SPA (x bits) | |
+ +---------------+ |
+ | INTEN | |
+ +---------------+ |
+ | OFLEN | |
+ +---------------+ +---> 0x28000 +-----------------+ 0x00028000 |
+ | | SSP SHIM | |
+ 0x8 +---------------+ LOSIDV | | generic | |
+ | L1OSIVD15 | | +-----------------+ 0x00028100 |
+ +---------------+ | | SSP IP | |
+ | L1OSIDV.. | | +-----------------+ 0x00028C00 |
+ +---------------+ | | SSP SHIM | |
+ | L1OSIDV1 | | | vendor-specific | |
+ +---------------+ | +-----------------+ |
+ | v
+ 0x20 +---------------+ LEPTR | +-----------------+ 0x00028000 + 0x1000 * x
+ | ID = 0xC0 | | | SSP SHIM |
+ +---------------+ | | generic |
+ | VER | | +-----------------+ 0x00028100 + 0x1000 * x
+ +---------------+ | | SSP IP |
+ | PTR |-----------+ +-----------------+ 0x00028C00 + 0x1000 * x
+ +---------------+ | SSP SHIM |
+ | vendor-specific |
+ +-----------------+
low-level driver implementation details. It only describes the standard
way to write a PCI sound driver on ALSA.
-This document is still a draft version. Any feedback and corrections,
-please!!
-
File Tree Structure
===================
General
-------
-The file tree structure of ALSA driver is depicted below.
-
-::
+The file tree structure of ALSA driver is depicted below::
sound
/core
core/oss
~~~~~~~~
-The codes for PCM and mixer OSS emulation modules are stored in this
-directory. The rawmidi OSS emulation is included in the ALSA rawmidi
+The code for OSS PCM and mixer emulation modules is stored in this
+directory. The OSS rawmidi emulation is included in the ALSA rawmidi
code since it's quite small. The sequencer code is stored in
``core/seq/oss`` directory (see `below <core/seq/oss_>`__).
This directory and its sub-directories are for the ALSA sequencer. This
directory contains the sequencer core and primary sequencer modules such
-like snd-seq-midi, snd-seq-virmidi, etc. They are compiled only when
+as snd-seq-midi, snd-seq-virmidi, etc. They are compiled only when
``CONFIG_SND_SEQUENCER`` is set in the kernel config.
core/seq/oss
~~~~~~~~~~~~
-This contains the OSS sequencer emulation codes.
+This contains the OSS sequencer emulation code.
include directory
-----------------
This is the place for the public header files of ALSA drivers, which are
-to be exported to user-space, or included by several files at different
+to be exported to user-space, or included by several files in different
directories. Basically, the private header files should not be placed in
this directory, but you may still find files there, due to historical
reasons :)
This directory contains code shared among different drivers on different
architectures. They are hence supposed not to be architecture-specific.
-For example, the dummy pcm driver and the serial MIDI driver are found
+For example, the dummy PCM driver and the serial MIDI driver are found
in this directory. In the sub-directories, there is code for components
which are independent from bus and cpu architectures.
usb directory
-------------
-This directory contains the USB-audio driver. In the latest version, the
-USB MIDI driver is integrated in the usb-audio driver.
+This directory contains the USB-audio driver.
+The USB MIDI driver is integrated in the usb-audio driver.
pcmcia directory
----------------
oss directory
-------------
-Here contains OSS/Lite codes.
-All codes have been deprecated except for dmasound on m68k as of
-writing this.
+This contains OSS/Lite code.
+At the time of writing, all code has been removed except for dmasound
+on m68k.
Basic Flow for PCI Drivers
error:
snd_card_free(card);
- return err;
+ return err;
}
/* destructor -- see the "Destructor" sub-section */
Each time the ``probe`` callback is called, check the availability of
the device. If not available, simply increment the device index and
-returns. dev will be incremented also later (`step 7
+return. dev will be incremented also later (`step 7
<7) Set the PCI driver data and return zero._>`__).
2) Create a card instance
3) Create a main component
~~~~~~~~~~~~~~~~~~~~~~~~~~
-In this part, the PCI resources are allocated.
-
-::
+In this part, the PCI resources are allocated::
struct mychip *chip;
....
When something goes wrong, the probe function needs to deal with the
error. In this example, we have a single error handling path placed
-at the end of the function.
-
-::
+at the end of the function::
error:
snd_card_free(card);
- return err;
+ return err;
Since each component can be properly freed, the single
:c:func:`snd_card_free()` call should suffice in most cases.
Destructor
----------
-The destructor, remove callback, simply releases the card instance. Then
-the ALSA middle layer will release all the attached components
+The destructor, the remove callback, simply releases the card instance.
+Then the ALSA middle layer will release all the attached components
automatically.
-It would be typically just calling :c:func:`snd_card_free()`:
-
-::
+It would be typically just calling :c:func:`snd_card_free()`::
static void snd_mychip_remove(struct pci_dev *pci)
{
------------
For the above example, at least the following include files are
-necessary.
-
-::
+necessary::
#include <linux/init.h>
#include <linux/pci.h>
resources at destruction.
As mentioned above, to create a card instance, call
-:c:func:`snd_card_new()`.
-
-::
+:c:func:`snd_card_new()`::
struct snd_card *card;
int err;
can be a PCM instance, a control interface, a raw MIDI interface, etc.
Each such instance has one component entry.
-A component can be created via :c:func:`snd_device_new()`
-function.
-
-::
+A component can be created via the :c:func:`snd_device_new()`
+function::
snd_device_new(card, SNDRV_DEV_XXX, chip, &ops);
argument. This pointer (``chip`` in the above example) is used as the
identifier for the instance.
-Each pre-defined ALSA component such as ac97 and pcm calls
+Each pre-defined ALSA component such as AC97 and PCM calls
:c:func:`snd_device_new()` inside its constructor. The destructor
for each component is defined in the callback pointers. Hence, you don't
need to take care of calling a destructor for such a component.
------------------
Chip-specific information, e.g. the I/O port address, its resource
-pointer, or the irq number, is stored in the chip-specific record.
-
-::
+pointer, or the irq number, is stored in the chip-specific record::
struct mychip {
....
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As mentioned above, you can pass the extra-data-length to the 5th
-argument of :c:func:`snd_card_new()`, i.e.
-
-::
+argument of :c:func:`snd_card_new()`, e.g.::
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
sizeof(struct mychip), &card);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After allocating a card instance via :c:func:`snd_card_new()`
-(with ``0`` on the 4th arg), call :c:func:`kzalloc()`.
-
-::
+(with ``0`` on the 4th arg), call :c:func:`kzalloc()`::
struct snd_card *card;
struct mychip *chip;
};
-Then, set the card pointer in the returned chip instance.
-
-::
+Then, set the card pointer in the returned chip instance::
chip->card = card;
Next, initialize the fields, and register this chip record as a
-low-level device with a specified ``ops``,
-
-::
+low-level device with a specified ``ops``::
static const struct snd_device_ops ops = {
.dev_free = snd_mychip_dev_free,
snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
:c:func:`snd_mychip_dev_free()` is the device-destructor
-function, which will call the real destructor.
-
-::
+function, which will call the real destructor::
static int snd_mychip_dev_free(struct snd_device *device)
{
where :c:func:`snd_mychip_free()` is the real destructor.
-The demerit of this method is the obviously more amount of codes.
-The merit is, however, you can trigger the own callback at registering
-and disconnecting the card via setting in snd_device_ops.
-About the registering and disconnecting the card, see the subsections
+The demerit of this method is the obviously larger amount of code.
+The merit is, however, that you can trigger your own callback at
+registering and disconnecting the card via a setting in snd_device_ops.
+About registering and disconnecting the card, see the subsections
below.
-----------------
In this section, we'll complete the chip-specific constructor,
-destructor and PCI entries. Example code is shown first, below.
-
-::
+destructor and PCI entries. Example code is shown first, below::
struct mychip {
struct snd_card *card;
accessed I/O range. In some cases, you might need to call
:c:func:`pci_set_master()` function, too.
-Suppose the 28bit mask, and the code to be added would be like:
-
-::
+Suppose a 28bit mask, the code to be added would look like::
err = pci_enable_device(pci);
if (err < 0)
Now assume that the PCI device has an I/O port with 8 bytes and an
interrupt. Then struct mychip will have the
-following fields:
-
-::
+following fields::
struct mychip {
struct snd_card *card;
For an I/O port (and also a memory region), you need to have the
resource pointer for the standard resource management. For an irq, you
have to keep only the irq number (integer). But you need to initialize
-this number as -1 before actual allocation, since irq 0 is valid. The
+this number to -1 before actual allocation, since irq 0 is valid. The
port address and its resource pointer can be initialized as null by
:c:func:`kzalloc()` automatically, so you don't have to take care of
resetting them.
-The allocation of an I/O port is done like this:
-
-::
+The allocation of an I/O port is done like this::
err = pci_request_regions(pci, "My Chip");
if (err < 0) {
must be released via :c:func:`kfree()`, but there is a problem with
this. This issue will be explained later.
-The allocation of an interrupt source is done like this:
-
-::
+The allocation of an interrupt source is done like this::
if (request_irq(pci->irq, snd_mychip_interrupt,
IRQF_SHARED, KBUILD_MODNAME, chip)) {
I won't give details about the interrupt handler at this point, but at
least its appearance can be explained now. The interrupt handler looks
-usually like the following:
-
-::
+usually as follows::
static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
{
}
After requesting the IRQ, you can passed it to ``card->sync_irq``
-field:
-::
+field::
card->irq = chip->irq;
-This allows PCM core automatically performing
-:c:func:`synchronize_irq()` at the necessary timing like ``hw_free``.
+This allows the PCM core to automatically call
+:c:func:`synchronize_irq()` at the right time, like before ``hw_free``.
See the later section `sync_stop callback`_ for details.
Now let's write the corresponding destructor for the resources above.
so the disabling code is not written here.
To release the resources, the “check-and-release” method is a safer way.
-For the interrupt, do like this:
-
-::
+For the interrupt, do like this::
if (chip->irq >= 0)
free_irq(chip->irq, chip);
:c:func:`pci_request_regions()` like in this example, release the
resource(s) using the corresponding function,
:c:func:`pci_release_region()` or
-:c:func:`pci_release_regions()`.
-
-::
+:c:func:`pci_release_regions()`::
pci_release_regions(chip->pci);
:c:func:`request_mem_region()`, you can release it via
:c:func:`release_resource()`. Suppose that you keep the resource
pointer returned from :c:func:`request_region()` in
-chip->res_port, the release procedure looks like:
-
-::
+chip->res_port, the release procedure looks like::
release_and_free_resource(chip->res_port);
Don't forget to call :c:func:`pci_disable_device()` before the
end.
-And finally, release the chip-specific record.
-
-::
+And finally, release the chip-specific record::
kfree(chip);
-We didn't implement the hardware disabling part in the above. If you
+We didn't implement the hardware disabling part above. If you
need to do this, please note that the destructor may be called even
before the initialization of the chip is completed. It would be better
to have a flag to skip hardware disabling if the hardware was not
initialized yet.
When the chip-data is assigned to the card using
-:c:func:`snd_device_new()` with ``SNDRV_DEV_LOWLELVEL`` , its
-destructor is called at the last. That is, it is assured that all other
+:c:func:`snd_device_new()` with ``SNDRV_DEV_LOWLELVEL``, its
+destructor is called last. That is, it is assured that all other
components like PCMs and controls have already been released. You don't
have to stop PCMs, etc. explicitly, but just call low-level hardware
stopping.
The management of a memory-mapped region is almost as same as the
-management of an I/O port. You'll need three fields like the
-following:
-
-::
+management of an I/O port. You'll need two fields as follows::
struct mychip {
....
void __iomem *iobase_virt;
};
-and the allocation would be like below:
-
-::
+and the allocation would look like below::
err = pci_request_regions(pci, "My Chip");
if (err < 0) {
chip->iobase_virt = ioremap(chip->iobase_phys,
pci_resource_len(pci, 0));
-and the corresponding destructor would be:
-
-::
+and the corresponding destructor would be::
static int snd_mychip_free(struct mychip *chip)
{
}
Of course, a modern way with :c:func:`pci_iomap()` will make things a
-bit easier, too.
-
-::
+bit easier, too::
err = pci_request_regions(pci, "My Chip");
if (err < 0) {
this chipset. It's a table of PCI vendor/device ID number, and some
masks.
-For example,
-
-::
+For example::
static struct pci_device_id snd_mychip_ids[] = {
{ PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_BAR,
all-zero entry.
Then, prepare the struct pci_driver
-record:
-
-::
+record::
static struct pci_driver driver = {
.name = KBUILD_MODNAME,
The ``probe`` and ``remove`` functions have already been defined in
the previous sections. The ``name`` field is the name string of this
-device. Note that you must not use a slash “/” in this string.
-
-And at last, the module entries:
+device. Note that you must not use slashes (“/”) in this string.
-::
+And at last, the module entries::
static int __init alsa_card_mychip_init(void)
{
for each driver to implement the low-level functions to access its
hardware.
-For accessing to the PCM layer, you need to include ``<sound/pcm.h>``
+To access the PCM layer, you need to include ``<sound/pcm.h>``
first. In addition, ``<sound/pcm_params.h>`` might be needed if you
-access to some functions related with hw_param.
+access some functions related with hw_param.
-Each card device can have up to four pcm instances. A pcm instance
-corresponds to a pcm device file. The limitation of number of instances
-comes only from the available bit size of the Linux's device numbers.
-Once when 64bit device number is used, we'll have more pcm instances
+Each card device can have up to four PCM instances. A PCM instance
+corresponds to a PCM device file. The limitation of number of instances
+comes only from the available bit size of Linux' device numbers.
+Once 64bit device numbers are used, we'll have more PCM instances
available.
-A pcm instance consists of pcm playback and capture streams, and each
-pcm stream consists of one or more pcm substreams. Some soundcards
+A PCM instance consists of PCM playback and capture streams, and each
+PCM stream consists of one or more PCM substreams. Some soundcards
support multiple playback functions. For example, emu10k1 has a PCM
playback of 32 stereo substreams. In this case, at each open, a free
substream is (usually) automatically chosen and opened. Meanwhile, when
-only one substream exists and it was already opened, the successful open
+only one substream exists and it was already opened, a subsequent open
will either block or error with ``EAGAIN`` according to the file open
mode. But you don't have to care about such details in your driver. The
PCM middle layer will take care of such work.
-----------------
The example code below does not include any hardware access routines but
-shows only the skeleton, how to build up the PCM interfaces.
-
-::
+shows only the skeleton, how to build up the PCM interfaces::
#include <sound/pcm.h>
....
PCM Constructor
---------------
-A pcm instance is allocated by the :c:func:`snd_pcm_new()`
-function. It would be better to create a constructor for pcm, namely,
-
-::
+A PCM instance is allocated by the :c:func:`snd_pcm_new()`
+function. It would be better to create a constructor for the PCM, namely::
static int snd_mychip_new_pcm(struct mychip *chip)
{
pcm->private_data = chip;
strcpy(pcm->name, "My Chip");
chip->pcm = pcm;
- ....
+ ...
return 0;
}
-The :c:func:`snd_pcm_new()` function takes four arguments. The
-first argument is the card pointer to which this pcm is assigned, and
+The :c:func:`snd_pcm_new()` function takes six arguments. The
+first argument is the card pointer to which this PCM is assigned, and
the second is the ID string.
The third argument (``index``, 0 in the above) is the index of this new
-pcm. It begins from zero. If you create more than one pcm instances,
+PCM. It begins from zero. If you create more than one PCM instances,
specify the different numbers in this argument. For example, ``index =
1`` for the second PCM device.
numbers, but they must be handled properly in open/close, etc.
callbacks. When you need to know which substream you are referring to,
then it can be obtained from struct snd_pcm_substream data passed to each
-callback as follows:
-
-::
+callback as follows::
struct snd_pcm_substream *substream;
int index = substream->number;
-After the pcm is created, you need to set operators for each pcm stream.
-
-::
+After the PCM is created, you need to set operators for each PCM stream::
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_mychip_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_mychip_capture_ops);
-The operators are defined typically like this:
-
-::
+The operators are defined typically like this::
static struct snd_pcm_ops snd_mychip_playback_ops = {
.open = snd_mychip_pcm_open,
After setting the operators, you probably will want to pre-allocate the
buffer and set up the managed allocation mode.
-For that, simply call the following:
-
-::
+For that, simply call the following::
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
&chip->pci->dev,
64*1024, 64*1024);
-It will allocate a buffer up to 64kB as default. Buffer management
+It will allocate a buffer up to 64kB by default. Buffer management
details will be described in the later section `Buffer and Memory
Management`_.
-Additionally, you can set some extra information for this pcm in
+Additionally, you can set some extra information for this PCM in
``pcm->info_flags``. The available values are defined as
``SNDRV_PCM_INFO_XXX`` in ``<sound/asound.h>``, which is used for the
hardware definition (described later). When your soundchip supports only
-half-duplex, specify like this:
-
-::
+half-duplex, specify it like this::
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
... And the Destructor?
-----------------------
-The destructor for a pcm instance is not always necessary. Since the pcm
+The destructor for a PCM instance is not always necessary. Since the PCM
device will be released by the middle layer code automatically, you
don't have to call the destructor explicitly.
The destructor would be necessary if you created special records
internally and needed to release them. In such a case, set the
-destructor function to ``pcm->private_free``:
-
-::
+destructor function to ``pcm->private_free``::
static void mychip_pcm_free(struct snd_pcm *pcm)
{
When the PCM substream is opened, a PCM runtime instance is allocated
and assigned to the substream. This pointer is accessible via
``substream->runtime``. This runtime pointer holds most information you
-need to control the PCM: the copy of hw_params and sw_params
+need to control the PCM: a copy of hw_params and sw_params
configurations, the buffer pointers, mmap records, spinlocks, etc.
The definition of runtime instance is found in ``<sound/pcm.h>``. Here
-are the contents of this file:
-
-::
+is the relevant part of this file::
struct _snd_pcm_runtime {
/* -- Status -- */
unsigned int period_step;
unsigned int sleep_min; /* min ticks to sleep */
snd_pcm_uframes_t start_threshold;
- snd_pcm_uframes_t stop_threshold;
- snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
- noise is nearest than this */
- snd_pcm_uframes_t silence_size; /* Silence filling size */
+ /*
+ * The following two thresholds alleviate playback buffer underruns; when
+ * hw_avail drops below the threshold, the respective action is triggered:
+ */
+ snd_pcm_uframes_t stop_threshold; /* - stop playback */
+ snd_pcm_uframes_t silence_threshold; /* - pre-fill buffer with silence */
+ snd_pcm_uframes_t silence_size; /* max size of silence pre-fill; when >= boundary,
+ * fill played area with silence immediately */
snd_pcm_uframes_t boundary; /* pointers wrap point */
- snd_pcm_uframes_t silenced_start;
- snd_pcm_uframes_t silenced_size;
+ /* internal data of auto-silencer */
+ snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
+ snd_pcm_uframes_t silence_filled; /* size filled with silence */
snd_pcm_sync_id_t sync; /* hardware synchronization ID */
The hardware descriptor (struct snd_pcm_hardware) contains the definitions of
the fundamental hardware configuration. Above all, you'll need to define this
-in the `PCM open callback`_. Note that the runtime instance holds the copy of
-the descriptor, not the pointer to the existing descriptor. That is,
+in the `PCM open callback`_. Note that the runtime instance holds a copy of
+the descriptor, not a pointer to the existing descriptor. That is,
in the open callback, you can modify the copied descriptor
(``runtime->hw``) as you need. For example, if the maximum number of
channels is 1 only on some chip models, you can still use the same
-hardware descriptor and change the channels_max later:
-
-::
+hardware descriptor and change the channels_max later::
struct snd_pcm_runtime *runtime = substream->runtime;
...
if (chip->model == VERY_OLD_ONE)
runtime->hw.channels_max = 1;
-Typically, you'll have a hardware descriptor as below:
-
-::
+Typically, you'll have a hardware descriptor as below::
static struct snd_pcm_hardware snd_mychip_playback_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
};
- The ``info`` field contains the type and capabilities of this
- pcm. The bit flags are defined in ``<sound/asound.h>`` as
+ PCM. The bit flags are defined in ``<sound/asound.h>`` as
``SNDRV_PCM_INFO_XXX``. Here, at least, you have to specify whether
- the mmap is supported and which interleaved format is
+ mmap is supported and which interleaving formats are
supported. When the hardware supports mmap, add the
``SNDRV_PCM_INFO_MMAP`` flag here. When the hardware supports the
- interleaved or the non-interleaved formats,
+ interleaved or the non-interleaved formats, the
``SNDRV_PCM_INFO_INTERLEAVED`` or ``SNDRV_PCM_INFO_NONINTERLEAVED``
flag must be set, respectively. If both are supported, you can set
both, too.
In the above example, ``MMAP_VALID`` and ``BLOCK_TRANSFER`` are
specified for the OSS mmap mode. Usually both are set. Of course,
- ``MMAP_VALID`` is set only if the mmap is really supported.
+ ``MMAP_VALID`` is set only if mmap is really supported.
The other possible flags are ``SNDRV_PCM_INFO_PAUSE`` and
- ``SNDRV_PCM_INFO_RESUME``. The ``PAUSE`` bit means that the pcm
+ ``SNDRV_PCM_INFO_RESUME``. The ``PAUSE`` bit means that the PCM
supports the “pause” operation, while the ``RESUME`` bit means that
- the pcm supports the full “suspend/resume” operation. If the
+ the PCM supports the full “suspend/resume” operation. If the
``PAUSE`` flag is set, the ``trigger`` callback below must handle
the corresponding (pause push/release) commands. The suspend/resume
trigger commands can be defined even without the ``RESUME``
- flag. See `Power Management`_ section for details.
+ flag. See the `Power Management`_ section for details.
When the PCM substreams can be synchronized (typically,
- synchronized start/stop of a playback and a capture streams), you
+ synchronized start/stop of a playback and a capture stream), you
can give ``SNDRV_PCM_INFO_SYNC_START``, too. In this case, you'll
need to check the linked-list of PCM substreams in the trigger
- callback. This will be described in the later section.
+ callback. This will be described in a later section.
-- ``formats`` field contains the bit-flags of supported formats
+- The ``formats`` field contains the bit-flags of supported formats
(``SNDRV_PCM_FMTBIT_XXX``). If the hardware supports more than one
format, give all or'ed bits. In the example above, the signed 16bit
little-endian format is specified.
-- ``rates`` field contains the bit-flags of supported rates
+- The ``rates`` field contains the bit-flags of supported rates
(``SNDRV_PCM_RATE_XXX``). When the chip supports continuous rates,
- pass ``CONTINUOUS`` bit additionally. The pre-defined rate bits are
- provided only for typical rates. If your chip supports
+ pass the ``CONTINUOUS`` bit additionally. The pre-defined rate bits
+ are provided only for typical rates. If your chip supports
unconventional rates, you need to add the ``KNOT`` bit and set up
the hardware constraint manually (explained later).
- ``rate_min`` and ``rate_max`` define the minimum and maximum sample
rate. This should correspond somehow to ``rates`` bits.
-- ``channels_min`` and ``channels_max`` define, as you might already
+- ``channels_min`` and ``channels_max`` define, as you might have already
expected, the minimum and maximum number of channels.
- ``buffer_bytes_max`` defines the maximum buffer size in
number of periods in the buffer.
The “period” is a term that corresponds to a fragment in the OSS
- world. The period defines the size at which a PCM interrupt is
- generated. This size strongly depends on the hardware. Generally,
- the smaller period size will give you more interrupts, that is,
- more controls. In the case of capture, this size defines the input
- latency. On the other hand, the whole buffer size defines the
- output latency for the playback direction.
+ world. The period defines the point at which a PCM interrupt is
+ generated. This point strongly depends on the hardware. Generally,
+ a smaller period size will give you more interrupts, which results
+ in being able to fill/drain the buffer more timely. In the case of
+ capture, this size defines the input latency. On the other hand,
+ the whole buffer size defines the output latency for the playback
+ direction.
- There is also a field ``fifo_size``. This specifies the size of the
- hardware FIFO, but currently it is neither used in the driver nor
+ hardware FIFO, but currently it is neither used by the drivers nor
in the alsa-lib. So, you can ignore this field.
PCM Configurations
are stored in “frames” in the runtime. In the ALSA world, ``1 frame =
channels \* samples-size``. For conversion between frames and bytes,
you can use the :c:func:`frames_to_bytes()` and
-:c:func:`bytes_to_frames()` helper functions.
-
-::
+:c:func:`bytes_to_frames()` helper functions::
period_bytes = frames_to_bytes(runtime, runtime->period_size);
Also, many software parameters (sw_params) are stored in frames, too.
-Please check the type of the field. ``snd_pcm_uframes_t`` is for the
-frames as unsigned integer while ``snd_pcm_sframes_t`` is for the
+Please check the type of the field. ``snd_pcm_uframes_t`` is for
+frames as unsigned integer while ``snd_pcm_sframes_t`` is for
frames as signed integer.
DMA Buffer Information
~~~~~~~~~~~~~~~~~~~~~~
-The DMA buffer is defined by the following four fields, ``dma_area``,
-``dma_addr``, ``dma_bytes`` and ``dma_private``. The ``dma_area``
+The DMA buffer is defined by the following four fields: ``dma_area``,
+``dma_addr``, ``dma_bytes`` and ``dma_private``. ``dma_area``
holds the buffer pointer (the logical address). You can call
:c:func:`memcpy()` from/to this pointer. Meanwhile, ``dma_addr`` holds
the physical address of the buffer. This field is specified only when
-the buffer is a linear buffer. ``dma_bytes`` holds the size of buffer
-in bytes. ``dma_private`` is used for the ALSA DMA allocator.
+the buffer is a linear buffer. ``dma_bytes`` holds the size of the
+buffer in bytes. ``dma_private`` is used for the ALSA DMA allocator.
If you use either the managed buffer allocation mode or the standard
API function :c:func:`snd_pcm_lib_malloc_pages()` for allocating the buffer,
these fields are set by the ALSA middle layer, and you should *not*
change them by yourself. You can read them but not write them. On the
other hand, if you want to allocate the buffer by yourself, you'll
-need to manage it in hw_params callback. At least, ``dma_bytes`` is
+need to manage it in the hw_params callback. At least, ``dma_bytes`` is
mandatory. ``dma_area`` is necessary when the buffer is mmapped. If
your driver doesn't support mmap, this field is not
necessary. ``dma_addr`` is also optional. You can use dma_private as
~~~~~~~~~~~~~~
The running status can be referred via ``runtime->status``. This is
-the pointer to the struct snd_pcm_mmap_status record.
+a pointer to a struct snd_pcm_mmap_status record.
For example, you can get the current
DMA hardware pointer via ``runtime->status->hw_ptr``.
The DMA application pointer can be referred via ``runtime->control``,
-which points to the struct snd_pcm_mmap_control record.
-However, accessing directly to this value is not recommended.
+which points to a struct snd_pcm_mmap_control record.
+However, accessing this value directly is not recommended.
Private Data
~~~~~~~~~~~~
``runtime->private_data``. Usually, this is done in the `PCM open
callback`_. Don't mix this with ``pcm->private_data``. The
``pcm->private_data`` usually points to the chip instance assigned
-statically at the creation of PCM, while the ``runtime->private_data``
-points to a dynamic data structure created at the PCM open
-callback.
-
-::
+statically at creation time of the PCM device, while
+``runtime->private_data``
+points to a dynamic data structure created in the PCM open
+callback::
static int snd_xxx_open(struct snd_pcm_substream *substream)
{
Operators
---------
-OK, now let me give details about each pcm callback (``ops``). In
+OK, now let me give details about each PCM callback (``ops``). In
general, every callback must return 0 if successful, or a negative
error number such as ``-EINVAL``. To choose an appropriate error
number, it is advised to check what value other parts of the kernel
return when the same kind of request fails.
-The callback function takes at least the argument with
+Each callback function takes at least one argument containing a
struct snd_pcm_substream pointer. To retrieve the chip
record from the given substream instance, you can use the following
-macro.
-
-::
+macro::
- int xxx() {
+ int xxx(...) {
struct mychip *chip = snd_pcm_substream_chip(substream);
....
}
static int snd_xxx_open(struct snd_pcm_substream *substream);
-This is called when a pcm substream is opened.
+This is called when a PCM substream is opened.
At least, here you have to initialize the ``runtime->hw``
-record. Typically, this is done by like this:
-
-::
+record. Typically, this is done like this::
static int snd_xxx_open(struct snd_pcm_substream *substream)
{
where ``snd_mychip_playback_hw`` is the pre-defined hardware
description.
-You can allocate a private data in this callback, as described in
+You can allocate private data in this callback, as described in the
`Private Data`_ section.
If the hardware configuration needs more constraints, set the hardware
static int snd_xxx_close(struct snd_pcm_substream *substream);
-Obviously, this is called when a pcm substream is closed.
-
-Any private instance for a pcm substream allocated in the ``open``
-callback will be released here.
+Obviously, this is called when a PCM substream is closed.
-::
+Any private instance for a PCM substream allocated in the ``open``
+callback will be released here::
static int snd_xxx_close(struct snd_pcm_substream *substream)
{
ioctl callback
~~~~~~~~~~~~~~
-This is used for any special call to pcm ioctls. But usually you can
-leave it as NULL, then PCM core calls the generic ioctl callback
-function :c:func:`snd_pcm_lib_ioctl()`. If you need to deal with the
+This is used for any special call to PCM ioctls. But usually you can
+leave it NULL, then the PCM core calls the generic ioctl callback
+function :c:func:`snd_pcm_lib_ioctl()`. If you need to deal with a
unique setup of channel info or reset procedure, you can pass your own
callback function here.
static int snd_xxx_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params);
-This is called when the hardware parameter (``hw_params``) is set up
+This is called when the hardware parameters (``hw_params``) are set up
by the application, that is, once when the buffer size, the period
-size, the format, etc. are defined for the pcm substream.
+size, the format, etc. are defined for the PCM substream.
Many hardware setups should be done in this callback, including the
allocation of buffers.
-Parameters to be initialized are retrieved by
+Parameters to be initialized are retrieved by the
:c:func:`params_xxx()` macros.
-When you set up the managed buffer allocation mode for the substream,
+When you choose managed buffer allocation mode for the substream,
a buffer is already allocated before this callback gets
called. Alternatively, you can call a helper function below for
-allocating the buffer, too.
-
-::
+allocating the buffer::
snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
DMA buffers have been pre-allocated. See the section `Buffer Types`_
for more details.
-Note that this and ``prepare`` callbacks may be called multiple times
-per initialization. For example, the OSS emulation may call these
+Note that this one and the ``prepare`` callback may be called multiple
+times per initialization. For example, the OSS emulation may call these
callbacks at each change via its ioctl.
Thus, you need to be careful not to allocate the same buffers many
above many times is OK. It will release the previous buffer
automatically when it was already allocated.
-Another note is that this callback is non-atomic (schedulable) as
+Another note is that this callback is non-atomic (schedulable) by
default, i.e. when no ``nonatomic`` flag set. This is important,
because the ``trigger`` callback is atomic (non-schedulable). That is,
-mutexes or any schedule-related functions are not available in
+mutexes or any schedule-related functions are not available in the
``trigger`` callback. Please see the subsection Atomicity_ for
details.
This function is always called before the close callback is called.
Also, the callback may be called multiple times, too. Keep track
-whether the resource was already released.
+whether each resource was already released.
-When you have set up the managed buffer allocation mode for the PCM
+When you have chosen managed buffer allocation mode for the PCM
substream, the allocated PCM buffer will be automatically released
after this callback gets called. Otherwise you'll have to release the
buffer manually. Typically, when the buffer was allocated from the
pre-allocated pool, you can use the standard API function
-:c:func:`snd_pcm_lib_malloc_pages()` like:
-
-::
+:c:func:`snd_pcm_lib_malloc_pages()` like::
snd_pcm_lib_free_pages(substream);
static int snd_xxx_prepare(struct snd_pcm_substream *substream);
-This callback is called when the pcm is “prepared”. You can set the
+This callback is called when the PCM is “prepared”. You can set the
format type, sample rate, etc. here. The difference from ``hw_params``
is that the ``prepare`` callback will be called each time
:c:func:`snd_pcm_prepare()` is called, i.e. when recovering after
underruns, etc.
-Note that this callback is now non-atomic. You can use
+Note that this callback is non-atomic. You can use
schedule-related functions safely in this callback.
In this and the following callbacks, you can refer to the values via
static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd);
-This is called when the pcm is started, stopped or paused.
-
-Which action is specified in the second argument,
-``SNDRV_PCM_TRIGGER_XXX`` in ``<sound/pcm.h>``. At least, the ``START``
-and ``STOP`` commands must be defined in this callback.
+This is called when the PCM is started, stopped or paused.
-::
+The action is specified in the second argument, ``SNDRV_PCM_TRIGGER_XXX``
+defined in ``<sound/pcm.h>``. At least, the ``START``
+and ``STOP`` commands must be defined in this callback::
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
return -EINVAL;
}
-When the pcm supports the pause operation (given in the info field of
+When the PCM supports the pause operation (given in the info field of
the hardware table), the ``PAUSE_PUSH`` and ``PAUSE_RELEASE`` commands
-must be handled here, too. The former is the command to pause the pcm,
-and the latter to restart the pcm again.
+must be handled here, too. The former is the command to pause the PCM,
+and the latter to restart the PCM again.
-When the pcm supports the suspend/resume operation, regardless of full
+When the PCM supports the suspend/resume operation, regardless of full
or partial suspend/resume support, the ``SUSPEND`` and ``RESUME``
commands must be handled, too. These commands are issued when the
power-management status is changed. Obviously, the ``SUSPEND`` and
-``RESUME`` commands suspend and resume the pcm substream, and usually,
+``RESUME`` commands suspend and resume the PCM substream, and usually,
they are identical to the ``STOP`` and ``START`` commands, respectively.
See the `Power Management`_ section for details.
-As mentioned, this callback is atomic as default unless ``nonatomic``
+As mentioned, this callback is atomic by default unless the ``nonatomic``
flag set, and you cannot call functions which may sleep. The
``trigger`` callback should be as minimal as possible, just really
-triggering the DMA. The other stuff should be initialized
+triggering the DMA. The other stuff should be initialized in
``hw_params`` and ``prepare`` callbacks properly beforehand.
sync_stop callback
static int snd_xxx_sync_stop(struct snd_pcm_substream *substream);
This callback is optional, and NULL can be passed. It's called after
-the PCM core stops the stream and changes the stream state
+the PCM core stops the stream, before it changes the stream state via
``prepare``, ``hw_params`` or ``hw_free``.
Since the IRQ handler might be still pending, we need to wait until
the pending task finishes before moving to the next step; otherwise it
-might lead to a crash due to resource conflicts or access to the freed
+might lead to a crash due to resource conflicts or access to freed
resources. A typical behavior is to call a synchronization function
like :c:func:`synchronize_irq()` here.
-For majority of drivers that need only a call of
+For the majority of drivers that need only a call of
:c:func:`synchronize_irq()`, there is a simpler setup, too.
-While keeping NULL to ``sync_stop`` PCM callback, the driver can set
-``card->sync_irq`` field to store the valid interrupt number after
-requesting an IRQ, instead. Then PCM core will look call
+While keeping the ``sync_stop`` PCM callback NULL, the driver can set
+the ``card->sync_irq`` field to the returned interrupt number after
+requesting an IRQ, instead. Then PCM core will call
:c:func:`synchronize_irq()` with the given IRQ appropriately.
-If the IRQ handler is released at the card destructor, you don't need
+If the IRQ handler is released by the card destructor, you don't need
to clear ``card->sync_irq``, as the card itself is being released.
So, usually you'll need to add just a single line for assigning
``card->sync_irq`` in the driver code unless the driver re-acquires
static snd_pcm_uframes_t snd_xxx_pointer(struct snd_pcm_substream *substream)
This callback is called when the PCM middle layer inquires the current
-hardware position on the buffer. The position must be returned in
+hardware position in the buffer. The position must be returned in
frames, ranging from 0 to ``buffer_size - 1``.
-This is called usually from the buffer-update routine in the pcm
+This is usually called from the buffer-update routine in the PCM
middle layer, which is invoked when :c:func:`snd_pcm_period_elapsed()`
-is called in the interrupt routine. Then the pcm middle layer updates
+is called by the interrupt routine. Then the PCM middle layer updates
the position and calculates the available space, and wakes up the
sleeping poll threads, etc.
-This callback is also atomic as default.
+This callback is also atomic by default.
copy_user, copy_kernel and fill_silence ops
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These callbacks are not mandatory, and can be omitted in most cases.
These callbacks are used when the hardware buffer cannot be in the
-normal memory space. Some chips have their own buffer on the hardware
+normal memory space. Some chips have their own buffer in the hardware
which is not mappable. In such a case, you have to transfer the data
manually from the memory buffer to the hardware buffer. Or, if the
buffer is non-contiguous on both physical and virtual memory spaces,
these callbacks must be defined, too.
If these two callbacks are defined, copy and set-silence operations
-are done by them. The detailed will be described in the later section
+are done by them. The details will be described in the later section
`Buffer and Memory Management`_.
ack callback
emu10k1-fx and cs46xx need to track the current ``appl_ptr`` for the
internal buffer, and this callback is useful only for such a purpose.
-This callback is atomic as default.
+The callback function may return 0 or a negative error. When the
+return value is ``-EPIPE``, PCM core treats that as a buffer XRUN,
+and changes the state to ``SNDRV_PCM_STATE_XRUN`` automatically.
+
+This callback is atomic by default.
page callback
~~~~~~~~~~~~~
This callback is optional too. The mmap calls this callback to get the
page fault address.
-Since the recent changes, you need no special callback any longer for
-the standard SG-buffer or vmalloc-buffer. Hence this callback should
-be rarely used.
+You need no special callback for the standard SG-buffer or vmalloc-
+buffer. Hence this callback should be rarely used.
-mmap calllback
-~~~~~~~~~~~~~~
+mmap callback
+~~~~~~~~~~~~~
This is another optional callback for controlling mmap behavior.
-Once when defined, PCM core calls this callback when a page is
-memory-mapped instead of dealing via the standard helper.
+When defined, the PCM core calls this callback when a page is
+memory-mapped, instead of using the standard helper.
If you need special handling (due to some architecture or
device-specific issues), implement everything here as you like.
PCM Interrupt Handler
---------------------
-The rest of pcm stuff is the PCM interrupt handler. The role of PCM
+The remainder of the PCM stuff is the PCM interrupt handler. The role
+of the PCM
interrupt handler in the sound driver is to update the buffer position
and to tell the PCM middle layer when the buffer position goes across
-the prescribed period size. To inform this, call the
+the specified period boundary. To inform about this, call the
:c:func:`snd_pcm_period_elapsed()` function.
-There are several types of sound chips to generate the interrupts.
+There are several ways sound chips can generate interrupts.
Interrupts at the period (fragment) boundary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pointer value at ``open`` callback (and reset at ``close`` callback).
If you acquire a spinlock in the interrupt handler, and the lock is used
-in other pcm callbacks, too, then you have to release the lock before
+in other PCM callbacks, too, then you have to release the lock before
calling :c:func:`snd_pcm_period_elapsed()`, because
-:c:func:`snd_pcm_period_elapsed()` calls other pcm callbacks
+:c:func:`snd_pcm_period_elapsed()` calls other PCM callbacks
inside.
-Typical code would be like:
-
-::
+Typical code would look like::
static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}
+Also, when the device can detect a buffer underrun/overrun, the driver
+can notify the XRUN status to the PCM core by calling
+:c:func:`snd_pcm_stop_xrun()`. This function stops the stream and sets
+the PCM state to ``SNDRV_PCM_STATE_XRUN``. Note that it must be called
+outside the PCM stream lock, hence it can't be called from the atomic
+callback.
High frequency timer interrupts
When the accumulated size exceeds the period size, call
:c:func:`snd_pcm_period_elapsed()` and reset the accumulator.
-Typical code would be like the following.
-
-::
+Typical code would look as follows::
static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
On calling :c:func:`snd_pcm_period_elapsed()`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In both cases, even if more than one period are elapsed, you don't have
+In both cases, even if more than one period has elapsed, you don't have
to call :c:func:`snd_pcm_period_elapsed()` many times. Call only
-once. And the pcm layer will check the current hardware pointer and
+once. And the PCM layer will check the current hardware pointer and
update to the latest status.
Atomicity
usually avoided via spin-locks, mutexes or semaphores. In general, if a
race condition can happen in an interrupt handler, it has to be managed
atomically, and you have to use a spinlock to protect the critical
-session. If the critical section is not in interrupt handler code and if
+section. If the critical section is not in interrupt handler code and if
taking a relatively long time to execute is acceptable, you should use
mutexes or semaphores instead.
-As already seen, some pcm callbacks are atomic and some are not. For
-example, the ``hw_params`` callback is non-atomic, while ``trigger``
+As already seen, some PCM callbacks are atomic and some are not. For
+example, the ``hw_params`` callback is non-atomic, while the ``trigger``
callback is atomic. This means, the latter is called already in a
-spinlock held by the PCM middle layer. Please take this atomicity into
-account when you choose a locking scheme in the callbacks.
+spinlock held by the PCM middle layer, the PCM stream lock. Please
+take this atomicity into account when you choose a locking scheme in
+the callbacks.
In the atomic callbacks, you cannot use functions which may call
:c:func:`schedule()` or go to :c:func:`sleep()`. Semaphores and
All three atomic callbacks (trigger, pointer, and ack) are called with
local interrupts disabled.
-The recent changes in PCM core code, however, allow all PCM operations
-to be non-atomic. This assumes that the all caller sides are in
+However, it is possible to request all PCM operations to be non-atomic.
+This assumes that all call sites are in
non-atomic contexts. For example, the function
:c:func:`snd_pcm_period_elapsed()` is called typically from the
interrupt handler. But, if you set up the driver to use a threaded
interrupt handler, this call can be in non-atomic context, too. In such
-a case, you can set ``nonatomic`` filed of struct snd_pcm object
+a case, you can set the ``nonatomic`` field of the struct snd_pcm object
after creating it. When this flag is set, mutex and rwsem are used internally
in the PCM core instead of spin and rwlocks, so that you can call all PCM
functions safely in a non-atomic
context.
+Also, in some cases, you might need to call
+:c:func:`snd_pcm_period_elapsed()` in the atomic context (e.g. the
+period gets elapsed during ``ack`` or other callback). There is a
+variant that can be called inside the PCM stream lock
+:c:func:`snd_pcm_period_elapsed_under_stream_lock()` for that purpose,
+too.
+
Constraints
-----------
-If your chip supports unconventional sample rates, or only the limited
-samples, you need to set a constraint for the condition.
+Due to physical limitations, hardware is not infinitely configurable.
+These limitations are expressed by setting constraints.
-For example, in order to restrict the sample rates in the some supported
+For example, in order to restrict the sample rates to some supported
values, use :c:func:`snd_pcm_hw_constraint_list()`. You need to
-call this function in the open callback.
-
-::
+call this function in the open callback::
static unsigned int rates[] =
{4000, 10000, 22050, 44100};
....
}
-
-
There are many different constraints. Look at ``sound/pcm.h`` for a
complete list. You can even define your own constraint rules. For
example, let's suppose my_chip can manage a substream of 1 channel if
and only if the format is ``S16_LE``, otherwise it supports any format
-specified in struct snd_pcm_hardware> (or in any other
-constraint_list). You can build a rule like this:
-
-::
+specified in struct snd_pcm_hardware (or in any other
+constraint_list). You can build a rule like this::
static int hw_rule_channels_by_format(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
}
-Then you need to call this function to add your rule:
-
-::
+Then you need to call this function to add your rule::
snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels_by_format, NULL,
The rule function is called when an application sets the PCM format, and
it refines the number of channels accordingly. But an application may
set the number of channels before setting the format. Thus you also need
-to define the inverse rule:
-
-::
+to define the inverse rule::
static int hw_rule_format_by_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
}
-... and in the open callback:
-
-::
+... and in the open callback::
snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
hw_rule_format_by_channels, NULL,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
One typical usage of the hw constraints is to align the buffer size
-with the period size. As default, ALSA PCM core doesn't enforce the
+with the period size. By default, ALSA PCM core doesn't enforce the
buffer size to be aligned with the period size. For example, it'd be
possible to have a combination like 256 period bytes with 999 buffer
bytes.
Many device chips, however, require the buffer to be a multiple of
periods. In such a case, call
:c:func:`snd_pcm_hw_constraint_integer()` for
-``SNDRV_PCM_HW_PARAM_PERIODS``.
-
-::
+``SNDRV_PCM_HW_PARAM_PERIODS``::
snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
This assures that the number of periods is integer, hence the buffer
size is aligned with the period size.
-The hw constraint is a very much powerful mechanism to define the
+The hw constraint is a very powerful mechanism to define the
preferred PCM configuration, and there are relevant helpers.
I won't give more details here, rather I would like to say, “Luke, use
the source.”
To create a new control, you need to define the following three
callbacks: ``info``, ``get`` and ``put``. Then, define a
-struct snd_kcontrol_new record, such as:
-
-::
+struct snd_kcontrol_new record, such as::
static struct snd_kcontrol_new my_control = {
for this record. When using the generic ``info``, ``get`` and ``put``
callbacks, you can pass a value through this field. If several small
numbers are necessary, you can combine them in bitwise. Or, it's
-possible to give a pointer (casted to unsigned long) of some record to
+possible to store a pointer (casted to unsigned long) of some record in
this field, too.
The ``tlv`` field can be used to provide metadata about the control;
given control. The default access type is
``SNDRV_CTL_ELEM_ACCESS_READWRITE``, which means both read and write are
allowed to this control. When the access flag is omitted (i.e. = 0), it
-is considered as ``READWRITE`` access as default.
+is considered as ``READWRITE`` access by default.
When the control is read-only, pass ``SNDRV_CTL_ELEM_ACCESS_READ``
instead. In this case, you don't have to define the ``put`` callback.
changed without `Change notification`_. Applications should poll such
a control constantly.
-When the control is inactive, set the ``INACTIVE`` flag, too. There are
-``LOCK`` and ``OWNER`` flags to change the write permissions.
+When the control may be updated, but currently has no effect on anything,
+setting the ``INACTIVE`` flag may be appropriate. For example, PCM
+controls should be inactive while no PCM device is open.
+
+There are ``LOCK`` and ``OWNER`` flags to change the write permissions.
Control Callbacks
-----------------
The ``info`` callback is used to get detailed information on this
control. This must store the values of the given
struct snd_ctl_elem_info object. For example,
-for a boolean control with a single element:
-
-::
+for a boolean control with a single element::
static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,
``BOOLEAN``, ``INTEGER``, ``ENUMERATED``, ``BYTES``, ``IEC958`` and
``INTEGER64``. The ``count`` field specifies the number of elements in
this control. For example, a stereo volume would have count = 2. The
-``value`` field is a union, and the values stored are depending on the
+``value`` field is a union, and the values stored depend on the
type. The boolean and integer types are identical.
-The enumerated type is a bit different from others. You'll need to set
-the string for the currently given item index.
-
-::
+The enumerated type is a bit different from the others. You'll need to
+set the string for the selectec item index::
static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
get callback
~~~~~~~~~~~~
-This callback is used to read the current value of the control and to
-return to user-space.
-
-For example,
-
-::
+This callback is used to read the current value of the control, so it
+can be returned to user-space.
+For example::
static int snd_myctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
The ``value`` field depends on the type of control as well as on the
info callback. For example, the sb driver uses this field to store the
register offset, the bit-shift and the bit-mask. The ``private_value``
-field is set as follows:
-
-::
+field is set as follows::
.private_value = reg | (shift << 16) | (mask << 24)
-and is retrieved in callbacks like
-
-::
+and is retrieved in callbacks like::
static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
}
In the ``get`` callback, you have to fill all the elements if the
-control has more than one elements, i.e. ``count > 1``. In the example
+control has more than one element, i.e. ``count > 1``. In the example
above, we filled only one element (``value.integer.value[0]``) since
-it's assumed as ``count = 1``.
+``count = 1`` is assumed.
put callback
~~~~~~~~~~~~
-This callback is used to write a value from user-space.
-
-For example,
-
-::
+This callback is used to write a value coming from user-space.
+For example::
static int snd_myctl_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
return a negative error code as usual.
As in the ``get`` callback, when the control has more than one
-elements, all elements must be evaluated in this callback, too.
+element, all elements must be evaluated in this callback, too.
Callbacks are not atomic
~~~~~~~~~~~~~~~~~~~~~~~~
-All these three callbacks are basically not atomic.
+All these three callbacks are not-atomic.
Control Constructor
-------------------
a control, there are two functions to be called,
:c:func:`snd_ctl_new1()` and :c:func:`snd_ctl_add()`.
-In the simplest way, you can do like this:
-
-::
+In the simplest way, you can do it like this::
err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip));
if (err < 0)
-------------------
If you need to change and update a control in the interrupt routine, you
-can call :c:func:`snd_ctl_notify()`. For example,
-
-::
+can call :c:func:`snd_ctl_notify()`. For example::
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);
Metadata
--------
-To provide information about the dB values of a mixer control, use on of
+To provide information about the dB values of a mixer control, use one of
the ``DECLARE_TLV_xxx`` macros from ``<sound/tlv.h>`` to define a
variable containing this information, set the ``tlv.p`` field to point to
this variable, and include the ``SNDRV_CTL_ELEM_ACCESS_TLV_READ`` flag
-in the ``access`` field; like this:
-
-::
+in the ``access`` field; like this::
static DECLARE_TLV_DB_SCALE(db_scale_my_control, -4050, 150, 0);
----------------
To create an ac97 instance, first call :c:func:`snd_ac97_bus()`
-with an ``ac97_bus_ops_t`` record with callback functions.
-
-::
+with an ``ac97_bus_ops_t`` record with callback functions::
struct snd_ac97_bus *bus;
static struct snd_ac97_bus_ops ops = {
The bus record is shared among all belonging ac97 instances.
-And then call :c:func:`snd_ac97_mixer()` with an struct snd_ac97_template
-record together with the bus pointer created above.
-
-::
+And then call :c:func:`snd_ac97_mixer()` with a struct snd_ac97_template
+record together with the bus pointer created above::
struct snd_ac97_template ac97;
int err;
hardware low-level codes.
The ``read`` callback returns the register value specified in the
-argument.
-
-::
+argument::
static unsigned short snd_mychip_ac97_read(struct snd_ac97 *ac97,
unsigned short reg)
Here, the chip can be cast from ``ac97->private_data``.
Meanwhile, the ``write`` callback is used to set the register
-value
-
-::
+value::
static void snd_mychip_ac97_write(struct snd_ac97 *ac97,
unsigned short reg, unsigned short val)
the given register (``AC97_XXX``). The difference between them is that
:c:func:`snd_ac97_update()` doesn't write a value if the given
value has been already set, while :c:func:`snd_ac97_write()`
-always rewrites the value.
-
-::
+always rewrites the value::
snd_ac97_write(ac97, AC97_MASTER, 0x8080);
snd_ac97_update(ac97, AC97_MASTER, 0x8080);
:c:func:`snd_ac97_read()` is used to read the value of the given
-register. For example,
-
-::
+register. For example::
value = snd_ac97_read(ac97, AC97_MASTER);
:c:func:`snd_ac97_update_bits()` is used to update some bits in
-the given register.
-
-::
+the given register::
snd_ac97_update_bits(ac97, reg, mask, value);
Also, there is a function to change the sample rate (of a given register
such as ``AC97_PCM_FRONT_DAC_RATE``) when VRA or DRA is supported by the
-codec: :c:func:`snd_ac97_set_rate()`.
-
-::
+codec: :c:func:`snd_ac97_set_rate()`::
snd_ac97_set_rate(ac97, AC97_PCM_FRONT_DAC_RATE, 44100);
MIDI Constructor
----------------
-To create a rawmidi object, call :c:func:`snd_mpu401_uart_new()`.
-
-::
+To create a rawmidi object, call :c:func:`snd_mpu401_uart_new()`::
struct snd_rawmidi *rmidi;
snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, port, info_flags,
field of struct snd_mpu401 manually afterward.
However, struct snd_mpu401 pointer is
not returned explicitly by :c:func:`snd_mpu401_uart_new()`. You
-need to cast ``rmidi->private_data`` to struct snd_mpu401 explicitly,
-
-::
+need to cast ``rmidi->private_data`` to struct snd_mpu401 explicitly::
struct snd_mpu401 *mpu;
mpu = rmidi->private_data;
-and reset the ``cport`` as you like:
-
-::
+and reset the ``cport`` as you like::
mpu->cport = my_own_control_port;
In this case, you need to pass the private_data of the returned rawmidi
object from :c:func:`snd_mpu401_uart_new()` as the second
-argument of :c:func:`snd_mpu401_uart_interrupt()`.
-
-::
+argument of :c:func:`snd_mpu401_uart_interrupt()`::
snd_mpu401_uart_interrupt(irq, rmidi->private_data, regs);
-------------------
To create a rawmidi device, call the :c:func:`snd_rawmidi_new()`
-function:
-
-::
+function::
struct snd_rawmidi *rmidi;
err = snd_rawmidi_new(chip->card, "MyMIDI", 0, outs, ins, &rmidi);
After the rawmidi device is created, you need to set the operators
(callbacks) for each substream. There are helper functions to set the
-operators for all the substreams of a device:
-
-::
+operators for all the substreams of a device::
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_mymidi_output_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_mymidi_input_ops);
-The operators are usually defined like this:
-
-::
+The operators are usually defined like this::
static struct snd_rawmidi_ops snd_mymidi_output_ops = {
.open = snd_mymidi_output_open,
These callbacks are explained in the `RawMIDI Callbacks`_ section.
If there are more than one substream, you should give a unique name to
-each of them:
-
-::
+each of them::
struct snd_rawmidi_substream *substream;
list_for_each_entry(substream,
If there is more than one port, your callbacks can determine the port
index from the struct snd_rawmidi_substream data passed to each
-callback:
-
-::
+callback::
struct snd_rawmidi_substream *substream;
int index = substream->number;
requested when there are no more data in the buffer. After the data have
been transmitted successfully, call
:c:func:`snd_rawmidi_transmit_ack()` to remove the data from the
-substream buffer:
-
-::
+substream buffer::
unsigned char data;
while (snd_rawmidi_transmit_peek(substream, &data, 1) == 1) {
If you know beforehand that the hardware will accept data, you can use
the :c:func:`snd_rawmidi_transmit()` function which reads some
-data and removes them from the buffer at once:
-
-::
+data and removes them from the buffer at once::
while (snd_mychip_transmit_possible()) {
unsigned char data;
from the device is usually done in an interrupt handler.
When data reception is enabled, your interrupt handler should call
-:c:func:`snd_rawmidi_receive()` for all received data:
-
-::
+:c:func:`snd_rawmidi_receive()` for all received data::
void snd_mychip_midi_interrupt(...)
{
OSS direct-FM compatible API in ``/dev/dmfmX`` device.
To create the OPL3 component, you have two functions to call. The first
-one is a constructor for the ``opl3_t`` instance.
-
-::
+one is a constructor for the ``opl3_t`` instance::
struct snd_opl3 *opl3;
snd_opl3_create(card, lport, rport, OPL3_HW_OPL3_XXX,
When the accessing the hardware requires special method instead of the
standard I/O access, you can create opl3 instance separately with
-:c:func:`snd_opl3_new()`.
-
-::
+:c:func:`snd_opl3_new()`::
struct snd_opl3 *opl3;
snd_opl3_new(card, OPL3_HW_OPL3_XXX, &opl3);
it internally.
If the opl3 instance is created successfully, then create a hwdep device
-for this opl3.
-
-::
+for this opl3::
struct snd_hwdep *opl3hwdep;
snd_opl3_hwdep_new(opl3, 0, 1, &opl3hwdep);
``isa/sb/sb16_csp.c``.
The creation of the ``hwdep`` instance is done via
-:c:func:`snd_hwdep_new()`.
-
-::
+:c:func:`snd_hwdep_new()`::
struct snd_hwdep *hw;
snd_hwdep_new(card, "My HWDEP", 0, &hw);
where the third argument is the index number.
You can then pass any pointer value to the ``private_data``. If you
-assign a private data, you should define the destructor, too. The
-destructor function is set in the ``private_free`` field.
-
-::
+assign private data, you should define a destructor, too. The
+destructor function is set in the ``private_free`` field::
struct mydata *p = kmalloc(sizeof(*p), GFP_KERNEL);
hw->private_data = p;
hw->private_free = mydata_free;
-and the implementation of the destructor would be:
-
-::
+and the implementation of the destructor would be::
static void mydata_free(struct snd_hwdep *hw)
{
The arbitrary file operations can be defined for this instance. The file
operators are defined in the ``ops`` table. For example, assume that
-this chip needs an ioctl.
-
-::
+this chip needs an ioctl::
hw->ops.open = mydata_open;
hw->ops.ioctl = mydata_ioctl;
ALSA provides several different buffer allocation functions depending on
the bus and the architecture. All these have a consistent API. The
-allocation of physically-contiguous pages is done via
+allocation of physically-contiguous pages is done via the
:c:func:`snd_malloc_xxx_pages()` function, where xxx is the bus
type.
-The allocation of pages with fallback is
-:c:func:`snd_malloc_xxx_pages_fallback()`. This function tries
-to allocate the specified pages but if the pages are not available, it
-tries to reduce the page sizes until enough space is found.
+The allocation of pages with fallback is done via
+:c:func:`snd_dma_alloc_pages_fallback()`. This function tries
+to allocate the specified number of pages, but if not enough pages are
+available, it tries to reduce the request size until enough space
+is found, down to one page.
-The release the pages, call :c:func:`snd_free_xxx_pages()`
+To release the pages, call the :c:func:`snd_dma_free_pages()`
function.
Usually, ALSA drivers try to allocate and reserve a large contiguous
-physical space at the time the module is loaded for the later use. This
+physical space at the time the module is loaded for later use. This
is called “pre-allocation”. As already written, you can call the
-following function at pcm instance construction time (in the case of PCI
-bus).
-
-::
+following function at PCM instance construction time (in the case of PCI
+bus)::
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
&pci->dev, size, max);
-where ``size`` is the byte size to be pre-allocated and the ``max`` is
-the maximum size to be changed via the ``prealloc`` proc file. The
+where ``size`` is the byte size to be pre-allocated and ``max`` is
+the maximum size settable via the ``prealloc`` proc file. The
allocator will try to get an area as large as possible within the
given size.
(typically identical as ``card->dev``) to the third argument with
``SNDRV_DMA_TYPE_DEV`` type.
-For the continuous buffer unrelated to the
+A continuous buffer unrelated to the
bus can be pre-allocated with ``SNDRV_DMA_TYPE_CONTINUOUS`` type.
You can pass NULL to the device pointer in that case, which is the
-default mode implying to allocate with ``GFP_KERNEL`` flag.
+default mode implying to allocate with the ``GFP_KERNEL`` flag.
If you need a restricted (lower) address, set up the coherent DMA mask
bits for the device, and pass the device pointer, like the normal
device memory allocations. For this type, it's still allowed to pass
device pointer (see the `Non-Contiguous Buffers`_ section).
Once the buffer is pre-allocated, you can use the allocator in the
-``hw_params`` callback:
-
-::
+``hw_params`` callback::
snd_pcm_lib_malloc_pages(substream, size);
Note that you have to pre-allocate to use this function.
-Most of drivers use, though, rather the newly introduced "managed
-buffer allocation mode" instead of the manual allocation or release.
+But most drivers use the "managed buffer allocation mode" instead
+of manual allocation and release.
This is done by calling :c:func:`snd_pcm_set_managed_buffer_all()`
-instead of :c:func:`snd_pcm_lib_preallocate_pages_for_all()`.
-
-::
+instead of :c:func:`snd_pcm_lib_preallocate_pages_for_all()`::
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
&pci->dev, size, max);
-where passed arguments are identical in both functions.
+where the passed arguments are identical for both functions.
The difference in the managed mode is that PCM core will call
:c:func:`snd_pcm_lib_malloc_pages()` internally already before calling
the PCM ``hw_params`` callback, and call :c:func:`snd_pcm_lib_free_pages()`
after the PCM ``hw_free`` callback automatically. So the driver
doesn't have to call these functions explicitly in its callback any
-longer. This made many driver code having NULL ``hw_params`` and
+longer. This allows many drivers to have NULL ``hw_params`` and
``hw_free`` entries.
External Hardware Buffers
-------------------------
-Some chips have their own hardware buffers and the DMA transfer from the
+Some chips have their own hardware buffers and DMA transfer from the
host memory is not available. In such a case, you need to either 1)
copy/set the audio data directly to the external hardware buffer, or 2)
make an intermediate buffer and copy/set the data from it to the
The first case works fine if the external hardware buffer is large
enough. This method doesn't need any extra buffers and thus is more
-effective. You need to define the ``copy_user`` and ``copy_kernel``
-callbacks for the data transfer, in addition to ``fill_silence``
+efficient. You need to define the ``copy_user`` and ``copy_kernel``
+callbacks for the data transfer, in addition to the ``fill_silence``
callback for playback. However, there is a drawback: it cannot be
mmapped. The examples are GUS's GF1 PCM or emu8000's wavetable PCM.
on certain architectures like the Intel one. In non-mmap mode, the data
cannot be transferred as in the normal way. Thus you need to define the
``copy_user``, ``copy_kernel`` and ``fill_silence`` callbacks as well,
-as in the cases above. The examples are found in ``rme32.c`` and
+as in the cases above. Examples are found in ``rme32.c`` and
``rme96.c``.
The implementation of the ``copy_user``, ``copy_kernel`` and
``silence`` callbacks depends upon whether the hardware supports
interleaved or non-interleaved samples. The ``copy_user`` callback is
-defined like below, a bit differently depending whether the direction
-is playback or capture:
-
-::
+defined like below, a bit differently depending on whether the direction
+is playback or capture::
static int playback_copy_user(struct snd_pcm_substream *substream,
int channel, unsigned long pos,
void __user *dst, unsigned long count);
In the case of interleaved samples, the second argument (``channel``) is
-not used. The third argument (``pos``) points the current position
-offset in bytes.
+not used. The third argument (``pos``) specifies the position in bytes.
The meaning of the fourth argument is different between playback and
capture. For playback, it holds the source data pointer, and for
What you have to do in this callback is again different between playback
and capture directions. In the playback case, you copy the given amount
of data (``count``) at the specified pointer (``src``) to the specified
-offset (``pos``) on the hardware buffer. When coded like memcpy-like
-way, the copy would be like:
-
-::
+offset (``pos``) in the hardware buffer. When coded like memcpy-like
+way, the copy would look like::
my_memcpy_from_user(my_buffer + pos, src, count);
For the capture direction, you copy the given amount of data (``count``)
-at the specified offset (``pos``) on the hardware buffer to the
-specified pointer (``dst``).
-
-::
+at the specified offset (``pos``) in the hardware buffer to the
+specified pointer (``dst``)::
my_memcpy_to_user(dst, my_buffer + pos, count);
-Here the functions are named as ``from_user`` and ``to_user`` because
+Here the functions are named ``from_user`` and ``to_user`` because
it's the user-space buffer that is passed to these callbacks. That
-is, the callback is supposed to copy from/to the user-space data
+is, the callback is supposed to copy data from/to the user-space
directly to/from the hardware buffer.
Careful readers might notice that these callbacks receive the
arguments in bytes, not in frames like other callbacks. It's because
-it would make coding easier like the examples above, and also it makes
-easier to unify both the interleaved and non-interleaved cases, as
-explained in the following.
+this makes coding easier like in the examples above, and also it makes
+it easier to unify both the interleaved and non-interleaved cases, as
+explained below.
In the case of non-interleaved samples, the implementation will be a bit
-more complicated. The callback is called for each channel, passed by
-the second argument, so totally it's called for N-channels times per
-transfer.
-
-The meaning of other arguments are almost same as the interleaved
-case. The callback is supposed to copy the data from/to the given
-user-space buffer, but only for the given channel. For the detailed
-implementations, please check ``isa/gus/gus_pcm.c`` or
-"pci/rme9652/rme9652.c" as examples.
+more complicated. The callback is called for each channel, passed in
+the second argument, so in total it's called N times per transfer.
-The above callbacks are the copy from/to the user-space buffer. There
-are some cases where we want copy from/to the kernel-space buffer
-instead. In such a case, ``copy_kernel`` callback is called. It'd
-look like:
+The meaning of the other arguments are almost the same as in the
+interleaved case. The callback is supposed to copy the data from/to
+the given user-space buffer, but only for the given channel. For
+details, please check ``isa/gus/gus_pcm.c`` or ``pci/rme9652/rme9652.c``
+as examples.
-::
+The above callbacks are the copies from/to the user-space buffer. There
+are some cases where we want to copy from/to the kernel-space buffer
+instead. In such a case, the ``copy_kernel`` callback is called. It'd
+look like::
static int playback_copy_kernel(struct snd_pcm_substream *substream,
int channel, unsigned long pos,
void *dst, unsigned long count);
As found easily, the only difference is that the buffer pointer is
-without ``__user`` prefix; that is, a kernel-buffer pointer is passed
+without a ``__user`` prefix; that is, a kernel-buffer pointer is passed
in the fourth argument. Correspondingly, the implementation would be
-a version without the user-copy, such as:
-
-::
+a version without the user-copy, such as::
my_memcpy(my_buffer + pos, src, count);
Usually for the playback, another callback ``fill_silence`` is
defined. It's implemented in a similar way as the copy callbacks
-above:
-
-::
+above::
static int silence(struct snd_pcm_substream *substream, int channel,
unsigned long pos, unsigned long count);
The meanings of arguments are the same as in the ``copy_user`` and
``copy_kernel`` callbacks, although there is no buffer pointer
argument. In the case of interleaved samples, the channel argument has
-no meaning, as well as on ``copy_*`` callbacks.
+no meaning, as for the ``copy_*`` callbacks.
-The role of ``fill_silence`` callback is to set the given amount
-(``count``) of silence data at the specified offset (``pos``) on the
+The role of the ``fill_silence`` callback is to set the given amount
+(``count``) of silence data at the specified offset (``pos``) in the
hardware buffer. Suppose that the data format is signed (that is, the
silent-data is 0), and the implementation using a memset-like function
-would be like:
-
-::
+would look like::
my_memset(my_buffer + pos, 0, count);
In the case of non-interleaved samples, again, the implementation
-becomes a bit more complicated, as it's called N-times per transfer
+becomes a bit more complicated, as it's called N times per transfer
for each channel. See, for example, ``isa/gus/gus_pcm.c``.
Non-Contiguous Buffers
----------------------
-If your hardware supports the page table as in emu10k1 or the buffer
-descriptors as in via82xx, you can use the scatter-gather (SG) DMA. ALSA
+If your hardware supports a page table as in emu10k1 or buffer
+descriptors as in via82xx, you can use scatter-gather (SG) DMA. ALSA
provides an interface for handling SG-buffers. The API is provided in
``<sound/pcm.h>``.
For creating the SG-buffer handler, call
:c:func:`snd_pcm_set_managed_buffer()` or
:c:func:`snd_pcm_set_managed_buffer_all()` with
-``SNDRV_DMA_TYPE_DEV_SG`` in the PCM constructor like other PCI
-pre-allocator. You need to pass ``&pci->dev``, where pci is
-the struct pci_dev pointer of the chip as
-well.
-
-::
+``SNDRV_DMA_TYPE_DEV_SG`` in the PCM constructor like for other PCI
+pre-allocations. You need to pass ``&pci->dev``, where pci is
+the struct pci_dev pointer of the chip as well::
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
&pci->dev, size, max);
The ``struct snd_sg_buf`` instance is created as
-``substream->dma_private`` in turn. You can cast the pointer like:
-
-::
+``substream->dma_private`` in turn. You can cast the pointer like::
struct snd_sg_buf *sgbuf = (struct snd_sg_buf *)substream->dma_private;
-Then in :c:func:`snd_pcm_lib_malloc_pages()` call, the common SG-buffer
+Then in the :c:func:`snd_pcm_lib_malloc_pages()` call, the common SG-buffer
handler will allocate the non-contiguous kernel pages of the given size
-and map them onto the virtually contiguous memory. The virtual pointer
-is addressed in runtime->dma_area. The physical address
+and map them as virtually contiguous memory. The virtual pointer
+is addressed via runtime->dma_area. The physical address
(``runtime->dma_addr``) is set to zero, because the buffer is
physically non-contiguous. The physical address table is set up in
``sgbuf->table``. You can get the physical address at a certain offset
------------------
It's possible to use a buffer allocated via :c:func:`vmalloc()`, for
-example, for an intermediate buffer. In the recent version of kernel,
-you can simply allocate it via standard
-:c:func:`snd_pcm_lib_malloc_pages()` and co after setting up the
-buffer preallocation with ``SNDRV_DMA_TYPE_VMALLOC`` type.
-
-::
+example, for an intermediate buffer.
+You can simply allocate it via the standard
+:c:func:`snd_pcm_lib_malloc_pages()` and co. after setting up the
+buffer preallocation with ``SNDRV_DMA_TYPE_VMALLOC`` type::
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
NULL, 0, 0);
-The NULL is passed to the device pointer argument, which indicates
-that the default pages (GFP_KERNEL and GFP_HIGHMEM) will be
+NULL is passed as the device pointer argument, which indicates
+that default pages (GFP_KERNEL and GFP_HIGHMEM) will be
allocated.
-Also, note that zero is passed to both the size and the max size
-arguments here. Since each vmalloc call should succeed at any time,
+Also, note that zero is passed as both the size and the max size
+argument here. Since each vmalloc call should succeed at any time,
we don't need to pre-allocate the buffers like other continuous
pages.
driver and want to get a running status or register dumps. The API is
found in ``<sound/info.h>``.
-To create a proc file, call :c:func:`snd_card_proc_new()`.
-
-::
+To create a proc file, call :c:func:`snd_card_proc_new()`::
struct snd_info_entry *entry;
int err = snd_card_proc_new(card, "my-file", &entry);
When the creation is successful, the function stores a new instance in
the pointer given in the third argument. It is initialized as a text
proc file for read only. To use this proc file as a read-only text file
-as it is, set the read callback with a private data via
-:c:func:`snd_info_set_text_ops()`.
-
-::
+as-is, set the read callback with private data via
+:c:func:`snd_info_set_text_ops()`::
snd_info_set_text_ops(entry, chip, my_proc_read);
where the second argument (``chip``) is the private data to be used in
-the callbacks. The third parameter specifies the read buffer size and
+the callback. The third parameter specifies the read buffer size and
the fourth (``my_proc_read``) is the callback function, which is
-defined like
-
-::
+defined like::
static void my_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer);
In the read callback, use :c:func:`snd_iprintf()` for output
strings, which works just like normal :c:func:`printf()`. For
-example,
-
-::
+example::
static void my_proc_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
snd_iprintf(buffer, "Port = %ld\n", chip->port);
}
-The file permissions can be changed afterwards. As default, it's set as
+The file permissions can be changed afterwards. By default, they are
read only for all users. If you want to add write permission for the
-user (root as default), do as follows:
-
-::
+user (root by default), do as follows::
entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
-and set the write buffer size and the callback
-
-::
+and set the write buffer size and the callback::
entry->c.text.write = my_proc_write;
-For the write callback, you can use :c:func:`snd_info_get_line()`
+In the write callback, you can use :c:func:`snd_info_get_line()`
to get a text line, and :c:func:`snd_info_get_str()` to retrieve
a string from the line. Some examples are found in
``core/oss/mixer_oss.c``, core/oss/and ``pcm_oss.c``.
-For a raw-data proc-file, set the attributes as follows:
-
-::
+For a raw-data proc-file, set the attributes as follows::
static const struct snd_info_entry_ops my_file_io_ops = {
.read = my_file_io_read,
entry->size = 4096;
entry->mode = S_IFREG | S_IRUGO;
-For the raw data, ``size`` field must be set properly. This specifies
+For raw data, ``size`` field must be set properly. This specifies
the maximum size of the proc file access.
The read/write callbacks of raw mode are more direct than the text mode.
You need to use a low-level I/O functions such as
-:c:func:`copy_from_user()` and :c:func:`copy_to_user()` to transfer the data.
-
-::
+:c:func:`copy_from_user()` and :c:func:`copy_to_user()` to transfer the
+data::
static ssize_t my_file_io_read(struct snd_info_entry *entry,
void *file_private_data,
If the chip is supposed to work with suspend/resume functions, you need
to add power-management code to the driver. The additional code for
power-management should be ifdef-ed with ``CONFIG_PM``, or annotated
-with __maybe_unused attribute; otherwise the compiler will complain
-you.
+with __maybe_unused attribute; otherwise the compiler will complain.
If the driver *fully* supports suspend/resume that is, the device can be
properly resumed to its state when suspend was called, you can set the
-``SNDRV_PCM_INFO_RESUME`` flag in the pcm info field. Usually, this is
+``SNDRV_PCM_INFO_RESUME`` flag in the PCM info field. Usually, this is
possible when the registers of the chip can be safely saved and restored
to RAM. If this is set, the trigger callback is called with
``SNDRV_PCM_TRIGGER_RESUME`` after the resume callback completes.
callbacks. In such a case, applications would reset the status by
calling :c:func:`snd_pcm_prepare()` and restart the stream
appropriately. Hence, you can define suspend/resume callbacks below but
-don't set ``SNDRV_PCM_INFO_RESUME`` info flag to the PCM.
+don't set the ``SNDRV_PCM_INFO_RESUME`` info flag to the PCM.
Note that the trigger with SUSPEND can always be called when
:c:func:`snd_pcm_suspend_all()` is called, regardless of the
callback when no ``SNDRV_PCM_INFO_RESUME`` flag is set. But, it's better
to keep it for compatibility reasons.)
-In the earlier version of ALSA drivers, a common power-management layer
-was provided, but it has been removed. The driver needs to define the
+The driver needs to define the
suspend/resume hooks according to the bus the device is connected to. In
-the case of PCI drivers, the callbacks look like below:
-
-::
+the case of PCI drivers, the callbacks look like below::
static int __maybe_unused snd_my_suspend(struct device *dev)
{
return 0;
}
-The scheme of the real suspend job is as follows.
+The scheme of the real suspend job is as follows:
1. Retrieve the card and the chip data.
5. Stop the hardware if necessary.
-A typical code would be like:
-
-::
+Typical code would look like::
static int __maybe_unused mychip_suspend(struct device *dev)
{
}
-The scheme of the real resume job is as follows.
+The scheme of the real resume job is as follows:
1. Retrieve the card and the chip data.
3. Restore the saved registers if necessary.
-4. Resume the mixer, e.g. calling :c:func:`snd_ac97_resume()`.
+4. Resume the mixer, e.g. by calling :c:func:`snd_ac97_resume()`.
5. Restart the hardware (if any).
6. Call :c:func:`snd_power_change_state()` with
``SNDRV_CTL_POWER_D0`` to notify the processes.
-A typical code would be like:
-
-::
+Typical code would look like::
static int __maybe_unused mychip_resume(struct pci_dev *pci)
{
OK, we have all callbacks now. Let's set them up. In the initialization
of the card, make sure that you can get the chip data from the card
instance, typically via ``private_data`` field, in case you created the
-chip data individually.
-
-::
+chip data individually::
static int snd_mychip_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
}
When you created the chip data with :c:func:`snd_card_new()`, it's
-anyway accessible via ``private_data`` field.
-
-::
+anyway accessible via ``private_data`` field::
static int snd_mychip_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
....
}
-If you need a space to save the registers, allocate the buffer for it
+If you need space to save the registers, allocate the buffer for it
here, too, since it would be fatal if you cannot allocate a memory in
the suspend phase. The allocated buffer should be released in the
corresponding destructor.
-And next, set suspend/resume callbacks to the pci_driver.
-
-::
+And next, set suspend/resume callbacks to the pci_driver::
static SIMPLE_DEV_PM_OPS(snd_my_pm_ops, mychip_suspend, mychip_resume);
If the module supports multiple cards (usually up to 8 = ``SNDRV_CARDS``
cards), they should be arrays. The default initial values are defined
-already as constants for easier programming:
-
-::
+already as constants for easier programming::
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
``module_param()``, ``module_param_array()`` and
:c:func:`MODULE_PARM_DESC()` macros.
-The typical coding would be like below:
-
-::
+Typical code would look as below::
#define CARD_NAME "My Chip"
Also, don't forget to define the module description and the license.
Especially, the recent modprobe requires to define the
-module license as GPL, etc., otherwise the system is shown as “tainted”.
-
-::
+module license as GPL, etc., otherwise the system is shown as “tainted”::
MODULE_DESCRIPTION("Sound driver for My Chip");
MODULE_LICENSE("GPL");
1. Modify sound/pci/Makefile
- Suppose you have a file xyz.c. Add the following two lines
-
-::
+ Suppose you have a file xyz.c. Add the following two lines::
snd-xyz-objs := xyz.o
obj-$(CONFIG_SND_XYZ) += snd-xyz.o
2. Create the Kconfig entry
- Add the new entry of Kconfig for your xyz driver. config SND_XYZ
- tristate "Foobar XYZ" depends on SND select SND_PCM help Say Y here
- to include support for Foobar XYZ soundcard. To compile this driver
- as a module, choose M here: the module will be called snd-xyz. the
- line, select SND_PCM, specifies that the driver xyz supports PCM. In
- addition to SND_PCM, the following components are supported for
- select command: SND_RAWMIDI, SND_TIMER, SND_HWDEP,
- SND_MPU401_UART, SND_OPL3_LIB, SND_OPL4_LIB, SND_VX_LIB,
- SND_AC97_CODEC. Add the select command for each supported
- component.
+ Add the new entry of Kconfig for your xyz driver::
+
+ config SND_XYZ
+ tristate "Foobar XYZ"
+ depends on SND
+ select SND_PCM
+ help
+ Say Y here to include support for Foobar XYZ soundcard.
+ To compile this driver as a module, choose M here:
+ the module will be called snd-xyz.
+
+The line ``select SND_PCM`` specifies that the driver xyz supports PCM.
+In addition to SND_PCM, the following components are supported for
+select command: SND_RAWMIDI, SND_TIMER, SND_HWDEP, SND_MPU401_UART,
+SND_OPL3_LIB, SND_OPL4_LIB, SND_VX_LIB, SND_AC97_CODEC.
+Add the select command for each supported component.
- Note that some selections imply the lowlevel selections. For example,
- PCM includes TIMER, MPU401_UART includes RAWMIDI, AC97_CODEC
- includes PCM, and OPL3_LIB includes HWDEP. You don't need to give
- the lowlevel selections again.
+Note that some selections imply the lowlevel selections. For example,
+PCM includes TIMER, MPU401_UART includes RAWMIDI, AC97_CODEC
+includes PCM, and OPL3_LIB includes HWDEP. You don't need to give
+the lowlevel selections again.
- For the details of Kconfig script, refer to the kbuild documentation.
+For the details of Kconfig script, refer to the kbuild documentation.
Drivers with Several Source Files
---------------------------------
located in the new subdirectory, sound/pci/xyz.
1. Add a new directory (``sound/pci/xyz``) in ``sound/pci/Makefile``
- as below
-
-::
+ as below::
obj-$(CONFIG_SND) += sound/pci/xyz/
-2. Under the directory ``sound/pci/xyz``, create a Makefile
-
-::
+2. Under the directory ``sound/pci/xyz``, create a Makefile::
snd-xyz-objs := xyz.o abc.o def.o
obj-$(CONFIG_SND_XYZ) += snd-xyz.o
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/iio/adc/cirrus,ep9301-adc.yaml
+F: Documentation/devicetree/bindings/sound/cirrus,ep9301-*
F: arch/arm/boot/compressed/misc-ep93xx.h
F: arch/arm/mach-ep93xx/
F: drivers/iio/adc/ep93xx_adc.c
S: Maintained
F: Documentation/devicetree/bindings/sound/cirrus,cs*
F: include/dt-bindings/sound/cs*
+F: include/sound/cs*
F: sound/pci/hda/cs*
F: sound/pci/hda/hda_cs_dsp_ctl.*
F: sound/soc/codecs/cs*
F: drivers/soc/fsl/qe/
F: include/soc/fsl/qe/
+FREESCALE QUICC ENGINE QMC DRIVER
+M: Herve Codina <herve.codina@bootlin.com>
+L: linuxppc-dev@lists.ozlabs.org
+S: Maintained
+F: Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-scc-qmc.yaml
+F: drivers/soc/fsl/qe/qmc.c
+F: include/soc/fsl/qe/qmc.h
+
+FREESCALE QUICC ENGINE TSA DRIVER
+M: Herve Codina <herve.codina@bootlin.com>
+L: linuxppc-dev@lists.ozlabs.org
+S: Maintained
+F: Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-tsa.yaml
+F: drivers/soc/fsl/qe/tsa.c
+F: drivers/soc/fsl/qe/tsa.h
+F: include/dt-bindings/soc/cpm1-fsl,tsa.h
+
FREESCALE QUICC ENGINE UCC ETHERNET DRIVER
M: Li Yang <leoyang.li@nxp.com>
L: netdev@vger.kernel.org
F: sound/soc/fsl/imx*
F: sound/soc/fsl/mpc8610_hpcd.c
+FREESCALE SOC SOUND QMC DRIVER
+M: Herve Codina <herve.codina@bootlin.com>
+L: alsa-devel@alsa-project.org (moderated for non-subscribers)
+L: linuxppc-dev@lists.ozlabs.org
+S: Maintained
+F: Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml
+F: sound/soc/fsl/fsl_qmc_audio.c
+
FREESCALE USB PERIPHERAL DRIVERS
M: Li Yang <leoyang.li@nxp.com>
L: linux-usb@vger.kernel.org
F: drivers/spi/spi-at91-usart.c
MICROCHIP AUDIO ASOC DRIVERS
-M: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
+M: Claudiu Beznea <claudiu.beznea@microchip.com>
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
S: Supported
+F: Documentation/devicetree/bindings/sound/atmel*
+F: Documentation/devicetree/bindings/sound/axentia,tse850-pcm5142.txt
+F: Documentation/devicetree/bindings/sound/microchip,sama7g5-*
+F: Documentation/devicetree/bindings/sound/mikroe,mikroe-proto.txt
F: sound/soc/atmel
MICROCHIP CSI2DC DRIVER
F: drivers/spi/spi-atmel.*
MICROCHIP SSC DRIVER
-M: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
+M: Claudiu Beznea <claudiu.beznea@microchip.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Supported
+F: Documentation/devicetree/bindings/misc/atmel-ssc.txt
F: drivers/misc/atmel-ssc.c
F: include/linux/atmel-ssc.h
int i, ret;
unsigned long flags;
- if (command & 0xffffff0f)
+ if (command & 0xffffff03)
return -EINVAL;
spin_lock_irqsave(&cmd_lock, flags);
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
+#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
static const struct cs_dsp_ops cs_dsp_adsp1_ops;
static const struct cs_dsp_ops cs_dsp_adsp2_ops[];
static const struct cs_dsp_ops cs_dsp_halo_ops;
+static const struct cs_dsp_ops cs_dsp_halo_ao_ops;
struct cs_dsp_buf {
struct list_head list;
},
};
+static int cs_dsp_coeff_base_reg(struct cs_dsp_coeff_ctl *ctl, unsigned int *reg,
+ unsigned int off);
+
+static int cs_dsp_debugfs_read_controls_show(struct seq_file *s, void *ignored)
+{
+ struct cs_dsp *dsp = s->private;
+ struct cs_dsp_coeff_ctl *ctl;
+ unsigned int reg;
+
+ list_for_each_entry(ctl, &dsp->ctl_list, list) {
+ cs_dsp_coeff_base_reg(ctl, ®, 0);
+ seq_printf(s, "%22.*s: %#8zx %s:%08x %#8x %s %#8x %#4x %c%c%c%c %s %s\n",
+ ctl->subname_len, ctl->subname, ctl->len,
+ cs_dsp_mem_region_name(ctl->alg_region.type),
+ ctl->offset, reg, ctl->fw_name, ctl->alg_region.alg, ctl->type,
+ ctl->flags & WMFW_CTL_FLAG_VOLATILE ? 'V' : '-',
+ ctl->flags & WMFW_CTL_FLAG_SYS ? 'S' : '-',
+ ctl->flags & WMFW_CTL_FLAG_READABLE ? 'R' : '-',
+ ctl->flags & WMFW_CTL_FLAG_WRITEABLE ? 'W' : '-',
+ ctl->enabled ? "enabled" : "disabled",
+ ctl->set ? "dirty" : "clean");
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(cs_dsp_debugfs_read_controls);
+
/**
* cs_dsp_init_debugfs() - Create and populate DSP representation in debugfs
* @dsp: pointer to DSP structure
debugfs_create_file(cs_dsp_debugfs_fops[i].name, 0444, root,
dsp, &cs_dsp_debugfs_fops[i].fops);
+ debugfs_create_file("controls", 0444, root, dsp,
+ &cs_dsp_debugfs_read_controls_fops);
+
dsp->debugfs_root = root;
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_init_debugfs, FW_CS_DSP);
int regions = 0;
int ret, offset, type;
+ if (!firmware)
+ return 0;
+
ret = -EINVAL;
pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer);
*/
int cs_dsp_halo_init(struct cs_dsp *dsp)
{
- dsp->ops = &cs_dsp_halo_ops;
+ if (dsp->no_core_startstop)
+ dsp->ops = &cs_dsp_halo_ao_ops;
+ else
+ dsp->ops = &cs_dsp_halo_ops;
return cs_dsp_common_init(dsp);
}
.stop_core = cs_dsp_halo_stop_core,
};
+static const struct cs_dsp_ops cs_dsp_halo_ao_ops = {
+ .parse_sizes = cs_dsp_adsp2_parse_sizes,
+ .validate_version = cs_dsp_halo_validate_version,
+ .setup_algs = cs_dsp_halo_setup_algs,
+ .region_to_reg = cs_dsp_halo_region_to_reg,
+ .show_fw_status = cs_dsp_halo_show_fw_status,
+};
+
/**
* cs_dsp_chunk_write() - Format data to a DSP memory chunk
* @ch: Pointer to the chunk structure
bool
default y if UCC_FAST || UCC_SLOW
+config CPM_TSA
+ tristate "CPM TSA support"
+ depends on OF && HAS_IOMEM
+ depends on CPM1 || COMPILE_TEST
+ help
+ Freescale CPM Time Slot Assigner (TSA)
+ controller.
+
+ This option enables support for this
+ controller
+
+config CPM_QMC
+ tristate "CPM QMC support"
+ depends on OF && HAS_IOMEM
+ depends on CPM1 || (FSL_SOC && COMPILE_TEST)
+ depends on CPM_TSA
+ help
+ Freescale CPM QUICC Multichannel Controller
+ (QMC)
+
+ This option enables support for this
+ controller
+
config QE_TDM
bool
default y if FSL_UCC_HDLC
#
obj-$(CONFIG_QUICC_ENGINE)+= qe.o qe_common.o qe_ic.o qe_io.o
obj-$(CONFIG_CPM) += qe_common.o
+obj-$(CONFIG_CPM_TSA) += tsa.o
+obj-$(CONFIG_CPM_QMC) += qmc.o
obj-$(CONFIG_UCC) += ucc.o
obj-$(CONFIG_UCC_SLOW) += ucc_slow.o
obj-$(CONFIG_UCC_FAST) += ucc_fast.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * QMC driver
+ *
+ * Copyright 2022 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+
+#include <soc/fsl/qe/qmc.h>
+#include <linux/dma-mapping.h>
+#include <linux/hdlc.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <soc/fsl/cpm.h>
+#include <sysdev/fsl_soc.h>
+#include "tsa.h"
+
+/* SCC general mode register high (32 bits) */
+#define SCC_GSMRL 0x00
+#define SCC_GSMRL_ENR (1 << 5)
+#define SCC_GSMRL_ENT (1 << 4)
+#define SCC_GSMRL_MODE_QMC (0x0A << 0)
+
+/* SCC general mode register low (32 bits) */
+#define SCC_GSMRH 0x04
+#define SCC_GSMRH_CTSS (1 << 7)
+#define SCC_GSMRH_CDS (1 << 8)
+#define SCC_GSMRH_CTSP (1 << 9)
+#define SCC_GSMRH_CDP (1 << 10)
+
+/* SCC event register (16 bits) */
+#define SCC_SCCE 0x10
+#define SCC_SCCE_IQOV (1 << 3)
+#define SCC_SCCE_GINT (1 << 2)
+#define SCC_SCCE_GUN (1 << 1)
+#define SCC_SCCE_GOV (1 << 0)
+
+/* SCC mask register (16 bits) */
+#define SCC_SCCM 0x14
+/* Multichannel base pointer (32 bits) */
+#define QMC_GBL_MCBASE 0x00
+/* Multichannel controller state (16 bits) */
+#define QMC_GBL_QMCSTATE 0x04
+/* Maximum receive buffer length (16 bits) */
+#define QMC_GBL_MRBLR 0x06
+/* Tx time-slot assignment table pointer (16 bits) */
+#define QMC_GBL_TX_S_PTR 0x08
+/* Rx pointer (16 bits) */
+#define QMC_GBL_RXPTR 0x0A
+/* Global receive frame threshold (16 bits) */
+#define QMC_GBL_GRFTHR 0x0C
+/* Global receive frame count (16 bits) */
+#define QMC_GBL_GRFCNT 0x0E
+/* Multichannel interrupt base address (32 bits) */
+#define QMC_GBL_INTBASE 0x10
+/* Multichannel interrupt pointer (32 bits) */
+#define QMC_GBL_INTPTR 0x14
+/* Rx time-slot assignment table pointer (16 bits) */
+#define QMC_GBL_RX_S_PTR 0x18
+/* Tx pointer (16 bits) */
+#define QMC_GBL_TXPTR 0x1A
+/* CRC constant (32 bits) */
+#define QMC_GBL_C_MASK32 0x1C
+/* Time slot assignment table Rx (32 x 16 bits) */
+#define QMC_GBL_TSATRX 0x20
+/* Time slot assignment table Tx (32 x 16 bits) */
+#define QMC_GBL_TSATTX 0x60
+/* CRC constant (16 bits) */
+#define QMC_GBL_C_MASK16 0xA0
+
+/* TSA entry (16bit entry in TSATRX and TSATTX) */
+#define QMC_TSA_VALID (1 << 15)
+#define QMC_TSA_WRAP (1 << 14)
+#define QMC_TSA_MASK (0x303F)
+#define QMC_TSA_CHANNEL(x) ((x) << 6)
+
+/* Tx buffer descriptor base address (16 bits, offset from MCBASE) */
+#define QMC_SPE_TBASE 0x00
+
+/* Channel mode register (16 bits) */
+#define QMC_SPE_CHAMR 0x02
+#define QMC_SPE_CHAMR_MODE_HDLC (1 << 15)
+#define QMC_SPE_CHAMR_MODE_TRANSP ((0 << 15) | (1 << 13))
+#define QMC_SPE_CHAMR_ENT (1 << 12)
+#define QMC_SPE_CHAMR_POL (1 << 8)
+#define QMC_SPE_CHAMR_HDLC_IDLM (1 << 13)
+#define QMC_SPE_CHAMR_HDLC_CRC (1 << 7)
+#define QMC_SPE_CHAMR_HDLC_NOF (0x0f << 0)
+#define QMC_SPE_CHAMR_TRANSP_RD (1 << 14)
+#define QMC_SPE_CHAMR_TRANSP_SYNC (1 << 10)
+
+/* Tx internal state (32 bits) */
+#define QMC_SPE_TSTATE 0x04
+/* Tx buffer descriptor pointer (16 bits) */
+#define QMC_SPE_TBPTR 0x0C
+/* Zero-insertion state (32 bits) */
+#define QMC_SPE_ZISTATE 0x14
+/* Channel’s interrupt mask flags (16 bits) */
+#define QMC_SPE_INTMSK 0x1C
+/* Rx buffer descriptor base address (16 bits, offset from MCBASE) */
+#define QMC_SPE_RBASE 0x20
+/* HDLC: Maximum frame length register (16 bits) */
+#define QMC_SPE_MFLR 0x22
+/* TRANSPARENT: Transparent maximum receive length (16 bits) */
+#define QMC_SPE_TMRBLR 0x22
+/* Rx internal state (32 bits) */
+#define QMC_SPE_RSTATE 0x24
+/* Rx buffer descriptor pointer (16 bits) */
+#define QMC_SPE_RBPTR 0x2C
+/* Packs 4 bytes to 1 long word before writing to buffer (32 bits) */
+#define QMC_SPE_RPACK 0x30
+/* Zero deletion state (32 bits) */
+#define QMC_SPE_ZDSTATE 0x34
+
+/* Transparent synchronization (16 bits) */
+#define QMC_SPE_TRNSYNC 0x3C
+#define QMC_SPE_TRNSYNC_RX(x) ((x) << 8)
+#define QMC_SPE_TRNSYNC_TX(x) ((x) << 0)
+
+/* Interrupt related registers bits */
+#define QMC_INT_V (1 << 15)
+#define QMC_INT_W (1 << 14)
+#define QMC_INT_NID (1 << 13)
+#define QMC_INT_IDL (1 << 12)
+#define QMC_INT_GET_CHANNEL(x) (((x) & 0x0FC0) >> 6)
+#define QMC_INT_MRF (1 << 5)
+#define QMC_INT_UN (1 << 4)
+#define QMC_INT_RXF (1 << 3)
+#define QMC_INT_BSY (1 << 2)
+#define QMC_INT_TXB (1 << 1)
+#define QMC_INT_RXB (1 << 0)
+
+/* BD related registers bits */
+#define QMC_BD_RX_E (1 << 15)
+#define QMC_BD_RX_W (1 << 13)
+#define QMC_BD_RX_I (1 << 12)
+#define QMC_BD_RX_L (1 << 11)
+#define QMC_BD_RX_F (1 << 10)
+#define QMC_BD_RX_CM (1 << 9)
+#define QMC_BD_RX_UB (1 << 7)
+#define QMC_BD_RX_LG (1 << 5)
+#define QMC_BD_RX_NO (1 << 4)
+#define QMC_BD_RX_AB (1 << 3)
+#define QMC_BD_RX_CR (1 << 2)
+
+#define QMC_BD_TX_R (1 << 15)
+#define QMC_BD_TX_W (1 << 13)
+#define QMC_BD_TX_I (1 << 12)
+#define QMC_BD_TX_L (1 << 11)
+#define QMC_BD_TX_TC (1 << 10)
+#define QMC_BD_TX_CM (1 << 9)
+#define QMC_BD_TX_UB (1 << 7)
+#define QMC_BD_TX_PAD (0x0f << 0)
+
+/* Numbers of BDs and interrupt items */
+#define QMC_NB_TXBDS 8
+#define QMC_NB_RXBDS 8
+#define QMC_NB_INTS 128
+
+struct qmc_xfer_desc {
+ union {
+ void (*tx_complete)(void *context);
+ void (*rx_complete)(void *context, size_t length);
+ };
+ void *context;
+};
+
+struct qmc_chan {
+ struct list_head list;
+ unsigned int id;
+ struct qmc *qmc;
+ void *__iomem s_param;
+ enum qmc_mode mode;
+ u64 tx_ts_mask;
+ u64 rx_ts_mask;
+ bool is_reverse_data;
+
+ spinlock_t tx_lock;
+ cbd_t __iomem *txbds;
+ cbd_t __iomem *txbd_free;
+ cbd_t __iomem *txbd_done;
+ struct qmc_xfer_desc tx_desc[QMC_NB_TXBDS];
+ u64 nb_tx_underrun;
+ bool is_tx_stopped;
+
+ spinlock_t rx_lock;
+ cbd_t __iomem *rxbds;
+ cbd_t __iomem *rxbd_free;
+ cbd_t __iomem *rxbd_done;
+ struct qmc_xfer_desc rx_desc[QMC_NB_RXBDS];
+ u64 nb_rx_busy;
+ int rx_pending;
+ bool is_rx_halted;
+ bool is_rx_stopped;
+};
+
+struct qmc {
+ struct device *dev;
+ struct tsa_serial *tsa_serial;
+ void *__iomem scc_regs;
+ void *__iomem scc_pram;
+ void *__iomem dpram;
+ u16 scc_pram_offset;
+ cbd_t __iomem *bd_table;
+ dma_addr_t bd_dma_addr;
+ size_t bd_size;
+ u16 __iomem *int_table;
+ u16 __iomem *int_curr;
+ dma_addr_t int_dma_addr;
+ size_t int_size;
+ struct list_head chan_head;
+ struct qmc_chan *chans[64];
+};
+
+static inline void qmc_write16(void *__iomem addr, u16 val)
+{
+ iowrite16be(val, addr);
+}
+
+static inline u16 qmc_read16(void *__iomem addr)
+{
+ return ioread16be(addr);
+}
+
+static inline void qmc_setbits16(void *__iomem addr, u16 set)
+{
+ qmc_write16(addr, qmc_read16(addr) | set);
+}
+
+static inline void qmc_clrbits16(void *__iomem addr, u16 clr)
+{
+ qmc_write16(addr, qmc_read16(addr) & ~clr);
+}
+
+static inline void qmc_write32(void *__iomem addr, u32 val)
+{
+ iowrite32be(val, addr);
+}
+
+static inline u32 qmc_read32(void *__iomem addr)
+{
+ return ioread32be(addr);
+}
+
+static inline void qmc_setbits32(void *__iomem addr, u32 set)
+{
+ qmc_write32(addr, qmc_read32(addr) | set);
+}
+
+
+int qmc_chan_get_info(struct qmc_chan *chan, struct qmc_chan_info *info)
+{
+ struct tsa_serial_info tsa_info;
+ int ret;
+
+ /* Retrieve info from the TSA related serial */
+ ret = tsa_serial_get_info(chan->qmc->tsa_serial, &tsa_info);
+ if (ret)
+ return ret;
+
+ info->mode = chan->mode;
+ info->rx_fs_rate = tsa_info.rx_fs_rate;
+ info->rx_bit_rate = tsa_info.rx_bit_rate;
+ info->nb_tx_ts = hweight64(chan->tx_ts_mask);
+ info->tx_fs_rate = tsa_info.tx_fs_rate;
+ info->tx_bit_rate = tsa_info.tx_bit_rate;
+ info->nb_rx_ts = hweight64(chan->rx_ts_mask);
+
+ return 0;
+}
+EXPORT_SYMBOL(qmc_chan_get_info);
+
+int qmc_chan_set_param(struct qmc_chan *chan, const struct qmc_chan_param *param)
+{
+ if (param->mode != chan->mode)
+ return -EINVAL;
+
+ switch (param->mode) {
+ case QMC_HDLC:
+ if ((param->hdlc.max_rx_buf_size % 4) ||
+ (param->hdlc.max_rx_buf_size < 8))
+ return -EINVAL;
+
+ qmc_write16(chan->qmc->scc_pram + QMC_GBL_MRBLR,
+ param->hdlc.max_rx_buf_size - 8);
+ qmc_write16(chan->s_param + QMC_SPE_MFLR,
+ param->hdlc.max_rx_frame_size);
+ if (param->hdlc.is_crc32) {
+ qmc_setbits16(chan->s_param + QMC_SPE_CHAMR,
+ QMC_SPE_CHAMR_HDLC_CRC);
+ } else {
+ qmc_clrbits16(chan->s_param + QMC_SPE_CHAMR,
+ QMC_SPE_CHAMR_HDLC_CRC);
+ }
+ break;
+
+ case QMC_TRANSPARENT:
+ qmc_write16(chan->s_param + QMC_SPE_TMRBLR,
+ param->transp.max_rx_buf_size);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(qmc_chan_set_param);
+
+int qmc_chan_write_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,
+ void (*complete)(void *context), void *context)
+{
+ struct qmc_xfer_desc *xfer_desc;
+ unsigned long flags;
+ cbd_t *__iomem bd;
+ u16 ctrl;
+ int ret;
+
+ /*
+ * R bit UB bit
+ * 0 0 : The BD is free
+ * 1 1 : The BD is in used, waiting for transfer
+ * 0 1 : The BD is in used, waiting for completion
+ * 1 0 : Should not append
+ */
+
+ spin_lock_irqsave(&chan->tx_lock, flags);
+ bd = chan->txbd_free;
+
+ ctrl = qmc_read16(&bd->cbd_sc);
+ if (ctrl & (QMC_BD_TX_R | QMC_BD_TX_UB)) {
+ /* We are full ... */
+ ret = -EBUSY;
+ goto end;
+ }
+
+ qmc_write16(&bd->cbd_datlen, length);
+ qmc_write32(&bd->cbd_bufaddr, addr);
+
+ xfer_desc = &chan->tx_desc[bd - chan->txbds];
+ xfer_desc->tx_complete = complete;
+ xfer_desc->context = context;
+
+ /* Activate the descriptor */
+ ctrl |= (QMC_BD_TX_R | QMC_BD_TX_UB);
+ wmb(); /* Be sure to flush the descriptor before control update */
+ qmc_write16(&bd->cbd_sc, ctrl);
+
+ if (!chan->is_tx_stopped)
+ qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_POL);
+
+ if (ctrl & QMC_BD_TX_W)
+ chan->txbd_free = chan->txbds;
+ else
+ chan->txbd_free++;
+
+ ret = 0;
+
+end:
+ spin_unlock_irqrestore(&chan->tx_lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL(qmc_chan_write_submit);
+
+static void qmc_chan_write_done(struct qmc_chan *chan)
+{
+ struct qmc_xfer_desc *xfer_desc;
+ void (*complete)(void *context);
+ unsigned long flags;
+ void *context;
+ cbd_t *__iomem bd;
+ u16 ctrl;
+
+ /*
+ * R bit UB bit
+ * 0 0 : The BD is free
+ * 1 1 : The BD is in used, waiting for transfer
+ * 0 1 : The BD is in used, waiting for completion
+ * 1 0 : Should not append
+ */
+
+ spin_lock_irqsave(&chan->tx_lock, flags);
+ bd = chan->txbd_done;
+
+ ctrl = qmc_read16(&bd->cbd_sc);
+ while (!(ctrl & QMC_BD_TX_R)) {
+ if (!(ctrl & QMC_BD_TX_UB))
+ goto end;
+
+ xfer_desc = &chan->tx_desc[bd - chan->txbds];
+ complete = xfer_desc->tx_complete;
+ context = xfer_desc->context;
+ xfer_desc->tx_complete = NULL;
+ xfer_desc->context = NULL;
+
+ qmc_write16(&bd->cbd_sc, ctrl & ~QMC_BD_TX_UB);
+
+ if (ctrl & QMC_BD_TX_W)
+ chan->txbd_done = chan->txbds;
+ else
+ chan->txbd_done++;
+
+ if (complete) {
+ spin_unlock_irqrestore(&chan->tx_lock, flags);
+ complete(context);
+ spin_lock_irqsave(&chan->tx_lock, flags);
+ }
+
+ bd = chan->txbd_done;
+ ctrl = qmc_read16(&bd->cbd_sc);
+ }
+
+end:
+ spin_unlock_irqrestore(&chan->tx_lock, flags);
+}
+
+int qmc_chan_read_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,
+ void (*complete)(void *context, size_t length), void *context)
+{
+ struct qmc_xfer_desc *xfer_desc;
+ unsigned long flags;
+ cbd_t *__iomem bd;
+ u16 ctrl;
+ int ret;
+
+ /*
+ * E bit UB bit
+ * 0 0 : The BD is free
+ * 1 1 : The BD is in used, waiting for transfer
+ * 0 1 : The BD is in used, waiting for completion
+ * 1 0 : Should not append
+ */
+
+ spin_lock_irqsave(&chan->rx_lock, flags);
+ bd = chan->rxbd_free;
+
+ ctrl = qmc_read16(&bd->cbd_sc);
+ if (ctrl & (QMC_BD_RX_E | QMC_BD_RX_UB)) {
+ /* We are full ... */
+ ret = -EBUSY;
+ goto end;
+ }
+
+ qmc_write16(&bd->cbd_datlen, 0); /* data length is updated by the QMC */
+ qmc_write32(&bd->cbd_bufaddr, addr);
+
+ xfer_desc = &chan->rx_desc[bd - chan->rxbds];
+ xfer_desc->rx_complete = complete;
+ xfer_desc->context = context;
+
+ /* Activate the descriptor */
+ ctrl |= (QMC_BD_RX_E | QMC_BD_RX_UB);
+ wmb(); /* Be sure to flush data before descriptor activation */
+ qmc_write16(&bd->cbd_sc, ctrl);
+
+ /* Restart receiver if needed */
+ if (chan->is_rx_halted && !chan->is_rx_stopped) {
+ /* Restart receiver */
+ if (chan->mode == QMC_TRANSPARENT)
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+ else
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+ qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+ chan->is_rx_halted = false;
+ }
+ chan->rx_pending++;
+
+ if (ctrl & QMC_BD_RX_W)
+ chan->rxbd_free = chan->rxbds;
+ else
+ chan->rxbd_free++;
+
+ ret = 0;
+end:
+ spin_unlock_irqrestore(&chan->rx_lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL(qmc_chan_read_submit);
+
+static void qmc_chan_read_done(struct qmc_chan *chan)
+{
+ void (*complete)(void *context, size_t size);
+ struct qmc_xfer_desc *xfer_desc;
+ unsigned long flags;
+ cbd_t *__iomem bd;
+ void *context;
+ u16 datalen;
+ u16 ctrl;
+
+ /*
+ * E bit UB bit
+ * 0 0 : The BD is free
+ * 1 1 : The BD is in used, waiting for transfer
+ * 0 1 : The BD is in used, waiting for completion
+ * 1 0 : Should not append
+ */
+
+ spin_lock_irqsave(&chan->rx_lock, flags);
+ bd = chan->rxbd_done;
+
+ ctrl = qmc_read16(&bd->cbd_sc);
+ while (!(ctrl & QMC_BD_RX_E)) {
+ if (!(ctrl & QMC_BD_RX_UB))
+ goto end;
+
+ xfer_desc = &chan->rx_desc[bd - chan->rxbds];
+ complete = xfer_desc->rx_complete;
+ context = xfer_desc->context;
+ xfer_desc->rx_complete = NULL;
+ xfer_desc->context = NULL;
+
+ datalen = qmc_read16(&bd->cbd_datlen);
+ qmc_write16(&bd->cbd_sc, ctrl & ~QMC_BD_RX_UB);
+
+ if (ctrl & QMC_BD_RX_W)
+ chan->rxbd_done = chan->rxbds;
+ else
+ chan->rxbd_done++;
+
+ chan->rx_pending--;
+
+ if (complete) {
+ spin_unlock_irqrestore(&chan->rx_lock, flags);
+ complete(context, datalen);
+ spin_lock_irqsave(&chan->rx_lock, flags);
+ }
+
+ bd = chan->rxbd_done;
+ ctrl = qmc_read16(&bd->cbd_sc);
+ }
+
+end:
+ spin_unlock_irqrestore(&chan->rx_lock, flags);
+}
+
+static int qmc_chan_command(struct qmc_chan *chan, u8 qmc_opcode)
+{
+ return cpm_command(chan->id << 2, (qmc_opcode << 4) | 0x0E);
+}
+
+static int qmc_chan_stop_rx(struct qmc_chan *chan)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&chan->rx_lock, flags);
+
+ /* Send STOP RECEIVE command */
+ ret = qmc_chan_command(chan, 0x0);
+ if (ret) {
+ dev_err(chan->qmc->dev, "chan %u: Send STOP RECEIVE failed (%d)\n",
+ chan->id, ret);
+ goto end;
+ }
+
+ chan->is_rx_stopped = true;
+
+end:
+ spin_unlock_irqrestore(&chan->rx_lock, flags);
+ return ret;
+}
+
+static int qmc_chan_stop_tx(struct qmc_chan *chan)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&chan->tx_lock, flags);
+
+ /* Send STOP TRANSMIT command */
+ ret = qmc_chan_command(chan, 0x1);
+ if (ret) {
+ dev_err(chan->qmc->dev, "chan %u: Send STOP TRANSMIT failed (%d)\n",
+ chan->id, ret);
+ goto end;
+ }
+
+ chan->is_tx_stopped = true;
+
+end:
+ spin_unlock_irqrestore(&chan->tx_lock, flags);
+ return ret;
+}
+
+int qmc_chan_stop(struct qmc_chan *chan, int direction)
+{
+ int ret;
+
+ if (direction & QMC_CHAN_READ) {
+ ret = qmc_chan_stop_rx(chan);
+ if (ret)
+ return ret;
+ }
+
+ if (direction & QMC_CHAN_WRITE) {
+ ret = qmc_chan_stop_tx(chan);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(qmc_chan_stop);
+
+static void qmc_chan_start_rx(struct qmc_chan *chan)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chan->rx_lock, flags);
+
+ /* Restart the receiver */
+ if (chan->mode == QMC_TRANSPARENT)
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+ else
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+ qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+ chan->is_rx_halted = false;
+
+ chan->is_rx_stopped = false;
+
+ spin_unlock_irqrestore(&chan->rx_lock, flags);
+}
+
+static void qmc_chan_start_tx(struct qmc_chan *chan)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&chan->tx_lock, flags);
+
+ /*
+ * Enable channel transmitter as it could be disabled if
+ * qmc_chan_reset() was called.
+ */
+ qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_ENT);
+
+ /* Set the POL bit in the channel mode register */
+ qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_POL);
+
+ chan->is_tx_stopped = false;
+
+ spin_unlock_irqrestore(&chan->tx_lock, flags);
+}
+
+int qmc_chan_start(struct qmc_chan *chan, int direction)
+{
+ if (direction & QMC_CHAN_READ)
+ qmc_chan_start_rx(chan);
+
+ if (direction & QMC_CHAN_WRITE)
+ qmc_chan_start_tx(chan);
+
+ return 0;
+}
+EXPORT_SYMBOL(qmc_chan_start);
+
+static void qmc_chan_reset_rx(struct qmc_chan *chan)
+{
+ struct qmc_xfer_desc *xfer_desc;
+ unsigned long flags;
+ cbd_t *__iomem bd;
+ u16 ctrl;
+
+ spin_lock_irqsave(&chan->rx_lock, flags);
+ bd = chan->rxbds;
+ do {
+ ctrl = qmc_read16(&bd->cbd_sc);
+ qmc_write16(&bd->cbd_sc, ctrl & ~(QMC_BD_RX_UB | QMC_BD_RX_E));
+
+ xfer_desc = &chan->rx_desc[bd - chan->rxbds];
+ xfer_desc->rx_complete = NULL;
+ xfer_desc->context = NULL;
+
+ bd++;
+ } while (!(ctrl & QMC_BD_RX_W));
+
+ chan->rxbd_free = chan->rxbds;
+ chan->rxbd_done = chan->rxbds;
+ qmc_write16(chan->s_param + QMC_SPE_RBPTR,
+ qmc_read16(chan->s_param + QMC_SPE_RBASE));
+
+ chan->rx_pending = 0;
+ chan->is_rx_stopped = false;
+
+ spin_unlock_irqrestore(&chan->rx_lock, flags);
+}
+
+static void qmc_chan_reset_tx(struct qmc_chan *chan)
+{
+ struct qmc_xfer_desc *xfer_desc;
+ unsigned long flags;
+ cbd_t *__iomem bd;
+ u16 ctrl;
+
+ spin_lock_irqsave(&chan->tx_lock, flags);
+
+ /* Disable transmitter. It will be re-enable on qmc_chan_start() */
+ qmc_clrbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_ENT);
+
+ bd = chan->txbds;
+ do {
+ ctrl = qmc_read16(&bd->cbd_sc);
+ qmc_write16(&bd->cbd_sc, ctrl & ~(QMC_BD_TX_UB | QMC_BD_TX_R));
+
+ xfer_desc = &chan->tx_desc[bd - chan->txbds];
+ xfer_desc->tx_complete = NULL;
+ xfer_desc->context = NULL;
+
+ bd++;
+ } while (!(ctrl & QMC_BD_TX_W));
+
+ chan->txbd_free = chan->txbds;
+ chan->txbd_done = chan->txbds;
+ qmc_write16(chan->s_param + QMC_SPE_TBPTR,
+ qmc_read16(chan->s_param + QMC_SPE_TBASE));
+
+ /* Reset TSTATE and ZISTATE to their initial value */
+ qmc_write32(chan->s_param + QMC_SPE_TSTATE, 0x30000000);
+ qmc_write32(chan->s_param + QMC_SPE_ZISTATE, 0x00000100);
+
+ spin_unlock_irqrestore(&chan->tx_lock, flags);
+}
+
+int qmc_chan_reset(struct qmc_chan *chan, int direction)
+{
+ if (direction & QMC_CHAN_READ)
+ qmc_chan_reset_rx(chan);
+
+ if (direction & QMC_CHAN_WRITE)
+ qmc_chan_reset_tx(chan);
+
+ return 0;
+}
+EXPORT_SYMBOL(qmc_chan_reset);
+
+static int qmc_check_chans(struct qmc *qmc)
+{
+ struct tsa_serial_info info;
+ bool is_one_table = false;
+ struct qmc_chan *chan;
+ u64 tx_ts_mask = 0;
+ u64 rx_ts_mask = 0;
+ u64 tx_ts_assigned_mask;
+ u64 rx_ts_assigned_mask;
+ int ret;
+
+ /* Retrieve info from the TSA related serial */
+ ret = tsa_serial_get_info(qmc->tsa_serial, &info);
+ if (ret)
+ return ret;
+
+ if ((info.nb_tx_ts > 64) || (info.nb_rx_ts > 64)) {
+ dev_err(qmc->dev, "Number of TSA Tx/Rx TS assigned not supported\n");
+ return -EINVAL;
+ }
+
+ /*
+ * If more than 32 TS are assigned to this serial, one common table is
+ * used for Tx and Rx and so masks must be equal for all channels.
+ */
+ if ((info.nb_tx_ts > 32) || (info.nb_rx_ts > 32)) {
+ if (info.nb_tx_ts != info.nb_rx_ts) {
+ dev_err(qmc->dev, "Number of TSA Tx/Rx TS assigned are not equal\n");
+ return -EINVAL;
+ }
+ is_one_table = true;
+ }
+
+ tx_ts_assigned_mask = info.nb_tx_ts == 64 ? U64_MAX : (((u64)1) << info.nb_tx_ts) - 1;
+ rx_ts_assigned_mask = info.nb_rx_ts == 64 ? U64_MAX : (((u64)1) << info.nb_rx_ts) - 1;
+
+ list_for_each_entry(chan, &qmc->chan_head, list) {
+ if (chan->tx_ts_mask > tx_ts_assigned_mask) {
+ dev_err(qmc->dev, "chan %u uses TSA unassigned Tx TS\n", chan->id);
+ return -EINVAL;
+ }
+ if (tx_ts_mask & chan->tx_ts_mask) {
+ dev_err(qmc->dev, "chan %u uses an already used Tx TS\n", chan->id);
+ return -EINVAL;
+ }
+
+ if (chan->rx_ts_mask > rx_ts_assigned_mask) {
+ dev_err(qmc->dev, "chan %u uses TSA unassigned Rx TS\n", chan->id);
+ return -EINVAL;
+ }
+ if (rx_ts_mask & chan->rx_ts_mask) {
+ dev_err(qmc->dev, "chan %u uses an already used Rx TS\n", chan->id);
+ return -EINVAL;
+ }
+
+ if (is_one_table && (chan->tx_ts_mask != chan->rx_ts_mask)) {
+ dev_err(qmc->dev, "chan %u uses different Rx and Tx TS\n", chan->id);
+ return -EINVAL;
+ }
+
+ tx_ts_mask |= chan->tx_ts_mask;
+ rx_ts_mask |= chan->rx_ts_mask;
+ }
+
+ return 0;
+}
+
+static unsigned int qmc_nb_chans(struct qmc *qmc)
+{
+ unsigned int count = 0;
+ struct qmc_chan *chan;
+
+ list_for_each_entry(chan, &qmc->chan_head, list)
+ count++;
+
+ return count;
+}
+
+static int qmc_of_parse_chans(struct qmc *qmc, struct device_node *np)
+{
+ struct device_node *chan_np;
+ struct qmc_chan *chan;
+ const char *mode;
+ u32 chan_id;
+ u64 ts_mask;
+ int ret;
+
+ for_each_available_child_of_node(np, chan_np) {
+ ret = of_property_read_u32(chan_np, "reg", &chan_id);
+ if (ret) {
+ dev_err(qmc->dev, "%pOF: failed to read reg\n", chan_np);
+ of_node_put(chan_np);
+ return ret;
+ }
+ if (chan_id > 63) {
+ dev_err(qmc->dev, "%pOF: Invalid chan_id\n", chan_np);
+ of_node_put(chan_np);
+ return -EINVAL;
+ }
+
+ chan = devm_kzalloc(qmc->dev, sizeof(*chan), GFP_KERNEL);
+ if (!chan) {
+ of_node_put(chan_np);
+ return -ENOMEM;
+ }
+
+ chan->id = chan_id;
+ spin_lock_init(&chan->rx_lock);
+ spin_lock_init(&chan->tx_lock);
+
+ ret = of_property_read_u64(chan_np, "fsl,tx-ts-mask", &ts_mask);
+ if (ret) {
+ dev_err(qmc->dev, "%pOF: failed to read fsl,tx-ts-mask\n",
+ chan_np);
+ of_node_put(chan_np);
+ return ret;
+ }
+ chan->tx_ts_mask = ts_mask;
+
+ ret = of_property_read_u64(chan_np, "fsl,rx-ts-mask", &ts_mask);
+ if (ret) {
+ dev_err(qmc->dev, "%pOF: failed to read fsl,rx-ts-mask\n",
+ chan_np);
+ of_node_put(chan_np);
+ return ret;
+ }
+ chan->rx_ts_mask = ts_mask;
+
+ mode = "transparent";
+ ret = of_property_read_string(chan_np, "fsl,operational-mode", &mode);
+ if (ret && ret != -EINVAL) {
+ dev_err(qmc->dev, "%pOF: failed to read fsl,operational-mode\n",
+ chan_np);
+ of_node_put(chan_np);
+ return ret;
+ }
+ if (!strcmp(mode, "transparent")) {
+ chan->mode = QMC_TRANSPARENT;
+ } else if (!strcmp(mode, "hdlc")) {
+ chan->mode = QMC_HDLC;
+ } else {
+ dev_err(qmc->dev, "%pOF: Invalid fsl,operational-mode (%s)\n",
+ chan_np, mode);
+ of_node_put(chan_np);
+ return -EINVAL;
+ }
+
+ chan->is_reverse_data = of_property_read_bool(chan_np,
+ "fsl,reverse-data");
+
+ list_add_tail(&chan->list, &qmc->chan_head);
+ qmc->chans[chan->id] = chan;
+ }
+
+ return qmc_check_chans(qmc);
+}
+
+static int qmc_setup_tsa_64rxtx(struct qmc *qmc, const struct tsa_serial_info *info)
+{
+ struct qmc_chan *chan;
+ unsigned int i;
+ u16 val;
+
+ /*
+ * Use a common Tx/Rx 64 entries table.
+ * Everything was previously checked, Tx and Rx related stuffs are
+ * identical -> Used Rx related stuff to build the table
+ */
+
+ /* Invalidate all entries */
+ for (i = 0; i < 64; i++)
+ qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), 0x0000);
+
+ /* Set entries based on Rx stuff*/
+ list_for_each_entry(chan, &qmc->chan_head, list) {
+ for (i = 0; i < info->nb_rx_ts; i++) {
+ if (!(chan->rx_ts_mask & (((u64)1) << i)))
+ continue;
+
+ val = QMC_TSA_VALID | QMC_TSA_MASK |
+ QMC_TSA_CHANNEL(chan->id);
+ qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), val);
+ }
+ }
+
+ /* Set Wrap bit on last entry */
+ qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATRX + ((info->nb_rx_ts - 1) * 2),
+ QMC_TSA_WRAP);
+
+ /* Init pointers to the table */
+ val = qmc->scc_pram_offset + QMC_GBL_TSATRX;
+ qmc_write16(qmc->scc_pram + QMC_GBL_RX_S_PTR, val);
+ qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val);
+ qmc_write16(qmc->scc_pram + QMC_GBL_TX_S_PTR, val);
+ qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val);
+
+ return 0;
+}
+
+static int qmc_setup_tsa_32rx_32tx(struct qmc *qmc, const struct tsa_serial_info *info)
+{
+ struct qmc_chan *chan;
+ unsigned int i;
+ u16 val;
+
+ /*
+ * Use a Tx 32 entries table and a Rx 32 entries table.
+ * Everything was previously checked.
+ */
+
+ /* Invalidate all entries */
+ for (i = 0; i < 32; i++) {
+ qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), 0x0000);
+ qmc_write16(qmc->scc_pram + QMC_GBL_TSATTX + (i * 2), 0x0000);
+ }
+
+ /* Set entries based on Rx and Tx stuff*/
+ list_for_each_entry(chan, &qmc->chan_head, list) {
+ /* Rx part */
+ for (i = 0; i < info->nb_rx_ts; i++) {
+ if (!(chan->rx_ts_mask & (((u64)1) << i)))
+ continue;
+
+ val = QMC_TSA_VALID | QMC_TSA_MASK |
+ QMC_TSA_CHANNEL(chan->id);
+ qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), val);
+ }
+ /* Tx part */
+ for (i = 0; i < info->nb_tx_ts; i++) {
+ if (!(chan->tx_ts_mask & (((u64)1) << i)))
+ continue;
+
+ val = QMC_TSA_VALID | QMC_TSA_MASK |
+ QMC_TSA_CHANNEL(chan->id);
+ qmc_write16(qmc->scc_pram + QMC_GBL_TSATTX + (i * 2), val);
+ }
+ }
+
+ /* Set Wrap bit on last entries */
+ qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATRX + ((info->nb_rx_ts - 1) * 2),
+ QMC_TSA_WRAP);
+ qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATTX + ((info->nb_tx_ts - 1) * 2),
+ QMC_TSA_WRAP);
+
+ /* Init Rx pointers ...*/
+ val = qmc->scc_pram_offset + QMC_GBL_TSATRX;
+ qmc_write16(qmc->scc_pram + QMC_GBL_RX_S_PTR, val);
+ qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val);
+
+ /* ... and Tx pointers */
+ val = qmc->scc_pram_offset + QMC_GBL_TSATTX;
+ qmc_write16(qmc->scc_pram + QMC_GBL_TX_S_PTR, val);
+ qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val);
+
+ return 0;
+}
+
+static int qmc_setup_tsa(struct qmc *qmc)
+{
+ struct tsa_serial_info info;
+ int ret;
+
+ /* Retrieve info from the TSA related serial */
+ ret = tsa_serial_get_info(qmc->tsa_serial, &info);
+ if (ret)
+ return ret;
+
+ /*
+ * Setup one common 64 entries table or two 32 entries (one for Tx and
+ * one for Tx) according to assigned TS numbers.
+ */
+ return ((info.nb_tx_ts > 32) || (info.nb_rx_ts > 32)) ?
+ qmc_setup_tsa_64rxtx(qmc, &info) :
+ qmc_setup_tsa_32rx_32tx(qmc, &info);
+}
+
+static int qmc_setup_chan_trnsync(struct qmc *qmc, struct qmc_chan *chan)
+{
+ struct tsa_serial_info info;
+ u16 first_rx, last_tx;
+ u16 trnsync;
+ int ret;
+
+ /* Retrieve info from the TSA related serial */
+ ret = tsa_serial_get_info(chan->qmc->tsa_serial, &info);
+ if (ret)
+ return ret;
+
+ /* Find the first Rx TS allocated to the channel */
+ first_rx = chan->rx_ts_mask ? __ffs64(chan->rx_ts_mask) + 1 : 0;
+
+ /* Find the last Tx TS allocated to the channel */
+ last_tx = fls64(chan->tx_ts_mask);
+
+ trnsync = 0;
+ if (info.nb_rx_ts)
+ trnsync |= QMC_SPE_TRNSYNC_RX((first_rx % info.nb_rx_ts) * 2);
+ if (info.nb_tx_ts)
+ trnsync |= QMC_SPE_TRNSYNC_TX((last_tx % info.nb_tx_ts) * 2);
+
+ qmc_write16(chan->s_param + QMC_SPE_TRNSYNC, trnsync);
+
+ dev_dbg(qmc->dev, "chan %u: trnsync=0x%04x, rx %u/%u 0x%llx, tx %u/%u 0x%llx\n",
+ chan->id, trnsync,
+ first_rx, info.nb_rx_ts, chan->rx_ts_mask,
+ last_tx, info.nb_tx_ts, chan->tx_ts_mask);
+
+ return 0;
+}
+
+static int qmc_setup_chan(struct qmc *qmc, struct qmc_chan *chan)
+{
+ unsigned int i;
+ cbd_t __iomem *bd;
+ int ret;
+ u16 val;
+
+ chan->qmc = qmc;
+
+ /* Set channel specific parameter base address */
+ chan->s_param = qmc->dpram + (chan->id * 64);
+ /* 16 bd per channel (8 rx and 8 tx) */
+ chan->txbds = qmc->bd_table + (chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS));
+ chan->rxbds = qmc->bd_table + (chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS)) + QMC_NB_TXBDS;
+
+ chan->txbd_free = chan->txbds;
+ chan->txbd_done = chan->txbds;
+ chan->rxbd_free = chan->rxbds;
+ chan->rxbd_done = chan->rxbds;
+
+ /* TBASE and TBPTR*/
+ val = chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS) * sizeof(cbd_t);
+ qmc_write16(chan->s_param + QMC_SPE_TBASE, val);
+ qmc_write16(chan->s_param + QMC_SPE_TBPTR, val);
+
+ /* RBASE and RBPTR*/
+ val = ((chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS)) + QMC_NB_TXBDS) * sizeof(cbd_t);
+ qmc_write16(chan->s_param + QMC_SPE_RBASE, val);
+ qmc_write16(chan->s_param + QMC_SPE_RBPTR, val);
+ qmc_write32(chan->s_param + QMC_SPE_TSTATE, 0x30000000);
+ qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+ qmc_write32(chan->s_param + QMC_SPE_ZISTATE, 0x00000100);
+ if (chan->mode == QMC_TRANSPARENT) {
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+ qmc_write16(chan->s_param + QMC_SPE_TMRBLR, 60);
+ val = QMC_SPE_CHAMR_MODE_TRANSP | QMC_SPE_CHAMR_TRANSP_SYNC;
+ if (chan->is_reverse_data)
+ val |= QMC_SPE_CHAMR_TRANSP_RD;
+ qmc_write16(chan->s_param + QMC_SPE_CHAMR, val);
+ ret = qmc_setup_chan_trnsync(qmc, chan);
+ if (ret)
+ return ret;
+ } else {
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+ qmc_write16(chan->s_param + QMC_SPE_MFLR, 60);
+ qmc_write16(chan->s_param + QMC_SPE_CHAMR,
+ QMC_SPE_CHAMR_MODE_HDLC | QMC_SPE_CHAMR_HDLC_IDLM);
+ }
+
+ /* Do not enable interrupts now. They will be enabled later */
+ qmc_write16(chan->s_param + QMC_SPE_INTMSK, 0x0000);
+
+ /* Init Rx BDs and set Wrap bit on last descriptor */
+ BUILD_BUG_ON(QMC_NB_RXBDS == 0);
+ val = QMC_BD_RX_I;
+ for (i = 0; i < QMC_NB_RXBDS; i++) {
+ bd = chan->rxbds + i;
+ qmc_write16(&bd->cbd_sc, val);
+ }
+ bd = chan->rxbds + QMC_NB_RXBDS - 1;
+ qmc_write16(&bd->cbd_sc, val | QMC_BD_RX_W);
+
+ /* Init Tx BDs and set Wrap bit on last descriptor */
+ BUILD_BUG_ON(QMC_NB_TXBDS == 0);
+ val = QMC_BD_TX_I;
+ if (chan->mode == QMC_HDLC)
+ val |= QMC_BD_TX_L | QMC_BD_TX_TC;
+ for (i = 0; i < QMC_NB_TXBDS; i++) {
+ bd = chan->txbds + i;
+ qmc_write16(&bd->cbd_sc, val);
+ }
+ bd = chan->txbds + QMC_NB_TXBDS - 1;
+ qmc_write16(&bd->cbd_sc, val | QMC_BD_TX_W);
+
+ return 0;
+}
+
+static int qmc_setup_chans(struct qmc *qmc)
+{
+ struct qmc_chan *chan;
+ int ret;
+
+ list_for_each_entry(chan, &qmc->chan_head, list) {
+ ret = qmc_setup_chan(qmc, chan);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int qmc_finalize_chans(struct qmc *qmc)
+{
+ struct qmc_chan *chan;
+ int ret;
+
+ list_for_each_entry(chan, &qmc->chan_head, list) {
+ /* Unmask channel interrupts */
+ if (chan->mode == QMC_HDLC) {
+ qmc_write16(chan->s_param + QMC_SPE_INTMSK,
+ QMC_INT_NID | QMC_INT_IDL | QMC_INT_MRF |
+ QMC_INT_UN | QMC_INT_RXF | QMC_INT_BSY |
+ QMC_INT_TXB | QMC_INT_RXB);
+ } else {
+ qmc_write16(chan->s_param + QMC_SPE_INTMSK,
+ QMC_INT_UN | QMC_INT_BSY |
+ QMC_INT_TXB | QMC_INT_RXB);
+ }
+
+ /* Forced stop the channel */
+ ret = qmc_chan_stop(chan, QMC_CHAN_ALL);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int qmc_setup_ints(struct qmc *qmc)
+{
+ unsigned int i;
+ u16 __iomem *last;
+
+ /* Raz all entries */
+ for (i = 0; i < (qmc->int_size / sizeof(u16)); i++)
+ qmc_write16(qmc->int_table + i, 0x0000);
+
+ /* Set Wrap bit on last entry */
+ if (qmc->int_size >= sizeof(u16)) {
+ last = qmc->int_table + (qmc->int_size / sizeof(u16)) - 1;
+ qmc_write16(last, QMC_INT_W);
+ }
+
+ return 0;
+}
+
+static void qmc_irq_gint(struct qmc *qmc)
+{
+ struct qmc_chan *chan;
+ unsigned int chan_id;
+ unsigned long flags;
+ u16 int_entry;
+
+ int_entry = qmc_read16(qmc->int_curr);
+ while (int_entry & QMC_INT_V) {
+ /* Clear all but the Wrap bit */
+ qmc_write16(qmc->int_curr, int_entry & QMC_INT_W);
+
+ chan_id = QMC_INT_GET_CHANNEL(int_entry);
+ chan = qmc->chans[chan_id];
+ if (!chan) {
+ dev_err(qmc->dev, "interrupt on invalid chan %u\n", chan_id);
+ goto int_next;
+ }
+
+ if (int_entry & QMC_INT_TXB)
+ qmc_chan_write_done(chan);
+
+ if (int_entry & QMC_INT_UN) {
+ dev_info(qmc->dev, "intr chan %u, 0x%04x (UN)\n", chan_id,
+ int_entry);
+ chan->nb_tx_underrun++;
+ }
+
+ if (int_entry & QMC_INT_BSY) {
+ dev_info(qmc->dev, "intr chan %u, 0x%04x (BSY)\n", chan_id,
+ int_entry);
+ chan->nb_rx_busy++;
+ /* Restart the receiver if needed */
+ spin_lock_irqsave(&chan->rx_lock, flags);
+ if (chan->rx_pending && !chan->is_rx_stopped) {
+ if (chan->mode == QMC_TRANSPARENT)
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080);
+ else
+ qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080);
+ qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000);
+ chan->is_rx_halted = false;
+ } else {
+ chan->is_rx_halted = true;
+ }
+ spin_unlock_irqrestore(&chan->rx_lock, flags);
+ }
+
+ if (int_entry & QMC_INT_RXB)
+ qmc_chan_read_done(chan);
+
+int_next:
+ if (int_entry & QMC_INT_W)
+ qmc->int_curr = qmc->int_table;
+ else
+ qmc->int_curr++;
+ int_entry = qmc_read16(qmc->int_curr);
+ }
+}
+
+static irqreturn_t qmc_irq_handler(int irq, void *priv)
+{
+ struct qmc *qmc = (struct qmc *)priv;
+ u16 scce;
+
+ scce = qmc_read16(qmc->scc_regs + SCC_SCCE);
+ qmc_write16(qmc->scc_regs + SCC_SCCE, scce);
+
+ if (unlikely(scce & SCC_SCCE_IQOV))
+ dev_info(qmc->dev, "IRQ queue overflow\n");
+
+ if (unlikely(scce & SCC_SCCE_GUN))
+ dev_err(qmc->dev, "Global transmitter underrun\n");
+
+ if (unlikely(scce & SCC_SCCE_GOV))
+ dev_err(qmc->dev, "Global receiver overrun\n");
+
+ /* normal interrupt */
+ if (likely(scce & SCC_SCCE_GINT))
+ qmc_irq_gint(qmc);
+
+ return IRQ_HANDLED;
+}
+
+static int qmc_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ unsigned int nb_chans;
+ struct resource *res;
+ struct qmc *qmc;
+ int irq;
+ int ret;
+
+ qmc = devm_kzalloc(&pdev->dev, sizeof(*qmc), GFP_KERNEL);
+ if (!qmc)
+ return -ENOMEM;
+
+ qmc->dev = &pdev->dev;
+ INIT_LIST_HEAD(&qmc->chan_head);
+
+ qmc->scc_regs = devm_platform_ioremap_resource_byname(pdev, "scc_regs");
+ if (IS_ERR(qmc->scc_regs))
+ return PTR_ERR(qmc->scc_regs);
+
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "scc_pram");
+ if (!res)
+ return -EINVAL;
+ qmc->scc_pram_offset = res->start - get_immrbase();
+ qmc->scc_pram = devm_ioremap_resource(qmc->dev, res);
+ if (IS_ERR(qmc->scc_pram))
+ return PTR_ERR(qmc->scc_pram);
+
+ qmc->dpram = devm_platform_ioremap_resource_byname(pdev, "dpram");
+ if (IS_ERR(qmc->dpram))
+ return PTR_ERR(qmc->dpram);
+
+ qmc->tsa_serial = devm_tsa_serial_get_byphandle(qmc->dev, np, "fsl,tsa-serial");
+ if (IS_ERR(qmc->tsa_serial)) {
+ return dev_err_probe(qmc->dev, PTR_ERR(qmc->tsa_serial),
+ "Failed to get TSA serial\n");
+ }
+
+ /* Connect the serial (SCC) to TSA */
+ ret = tsa_serial_connect(qmc->tsa_serial);
+ if (ret) {
+ dev_err(qmc->dev, "Failed to connect TSA serial\n");
+ return ret;
+ }
+
+ /* Parse channels informationss */
+ ret = qmc_of_parse_chans(qmc, np);
+ if (ret)
+ goto err_tsa_serial_disconnect;
+
+ nb_chans = qmc_nb_chans(qmc);
+
+ /* Init GMSR_H and GMSR_L registers */
+ qmc_write32(qmc->scc_regs + SCC_GSMRH,
+ SCC_GSMRH_CDS | SCC_GSMRH_CTSS | SCC_GSMRH_CDP | SCC_GSMRH_CTSP);
+
+ /* enable QMC mode */
+ qmc_write32(qmc->scc_regs + SCC_GSMRL, SCC_GSMRL_MODE_QMC);
+
+ /*
+ * Allocate the buffer descriptor table
+ * 8 rx and 8 tx descriptors per channel
+ */
+ qmc->bd_size = (nb_chans * (QMC_NB_TXBDS + QMC_NB_RXBDS)) * sizeof(cbd_t);
+ qmc->bd_table = dmam_alloc_coherent(qmc->dev, qmc->bd_size,
+ &qmc->bd_dma_addr, GFP_KERNEL);
+ if (!qmc->bd_table) {
+ dev_err(qmc->dev, "Failed to allocate bd table\n");
+ ret = -ENOMEM;
+ goto err_tsa_serial_disconnect;
+ }
+ memset(qmc->bd_table, 0, qmc->bd_size);
+
+ qmc_write32(qmc->scc_pram + QMC_GBL_MCBASE, qmc->bd_dma_addr);
+
+ /* Allocate the interrupt table */
+ qmc->int_size = QMC_NB_INTS * sizeof(u16);
+ qmc->int_table = dmam_alloc_coherent(qmc->dev, qmc->int_size,
+ &qmc->int_dma_addr, GFP_KERNEL);
+ if (!qmc->int_table) {
+ dev_err(qmc->dev, "Failed to allocate interrupt table\n");
+ ret = -ENOMEM;
+ goto err_tsa_serial_disconnect;
+ }
+ memset(qmc->int_table, 0, qmc->int_size);
+
+ qmc->int_curr = qmc->int_table;
+ qmc_write32(qmc->scc_pram + QMC_GBL_INTBASE, qmc->int_dma_addr);
+ qmc_write32(qmc->scc_pram + QMC_GBL_INTPTR, qmc->int_dma_addr);
+
+ /* Set MRBLR (valid for HDLC only) max MRU + max CRC */
+ qmc_write16(qmc->scc_pram + QMC_GBL_MRBLR, HDLC_MAX_MRU + 4);
+
+ qmc_write16(qmc->scc_pram + QMC_GBL_GRFTHR, 1);
+ qmc_write16(qmc->scc_pram + QMC_GBL_GRFCNT, 1);
+
+ qmc_write32(qmc->scc_pram + QMC_GBL_C_MASK32, 0xDEBB20E3);
+ qmc_write16(qmc->scc_pram + QMC_GBL_C_MASK16, 0xF0B8);
+
+ ret = qmc_setup_tsa(qmc);
+ if (ret)
+ goto err_tsa_serial_disconnect;
+
+ qmc_write16(qmc->scc_pram + QMC_GBL_QMCSTATE, 0x8000);
+
+ ret = qmc_setup_chans(qmc);
+ if (ret)
+ goto err_tsa_serial_disconnect;
+
+ /* Init interrupts table */
+ ret = qmc_setup_ints(qmc);
+ if (ret)
+ goto err_tsa_serial_disconnect;
+
+ /* Disable and clear interrupts, set the irq handler */
+ qmc_write16(qmc->scc_regs + SCC_SCCM, 0x0000);
+ qmc_write16(qmc->scc_regs + SCC_SCCE, 0x000F);
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ goto err_tsa_serial_disconnect;
+ ret = devm_request_irq(qmc->dev, irq, qmc_irq_handler, 0, "qmc", qmc);
+ if (ret < 0)
+ goto err_tsa_serial_disconnect;
+
+ /* Enable interrupts */
+ qmc_write16(qmc->scc_regs + SCC_SCCM,
+ SCC_SCCE_IQOV | SCC_SCCE_GINT | SCC_SCCE_GUN | SCC_SCCE_GOV);
+
+ ret = qmc_finalize_chans(qmc);
+ if (ret < 0)
+ goto err_disable_intr;
+
+ /* Enable transmiter and receiver */
+ qmc_setbits32(qmc->scc_regs + SCC_GSMRL, SCC_GSMRL_ENR | SCC_GSMRL_ENT);
+
+ platform_set_drvdata(pdev, qmc);
+
+ return 0;
+
+err_disable_intr:
+ qmc_write16(qmc->scc_regs + SCC_SCCM, 0);
+
+err_tsa_serial_disconnect:
+ tsa_serial_disconnect(qmc->tsa_serial);
+ return ret;
+}
+
+static int qmc_remove(struct platform_device *pdev)
+{
+ struct qmc *qmc = platform_get_drvdata(pdev);
+
+ /* Disable transmiter and receiver */
+ qmc_setbits32(qmc->scc_regs + SCC_GSMRL, 0);
+
+ /* Disable interrupts */
+ qmc_write16(qmc->scc_regs + SCC_SCCM, 0);
+
+ /* Disconnect the serial from TSA */
+ tsa_serial_disconnect(qmc->tsa_serial);
+
+ return 0;
+}
+
+static const struct of_device_id qmc_id_table[] = {
+ { .compatible = "fsl,cpm1-scc-qmc" },
+ {} /* sentinel */
+};
+MODULE_DEVICE_TABLE(of, qmc_id_table);
+
+static struct platform_driver qmc_driver = {
+ .driver = {
+ .name = "fsl-qmc",
+ .of_match_table = of_match_ptr(qmc_id_table),
+ },
+ .probe = qmc_probe,
+ .remove = qmc_remove,
+};
+module_platform_driver(qmc_driver);
+
+struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phandle_name)
+{
+ struct of_phandle_args out_args;
+ struct platform_device *pdev;
+ struct qmc_chan *qmc_chan;
+ struct qmc *qmc;
+ int ret;
+
+ ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1, 0,
+ &out_args);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ if (!of_match_node(qmc_driver.driver.of_match_table, out_args.np)) {
+ of_node_put(out_args.np);
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdev = of_find_device_by_node(out_args.np);
+ of_node_put(out_args.np);
+ if (!pdev)
+ return ERR_PTR(-ENODEV);
+
+ qmc = platform_get_drvdata(pdev);
+ if (!qmc) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ if (out_args.args_count != 1) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (out_args.args[0] >= ARRAY_SIZE(qmc->chans)) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ qmc_chan = qmc->chans[out_args.args[0]];
+ if (!qmc_chan) {
+ platform_device_put(pdev);
+ return ERR_PTR(-ENOENT);
+ }
+
+ return qmc_chan;
+}
+EXPORT_SYMBOL(qmc_chan_get_byphandle);
+
+void qmc_chan_put(struct qmc_chan *chan)
+{
+ put_device(chan->qmc->dev);
+}
+EXPORT_SYMBOL(qmc_chan_put);
+
+static void devm_qmc_chan_release(struct device *dev, void *res)
+{
+ struct qmc_chan **qmc_chan = res;
+
+ qmc_chan_put(*qmc_chan);
+}
+
+struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev,
+ struct device_node *np,
+ const char *phandle_name)
+{
+ struct qmc_chan *qmc_chan;
+ struct qmc_chan **dr;
+
+ dr = devres_alloc(devm_qmc_chan_release, sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return ERR_PTR(-ENOMEM);
+
+ qmc_chan = qmc_chan_get_byphandle(np, phandle_name);
+ if (!IS_ERR(qmc_chan)) {
+ *dr = qmc_chan;
+ devres_add(dev, dr);
+ } else {
+ devres_free(dr);
+ }
+
+ return qmc_chan;
+}
+EXPORT_SYMBOL(devm_qmc_chan_get_byphandle);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("CPM QMC driver");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TSA driver
+ *
+ * Copyright 2022 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+
+#include "tsa.h"
+#include <dt-bindings/soc/cpm1-fsl,tsa.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+
+/* TSA SI RAM routing tables entry */
+#define TSA_SIRAM_ENTRY_LAST (1 << 16)
+#define TSA_SIRAM_ENTRY_BYTE (1 << 17)
+#define TSA_SIRAM_ENTRY_CNT(x) (((x) & 0x0f) << 18)
+#define TSA_SIRAM_ENTRY_CSEL_MASK (0x7 << 22)
+#define TSA_SIRAM_ENTRY_CSEL_NU (0x0 << 22)
+#define TSA_SIRAM_ENTRY_CSEL_SCC2 (0x2 << 22)
+#define TSA_SIRAM_ENTRY_CSEL_SCC3 (0x3 << 22)
+#define TSA_SIRAM_ENTRY_CSEL_SCC4 (0x4 << 22)
+#define TSA_SIRAM_ENTRY_CSEL_SMC1 (0x5 << 22)
+#define TSA_SIRAM_ENTRY_CSEL_SMC2 (0x6 << 22)
+
+/* SI mode register (32 bits) */
+#define TSA_SIMODE 0x00
+#define TSA_SIMODE_SMC2 0x80000000
+#define TSA_SIMODE_SMC1 0x00008000
+#define TSA_SIMODE_TDMA(x) ((x) << 0)
+#define TSA_SIMODE_TDMB(x) ((x) << 16)
+#define TSA_SIMODE_TDM_MASK 0x0fff
+#define TSA_SIMODE_TDM_SDM_MASK 0x0c00
+#define TSA_SIMODE_TDM_SDM_NORM 0x0000
+#define TSA_SIMODE_TDM_SDM_ECHO 0x0400
+#define TSA_SIMODE_TDM_SDM_INTL_LOOP 0x0800
+#define TSA_SIMODE_TDM_SDM_LOOP_CTRL 0x0c00
+#define TSA_SIMODE_TDM_RFSD(x) ((x) << 8)
+#define TSA_SIMODE_TDM_DSC 0x0080
+#define TSA_SIMODE_TDM_CRT 0x0040
+#define TSA_SIMODE_TDM_STZ 0x0020
+#define TSA_SIMODE_TDM_CE 0x0010
+#define TSA_SIMODE_TDM_FE 0x0008
+#define TSA_SIMODE_TDM_GM 0x0004
+#define TSA_SIMODE_TDM_TFSD(x) ((x) << 0)
+
+/* SI global mode register (8 bits) */
+#define TSA_SIGMR 0x04
+#define TSA_SIGMR_ENB (1<<3)
+#define TSA_SIGMR_ENA (1<<2)
+#define TSA_SIGMR_RDM_MASK 0x03
+#define TSA_SIGMR_RDM_STATIC_TDMA 0x00
+#define TSA_SIGMR_RDM_DYN_TDMA 0x01
+#define TSA_SIGMR_RDM_STATIC_TDMAB 0x02
+#define TSA_SIGMR_RDM_DYN_TDMAB 0x03
+
+/* SI status register (8 bits) */
+#define TSA_SISTR 0x06
+
+/* SI command register (8 bits) */
+#define TSA_SICMR 0x07
+
+/* SI clock route register (32 bits) */
+#define TSA_SICR 0x0C
+#define TSA_SICR_SCC2(x) ((x) << 8)
+#define TSA_SICR_SCC3(x) ((x) << 16)
+#define TSA_SICR_SCC4(x) ((x) << 24)
+#define TSA_SICR_SCC_MASK 0x0ff
+#define TSA_SICR_SCC_GRX (1 << 7)
+#define TSA_SICR_SCC_SCX_TSA (1 << 6)
+#define TSA_SICR_SCC_RXCS_MASK (0x7 << 3)
+#define TSA_SICR_SCC_RXCS_BRG1 (0x0 << 3)
+#define TSA_SICR_SCC_RXCS_BRG2 (0x1 << 3)
+#define TSA_SICR_SCC_RXCS_BRG3 (0x2 << 3)
+#define TSA_SICR_SCC_RXCS_BRG4 (0x3 << 3)
+#define TSA_SICR_SCC_RXCS_CLK15 (0x4 << 3)
+#define TSA_SICR_SCC_RXCS_CLK26 (0x5 << 3)
+#define TSA_SICR_SCC_RXCS_CLK37 (0x6 << 3)
+#define TSA_SICR_SCC_RXCS_CLK48 (0x7 << 3)
+#define TSA_SICR_SCC_TXCS_MASK (0x7 << 0)
+#define TSA_SICR_SCC_TXCS_BRG1 (0x0 << 0)
+#define TSA_SICR_SCC_TXCS_BRG2 (0x1 << 0)
+#define TSA_SICR_SCC_TXCS_BRG3 (0x2 << 0)
+#define TSA_SICR_SCC_TXCS_BRG4 (0x3 << 0)
+#define TSA_SICR_SCC_TXCS_CLK15 (0x4 << 0)
+#define TSA_SICR_SCC_TXCS_CLK26 (0x5 << 0)
+#define TSA_SICR_SCC_TXCS_CLK37 (0x6 << 0)
+#define TSA_SICR_SCC_TXCS_CLK48 (0x7 << 0)
+
+/* Serial interface RAM pointer register (32 bits) */
+#define TSA_SIRP 0x10
+
+struct tsa_entries_area {
+ void *__iomem entries_start;
+ void *__iomem entries_next;
+ void *__iomem last_entry;
+};
+
+struct tsa_tdm {
+ bool is_enable;
+ struct clk *l1rclk_clk;
+ struct clk *l1rsync_clk;
+ struct clk *l1tclk_clk;
+ struct clk *l1tsync_clk;
+ u32 simode_tdm;
+};
+
+#define TSA_TDMA 0
+#define TSA_TDMB 1
+
+struct tsa {
+ struct device *dev;
+ void *__iomem si_regs;
+ void *__iomem si_ram;
+ resource_size_t si_ram_sz;
+ spinlock_t lock;
+ int tdms; /* TSA_TDMx ORed */
+ struct tsa_tdm tdm[2]; /* TDMa and TDMb */
+ struct tsa_serial {
+ unsigned int id;
+ struct tsa_serial_info info;
+ } serials[6];
+};
+
+static inline struct tsa *tsa_serial_get_tsa(struct tsa_serial *tsa_serial)
+{
+ /* The serials table is indexed by the serial id */
+ return container_of(tsa_serial, struct tsa, serials[tsa_serial->id]);
+}
+
+static inline void tsa_write32(void *__iomem addr, u32 val)
+{
+ iowrite32be(val, addr);
+}
+
+static inline void tsa_write8(void *__iomem addr, u32 val)
+{
+ iowrite8(val, addr);
+}
+
+static inline u32 tsa_read32(void *__iomem addr)
+{
+ return ioread32be(addr);
+}
+
+static inline void tsa_clrbits32(void *__iomem addr, u32 clr)
+{
+ tsa_write32(addr, tsa_read32(addr) & ~clr);
+}
+
+static inline void tsa_clrsetbits32(void *__iomem addr, u32 clr, u32 set)
+{
+ tsa_write32(addr, (tsa_read32(addr) & ~clr) | set);
+}
+
+int tsa_serial_connect(struct tsa_serial *tsa_serial)
+{
+ struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);
+ unsigned long flags;
+ u32 clear;
+ u32 set;
+
+ switch (tsa_serial->id) {
+ case FSL_CPM_TSA_SCC2:
+ clear = TSA_SICR_SCC2(TSA_SICR_SCC_MASK);
+ set = TSA_SICR_SCC2(TSA_SICR_SCC_SCX_TSA);
+ break;
+ case FSL_CPM_TSA_SCC3:
+ clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK);
+ set = TSA_SICR_SCC3(TSA_SICR_SCC_SCX_TSA);
+ break;
+ case FSL_CPM_TSA_SCC4:
+ clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK);
+ set = TSA_SICR_SCC4(TSA_SICR_SCC_SCX_TSA);
+ break;
+ default:
+ dev_err(tsa->dev, "Unsupported serial id %u\n", tsa_serial->id);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&tsa->lock, flags);
+ tsa_clrsetbits32(tsa->si_regs + TSA_SICR, clear, set);
+ spin_unlock_irqrestore(&tsa->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(tsa_serial_connect);
+
+int tsa_serial_disconnect(struct tsa_serial *tsa_serial)
+{
+ struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);
+ unsigned long flags;
+ u32 clear;
+
+ switch (tsa_serial->id) {
+ case FSL_CPM_TSA_SCC2:
+ clear = TSA_SICR_SCC2(TSA_SICR_SCC_MASK);
+ break;
+ case FSL_CPM_TSA_SCC3:
+ clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK);
+ break;
+ case FSL_CPM_TSA_SCC4:
+ clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK);
+ break;
+ default:
+ dev_err(tsa->dev, "Unsupported serial id %u\n", tsa_serial->id);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&tsa->lock, flags);
+ tsa_clrsetbits32(tsa->si_regs + TSA_SICR, clear, 0);
+ spin_unlock_irqrestore(&tsa->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(tsa_serial_disconnect);
+
+int tsa_serial_get_info(struct tsa_serial *tsa_serial, struct tsa_serial_info *info)
+{
+ memcpy(info, &tsa_serial->info, sizeof(*info));
+ return 0;
+}
+EXPORT_SYMBOL(tsa_serial_get_info);
+
+static void tsa_init_entries_area(struct tsa *tsa, struct tsa_entries_area *area,
+ u32 tdms, u32 tdm_id, bool is_rx)
+{
+ resource_size_t quarter;
+ resource_size_t half;
+
+ quarter = tsa->si_ram_sz/4;
+ half = tsa->si_ram_sz/2;
+
+ if (tdms == BIT(TSA_TDMA)) {
+ /* Only TDMA */
+ if (is_rx) {
+ /* First half of si_ram */
+ area->entries_start = tsa->si_ram;
+ area->entries_next = area->entries_start + half;
+ area->last_entry = NULL;
+ } else {
+ /* Second half of si_ram */
+ area->entries_start = tsa->si_ram + half;
+ area->entries_next = area->entries_start + half;
+ area->last_entry = NULL;
+ }
+ } else {
+ /* Only TDMB or both TDMs */
+ if (tdm_id == TSA_TDMA) {
+ if (is_rx) {
+ /* First half of first half of si_ram */
+ area->entries_start = tsa->si_ram;
+ area->entries_next = area->entries_start + quarter;
+ area->last_entry = NULL;
+ } else {
+ /* First half of second half of si_ram */
+ area->entries_start = tsa->si_ram + (2 * quarter);
+ area->entries_next = area->entries_start + quarter;
+ area->last_entry = NULL;
+ }
+ } else {
+ if (is_rx) {
+ /* Second half of first half of si_ram */
+ area->entries_start = tsa->si_ram + quarter;
+ area->entries_next = area->entries_start + quarter;
+ area->last_entry = NULL;
+ } else {
+ /* Second half of second half of si_ram */
+ area->entries_start = tsa->si_ram + (3 * quarter);
+ area->entries_next = area->entries_start + quarter;
+ area->last_entry = NULL;
+ }
+ }
+ }
+}
+
+static const char *tsa_serial_id2name(struct tsa *tsa, u32 serial_id)
+{
+ switch (serial_id) {
+ case FSL_CPM_TSA_NU: return "Not used";
+ case FSL_CPM_TSA_SCC2: return "SCC2";
+ case FSL_CPM_TSA_SCC3: return "SCC3";
+ case FSL_CPM_TSA_SCC4: return "SCC4";
+ case FSL_CPM_TSA_SMC1: return "SMC1";
+ case FSL_CPM_TSA_SMC2: return "SMC2";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static u32 tsa_serial_id2csel(struct tsa *tsa, u32 serial_id)
+{
+ switch (serial_id) {
+ case FSL_CPM_TSA_SCC2: return TSA_SIRAM_ENTRY_CSEL_SCC2;
+ case FSL_CPM_TSA_SCC3: return TSA_SIRAM_ENTRY_CSEL_SCC3;
+ case FSL_CPM_TSA_SCC4: return TSA_SIRAM_ENTRY_CSEL_SCC4;
+ case FSL_CPM_TSA_SMC1: return TSA_SIRAM_ENTRY_CSEL_SMC1;
+ case FSL_CPM_TSA_SMC2: return TSA_SIRAM_ENTRY_CSEL_SMC2;
+ default:
+ break;
+ }
+ return TSA_SIRAM_ENTRY_CSEL_NU;
+}
+
+static int tsa_add_entry(struct tsa *tsa, struct tsa_entries_area *area,
+ u32 count, u32 serial_id)
+{
+ void *__iomem addr;
+ u32 left;
+ u32 val;
+ u32 cnt;
+ u32 nb;
+
+ addr = area->last_entry ? area->last_entry + 4 : area->entries_start;
+
+ nb = DIV_ROUND_UP(count, 8);
+ if ((addr + (nb * 4)) > area->entries_next) {
+ dev_err(tsa->dev, "si ram area full\n");
+ return -ENOSPC;
+ }
+
+ if (area->last_entry) {
+ /* Clear last flag */
+ tsa_clrbits32(area->last_entry, TSA_SIRAM_ENTRY_LAST);
+ }
+
+ left = count;
+ while (left) {
+ val = TSA_SIRAM_ENTRY_BYTE | tsa_serial_id2csel(tsa, serial_id);
+
+ if (left > 16) {
+ cnt = 16;
+ } else {
+ cnt = left;
+ val |= TSA_SIRAM_ENTRY_LAST;
+ area->last_entry = addr;
+ }
+ val |= TSA_SIRAM_ENTRY_CNT(cnt - 1);
+
+ tsa_write32(addr, val);
+ addr += 4;
+ left -= cnt;
+ }
+
+ return 0;
+}
+
+static int tsa_of_parse_tdm_route(struct tsa *tsa, struct device_node *tdm_np,
+ u32 tdms, u32 tdm_id, bool is_rx)
+{
+ struct tsa_entries_area area;
+ const char *route_name;
+ u32 serial_id;
+ int len, i;
+ u32 count;
+ const char *serial_name;
+ struct tsa_serial_info *serial_info;
+ struct tsa_tdm *tdm;
+ int ret;
+ u32 ts;
+
+ route_name = is_rx ? "fsl,rx-ts-routes" : "fsl,tx-ts-routes";
+
+ len = of_property_count_u32_elems(tdm_np, route_name);
+ if (len < 0) {
+ dev_err(tsa->dev, "%pOF: failed to read %s\n", tdm_np, route_name);
+ return len;
+ }
+ if (len % 2 != 0) {
+ dev_err(tsa->dev, "%pOF: wrong %s format\n", tdm_np, route_name);
+ return -EINVAL;
+ }
+
+ tsa_init_entries_area(tsa, &area, tdms, tdm_id, is_rx);
+ ts = 0;
+ for (i = 0; i < len; i += 2) {
+ of_property_read_u32_index(tdm_np, route_name, i, &count);
+ of_property_read_u32_index(tdm_np, route_name, i + 1, &serial_id);
+
+ if (serial_id >= ARRAY_SIZE(tsa->serials)) {
+ dev_err(tsa->dev, "%pOF: invalid serial id (%u)\n",
+ tdm_np, serial_id);
+ return -EINVAL;
+ }
+
+ serial_name = tsa_serial_id2name(tsa, serial_id);
+ if (!serial_name) {
+ dev_err(tsa->dev, "%pOF: unsupported serial id (%u)\n",
+ tdm_np, serial_id);
+ return -EINVAL;
+ }
+
+ dev_dbg(tsa->dev, "tdm_id=%u, %s ts %u..%u -> %s\n",
+ tdm_id, route_name, ts, ts+count-1, serial_name);
+ ts += count;
+
+ ret = tsa_add_entry(tsa, &area, count, serial_id);
+ if (ret)
+ return ret;
+
+ serial_info = &tsa->serials[serial_id].info;
+ tdm = &tsa->tdm[tdm_id];
+ if (is_rx) {
+ serial_info->rx_fs_rate = clk_get_rate(tdm->l1rsync_clk);
+ serial_info->rx_bit_rate = clk_get_rate(tdm->l1rclk_clk);
+ serial_info->nb_rx_ts += count;
+ } else {
+ serial_info->tx_fs_rate = tdm->l1tsync_clk ?
+ clk_get_rate(tdm->l1tsync_clk) :
+ clk_get_rate(tdm->l1rsync_clk);
+ serial_info->tx_bit_rate = tdm->l1tclk_clk ?
+ clk_get_rate(tdm->l1tclk_clk) :
+ clk_get_rate(tdm->l1rclk_clk);
+ serial_info->nb_tx_ts += count;
+ }
+ }
+ return 0;
+}
+
+static inline int tsa_of_parse_tdm_rx_route(struct tsa *tsa,
+ struct device_node *tdm_np,
+ u32 tdms, u32 tdm_id)
+{
+ return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, true);
+}
+
+static inline int tsa_of_parse_tdm_tx_route(struct tsa *tsa,
+ struct device_node *tdm_np,
+ u32 tdms, u32 tdm_id)
+{
+ return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, false);
+}
+
+static int tsa_of_parse_tdms(struct tsa *tsa, struct device_node *np)
+{
+ struct device_node *tdm_np;
+ struct tsa_tdm *tdm;
+ struct clk *clk;
+ u32 tdm_id, val;
+ int ret;
+ int i;
+
+ tsa->tdms = 0;
+ tsa->tdm[0].is_enable = false;
+ tsa->tdm[1].is_enable = false;
+
+ for_each_available_child_of_node(np, tdm_np) {
+ ret = of_property_read_u32(tdm_np, "reg", &tdm_id);
+ if (ret) {
+ dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np);
+ of_node_put(tdm_np);
+ return ret;
+ }
+ switch (tdm_id) {
+ case 0:
+ tsa->tdms |= BIT(TSA_TDMA);
+ break;
+ case 1:
+ tsa->tdms |= BIT(TSA_TDMB);
+ break;
+ default:
+ dev_err(tsa->dev, "%pOF: Invalid tdm_id (%u)\n", tdm_np,
+ tdm_id);
+ of_node_put(tdm_np);
+ return -EINVAL;
+ }
+ }
+
+ for_each_available_child_of_node(np, tdm_np) {
+ ret = of_property_read_u32(tdm_np, "reg", &tdm_id);
+ if (ret) {
+ dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np);
+ of_node_put(tdm_np);
+ return ret;
+ }
+
+ tdm = &tsa->tdm[tdm_id];
+ tdm->simode_tdm = TSA_SIMODE_TDM_SDM_NORM;
+
+ val = 0;
+ ret = of_property_read_u32(tdm_np, "fsl,rx-frame-sync-delay-bits",
+ &val);
+ if (ret && ret != -EINVAL) {
+ dev_err(tsa->dev,
+ "%pOF: failed to read fsl,rx-frame-sync-delay-bits\n",
+ tdm_np);
+ of_node_put(tdm_np);
+ return ret;
+ }
+ if (val > 3) {
+ dev_err(tsa->dev,
+ "%pOF: Invalid fsl,rx-frame-sync-delay-bits (%u)\n",
+ tdm_np, val);
+ of_node_put(tdm_np);
+ return -EINVAL;
+ }
+ tdm->simode_tdm |= TSA_SIMODE_TDM_RFSD(val);
+
+ val = 0;
+ ret = of_property_read_u32(tdm_np, "fsl,tx-frame-sync-delay-bits",
+ &val);
+ if (ret && ret != -EINVAL) {
+ dev_err(tsa->dev,
+ "%pOF: failed to read fsl,tx-frame-sync-delay-bits\n",
+ tdm_np);
+ of_node_put(tdm_np);
+ return ret;
+ }
+ if (val > 3) {
+ dev_err(tsa->dev,
+ "%pOF: Invalid fsl,tx-frame-sync-delay-bits (%u)\n",
+ tdm_np, val);
+ of_node_put(tdm_np);
+ return -EINVAL;
+ }
+ tdm->simode_tdm |= TSA_SIMODE_TDM_TFSD(val);
+
+ if (of_property_read_bool(tdm_np, "fsl,common-rxtx-pins"))
+ tdm->simode_tdm |= TSA_SIMODE_TDM_CRT;
+
+ if (of_property_read_bool(tdm_np, "fsl,clock-falling-edge"))
+ tdm->simode_tdm |= TSA_SIMODE_TDM_CE;
+
+ if (of_property_read_bool(tdm_np, "fsl,fsync-rising-edge"))
+ tdm->simode_tdm |= TSA_SIMODE_TDM_FE;
+
+ if (of_property_read_bool(tdm_np, "fsl,double-speed-clock"))
+ tdm->simode_tdm |= TSA_SIMODE_TDM_DSC;
+
+ clk = of_clk_get_by_name(tdm_np, "l1rsync");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ clk_put(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ tdm->l1rsync_clk = clk;
+
+ clk = of_clk_get_by_name(tdm_np, "l1rclk");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ clk_put(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ tdm->l1rclk_clk = clk;
+
+ if (!(tdm->simode_tdm & TSA_SIMODE_TDM_CRT)) {
+ clk = of_clk_get_by_name(tdm_np, "l1tsync");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ clk_put(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ tdm->l1tsync_clk = clk;
+
+ clk = of_clk_get_by_name(tdm_np, "l1tclk");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ clk_put(clk);
+ of_node_put(tdm_np);
+ goto err;
+ }
+ tdm->l1tclk_clk = clk;
+ }
+
+ ret = tsa_of_parse_tdm_rx_route(tsa, tdm_np, tsa->tdms, tdm_id);
+ if (ret) {
+ of_node_put(tdm_np);
+ goto err;
+ }
+
+ ret = tsa_of_parse_tdm_tx_route(tsa, tdm_np, tsa->tdms, tdm_id);
+ if (ret) {
+ of_node_put(tdm_np);
+ goto err;
+ }
+
+ tdm->is_enable = true;
+ }
+ return 0;
+
+err:
+ for (i = 0; i < 2; i++) {
+ if (tsa->tdm[i].l1rsync_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
+ clk_put(tsa->tdm[i].l1rsync_clk);
+ }
+ if (tsa->tdm[i].l1rclk_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
+ clk_put(tsa->tdm[i].l1rclk_clk);
+ }
+ if (tsa->tdm[i].l1tsync_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
+ clk_put(tsa->tdm[i].l1rsync_clk);
+ }
+ if (tsa->tdm[i].l1tclk_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
+ clk_put(tsa->tdm[i].l1rclk_clk);
+ }
+ }
+ return ret;
+}
+
+static void tsa_init_si_ram(struct tsa *tsa)
+{
+ resource_size_t i;
+
+ /* Fill all entries as the last one */
+ for (i = 0; i < tsa->si_ram_sz; i += 4)
+ tsa_write32(tsa->si_ram + i, TSA_SIRAM_ENTRY_LAST);
+}
+
+static int tsa_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *res;
+ struct tsa *tsa;
+ unsigned int i;
+ u32 val;
+ int ret;
+
+ tsa = devm_kzalloc(&pdev->dev, sizeof(*tsa), GFP_KERNEL);
+ if (!tsa)
+ return -ENOMEM;
+
+ tsa->dev = &pdev->dev;
+
+ for (i = 0; i < ARRAY_SIZE(tsa->serials); i++)
+ tsa->serials[i].id = i;
+
+ spin_lock_init(&tsa->lock);
+
+ tsa->si_regs = devm_platform_ioremap_resource_byname(pdev, "si_regs");
+ if (IS_ERR(tsa->si_regs))
+ return PTR_ERR(tsa->si_regs);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "si_ram");
+ if (!res) {
+ dev_err(tsa->dev, "si_ram resource missing\n");
+ return -EINVAL;
+ }
+ tsa->si_ram_sz = resource_size(res);
+ tsa->si_ram = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(tsa->si_ram))
+ return PTR_ERR(tsa->si_ram);
+
+ tsa_init_si_ram(tsa);
+
+ ret = tsa_of_parse_tdms(tsa, np);
+ if (ret)
+ return ret;
+
+ /* Set SIMODE */
+ val = 0;
+ if (tsa->tdm[0].is_enable)
+ val |= TSA_SIMODE_TDMA(tsa->tdm[0].simode_tdm);
+ if (tsa->tdm[1].is_enable)
+ val |= TSA_SIMODE_TDMB(tsa->tdm[1].simode_tdm);
+
+ tsa_clrsetbits32(tsa->si_regs + TSA_SIMODE,
+ TSA_SIMODE_TDMA(TSA_SIMODE_TDM_MASK) |
+ TSA_SIMODE_TDMB(TSA_SIMODE_TDM_MASK),
+ val);
+
+ /* Set SIGMR */
+ val = (tsa->tdms == BIT(TSA_TDMA)) ?
+ TSA_SIGMR_RDM_STATIC_TDMA : TSA_SIGMR_RDM_STATIC_TDMAB;
+ if (tsa->tdms & BIT(TSA_TDMA))
+ val |= TSA_SIGMR_ENA;
+ if (tsa->tdms & BIT(TSA_TDMB))
+ val |= TSA_SIGMR_ENB;
+ tsa_write8(tsa->si_regs + TSA_SIGMR, val);
+
+ platform_set_drvdata(pdev, tsa);
+
+ return 0;
+}
+
+static int tsa_remove(struct platform_device *pdev)
+{
+ struct tsa *tsa = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (tsa->tdm[i].l1rsync_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
+ clk_put(tsa->tdm[i].l1rsync_clk);
+ }
+ if (tsa->tdm[i].l1rclk_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
+ clk_put(tsa->tdm[i].l1rclk_clk);
+ }
+ if (tsa->tdm[i].l1tsync_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
+ clk_put(tsa->tdm[i].l1rsync_clk);
+ }
+ if (tsa->tdm[i].l1tclk_clk) {
+ clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
+ clk_put(tsa->tdm[i].l1rclk_clk);
+ }
+ }
+ return 0;
+}
+
+static const struct of_device_id tsa_id_table[] = {
+ { .compatible = "fsl,cpm1-tsa" },
+ {} /* sentinel */
+};
+MODULE_DEVICE_TABLE(of, tsa_id_table);
+
+static struct platform_driver tsa_driver = {
+ .driver = {
+ .name = "fsl-tsa",
+ .of_match_table = of_match_ptr(tsa_id_table),
+ },
+ .probe = tsa_probe,
+ .remove = tsa_remove,
+};
+module_platform_driver(tsa_driver);
+
+struct tsa_serial *tsa_serial_get_byphandle(struct device_node *np,
+ const char *phandle_name)
+{
+ struct of_phandle_args out_args;
+ struct platform_device *pdev;
+ struct tsa_serial *tsa_serial;
+ struct tsa *tsa;
+ int ret;
+
+ ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1, 0, &out_args);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ if (!of_match_node(tsa_driver.driver.of_match_table, out_args.np)) {
+ of_node_put(out_args.np);
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdev = of_find_device_by_node(out_args.np);
+ of_node_put(out_args.np);
+ if (!pdev)
+ return ERR_PTR(-ENODEV);
+
+ tsa = platform_get_drvdata(pdev);
+ if (!tsa) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ if (out_args.args_count != 1) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (out_args.args[0] >= ARRAY_SIZE(tsa->serials)) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ tsa_serial = &tsa->serials[out_args.args[0]];
+
+ /*
+ * Be sure that the serial id matches the phandle arg.
+ * The tsa_serials table is indexed by serial ids. The serial id is set
+ * during the probe() call and needs to be coherent.
+ */
+ if (WARN_ON(tsa_serial->id != out_args.args[0])) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return tsa_serial;
+}
+EXPORT_SYMBOL(tsa_serial_get_byphandle);
+
+void tsa_serial_put(struct tsa_serial *tsa_serial)
+{
+ struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);
+
+ put_device(tsa->dev);
+}
+EXPORT_SYMBOL(tsa_serial_put);
+
+static void devm_tsa_serial_release(struct device *dev, void *res)
+{
+ struct tsa_serial **tsa_serial = res;
+
+ tsa_serial_put(*tsa_serial);
+}
+
+struct tsa_serial *devm_tsa_serial_get_byphandle(struct device *dev,
+ struct device_node *np,
+ const char *phandle_name)
+{
+ struct tsa_serial *tsa_serial;
+ struct tsa_serial **dr;
+
+ dr = devres_alloc(devm_tsa_serial_release, sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return ERR_PTR(-ENOMEM);
+
+ tsa_serial = tsa_serial_get_byphandle(np, phandle_name);
+ if (!IS_ERR(tsa_serial)) {
+ *dr = tsa_serial;
+ devres_add(dev, dr);
+ } else {
+ devres_free(dr);
+ }
+
+ return tsa_serial;
+}
+EXPORT_SYMBOL(devm_tsa_serial_get_byphandle);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("CPM TSA driver");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * TSA management
+ *
+ * Copyright 2022 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+#ifndef __SOC_FSL_TSA_H__
+#define __SOC_FSL_TSA_H__
+
+#include <linux/types.h>
+
+struct device_node;
+struct device;
+struct tsa_serial;
+
+struct tsa_serial *tsa_serial_get_byphandle(struct device_node *np,
+ const char *phandle_name);
+void tsa_serial_put(struct tsa_serial *tsa_serial);
+struct tsa_serial *devm_tsa_serial_get_byphandle(struct device *dev,
+ struct device_node *np,
+ const char *phandle_name);
+
+/* Connect and disconnect the TSA serial */
+int tsa_serial_connect(struct tsa_serial *tsa_serial);
+int tsa_serial_disconnect(struct tsa_serial *tsa_serial);
+
+/* Cell information */
+struct tsa_serial_info {
+ unsigned long rx_fs_rate;
+ unsigned long rx_bit_rate;
+ u8 nb_rx_ts;
+ unsigned long tx_fs_rate;
+ unsigned long tx_bit_rate;
+ u8 nb_tx_ts;
+};
+
+/* Get information */
+int tsa_serial_get_info(struct tsa_serial *tsa_serial, struct tsa_serial_info *info);
+
+#endif /* __SOC_FSL_TSA_H__ */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+
+#ifndef __DT_BINDINGS_SOC_FSL_TSA_H
+#define __DT_BINDINGS_SOC_FSL_TSA_H
+
+#define FSL_CPM_TSA_NU 0 /* Pseuso Cell Id for not used item */
+#define FSL_CPM_TSA_SCC2 1
+#define FSL_CPM_TSA_SCC3 2
+#define FSL_CPM_TSA_SCC4 3
+#define FSL_CPM_TSA_SMC1 4
+#define FSL_CPM_TSA_SMC2 5
+
+#endif
#define CS35L45_ASP_TX_HIZ_UNUSED 0x1
#define CS35L45_ASP_TX_HIZ_DISABLED 0x2
+/*
+ * Optional GPIOX Sub-nodes:
+ * The cs35l45 node can have up to three "cirrus,gpio-ctrlX" ('X' = [1,2,3])
+ * sub-nodes for configuring the GPIO pins.
+ *
+ * - gpio-dir : GPIO pin direction. Valid only when 'gpio-ctrl'
+ * is 1.
+ * 0 = Output
+ * 1 = Input (Default)
+ *
+ * - gpio-lvl : GPIO level. Valid only when 'gpio-ctrl' is 1 and 'gpio-dir' is 0.
+ *
+ * 0 = Low (Default)
+ * 1 = High
+ *
+ * - gpio-op-cfg : GPIO output configuration. Valid only when 'gpio-ctrl' is 1
+ * and 'gpio-dir' is 0.
+ *
+ * 0 = CMOS (Default)
+ * 1 = Open Drain
+ *
+ * - gpio-pol : GPIO output polarity select. Valid only when 'gpio-ctrl' is 1
+ * and 'gpio-dir' is 0.
+ *
+ * 0 = Non-inverted, Active High (Default)
+ * 1 = Inverted, Active Low
+ *
+ * - gpio-invert : Defines the polarity of the GPIO pin if configured
+ * as input.
+ *
+ * 0 = Not inverted (Default)
+ * 1 = Inverted
+ *
+ * - gpio-ctrl : Defines the function of the GPIO pin.
+ *
+ * GPIO1:
+ * 0 = High impedance input (Default)
+ * 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir'
+ * 2 = Pin acts as MDSYNC, direction controlled by MDSYNC
+ * 3-7 = Reserved
+ *
+ * GPIO2:
+ * 0 = High impedance input (Default)
+ * 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir'
+ * 2 = Pin acts as open drain INT
+ * 3 = Reserved
+ * 4 = Pin acts as push-pull output INT. Active low.
+ * 5 = Pin acts as push-pull output INT. Active high.
+ * 6,7 = Reserved
+ *
+ * GPIO3:
+ * 0 = High impedance input (Default)
+ * 1 = Pin acts as a GPIO, direction controlled by 'gpio-dir'
+ * 2-7 = Reserved
+ */
+#define CS35L45_NUM_GPIOS 0x3
+
#endif /* DT_CS35L45_H */
unsigned int sysclk_reg;
unsigned int sysclk_mask;
unsigned int sysclk_shift;
+ bool no_core_startstop;
struct list_head alg_regions;
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QMC management
+ *
+ * Copyright 2022 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+#ifndef __SOC_FSL_QMC_H__
+#define __SOC_FSL_QMC_H__
+
+#include <linux/types.h>
+
+struct device_node;
+struct device;
+struct qmc_chan;
+
+struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phandle_name);
+void qmc_chan_put(struct qmc_chan *chan);
+struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev, struct device_node *np,
+ const char *phandle_name);
+
+enum qmc_mode {
+ QMC_TRANSPARENT,
+ QMC_HDLC,
+};
+
+struct qmc_chan_info {
+ enum qmc_mode mode;
+ unsigned long rx_fs_rate;
+ unsigned long rx_bit_rate;
+ u8 nb_rx_ts;
+ unsigned long tx_fs_rate;
+ unsigned long tx_bit_rate;
+ u8 nb_tx_ts;
+};
+
+int qmc_chan_get_info(struct qmc_chan *chan, struct qmc_chan_info *info);
+
+struct qmc_chan_param {
+ enum qmc_mode mode;
+ union {
+ struct {
+ u16 max_rx_buf_size;
+ u16 max_rx_frame_size;
+ bool is_crc32;
+ } hdlc;
+ struct {
+ u16 max_rx_buf_size;
+ } transp;
+ };
+};
+
+int qmc_chan_set_param(struct qmc_chan *chan, const struct qmc_chan_param *param);
+
+int qmc_chan_write_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,
+ void (*complete)(void *context), void *context);
+
+int qmc_chan_read_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length,
+ void (*complete)(void *context, size_t length),
+ void *context);
+
+#define QMC_CHAN_READ (1<<0)
+#define QMC_CHAN_WRITE (1<<1)
+#define QMC_CHAN_ALL (QMC_CHAN_READ | QMC_CHAN_WRITE)
+
+int qmc_chan_start(struct qmc_chan *chan, int direction);
+int qmc_chan_stop(struct qmc_chan *chan, int direction);
+int qmc_chan_reset(struct qmc_chan *chan, int direction);
+
+#endif /* __SOC_FSL_QMC_H__ */
#ifdef CONFIG_PM
void snd_ac97_suspend(struct snd_ac97 *ac97);
void snd_ac97_resume(struct snd_ac97 *ac97);
+#else
+static inline void snd_ac97_suspend(struct snd_ac97 *ac97) {}
+static inline void snd_ac97_resume(struct snd_ac97 *ac97) {}
#endif
int snd_ac97_reset(struct snd_ac97 *ac97, bool try_warm, unsigned int id,
unsigned int id_mask);
#define __CS35L41_H
#include <linux/regmap.h>
+#include <linux/completion.h>
#include <linux/firmware/cirrus/cs_dsp.h>
#define CS35L41_FIRSTREG 0x00000000
#define CS35L36_PUP_DONE_IRQ_UNMASK 0x5F
#define CS35L36_PUP_DONE_IRQ_MASK 0xBF
+#define CS35L41_SYNC_EN_MASK BIT(8)
#define CS35L41_AMP_SHORT_ERR 0x80000000
#define CS35L41_BST_SHORT_ERR 0x0100
#define CS35L41_BST_DCM_UVP_ERR 0x80
#define CS35L41_OTP_BOOT_DONE 0x02
#define CS35L41_PLL_UNLOCK 0x10
+#define CS35L41_PLL_LOCK BIT(1)
#define CS35L41_OTP_BOOT_ERR 0x80000000
#define CS35L41_AMP_SHORT_ERR_RLS 0x02
#define CS35L41_INT1_MASK_DEFAULT 0x7FFCFE3F
#define CS35L41_INT1_UNMASK_PUP 0xFEFFFFFF
#define CS35L41_INT1_UNMASK_PDN 0xFF7FFFFF
+#define CS35L41_INT3_PLL_LOCK_SHIFT 1
+#define CS35L41_INT3_PLL_LOCK_MASK BIT(CS35L41_INT3_PLL_LOCK_SHIFT)
#define CS35L41_GPIO_DIR_MASK 0x80000000
#define CS35L41_GPIO_DIR_SHIFT 31
enum cs35l41_boost_type {
CS35L41_INT_BOOST,
CS35L41_EXT_BOOST,
+ CS35L41_SHD_BOOST_ACTV,
+ CS35L41_SHD_BOOST_PASS,
+
+ // Not present in Binding Documentation, so no system should use this value.
+ // This value is only used in CLSA0100 Laptop
CS35L41_EXT_BOOST_NO_VSPK_SWITCH,
};
int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
struct cs35l41_hw_cfg *hw_cfg);
bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type);
-int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable);
+int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable,
+ struct completion *pll_lock);
#endif /* __CS35L41_H */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Common definitions for Cirrus Logic CS35L56 smart amp
+ *
+ * Copyright (C) 2023 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef __CS35L56_H
+#define __CS35L56_H
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
+
+#define CS35L56_DEVID 0x0000000
+#define CS35L56_REVID 0x0000004
+#define CS35L56_RELID 0x000000C
+#define CS35L56_OTPID 0x0000010
+#define CS35L56_SFT_RESET 0x0000020
+#define CS35L56_GLOBAL_ENABLES 0x0002014
+#define CS35L56_BLOCK_ENABLES 0x0002018
+#define CS35L56_BLOCK_ENABLES2 0x000201C
+#define CS35L56_REFCLK_INPUT 0x0002C04
+#define CS35L56_GLOBAL_SAMPLE_RATE 0x0002C0C
+#define CS35L56_ASP1_ENABLES1 0x0004800
+#define CS35L56_ASP1_CONTROL1 0x0004804
+#define CS35L56_ASP1_CONTROL2 0x0004808
+#define CS35L56_ASP1_CONTROL3 0x000480C
+#define CS35L56_ASP1_FRAME_CONTROL1 0x0004810
+#define CS35L56_ASP1_FRAME_CONTROL5 0x0004820
+#define CS35L56_ASP1_DATA_CONTROL1 0x0004830
+#define CS35L56_ASP1_DATA_CONTROL5 0x0004840
+#define CS35L56_DACPCM1_INPUT 0x0004C00
+#define CS35L56_DACPCM2_INPUT 0x0004C08
+#define CS35L56_ASP1TX1_INPUT 0x0004C20
+#define CS35L56_ASP1TX2_INPUT 0x0004C24
+#define CS35L56_ASP1TX3_INPUT 0x0004C28
+#define CS35L56_ASP1TX4_INPUT 0x0004C2C
+#define CS35L56_DSP1RX1_INPUT 0x0004C40
+#define CS35L56_DSP1RX2_INPUT 0x0004C44
+#define CS35L56_SWIRE_DP3_CH1_INPUT 0x0004C70
+#define CS35L56_SWIRE_DP3_CH2_INPUT 0x0004C74
+#define CS35L56_SWIRE_DP3_CH3_INPUT 0x0004C78
+#define CS35L56_SWIRE_DP3_CH4_INPUT 0x0004C7C
+#define CS35L56_IRQ1_CFG 0x000E000
+#define CS35L56_IRQ1_STATUS 0x000E004
+#define CS35L56_IRQ1_EINT_1 0x000E010
+#define CS35L56_IRQ1_EINT_2 0x000E014
+#define CS35L56_IRQ1_EINT_4 0x000E01C
+#define CS35L56_IRQ1_EINT_8 0x000E02C
+#define CS35L56_IRQ1_EINT_18 0x000E054
+#define CS35L56_IRQ1_EINT_20 0x000E05C
+#define CS35L56_IRQ1_MASK_1 0x000E090
+#define CS35L56_IRQ1_MASK_2 0x000E094
+#define CS35L56_IRQ1_MASK_4 0x000E09C
+#define CS35L56_IRQ1_MASK_8 0x000E0AC
+#define CS35L56_IRQ1_MASK_18 0x000E0D4
+#define CS35L56_IRQ1_MASK_20 0x000E0DC
+#define CS35L56_DSP_VIRTUAL1_MBOX_1 0x0011020
+#define CS35L56_DSP_VIRTUAL1_MBOX_2 0x0011024
+#define CS35L56_DSP_VIRTUAL1_MBOX_3 0x0011028
+#define CS35L56_DSP_VIRTUAL1_MBOX_4 0x001102C
+#define CS35L56_DSP_VIRTUAL1_MBOX_5 0x0011030
+#define CS35L56_DSP_VIRTUAL1_MBOX_6 0x0011034
+#define CS35L56_DSP_VIRTUAL1_MBOX_7 0x0011038
+#define CS35L56_DSP_VIRTUAL1_MBOX_8 0x001103C
+#define CS35L56_DSP_RESTRICT_STS1 0x00190F0
+#define CS35L56_DSP1_XMEM_PACKED_0 0x2000000
+#define CS35L56_DSP1_XMEM_PACKED_6143 0x2005FFC
+#define CS35L56_DSP1_XMEM_UNPACKED32_0 0x2400000
+#define CS35L56_DSP1_XMEM_UNPACKED32_4095 0x2403FFC
+#define CS35L56_DSP1_SYS_INFO_ID 0x25E0000
+#define CS35L56_DSP1_SYS_INFO_END 0x25E004C
+#define CS35L56_DSP1_AHBM_WINDOW_DEBUG_0 0x25E2040
+#define CS35L56_DSP1_AHBM_WINDOW_DEBUG_1 0x25E2044
+#define CS35L56_DSP1_XMEM_UNPACKED24_0 0x2800000
+#define CS35L56_DSP1_HALO_STATE_A1 0x2801E58
+#define CS35L56_DSP1_HALO_STATE 0x28021E0
+#define CS35L56_DSP1_PM_CUR_STATE_A1 0x2804000
+#define CS35L56_DSP1_PM_CUR_STATE 0x2804308
+#define CS35L56_DSP1_XMEM_UNPACKED24_8191 0x2807FFC
+#define CS35L56_DSP1_CORE_BASE 0x2B80000
+#define CS35L56_DSP1_SCRATCH1 0x2B805C0
+#define CS35L56_DSP1_SCRATCH2 0x2B805C8
+#define CS35L56_DSP1_SCRATCH3 0x2B805D0
+#define CS35L56_DSP1_SCRATCH4 0x2B805D8
+#define CS35L56_DSP1_YMEM_PACKED_0 0x2C00000
+#define CS35L56_DSP1_YMEM_PACKED_4604 0x2C047F0
+#define CS35L56_DSP1_YMEM_UNPACKED32_0 0x3000000
+#define CS35L56_DSP1_YMEM_UNPACKED32_3070 0x3002FF8
+#define CS35L56_DSP1_YMEM_UNPACKED24_0 0x3400000
+#define CS35L56_MAIN_RENDER_USER_MUTE 0x3400024
+#define CS35L56_MAIN_RENDER_USER_VOLUME 0x340002C
+#define CS35L56_MAIN_POSTURE_NUMBER 0x3400094
+#define CS35L56_PROTECTION_STATUS 0x34000D8
+#define CS35L56_TRANSDUCER_ACTUAL_PS 0x3400150
+#define CS35L56_DSP1_YMEM_UNPACKED24_6141 0x3405FF4
+#define CS35L56_DSP1_PMEM_0 0x3800000
+#define CS35L56_DSP1_PMEM_5114 0x3804FE8
+
+/* DEVID */
+#define CS35L56_DEVID_MASK 0x00FFFFFF
+
+/* REVID */
+#define CS35L56_AREVID_MASK 0x000000F0
+#define CS35L56_MTLREVID_MASK 0x0000000F
+#define CS35L56_REVID_B0 0x000000B0
+
+/* ASP_ENABLES1 */
+#define CS35L56_ASP_RX2_EN_SHIFT 17
+#define CS35L56_ASP_RX1_EN_SHIFT 16
+#define CS35L56_ASP_TX4_EN_SHIFT 3
+#define CS35L56_ASP_TX3_EN_SHIFT 2
+#define CS35L56_ASP_TX2_EN_SHIFT 1
+#define CS35L56_ASP_TX1_EN_SHIFT 0
+
+/* ASP_CONTROL1 */
+#define CS35L56_ASP_BCLK_FREQ_MASK 0x0000003F
+#define CS35L56_ASP_BCLK_FREQ_SHIFT 0
+
+/* ASP_CONTROL2 */
+#define CS35L56_ASP_RX_WIDTH_MASK 0xFF000000
+#define CS35L56_ASP_RX_WIDTH_SHIFT 24
+#define CS35L56_ASP_TX_WIDTH_MASK 0x00FF0000
+#define CS35L56_ASP_TX_WIDTH_SHIFT 16
+#define CS35L56_ASP_FMT_MASK 0x00000700
+#define CS35L56_ASP_FMT_SHIFT 8
+#define CS35L56_ASP_BCLK_INV_MASK 0x00000040
+#define CS35L56_ASP_FSYNC_INV_MASK 0x00000004
+
+/* ASP_CONTROL3 */
+#define CS35L56_ASP1_DOUT_HIZ_CTRL_MASK 0x00000003
+
+/* ASP_DATA_CONTROL1 */
+#define CS35L56_ASP_TX_WL_MASK 0x0000003F
+
+/* ASP_DATA_CONTROL5 */
+#define CS35L56_ASP_RX_WL_MASK 0x0000003F
+
+/* ASPTXn_INPUT */
+#define CS35L56_ASP_TXn_SRC_MASK 0x0000007F
+
+/* SWIRETX[1..7]_SRC SDWTXn INPUT */
+#define CS35L56_SWIRETXn_SRC_MASK 0x0000007F
+
+/* IRQ1_STATUS */
+#define CS35L56_IRQ1_STS_MASK 0x00000001
+
+/* IRQ1_EINT_1 */
+#define CS35L56_AMP_SHORT_ERR_EINT1_MASK 0x80000000
+
+/* IRQ1_EINT_2 */
+#define CS35L56_DSP_VIRTUAL2_MBOX_WR_EINT1_MASK 0x00200000
+
+/* IRQ1_EINT_4 */
+#define CS35L56_OTP_BOOT_DONE_MASK 0x00000002
+
+/* IRQ1_EINT_8 */
+#define CS35L56_TEMP_ERR_EINT1_MASK 0x80000000
+
+/* Mixer input sources */
+#define CS35L56_INPUT_SRC_NONE 0x00
+#define CS35L56_INPUT_SRC_ASP1RX1 0x08
+#define CS35L56_INPUT_SRC_ASP1RX2 0x09
+#define CS35L56_INPUT_SRC_VMON 0x18
+#define CS35L56_INPUT_SRC_IMON 0x19
+#define CS35L56_INPUT_SRC_ERR_VOL 0x20
+#define CS35L56_INPUT_SRC_CLASSH 0x21
+#define CS35L56_INPUT_SRC_VDDBMON 0x28
+#define CS35L56_INPUT_SRC_VBSTMON 0x29
+#define CS35L56_INPUT_SRC_DSP1TX1 0x32
+#define CS35L56_INPUT_SRC_DSP1TX2 0x33
+#define CS35L56_INPUT_SRC_DSP1TX3 0x34
+#define CS35L56_INPUT_SRC_DSP1TX4 0x35
+#define CS35L56_INPUT_SRC_DSP1TX5 0x36
+#define CS35L56_INPUT_SRC_DSP1TX6 0x37
+#define CS35L56_INPUT_SRC_DSP1TX7 0x38
+#define CS35L56_INPUT_SRC_DSP1TX8 0x39
+#define CS35L56_INPUT_SRC_TEMPMON 0x3A
+#define CS35L56_INPUT_SRC_INTERPOLATOR 0x40
+#define CS35L56_INPUT_SRC_SWIRE_DP1_CHANNEL1 0x44
+#define CS35L56_INPUT_SRC_SWIRE_DP1_CHANNEL2 0x45
+#define CS35L56_INPUT_MASK 0x7F
+
+#define CS35L56_NUM_INPUT_SRC 21
+
+/* ASP formats */
+#define CS35L56_ASP_FMT_DSP_A 0
+#define CS35L56_ASP_FMT_I2S 2
+
+/* ASP HiZ modes */
+#define CS35L56_ASP_UNUSED_HIZ_OFF_HIZ 3
+
+/* MAIN_RENDER_ACTUAL_PS */
+#define CS35L56_PS0 0
+#define CS35L56_PS3 3
+
+/* CS35L56_DSP_RESTRICT_STS1 */
+#define CS35L56_RESTRICTED_MASK 0x7
+
+/* CS35L56_MAIN_RENDER_USER_MUTE */
+#define CS35L56_MAIN_RENDER_USER_MUTE_MASK 1
+
+/* CS35L56_MAIN_RENDER_USER_VOLUME */
+#define CS35L56_MAIN_RENDER_USER_VOLUME_MIN -400
+#define CS35L56_MAIN_RENDER_USER_VOLUME_MAX 400
+#define CS35L56_MAIN_RENDER_USER_VOLUME_MASK 0x0000FFC0
+#define CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT 6
+#define CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT 9
+
+/* CS35L56_MAIN_POSTURE_NUMBER */
+#define CS35L56_MAIN_POSTURE_MIN 0
+#define CS35L56_MAIN_POSTURE_MAX 255
+#define CS35L56_MAIN_POSTURE_MASK CS35L56_MAIN_POSTURE_MAX
+
+/* CS35L56_PROTECTION_STATUS */
+#define CS35L56_FIRMWARE_MISSING BIT(0)
+
+/* Software Values */
+#define CS35L56_HALO_STATE_SHUTDOWN 1
+#define CS35L56_HALO_STATE_BOOT_DONE 2
+
+#define CS35L56_MBOX_CMD_AUDIO_PLAY 0x0B000001
+#define CS35L56_MBOX_CMD_AUDIO_PAUSE 0x0B000002
+#define CS35L56_MBOX_CMD_HIBERNATE_NOW 0x02000001
+#define CS35L56_MBOX_CMD_WAKEUP 0x02000002
+#define CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE 0x02000003
+#define CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE 0x02000004
+#define CS35L56_MBOX_CMD_SHUTDOWN 0x02000005
+#define CS35L56_MBOX_CMD_SYSTEM_RESET 0x02000007
+
+#define CS35L56_MBOX_TIMEOUT_US 5000
+#define CS35L56_MBOX_POLL_US 250
+
+#define CS35L56_PS0_POLL_US 500
+#define CS35L56_PS0_TIMEOUT_US 50000
+#define CS35L56_PS3_POLL_US 500
+#define CS35L56_PS3_TIMEOUT_US 300000
+
+#define CS35L56_CONTROL_PORT_READY_US 2200
+#define CS35L56_HALO_STATE_POLL_US 1000
+#define CS35L56_HALO_STATE_TIMEOUT_US 50000
+#define CS35L56_HIBERNATE_WAKE_POLL_US 500
+#define CS35L56_HIBERNATE_WAKE_TIMEOUT_US 5000
+#define CS35L56_RESET_PULSE_MIN_US 1100
+
+#define CS35L56_SDW1_PLAYBACK_PORT 1
+#define CS35L56_SDW1_CAPTURE_PORT 3
+
+#define CS35L56_NUM_BULK_SUPPLIES 3
+#define CS35L56_NUM_DSP_REGIONS 5
+
+extern struct regmap_config cs35l56_regmap_i2c;
+extern struct regmap_config cs35l56_regmap_spi;
+extern struct regmap_config cs35l56_regmap_sdw;
+
+extern const struct cs_dsp_region cs35l56_dsp1_regions[CS35L56_NUM_DSP_REGIONS];
+extern const char * const cs35l56_tx_input_texts[CS35L56_NUM_INPUT_SRC];
+extern const unsigned int cs35l56_tx_input_values[CS35L56_NUM_INPUT_SRC];
+
+void cs35l56_reread_firmware_registers(struct device *dev, struct regmap *regmap);
+int cs35l56_get_bclk_freq_id(unsigned int freq);
+void cs35l56_fill_supply_names(struct regulator_bulk_data *data);
+
+#endif /* ifndef __CS35L56_H */
/* ------------------- DEFINES -------------------- */
#define EMUPAGESIZE 4096
-#define MAXREQVOICES 8
#define MAXPAGES0 4096 /* 32 bit mode */
#define MAXPAGES1 8192 /* 31 bit mode */
-#define RESERVED 0
-#define NUM_MIDI 16
#define NUM_G 64 /* use all channels */
-#define NUM_FXSENDS 4
#define NUM_EFX_PLAYBACK 16
/* FIXME? - according to the OSS driver the EMU10K1 needs a 29 bit DMA mask */
#define AUDIGY_DMA_MASK 0xffffffffUL /* 32bit mode */
#define TMEMSIZE 256*1024
-#define TMEMSIZEREG 4
#define IP_TO_CP(ip) ((ip == 0) ? 0 : (((0x00001000uL | (ip & 0x00000FFFL)) << (((ip >> 12) & 0x000FL) + 4)) & 0xFFFF0000uL))
/* the relevant bits and zero to the other bits */
#define IPR_P16V 0x80000000 /* Bit set when the CA0151 P16V chip wishes
to interrupt */
-#define IPR_GPIOMSG 0x20000000 /* GPIO message interrupt (RE'd, still not sure
- which INTE bits enable it) */
+#define IPR_WATERMARK_REACHED 0x40000000
+#define IPR_A_GPIO 0x20000000 /* GPIO input pin change */
/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */
#define IPR_A_MIDITRANSBUFEMPTY2 0x10000000 /* MIDI UART transmit buffer empty */
#define IPR_CHANNELLOOP 0x00000040 /* Channel (half) loop interrupt(s) pending */
#define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */
/* highest set channel in CLIPL, CLIPH, HLIPL, */
- /* or HLIPH. When IP is written with CL set, */
+ /* or HLIPH. When IPR is written with CL set, */
/* the bit in H/CLIPL or H/CLIPH corresponding */
- /* to the CIN value written will be cleared. */
+ /* to the CN value written will be cleared. */
#define INTE 0x0c /* Interrupt enable register */
#define INTE_VIRTUALSB_MASK 0xc0000000 /* Virtual Soundblaster I/O port capture */
/* behavior and possibly random segfaults and */
/* lockups if enabled. */
+#define INTE_A_GPIOENABLE 0x00040000 /* Enable GPIO input change interrupts */
+
/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */
#define INTE_A_MIDITXENABLE2 0x00020000 /* Enable MIDI transmit-buffer-empty interrupts */
#define INTE_A_MIDIRXENABLE2 0x00010000 /* Enable MIDI receive-buffer-empty interrupts */
+#define INTE_A_SPDIF_BUFFULL_ENABLE 0x00008000
+#define INTE_A_SPDIF_HALFBUFFULL_ENABLE 0x00004000
#define INTE_SAMPLERATETRACKER 0x00002000 /* Enable sample rate tracker interrupts */
/* NOTE: This bit must always be enabled */
#define WC 0x10 /* Wall Clock register */
#define WC_SAMPLECOUNTER_MASK 0x03FFFFC0 /* Sample periods elapsed since reset */
#define WC_SAMPLECOUNTER 0x14060010
-#define WC_CURRENTCHANNEL 0x0000003F /* Channel [0..63] currently being serviced */
+#define WC_CURRENTCHANNEL_MASK 0x0000003F /* Channel [0..63] currently being serviced */
/* NOTE: Each channel takes 1/64th of a sample */
/* period to be serviced. */
+#define WC_CURRENTCHANNEL 0x06000010
#define HCFG 0x14 /* Hardware config register */
/* NOTE: There is no reason to use the legacy */
#define HCFG_CODECFORMAT_MASK 0x00030000 /* CODEC format */
/* Specific to Alice2, CA0102 */
+
#define HCFG_CODECFORMAT_AC97_1 0x00000000 /* AC97 CODEC format -- Ver 1.03 */
#define HCFG_CODECFORMAT_AC97_2 0x00010000 /* AC97 CODEC format -- Ver 2.1 */
#define HCFG_AUTOMUTE_ASYNC 0x00008000 /* When set, the async sample rate convertors */
/* I2S format input */
/* Rest of HCFG 0x0000000f same as below. LOCKSOUNDCACHE etc. */
-
-
/* Older chips */
+
#define HCFG_CODECFORMAT_AC97 0x00000000 /* AC97 CODEC format -- Primary Output */
#define HCFG_CODECFORMAT_I2S 0x00010000 /* I2S CODEC format -- Secondary (Rear) Output */
#define HCFG_GPINPUT0 0x00004000 /* External pin112 */
/* Should be set to 1 when the EMU10K1 is */
/* completely initialized. */
-//For Audigy, MPU port move to 0x70-0x74 ptr register
+// On Audigy, the MPU port moved to the 0x70-0x74 ptr registers
#define MUDATA 0x18 /* MPU401 data register (8 bits) */
#define MUSTAT_IRDYN 0x80 /* 0 = MIDI data or command ACK */
#define MUSTAT_ORDYN 0x40 /* 0 = MUDATA can accept a command or data */
-#define A_IOCFG 0x18 /* GPIO on Audigy card (16bits) */
-#define A_GPINPUT_MASK 0xff00
+#define A_GPIO 0x18 /* GPIO on Audigy card (16bits) */
+#define A_GPINPUT_MASK 0xff00 /* Alice/2 has 8 input pins */
+#define A3_GPINPUT_MASK 0x3f00 /* ... while Tina/2 has only 6 */
#define A_GPOUTPUT_MASK 0x00ff
+// The GPIO port is used for I/O config on Sound Blasters;
+// card-specific info can be found in the emu_chip_details table.
+// On E-MU cards the port is used as the interface to the FPGA.
+
// Audigy output/GPIO stuff taken from the kX drivers
+#define A_IOCFG A_GPIO
#define A_IOCFG_GPOUT0 0x0044 /* analog/digital */
#define A_IOCFG_DISABLE_ANALOG 0x0040 /* = 'enable' for Audigy2 (chiprev=4) */
#define A_IOCFG_ENABLE_DIGITAL 0x0004
#define A_IOCFG_REAR_JACK 0x8000
#define A_IOCFG_PHONES_JACK 0x0100 /* LiveDrive */
-/* outputs:
- * for audigy2 platinum: 0xa00
- * for a2 platinum ex: 0x1c00
- * for a1 platinum: 0x0
- */
-
#define TIMER 0x1a /* Timer terminal count register */
/* NOTE: After the rate is changed, a maximum */
/* of 1024 sample periods should be allowed */
/* before the new rate is guaranteed accurate. */
-#define TIMER_RATE_MASK 0x000003ff /* Timer interrupt rate in sample periods */
+#define TIMER_RATE_MASK 0x03ff /* Timer interrupt rate in sample periods */
/* 0 == 1024 periods, [1..4] are not useful */
-#define TIMER_RATE 0x0a00001a
#define AC97DATA 0x1c /* AC97 register set data register (16 bit) */
/* 0x00000000 2-channel output. */
/* 0x00000200 8-channel output. */
/* 0x00000004 pauses stream/irq fail. */
- /* Rest of bits no nothing to sound output */
+ /* Rest of bits do nothing to sound output */
/* bit 0: Enable P16V audio.
* bit 1: Lock P16V record memory cache.
* bit 2: Lock P16V playback memory cache.
*/
#define IPR3 0x38 /* Cdif interrupt pending register */
#define INTE3 0x3c /* Cdif interrupt enable register. */
+
/************************************************************************************************/
/* PCI function 1 registers, address = <val> + PCIBASE1 */
/************************************************************************************************/
#define JOYSTICK_BUTTONS 0x0f /* Joystick button data */
#define JOYSTICK_COMPARATOR 0xf0 /* Joystick comparator data */
-
/********************************************************************************************************/
/* Emu10k1 pointer-offset register set, accessed through the PTR and DATA registers */
/********************************************************************************************************/
+// No official documentation was released for EMU10K1, but some info
+// about playback can be extrapolated from the EMU8K documents:
+// "AWE32/EMU8000 Programmer’s Guide" (emu8kpgm.pdf) - registers
+// "AWE32 Developer's Information Pack" (adip301.pdf) - high-level view
+
+// The short version:
+// - The engine has 64 playback channels, also called voices. The channels
+// operate independently, except when paired for stereo (see below).
+// - PCM samples are fetched into the cache; see description of CD0 below.
+// - Samples are consumed at the rate CPF_CURRENTPITCH.
+// - 8-bit samples are transformed upon use: cooked = (raw ^ 0x80) << 8
+// - 8 samples are read at CCR_READADDRESS:CPF_FRACADDRESS and interpolated
+// according to CCCA_INTERPROM_*. With CCCA_INTERPROM_0 selected and a zero
+// CPF_FRACADDRESS, this results in CCR_READADDRESS[3] being used verbatim.
+// - The value is multiplied by CVCF_CURRENTVOL.
+// - The value goes through a filter with cutoff CVCF_CURRENTFILTER;
+// delay stages Z1 and Z2.
+// - The value is added by so-called `sends` to 4 (EMU10K1) / 8 (EMU10K2)
+// of the 16 (EMU10K1) / 64 (EMU10K2) FX bus accumulators via FXRT*,
+// multiplied by a per-send amount (*_FXSENDAMOUNT_*).
+// The scaling of the send amounts is exponential-ish.
+// - The DSP has a go at FXBUS* and outputs the values to EXTOUT* or EMU32OUT*.
+// - The pitch, volume, and filter cutoff can be modulated by two envelope
+// engines and two low frequency oscillators.
+// - To avoid abrupt changes to the parameters (which may cause audible
+// distortion), the modulation engine sets the target registers, towards
+// which the current registers "swerve" gradually.
+
#define CPF 0x00 /* Current pitch and fraction register */
#define CPF_CURRENTPITCH_MASK 0xffff0000 /* Current pitch (linear, 0x4000 == unity pitch shift) */
#define CPF_CURRENTPITCH 0x10100000
#define PSST_LOOPSTARTADDR_MASK 0x00ffffff /* Loop start address of the specified channel */
#define PSST_LOOPSTARTADDR 0x18000006
-#define DSL 0x07 /* Send D amount and loop start address register */
+#define DSL 0x07 /* Send D amount and loop end address register */
#define DSL_FXSENDAMOUNT_D_MASK 0xff000000 /* Linear level of channel output sent to FX send bus D */
#define DSL_FXSENDAMOUNT_D 0x08180007
#define DSL_LOOPENDADDR 0x18000007
#define CCCA 0x08 /* Filter Q, interp. ROM, byte size, cur. addr register */
-#define CCCA_RESONANCE 0xf0000000 /* Lowpass filter resonance (Q) height */
-#define CCCA_INTERPROMMASK 0x0e000000 /* Selects passband of interpolation ROM */
+#define CCCA_RESONANCE_MASK 0xf0000000 /* Lowpass filter resonance (Q) height */
+#define CCCA_RESONANCE 0x041c0008
+#define CCCA_INTERPROM_MASK 0x0e000000 /* Selects passband of interpolation ROM */
/* 1 == full band, 7 == lowpass */
/* ROM 0 is used when pitch shifting downward or less */
/* then 3 semitones upward. Increasingly higher ROM */
#define CCCA_INTERPROM_6 0x0c000000 /* Select interpolation ROM 6 */
#define CCCA_INTERPROM_7 0x0e000000 /* Select interpolation ROM 7 */
#define CCCA_8BITSELECT 0x01000000 /* 1 = Sound memory for this channel uses 8-bit samples */
+ /* 8-bit samples are unsigned, 16-bit ones signed */
#define CCCA_CURRADDR_MASK 0x00ffffff /* Current address of the selected channel */
#define CCCA_CURRADDR 0x18000008
#define CCR 0x09 /* Cache control register */
#define CCR_CACHEINVALIDSIZE 0x07190009
-#define CCR_CACHEINVALIDSIZE_MASK 0xfe000000 /* Number of invalid samples cache for this channel */
+#define CCR_CACHEINVALIDSIZE_MASK 0xfe000000 /* Number of invalid samples before the read address */
#define CCR_CACHELOOPFLAG 0x01000000 /* 1 = Cache has a loop service pending */
#define CCR_INTERLEAVEDSAMPLES 0x00800000 /* 1 = A cache service will fetch interleaved samples */
+ /* Auto-set from CPF_STEREO_MASK */
#define CCR_WORDSIZEDSAMPLES 0x00400000 /* 1 = A cache service will fetch word sized samples */
+ /* Auto-set from CCCA_8BITSELECT */
#define CCR_READADDRESS 0x06100009
-#define CCR_READADDRESS_MASK 0x003f0000 /* Location of cache just beyond current cache service */
+#define CCR_READADDRESS_MASK 0x003f0000 /* Next cached sample to play */
#define CCR_LOOPINVALSIZE 0x0000fe00 /* Number of invalid samples in cache prior to loop */
/* NOTE: This is valid only if CACHELOOPFLAG is set */
#define CCR_LOOPFLAG 0x00000100 /* Set for a single sample period when a loop occurs */
-#define CCR_CACHELOOPADDRHI 0x000000ff /* DSL_LOOPSTARTADDR's hi byte if CACHELOOPFLAG is set */
+#define CCR_CACHELOOPADDRHI 0x000000ff /* CLP_LOOPSTARTADDR's hi byte if CACHELOOPFLAG is set */
#define CLP 0x0a /* Cache loop register (valid if CCR_CACHELOOPFLAG = 1) */
/* NOTE: This register is normally not used */
-#define CLP_CACHELOOPADDR 0x0000ffff /* Cache loop address (DSL_LOOPSTARTADDR [0..15]) */
+#define CLP_CACHELOOPADDR 0x0000ffff /* Cache loop address low word */
#define FXRT 0x0b /* Effects send routing register */
/* NOTE: It is illegal to assign the same routing to */
#define FXRT_CHANNELC 0x0f000000 /* Effects send bus number for channel's effects send C */
#define FXRT_CHANNELD 0xf0000000 /* Effects send bus number for channel's effects send D */
-#define A_HR 0x0b /* High Resolution. 24bit playback from host to DSP. */
#define MAPA 0x0c /* Cache map A */
-
#define MAPB 0x0d /* Cache map B */
#define MAP_PTE_MASK0 0xfffff000 /* The 20 MSBs of the PTE indexed by the PTI */
#define MAP_PTE_MASK1 0xffffe000 /* The 19 MSBs of the PTE indexed by the PTI */
#define MAP_PTI_MASK1 0x00001fff /* The 13 bit index to one of the 8192 PTE dwords */
-/* 0x0e, 0x0f: Not used */
+/* 0x0e, 0x0f: Internal state, at least on Audigy */
#define ENVVOL 0x10 /* Volume envelope register */
#define ENVVOL_MASK 0x0000ffff /* Current value of volume envelope state variable */
/* 0x8000-n == 666*n usec delay */
#define ATKHLDV 0x11 /* Volume envelope hold and attack register */
-#define ATKHLDV_PHASE0 0x00008000 /* 0 = Begin attack phase */
+#define ATKHLDV_PHASE0_MASK 0x00008000 /* 0 = Begin attack phase */
#define ATKHLDV_HOLDTIME_MASK 0x00007f00 /* Envelope hold time (127-n == n*88.2msec) */
#define ATKHLDV_ATTACKTIME_MASK 0x0000007f /* Envelope attack time, log encoded */
/* 0 = infinite, 1 = 10.9msec, ... 0x7f = 5.5msec */
#define DCYSUSV 0x12 /* Volume envelope sustain and decay register */
-#define DCYSUSV_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */
+#define DCYSUSV_PHASE1_MASK 0x00008000 /* 0 = Begin decay phase, 1 = begin release phase */
#define DCYSUSV_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */
-#define DCYSUSV_CHANNELENABLE_MASK 0x00000080 /* 1 = Inhibit envelope engine from writing values in */
+#define DCYSUSV_CHANNELENABLE_MASK 0x00000080 /* 0 = Inhibit envelope engine from writing values in */
/* this channel and from writing to pitch, filter and */
/* volume targets. */
#define DCYSUSV_DECAYTIME_MASK 0x0000007f /* Volume envelope decay time, log encoded */
/* 0x8000-n == 666*n usec delay */
#define ATKHLDM 0x15 /* Modulation envelope hold and attack register */
-#define ATKHLDM_PHASE0 0x00008000 /* 0 = Begin attack phase */
+#define ATKHLDM_PHASE0_MASK 0x00008000 /* 0 = Begin attack phase */
#define ATKHLDM_HOLDTIME 0x00007f00 /* Envelope hold time (127-n == n*42msec) */
#define ATKHLDM_ATTACKTIME 0x0000007f /* Envelope attack time, log encoded */
/* 0 = infinite, 1 = 11msec, ... 0x7f = 5.5msec */
#define DCYSUSM 0x16 /* Modulation envelope decay and sustain register */
-#define DCYSUSM_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */
+#define DCYSUSM_PHASE1_MASK 0x00008000 /* 0 = Begin decay phase, 1 = begin release phase */
#define DCYSUSM_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */
#define DCYSUSM_DECAYTIME_MASK 0x0000007f /* Envelope decay time, log encoded */
/* 0 = 43.7msec, 1 = 21.8msec, 0x7f = 22msec */
#define IFATN_ATTENUATION_MASK 0x000000ff /* Initial attenuation in 0.375dB steps */
#define IFATN_ATTENUATION 0x08000019
-
#define PEFE 0x1a /* Pitch envelope and filter envelope amount register */
#define PEFE_PITCHAMOUNT_MASK 0x0000ff00 /* Pitch envlope amount */
/* Signed 2's complement, +/- one octave peak extremes */
#define PEFE_FILTERAMOUNT_MASK 0x000000ff /* Filter envlope amount */
/* Signed 2's complement, +/- six octaves peak extremes */
#define PEFE_FILTERAMOUNT 0x0800001a
+
#define FMMOD 0x1b /* Vibrato/filter modulation from LFO register */
#define FMMOD_MODVIBRATO 0x0000ff00 /* Vibrato LFO modulation depth */
/* Signed 2's complement, +/- one octave extremes */
#define FMMOD_MOFILTER 0x000000ff /* Filter LFO modulation depth */
/* Signed 2's complement, +/- three octave extremes */
-
#define TREMFRQ 0x1c /* Tremolo amount and modulation LFO frequency register */
#define TREMFRQ_DEPTH 0x0000ff00 /* Tremolo depth */
/* Signed 2's complement, with +/- 12dB extremes */
-
#define TREMFRQ_FREQUENCY 0x000000ff /* Tremolo LFO frequency */
/* ??Hz steps, maximum of ?? Hz. */
+
#define FM2FRQ2 0x1d /* Vibrato amount and vibrato LFO frequency register */
#define FM2FRQ2_DEPTH 0x0000ff00 /* Vibrato LFO vibrato depth */
/* Signed 2's complement, +/- one octave extremes */
/* is 16bit, 48KHz only. All 32 channels can be enabled */
/* simultaneously. */
-#define FXWC_DEFAULTROUTE_C (1<<0) /* left emu out? */
-#define FXWC_DEFAULTROUTE_B (1<<1) /* right emu out? */
-#define FXWC_DEFAULTROUTE_A (1<<12)
-#define FXWC_DEFAULTROUTE_D (1<<13)
-#define FXWC_ADCLEFT (1<<18)
-#define FXWC_CDROMSPDIFLEFT (1<<18)
-#define FXWC_ADCRIGHT (1<<19)
-#define FXWC_CDROMSPDIFRIGHT (1<<19)
-#define FXWC_MIC (1<<20)
-#define FXWC_ZOOMLEFT (1<<20)
-#define FXWC_ZOOMRIGHT (1<<21)
-#define FXWC_SPDIFLEFT (1<<22) /* 0x00400000 */
-#define FXWC_SPDIFRIGHT (1<<23) /* 0x00800000 */
-
#define A_TBLSZ 0x43 /* Effects Tank Internal Table Size. Only low byte or register used */
#define TCBS 0x44 /* Tank cache buffer size register */
#define FXBA 0x47 /* FX Buffer Address */
#define FXBA_MASK 0xfffff000 /* 20 bit base address */
-#define A_HWM 0x48 /* High PCI Water Mark - word access, defaults to 3f */
+#define A_HWM 0x48 /* High PCI Water Mark - word access, defaults to 3f */
#define MICBS 0x49 /* Microphone buffer size register */
#define FXBS 0x4b /* FX buffer size register */
-/* register: 0x4c..4f: ffff-ffff current amounts, per-channel */
-
-/* The following mask values define the size of the ADC, MIX and FX buffers in bytes */
+/* The following mask values define the size of the ADC, MIC and FX buffers in bytes */
#define ADCBS_BUFSIZE_NONE 0x00000000
#define ADCBS_BUFSIZE_384 0x00000001
#define ADCBS_BUFSIZE_448 0x00000002
#define ADCBS_BUFSIZE_57344 0x0000001e
#define ADCBS_BUFSIZE_65536 0x0000001f
-/* Current Send B, A Amounts */
-#define A_CSBA 0x4c
-
-/* Current Send D, C Amounts */
-#define A_CSDC 0x4d
-
-/* Current Send F, E Amounts */
-#define A_CSFE 0x4e
-
-/* Current Send H, G Amounts */
-#define A_CSHG 0x4f
+#define A_CSBA 0x4c /* FX send B & A current amounts */
+#define A_CSDC 0x4d /* FX send D & C current amounts */
+#define A_CSFE 0x4e /* FX send F & E current amounts */
+#define A_CSHG 0x4f /* FX send H & G current amounts */
+// NOTE: 0x50,51,52: 64-bit (split over voices 0 & 1)
+#define CDCS 0x50 /* CD-ROM digital channel status register */
-#define CDCS 0x50 /* CD-ROM digital channel status register */
+#define GPSCS 0x51 /* General Purpose SPDIF channel status register */
-#define GPSCS 0x51 /* General Purpose SPDIF channel status register*/
+// Corresponding EMU10K1_DBG_* constants are in the public header
+#define DBG 0x52
-#define DBG 0x52 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */
+#define A_SPSC 0x52 /* S/PDIF Input C Channel Status */
-/* S/PDIF Input C Channel Status */
-#define A_SPSC 0x52
+#define REG53 0x53 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */
-#define REG53 0x53 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */
+// Corresponding A_DBG_* constants are in the public header
+#define A_DBG 0x53
-#define A_DBG 0x53
-#define A_DBG_SINGLE_STEP 0x00020000 /* Set to zero to start dsp */
-#define A_DBG_ZC 0x40000000 /* zero tram counter */
-#define A_DBG_STEP_ADDR 0x000003ff
-#define A_DBG_SATURATION_OCCURED 0x20000000
-#define A_DBG_SATURATION_ADDR 0x0ffc0000
-
-// NOTE: 0x54,55,56: 64-bit
+// NOTE: 0x54,55,56: 64-bit (split over voices 0 & 1)
#define SPCS0 0x54 /* SPDIF output Channel Status 0 register */
#define SPCS1 0x55 /* SPDIF output Channel Status 1 register */
/* 0x57: Not used */
-/* The 32-bit CLIx and SOLx registers all have one bit per channel control/status */
+/* The 32-bit CLIx and SOLEx registers all have one bit per channel control/status */
#define CLIEL 0x58 /* Channel loop interrupt enable low register */
-
#define CLIEH 0x59 /* Channel loop interrupt enable high register */
#define CLIPL 0x5a /* Channel loop interrupt pending low register */
-
#define CLIPH 0x5b /* Channel loop interrupt pending high register */
#define SOLEL 0x5c /* Stop on loop enable low register */
-
#define SOLEH 0x5d /* Stop on loop enable high register */
#define SPBYPASS 0x5e /* SPDIF BYPASS mode register */
#define SPBYPASS_FORMAT 0x00000f00 /* If 1, SPDIF XX uses 24 bit, if 0 - 20 bit */
#define AC97SLOT 0x5f /* additional AC97 slots enable bits */
-#define AC97SLOT_REAR_RIGHT 0x01 /* Rear left */
-#define AC97SLOT_REAR_LEFT 0x02 /* Rear right */
-#define AC97SLOT_CNTR 0x10 /* Center enable */
-#define AC97SLOT_LFE 0x20 /* LFE enable */
+#define AC97SLOT_REAR_RIGHT 0x01 /* Rear left */
+#define AC97SLOT_REAR_LEFT 0x02 /* Rear right */
+#define AC97SLOT_CNTR 0x10 /* Center enable */
+#define AC97SLOT_LFE 0x20 /* LFE enable */
-/* PCB Revision */
-#define A_PCB 0x5f
+#define A_PCB 0x5f /* PCB Revision */
// NOTE: 0x60,61,62: 64-bit
#define CDSRCS 0x60 /* CD-ROM Sample Rate Converter status register */
#define FXIDX_MASK 0x0000ffff /* 16-bit value */
#define FXIDX_IDX 0x10000065
-/* The 32-bit HLIx and HLIPx registers all have one bit per channel control/status */
+/* The 32-bit HLIEx and HLIPx registers all have one bit per channel control/status */
#define HLIEL 0x66 /* Channel half loop interrupt enable low register */
-
#define HLIEH 0x67 /* Channel half loop interrupt enable high register */
#define HLIPL 0x68 /* Channel half loop interrupt pending low register */
-
#define HLIPH 0x69 /* Channel half loop interrupt pending high register */
-/* S/PDIF Host Record Index (bypasses SRC) */
-#define A_SPRI 0x6a
-/* S/PDIF Host Record Address */
-#define A_SPRA 0x6b
-/* S/PDIF Host Record Control */
-#define A_SPRC 0x6c
-/* Delayed Interrupt Counter & Enable */
-#define A_DICE 0x6d
-/* Tank Table Base */
-#define A_TTB 0x6e
-/* Tank Delay Offset */
-#define A_TDOF 0x6f
+#define A_SPRI 0x6a /* S/PDIF Host Record Index (bypasses SRC) */
+#define A_SPRA 0x6b /* S/PDIF Host Record Address */
+#define A_SPRC 0x6c /* S/PDIF Host Record Control */
+
+#define A_DICE 0x6d /* Delayed Interrupt Counter & Enable */
+
+#define A_TTB 0x6e /* Tank Table Base */
+#define A_TDOF 0x6f /* Tank Delay Offset */
/* This is the MPU port on the card (via the game port) */
#define A_MUDATA1 0x70
#define A_MUSTAT2 A_MUCMD2
/* The next two are the Audigy equivalent of FXWC */
-/* the Audigy can record any output (16bit, 48kHz, up to 64 channel simultaneously) */
+/* the Audigy can record any output (16bit, 48kHz, up to 64 channels simultaneously) */
/* Each bit selects a channel for recording */
#define A_FXWC1 0x74 /* Selects 0x7f-0x60 for FX recording */
#define A_FXWC2 0x75 /* Selects 0x9f-0x80 for FX recording */
-/* Extended Hardware Control */
-#define A_SPDIF_SAMPLERATE 0x76 /* Set the sample rate of SPDIF output */
-#define A_SAMPLE_RATE 0x76 /* Various sample rate settings. */
-#define A_SAMPLE_RATE_NOT_USED 0x0ffc111e /* Bits that are not used and cannot be set. */
-#define A_SAMPLE_RATE_UNKNOWN 0xf0030001 /* Bits that can be set, but have unknown use. */
+#define A_EHC 0x76 /* Extended Hardware Control */
+
+#define A_SPDIF_SAMPLERATE A_EHC /* Set the sample rate of SPDIF output */
#define A_SPDIF_RATE_MASK 0x000000e0 /* Any other values for rates, just use 48000 */
-#define A_SPDIF_48000 0x00000000
+#define A_SPDIF_48000 0x00000000 /* kX calls this BYPASS */
#define A_SPDIF_192000 0x00000020
#define A_SPDIF_96000 0x00000040
#define A_SPDIF_44100 0x00000080
+#define A_SPDIF_MUTED 0x000000c0
#define A_I2S_CAPTURE_RATE_MASK 0x00000e00 /* This sets the capture PCM rate, but it is */
-#define A_I2S_CAPTURE_48000 0x00000000 /* unclear if this sets the ADC rate as well. */
-#define A_I2S_CAPTURE_192000 0x00000200
-#define A_I2S_CAPTURE_96000 0x00000400
-#define A_I2S_CAPTURE_44100 0x00000800
-
-#define A_PCM_RATE_MASK 0x0000e000 /* This sets the playback PCM rate on the P16V */
-#define A_PCM_48000 0x00000000
-#define A_PCM_192000 0x00002000
-#define A_PCM_96000 0x00004000
-#define A_PCM_44100 0x00008000
-
-/* I2S0 Sample Rate Tracker Status */
-#define A_SRT3 0x77
-
-/* I2S1 Sample Rate Tracker Status */
-#define A_SRT4 0x78
-
-/* I2S2 Sample Rate Tracker Status */
-#define A_SRT5 0x79
+#define A_I2S_CAPTURE_RATE 0x03090076 /* unclear if this sets the ADC rate as well. */
+#define A_I2S_CAPTURE_48000 0x0
+#define A_I2S_CAPTURE_192000 0x1
+#define A_I2S_CAPTURE_96000 0x2
+#define A_I2S_CAPTURE_44100 0x4
+
+#define A_EHC_SRC48_MASK 0x0000e000 /* This sets the playback PCM rate on the P16V */
+#define A_EHC_SRC48_BYPASS 0x00000000
+#define A_EHC_SRC48_192 0x00002000
+#define A_EHC_SRC48_96 0x00004000
+#define A_EHC_SRC48_44 0x00008000
+#define A_EHC_SRC48_MUTED 0x0000c000
+
+#define A_EHC_P17V_TVM 0x00000001 /* Tank virtual memory mode */
+#define A_EHC_P17V_SEL0_MASK 0x00030000 /* Aka A_EHC_P16V_PB_RATE; 00: 48, 01: 44.1, 10: 96, 11: 192 */
+#define A_EHC_P17V_SEL1_MASK 0x000c0000
+#define A_EHC_P17V_SEL2_MASK 0x00300000
+#define A_EHC_P17V_SEL3_MASK 0x00c00000
+
+#define A_EHC_ASYNC_BYPASS 0x80000000
+
+#define A_SRT3 0x77 /* I2S0 Sample Rate Tracker Status */
+#define A_SRT4 0x78 /* I2S1 Sample Rate Tracker Status */
+#define A_SRT5 0x79 /* I2S2 Sample Rate Tracker Status */
/* - default to 0x01080000 on my audigy 2 ZS --rlrevell */
-/* Tank Table DMA Address */
-#define A_TTDA 0x7a
-/* Tank Table DMA Data */
-#define A_TTDD 0x7b
+#define A_SRT_ESTSAMPLERATE 0x001fffff
+#define A_SRT_RATELOCKED 0x01000000
+
+#define A_TTDA 0x7a /* Tank Table DMA Address */
+#define A_TTDD 0x7b /* Tank Table DMA Data */
#define A_FXRT2 0x7c
#define A_FXRT_CHANNELE 0x0000003f /* Effects send bus number for channel's effects send E */
#define A_FXSENDAMOUNT_F_MASK 0x00FF0000
#define A_FXSENDAMOUNT_G_MASK 0x0000FF00
#define A_FXSENDAMOUNT_H_MASK 0x000000FF
+
/* 0x7c, 0x7e "high bit is used for filtering" */
/* The send amounts for this one are the same as used with the emu10k1 */
#define A_FXRT_CHANNELD 0x3f000000
/* 0x7f: Not used */
-/* Each FX general purpose register is 32 bits in length, all bits are used */
-#define FXGPREGBASE 0x100 /* FX general purpose registers base */
-#define A_FXGPREGBASE 0x400 /* Audigy GPRs, 0x400 to 0x5ff */
-
-#define A_TANKMEMCTLREGBASE 0x100 /* Tank memory control registers base - only for Audigy */
-#define A_TANKMEMCTLREG_MASK 0x1f /* only 5 bits used - only for Audigy */
-
-/* Tank audio data is logarithmically compressed down to 16 bits before writing to TRAM and is */
-/* decompressed back to 20 bits on a read. There are a total of 160 locations, the last 32 */
-/* locations are for external TRAM. */
-#define TANKMEMDATAREGBASE 0x200 /* Tank memory data registers base */
-#define TANKMEMDATAREG_MASK 0x000fffff /* 20 bit tank audio data field */
-
-/* Combined address field and memory opcode or flag field. 160 locations, last 32 are external */
-#define TANKMEMADDRREGBASE 0x300 /* Tank memory address registers base */
-#define TANKMEMADDRREG_ADDR_MASK 0x000fffff /* 20 bit tank address field */
-#define TANKMEMADDRREG_CLEAR 0x00800000 /* Clear tank memory */
-#define TANKMEMADDRREG_ALIGN 0x00400000 /* Align read or write relative to tank access */
-#define TANKMEMADDRREG_WRITE 0x00200000 /* Write to tank memory */
-#define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */
-#define MICROCODEBASE 0x400 /* Microcode data base address */
+/* The public header defines the GPR and TRAM base addresses that
+ * are valid for _both_ CPU and DSP addressing. */
/* Each DSP microcode instruction is mapped into 2 doublewords */
/* NOTE: When writing, always write the LO doubleword first. Reads can be in either order. */
-#define LOWORD_OPX_MASK 0x000ffc00 /* Instruction operand X */
-#define LOWORD_OPY_MASK 0x000003ff /* Instruction operand Y */
-#define HIWORD_OPCODE_MASK 0x00f00000 /* Instruction opcode */
-#define HIWORD_RESULT_MASK 0x000ffc00 /* Instruction result */
-#define HIWORD_OPA_MASK 0x000003ff /* Instruction operand A */
+#define MICROCODEBASE 0x400 /* Microcode data base address */
+#define A_MICROCODEBASE 0x600
-/* Audigy Soundcard have a different instruction format */
-#define A_MICROCODEBASE 0x600
-#define A_LOWORD_OPY_MASK 0x000007ff
-#define A_LOWORD_OPX_MASK 0x007ff000
-#define A_HIWORD_OPCODE_MASK 0x0f000000
-#define A_HIWORD_RESULT_MASK 0x007ff000
-#define A_HIWORD_OPA_MASK 0x000007ff
+/************************************************************************************************/
+/* E-MU Digital Audio System overview */
+/************************************************************************************************/
+
+// - These cards use a regular PCI-attached Audigy chip (Alice2/Tina/Tina2);
+// the PCIe variants simply put the Audigy chip behind a PCI bridge.
+// - All physical PCM I/O is routed through an additional FPGA; the regular
+// EXTIN/EXTOUT ports are unconnected.
+// - The FPGA has a signal routing matrix, to connect each destination (output
+// socket or capture channel) to a source (input socket or playback channel).
+// - The FPGA is controlled via Audigy's GPIO port, while sample data is
+// transmitted via proprietary EMU32 serial links. On first-generation
+// E-MU 1010 cards, Audigy's I2S inputs are also used for sample data.
+// - The Audio/Micro Dock is attached to Hana via EDI, a "network" link.
+// - The Audigy chip operates in slave mode; the clock is supplied by the FPGA.
+// Gen1 E-MU 1010 cards have two crystals (for 44.1 kHz and 48 kHz multiples),
+// while the later cards use a single crystal and a PLL chip.
+// - The whole card is switched to 2x/4x mode to achieve 88.2/96/176.4/192 kHz
+// sample rates. Alice2/Tina keeps running at 44.1/48 kHz, but multiple channels
+// are bundled.
+// - The number of available EMU32/EDI channels is hit in 2x/4x mode, so the total
+// number of usable inputs/outputs is limited, esp. with ADAT in use.
+// - S/PDIF is unavailable in 4x mode (only over TOSLINK on newer 1010 cards) due
+// to being unspecified at 176.4/192 kHz. Therefore, the Dock's S/PDIF channels
+// can overlap with the Dock's ADC/DAC's high channels.
+// - The code names are mentioned below and in the emu_chip_details table.
/************************************************************************************************/
-/* EMU1010m HANA FPGA registers */
+/* EMU1010 FPGA registers */
/************************************************************************************************/
+
#define EMU_HANA_DESTHI 0x00 /* 0000xxx 3 bits Link Destination */
#define EMU_HANA_DESTLO 0x01 /* 00xxxxx 5 bits */
+
#define EMU_HANA_SRCHI 0x02 /* 0000xxx 3 bits Link Source */
#define EMU_HANA_SRCLO 0x03 /* 00xxxxx 5 bits */
+
#define EMU_HANA_DOCK_PWR 0x04 /* 000000x 1 bits Audio Dock power */
#define EMU_HANA_DOCK_PWR_ON 0x01 /* Audio Dock power on */
+
#define EMU_HANA_WCLOCK 0x05 /* 0000xxx 3 bits Word Clock source select */
/* Must be written after power on to reset DLL */
/* One is unable to detect the Audio dock without this */
#define EMU_HANA_WCLOCK_INT_44_1K 0x01
#define EMU_HANA_WCLOCK_HANA_SPDIF_IN 0x02
#define EMU_HANA_WCLOCK_HANA_ADAT_IN 0x03
-#define EMU_HANA_WCLOCK_SYNC_BNCN 0x04
+#define EMU_HANA_WCLOCK_SYNC_BNC 0x04
#define EMU_HANA_WCLOCK_2ND_HANA 0x05
#define EMU_HANA_WCLOCK_SRC_RESERVED 0x06
#define EMU_HANA_WCLOCK_OFF 0x07 /* For testing, forces fallback to DEFCLOCK */
#define EMU_HANA_IRQ_DOCK_LOST 0x08
#define EMU_HANA_SPDIF_MODE 0x0a /* 00xxxxx 5 bits SPDIF MODE */
-#define EMU_HANA_SPDIF_MODE_TX_COMSUMER 0x00
+#define EMU_HANA_SPDIF_MODE_TX_CONSUMER 0x00
#define EMU_HANA_SPDIF_MODE_TX_PRO 0x01
#define EMU_HANA_SPDIF_MODE_TX_NOCOPY 0x02
-#define EMU_HANA_SPDIF_MODE_RX_COMSUMER 0x00
+#define EMU_HANA_SPDIF_MODE_RX_CONSUMER 0x00
#define EMU_HANA_SPDIF_MODE_RX_PRO 0x04
#define EMU_HANA_SPDIF_MODE_RX_NOCOPY 0x08
#define EMU_HANA_SPDIF_MODE_RX_INVALID 0x10
#define EMU_HANA_OPTICAL_OUT_ADAT 0x02
#define EMU_HANA_MIDI_IN 0x0c /* 000000x 1 bit Control MIDI */
-#define EMU_HANA_MIDI_IN_FROM_HAMOA 0x00 /* HAMOA MIDI in to Alice 2 MIDI B */
-#define EMU_HANA_MIDI_IN_FROM_DOCK 0x01 /* Audio Dock MIDI in to Alice 2 MIDI B */
+#define EMU_HANA_MIDI_INA_FROM_HAMOA 0x01 /* HAMOA MIDI in to Alice 2 MIDI A */
+#define EMU_HANA_MIDI_INA_FROM_DOCK1 0x02 /* Audio Dock-1 MIDI in to Alice 2 MIDI A */
+#define EMU_HANA_MIDI_INA_FROM_DOCK2 0x03 /* Audio Dock-2 MIDI in to Alice 2 MIDI A */
+#define EMU_HANA_MIDI_INB_FROM_HAMOA 0x08 /* HAMOA MIDI in to Alice 2 MIDI B */
+#define EMU_HANA_MIDI_INB_FROM_DOCK1 0x10 /* Audio Dock-1 MIDI in to Alice 2 MIDI B */
+#define EMU_HANA_MIDI_INB_FROM_DOCK2 0x18 /* Audio Dock-2 MIDI in to Alice 2 MIDI B */
#define EMU_HANA_DOCK_LEDS_1 0x0d /* 000xxxx 4 bit Audio Dock LEDs */
#define EMU_HANA_DOCK_LEDS_1_MIDI1 0x01 /* MIDI 1 LED on */
#define EMU_HANA_DOCK_LEDS_3_MANUAL_SIGNAL 0x20 /* Manual Signal detection */
#define EMU_HANA_ADC_PADS 0x10 /* 0000xxx 3 bit Audio Dock ADC 14dB pads */
-#define EMU_HANA_DOCK_ADC_PAD1 0x01 /* 14dB Attenuation on Audio Dock ADC 1 */
-#define EMU_HANA_DOCK_ADC_PAD2 0x02 /* 14dB Attenuation on Audio Dock ADC 2 */
-#define EMU_HANA_DOCK_ADC_PAD3 0x04 /* 14dB Attenuation on Audio Dock ADC 3 */
-#define EMU_HANA_0202_ADC_PAD1 0x08 /* 14dB Attenuation on 0202 ADC 1 */
+#define EMU_HANA_DOCK_ADC_PAD1 0x01 /* 14dB Attenuation on Audio Dock ADC 1 */
+#define EMU_HANA_DOCK_ADC_PAD2 0x02 /* 14dB Attenuation on Audio Dock ADC 2 */
+#define EMU_HANA_DOCK_ADC_PAD3 0x04 /* 14dB Attenuation on Audio Dock ADC 3 */
+#define EMU_HANA_0202_ADC_PAD1 0x08 /* 14dB Attenuation on 0202 ADC 1 */
#define EMU_HANA_DOCK_MISC 0x11 /* 0xxxxxx 6 bit Audio Dock misc bits */
-#define EMU_HANA_DOCK_DAC1_MUTE 0x01 /* DAC 1 Mute */
-#define EMU_HANA_DOCK_DAC2_MUTE 0x02 /* DAC 2 Mute */
-#define EMU_HANA_DOCK_DAC3_MUTE 0x04 /* DAC 3 Mute */
-#define EMU_HANA_DOCK_DAC4_MUTE 0x08 /* DAC 4 Mute */
+#define EMU_HANA_DOCK_DAC1_MUTE 0x01 /* DAC 1 Mute */
+#define EMU_HANA_DOCK_DAC2_MUTE 0x02 /* DAC 2 Mute */
+#define EMU_HANA_DOCK_DAC3_MUTE 0x04 /* DAC 3 Mute */
+#define EMU_HANA_DOCK_DAC4_MUTE 0x08 /* DAC 4 Mute */
#define EMU_HANA_DOCK_PHONES_192_DAC1 0x00 /* DAC 1 Headphones source at 192kHz */
#define EMU_HANA_DOCK_PHONES_192_DAC2 0x10 /* DAC 2 Headphones source at 192kHz */
#define EMU_HANA_DOCK_PHONES_192_DAC3 0x20 /* DAC 3 Headphones source at 192kHz */
#define EMU_HANA_DOCK_PHONES_192_DAC4 0x30 /* DAC 4 Headphones source at 192kHz */
#define EMU_HANA_MIDI_OUT 0x12 /* 00xxxxx 5 bit Source for each MIDI out port */
-#define EMU_HANA_MIDI_OUT_0202 0x01 /* 0202 MIDI from Alice 2. 0 = A, 1 = B */
-#define EMU_HANA_MIDI_OUT_DOCK1 0x02 /* Audio Dock MIDI1 front, from Alice 2. 0 = A, 1 = B */
-#define EMU_HANA_MIDI_OUT_DOCK2 0x04 /* Audio Dock MIDI2 rear, from Alice 2. 0 = A, 1 = B */
-#define EMU_HANA_MIDI_OUT_SYNC2 0x08 /* Sync card. Not the actual MIDI out jack. 0 = A, 1 = B */
-#define EMU_HANA_MIDI_OUT_LOOP 0x10 /* 0 = bits (3:0) normal. 1 = MIDI loopback enabled. */
+#define EMU_HANA_MIDI_OUT_0202 0x01 /* 0202 MIDI from Alice 2. 0 = A, 1 = B */
+#define EMU_HANA_MIDI_OUT_DOCK1 0x02 /* Audio Dock MIDI1 front, from Alice 2. 0 = A, 1 = B */
+#define EMU_HANA_MIDI_OUT_DOCK2 0x04 /* Audio Dock MIDI2 rear, from Alice 2. 0 = A, 1 = B */
+#define EMU_HANA_MIDI_OUT_SYNC2 0x08 /* Sync card. Not the actual MIDI out jack. 0 = A, 1 = B */
+#define EMU_HANA_MIDI_OUT_LOOP 0x10 /* 0 = bits (3:0) normal. 1 = MIDI loopback enabled. */
#define EMU_HANA_DAC_PADS 0x13 /* 00xxxxx 5 bit DAC 14dB attenuation pads */
-#define EMU_HANA_DOCK_DAC_PAD1 0x01 /* 14dB Attenuation on AudioDock DAC 1. Left and Right */
-#define EMU_HANA_DOCK_DAC_PAD2 0x02 /* 14dB Attenuation on AudioDock DAC 2. Left and Right */
-#define EMU_HANA_DOCK_DAC_PAD3 0x04 /* 14dB Attenuation on AudioDock DAC 3. Left and Right */
-#define EMU_HANA_DOCK_DAC_PAD4 0x08 /* 14dB Attenuation on AudioDock DAC 4. Left and Right */
-#define EMU_HANA_0202_DAC_PAD1 0x10 /* 14dB Attenuation on 0202 DAC 1. Left and Right */
+#define EMU_HANA_DOCK_DAC_PAD1 0x01 /* 14dB Attenuation on AudioDock DAC 1. Left and Right */
+#define EMU_HANA_DOCK_DAC_PAD2 0x02 /* 14dB Attenuation on AudioDock DAC 2. Left and Right */
+#define EMU_HANA_DOCK_DAC_PAD3 0x04 /* 14dB Attenuation on AudioDock DAC 3. Left and Right */
+#define EMU_HANA_DOCK_DAC_PAD4 0x08 /* 14dB Attenuation on AudioDock DAC 4. Left and Right */
+#define EMU_HANA_0202_DAC_PAD1 0x10 /* 14dB Attenuation on 0202 DAC 1. Left and Right */
/* 0x14 - 0x1f Unused R/W registers */
-#define EMU_HANA_IRQ_STATUS 0x20 /* 000xxxx 4 bits IRQ Status */
-#if 0 /* Already defined for reg 0x09 IRQ_ENABLE */
-#define EMU_HANA_IRQ_WCLK_CHANGED 0x01
-#define EMU_HANA_IRQ_ADAT 0x02
-#define EMU_HANA_IRQ_DOCK 0x04
-#define EMU_HANA_IRQ_DOCK_LOST 0x08
-#endif
+
+#define EMU_HANA_IRQ_STATUS 0x20 /* 00xxxxx 5 bits IRQ Status */
+ /* Same bits as for EMU_HANA_IRQ_ENABLE */
+ /* Reading the register resets it. */
#define EMU_HANA_OPTION_CARDS 0x21 /* 000xxxx 4 bits Presence of option cards */
-#define EMU_HANA_OPTION_HAMOA 0x01 /* HAMOA card present */
-#define EMU_HANA_OPTION_SYNC 0x02 /* Sync card present */
-#define EMU_HANA_OPTION_DOCK_ONLINE 0x04 /* Audio Dock online and FPGA configured */
-#define EMU_HANA_OPTION_DOCK_OFFLINE 0x08 /* Audio Dock online and FPGA not configured */
+#define EMU_HANA_OPTION_HAMOA 0x01 /* Hamoa (analog I/O) card present */
+#define EMU_HANA_OPTION_SYNC 0x02 /* Sync card present */
+#define EMU_HANA_OPTION_DOCK_ONLINE 0x04 /* Audio/Micro dock present and FPGA configured */
+#define EMU_HANA_OPTION_DOCK_OFFLINE 0x08 /* Audio/Micro dock present and FPGA not configured */
-#define EMU_HANA_ID 0x22 /* 1010101 7 bits ID byte & 0x7f = 0x55 */
+#define EMU_HANA_ID 0x22 /* 1010101 7 bits ID byte & 0x7f = 0x55 with Alice2 */
+ /* 0010101 5 bits ID byte & 0x1f = 0x15 with Tina/2 */
#define EMU_HANA_MAJOR_REV 0x23 /* 0000xxx 3 bit Hana FPGA Major rev */
#define EMU_HANA_MINOR_REV 0x24 /* 0000xxx 3 bit Hana FPGA Minor rev */
#define EMU_DOCK_MINOR_REV 0x26 /* 0000xxx 3 bit Audio Dock FPGA Minor rev */
#define EMU_DOCK_BOARD_ID 0x27 /* 00000xx 2 bits Audio Dock ID pins */
-#define EMU_DOCK_BOARD_ID0 0x00 /* ID bit 0 */
-#define EMU_DOCK_BOARD_ID1 0x03 /* ID bit 1 */
+#define EMU_DOCK_BOARD_ID0 0x00 /* ID bit 0 */
+#define EMU_DOCK_BOARD_ID1 0x03 /* ID bit 1 */
#define EMU_HANA_WC_SPDIF_HI 0x28 /* 0xxxxxx 6 bit SPDIF IN Word clock, upper 6 bits */
#define EMU_HANA_WC_SPDIF_LO 0x29 /* 0xxxxxx 6 bit SPDIF IN Word clock, lower 6 bits */
#define EMU_HANA2_WC_SPDIF_HI 0x2e /* 0xxxxxx 6 bit HANA2 SPDIF IN Word clock, upper 6 bits */
#define EMU_HANA2_WC_SPDIF_LO 0x2f /* 0xxxxxx 6 bit HANA2 SPDIF IN Word clock, lower 6 bits */
+
/* 0x30 - 0x3f Unused Read only registers */
+// The meaning of this is not clear; kX-project just calls it "lock" in some info-only code.
+#define EMU_HANA_LOCK_STS_LO 0x38 /* 0xxxxxx lower 6 bits */
+#define EMU_HANA_LOCK_STS_HI 0x39 /* 0xxxxxx upper 6 bits */
+
/************************************************************************************************/
-/* EMU1010m HANA Destinations */
+/* EMU1010 Audio Destinations */
/************************************************************************************************/
-/* Hana, original 1010,1212,1820 using Alice2
- * Destiniations for SRATEX = 1X rates: 44.1 kHz or 48 kHz
+/* Hana, original 1010,1212m,1820[m] using Alice2
* 0x00, 0x00-0x0f: 16 EMU32 channels to Alice2
- * 0x01, 0x10-0x1f: 32 Elink channels to Audio Dock
- * 0x01, 0x00: Dock DAC 1 Left
- * 0x01, 0x04: Dock DAC 1 Right
- * 0x01, 0x08: Dock DAC 2 Left
- * 0x01, 0x0c: Dock DAC 2 Right
- * 0x01, 0x10: Dock DAC 3 Left
- * 0x01, 0x12: PHONES Left
- * 0x01, 0x14: Dock DAC 3 Right
- * 0x01, 0x16: PHONES Right
- * 0x01, 0x18: Dock DAC 4 Left
- * 0x01, 0x1a: S/PDIF Left
- * 0x01, 0x1c: Dock DAC 4 Right
- * 0x01, 0x1e: S/PDIF Right
+ * 0x01, 0x00-0x1f: 32 EDI channels to Audio Dock
+ * 0x00: Dock DAC 1 Left
+ * 0x04: Dock DAC 1 Right
+ * 0x08: Dock DAC 2 Left
+ * 0x0c: Dock DAC 2 Right
+ * 0x10: Dock DAC 3 Left
+ * 0x12: PHONES Left (n/a in 2x/4x mode; output mirrors DAC4 Left)
+ * 0x14: Dock DAC 3 Right
+ * 0x16: PHONES Right (n/a in 2x/4x mode; output mirrors DAC4 Right)
+ * 0x18: Dock DAC 4 Left
+ * 0x1a: S/PDIF Left
+ * 0x1c: Dock DAC 4 Right
+ * 0x1e: S/PDIF Right
* 0x02, 0x00: Hana S/PDIF Left
* 0x02, 0x01: Hana S/PDIF Right
- * 0x03, 0x00: Hanoa DAC Left
- * 0x03, 0x01: Hanoa DAC Right
+ * 0x03, 0x00: Hamoa DAC Left
+ * 0x03, 0x01: Hamoa DAC Right
* 0x04, 0x00-0x07: Hana ADAT
* 0x05, 0x00: I2S0 Left to Alice2
* 0x05, 0x01: I2S0 Right to Alice2
* Hana2 never released, but used Tina
* Not needed.
*
- * Hana3, rev2 1010,1212,1616 using Tina
- * Destinations for SRATEX = 1X rates: 44.1 kHz or 48 kHz
+ * Hana3, rev2 1010,1212m,1616[m] using Tina
* 0x00, 0x00-0x0f: 16 EMU32A channels to Tina
- * 0x01, 0x10-0x1f: 32 EDI channels to Micro Dock
- * 0x01, 0x00: Dock DAC 1 Left
- * 0x01, 0x04: Dock DAC 1 Right
- * 0x01, 0x08: Dock DAC 2 Left
- * 0x01, 0x0c: Dock DAC 2 Right
- * 0x01, 0x10: Dock DAC 3 Left
- * 0x01, 0x12: Dock S/PDIF Left
- * 0x01, 0x14: Dock DAC 3 Right
- * 0x01, 0x16: Dock S/PDIF Right
- * 0x01, 0x18-0x1f: Dock ADAT 0-7
+ * 0x01, 0x00-0x1f: 32 EDI channels to Micro Dock
+ * 0x00: Dock DAC 1 Left
+ * 0x04: Dock DAC 1 Right
+ * 0x08: Dock DAC 2 Left
+ * 0x0c: Dock DAC 2 Right
+ * 0x10: Dock DAC 3 Left
+ * 0x12: Dock S/PDIF Left
+ * 0x14: Dock DAC 3 Right
+ * 0x16: Dock S/PDIF Right
+ * 0x18-0x1f: Dock ADAT 0-7
* 0x02, 0x00: Hana3 S/PDIF Left
* 0x02, 0x01: Hana3 S/PDIF Right
- * 0x03, 0x00: Hanoa DAC Left
- * 0x03, 0x01: Hanoa DAC Right
+ * 0x03, 0x00: Hamoa DAC Left
+ * 0x03, 0x01: Hamoa DAC Right
* 0x04, 0x00-0x07: Hana3 ADAT 0-7
* 0x05, 0x00-0x0f: 16 EMU32B channels to Tina
* 0x06-0x07: Not used
*
* HanaLite, rev1 0404 using Alice2
- * Destiniations for SRATEX = 1X rates: 44.1 kHz or 48 kHz
- * 0x00, 0x00-0x0f: 16 EMU32 channels to Alice2
- * 0x01: Not used
- * 0x02, 0x00: S/PDIF Left
- * 0x02, 0x01: S/PDIF Right
- * 0x03, 0x00: DAC Left
- * 0x03, 0x01: DAC Right
- * 0x04-0x07: Not used
- *
- * HanaLiteLite, rev2 0404 using Alice2
- * Destiniations for SRATEX = 1X rates: 44.1 kHz or 48 kHz
- * 0x00, 0x00-0x0f: 16 EMU32 channels to Alice2
+ * HanaLiteLite, rev2 0404 using Tina
+ * 0x00, 0x00-0x0f: 16 EMU32 channels to Alice2/Tina
* 0x01: Not used
* 0x02, 0x00: S/PDIF Left
* 0x02, 0x01: S/PDIF Right
* 0x04-0x07: Not used
*
* Mana, Cardbus 1616 using Tina2
- * Destinations for SRATEX = 1X rates: 44.1 kHz or 48 kHz
* 0x00, 0x00-0x0f: 16 EMU32A channels to Tina2
- * 0x01, 0x10-0x1f: 32 EDI channels to Micro Dock
- * 0x01, 0x00: Dock DAC 1 Left
- * 0x01, 0x04: Dock DAC 1 Right
- * 0x01, 0x08: Dock DAC 2 Left
- * 0x01, 0x0c: Dock DAC 2 Right
- * 0x01, 0x10: Dock DAC 3 Left
- * 0x01, 0x12: Dock S/PDIF Left
- * 0x01, 0x14: Dock DAC 3 Right
- * 0x01, 0x16: Dock S/PDIF Right
- * 0x01, 0x18-0x1f: Dock ADAT 0-7
+ * 0x01, 0x00-0x1f: 32 EDI channels to Micro Dock
+ * (same as rev2 1010)
* 0x02: Not used
* 0x03, 0x00: Mana DAC Left
* 0x03, 0x01: Mana DAC Right
* 0x04, 0x00-0x0f: 16 EMU32B channels to Tina2
* 0x05-0x07: Not used
- *
- *
*/
+
/* 32-bit destinations of signal in the Hana FPGA. Destinations are either
- * physical outputs of Hana, or outputs going to Alice2 (audigy) for capture
- * - 16 x EMU_DST_ALICE2_EMU32_X.
- */
-/* EMU32 = 32-bit serial channel between Alice2 (audigy) and Hana (FPGA) */
-/* EMU_DST_ALICE2_EMU32_X - data channels from Hana to Alice2 used for capture.
- * Which data is fed into a EMU_DST_ALICE2_EMU32_X channel in Hana depends on
- * setup of mixer control for each destination - see emumixer.c -
- * snd_emu1010_output_enum_ctls[], snd_emu1010_input_enum_ctls[]
+ * physical outputs of Hana, or outputs going to Alice2/Tina for capture -
+ * 16 x EMU_DST_ALICE2_EMU32_X (2x on rev2 boards). Which data is fed into
+ * a channel depends on the mixer control setting for each destination - see
+ * emumixer.c - snd_emu1010_output_enum_ctls[], snd_emu1010_input_enum_ctls[]
*/
#define EMU_DST_ALICE2_EMU32_0 0x000f /* 16 EMU32 channels to Alice2 +0 to +0xf */
#define EMU_DST_ALICE2_EMU32_1 0x0000 /* 16 EMU32 channels to Alice2 +0 to +0xf */
#define EMU_DST_DOCK_SPDIF_RIGHT2 0x011f /* Audio Dock SPDIF Right, 2nd or 96kHz */
#define EMU_DST_HANA_SPDIF_LEFT1 0x0200 /* Hana SPDIF Left, 1st or 48kHz only */
#define EMU_DST_HANA_SPDIF_LEFT2 0x0202 /* Hana SPDIF Left, 2nd or 96kHz */
+#define EMU_DST_HANA_SPDIF_LEFT3 0x0204 /* Hana SPDIF Left, 3rd or 192kHz */
+#define EMU_DST_HANA_SPDIF_LEFT4 0x0206 /* Hana SPDIF Left, 4th or 192kHz */
#define EMU_DST_HANA_SPDIF_RIGHT1 0x0201 /* Hana SPDIF Right, 1st or 48kHz only */
#define EMU_DST_HANA_SPDIF_RIGHT2 0x0203 /* Hana SPDIF Right, 2nd or 96kHz */
+#define EMU_DST_HANA_SPDIF_RIGHT3 0x0205 /* Hana SPDIF Right, 3rd or 192kHz */
+#define EMU_DST_HANA_SPDIF_RIGHT4 0x0207 /* Hana SPDIF Right, 4th or 192kHz */
#define EMU_DST_HAMOA_DAC_LEFT1 0x0300 /* Hamoa DAC Left, 1st or 48kHz only */
#define EMU_DST_HAMOA_DAC_LEFT2 0x0302 /* Hamoa DAC Left, 2nd or 96kHz */
#define EMU_DST_HAMOA_DAC_LEFT3 0x0304 /* Hamoa DAC Left, 3rd or 192kHz */
#define EMU_DST_HAMOA_DAC_RIGHT2 0x0303 /* Hamoa DAC Right, 2nd or 96kHz */
#define EMU_DST_HAMOA_DAC_RIGHT3 0x0305 /* Hamoa DAC Right, 3rd or 192kHz */
#define EMU_DST_HAMOA_DAC_RIGHT4 0x0307 /* Hamoa DAC Right, 4th or 192kHz */
+// In S/MUX mode, the samples of one channel are adjacent.
#define EMU_DST_HANA_ADAT 0x0400 /* Hana ADAT 8 channel out +0 to +7 */
#define EMU_DST_ALICE_I2S0_LEFT 0x0500 /* Alice2 I2S0 Left */
#define EMU_DST_ALICE_I2S0_RIGHT 0x0501 /* Alice2 I2S0 Right */
#define EMU_DST_ALICE_I2S2_RIGHT 0x0701 /* Alice2 I2S2 Right */
/* Additional destinations for 1616(M)/Microdock */
-/* Microdock S/PDIF OUT Left, 1st or 48kHz only */
-#define EMU_DST_MDOCK_SPDIF_LEFT1 0x0112
-/* Microdock S/PDIF OUT Left, 2nd or 96kHz */
-#define EMU_DST_MDOCK_SPDIF_LEFT2 0x0113
-/* Microdock S/PDIF OUT Right, 1st or 48kHz only */
-#define EMU_DST_MDOCK_SPDIF_RIGHT1 0x0116
-/* Microdock S/PDIF OUT Right, 2nd or 96kHz */
-#define EMU_DST_MDOCK_SPDIF_RIGHT2 0x0117
-/* Microdock S/PDIF ADAT 8 channel out +8 to +f */
-#define EMU_DST_MDOCK_ADAT 0x0118
-
-/* Headphone jack on 1010 cardbus? 44.1/48kHz only? */
-#define EMU_DST_MANA_DAC_LEFT 0x0300
-/* Headphone jack on 1010 cardbus? 44.1/48kHz only? */
-#define EMU_DST_MANA_DAC_RIGHT 0x0301
+
+#define EMU_DST_MDOCK_SPDIF_LEFT1 0x0112 /* Microdock S/PDIF OUT Left, 1st or 48kHz only */
+#define EMU_DST_MDOCK_SPDIF_LEFT2 0x0113 /* Microdock S/PDIF OUT Left, 2nd or 96kHz */
+#define EMU_DST_MDOCK_SPDIF_RIGHT1 0x0116 /* Microdock S/PDIF OUT Right, 1st or 48kHz only */
+#define EMU_DST_MDOCK_SPDIF_RIGHT2 0x0117 /* Microdock S/PDIF OUT Right, 2nd or 96kHz */
+#define EMU_DST_MDOCK_ADAT 0x0118 /* Microdock S/PDIF ADAT 8 channel out +8 to +f */
+
+#define EMU_DST_MANA_DAC_LEFT 0x0300 /* Headphone jack on 1010 cardbus? 44.1/48kHz only? */
+#define EMU_DST_MANA_DAC_RIGHT 0x0301 /* Headphone jack on 1010 cardbus? 44.1/48kHz only? */
/************************************************************************************************/
-/* EMU1010m HANA Sources */
+/* EMU1010 Audio Sources */
/************************************************************************************************/
-/* Hana, original 1010,1212,1820 using Alice2
- * Sources SRATEX = 1X rates: 44.1 kHz or 48 kHz
- * 0x00,0x00-0x1f: Silence
- * 0x01, 0x10-0x1f: 32 Elink channels from Audio Dock
- * 0x01, 0x00: Dock Mic A
- * 0x01, 0x04: Dock Mic B
- * 0x01, 0x08: Dock ADC 1 Left
- * 0x01, 0x0c: Dock ADC 1 Right
- * 0x01, 0x10: Dock ADC 2 Left
- * 0x01, 0x14: Dock ADC 2 Right
- * 0x01, 0x18: Dock ADC 3 Left
- * 0x01, 0x1c: Dock ADC 3 Right
- * 0x02, 0x00: Hana ADC Left
- * 0x02, 0x01: Hana ADC Right
+/* Hana, original 1010,1212m,1820[m] using Alice2
+ * 0x00, 0x00-0x1f: Silence
+ * 0x01, 0x00-0x1f: 32 EDI channels from Audio Dock
+ * 0x00: Dock Mic A
+ * 0x04: Dock Mic B
+ * 0x08: Dock ADC 1 Left
+ * 0x0c: Dock ADC 1 Right
+ * 0x10: Dock ADC 2 Left
+ * 0x14: Dock ADC 2 Right
+ * 0x18: Dock ADC 3 Left
+ * 0x1c: Dock ADC 3 Right
+ * 0x02, 0x00: Hamoa ADC Left
+ * 0x02, 0x01: Hamoa ADC Right
* 0x03, 0x00-0x0f: 16 inputs from Alice2 Emu32A output
* 0x03, 0x10-0x1f: 16 inputs from Alice2 Emu32B output
* 0x04, 0x00-0x07: Hana ADAT
* Hana2 never released, but used Tina
* Not needed.
*
- * Hana3, rev2 1010,1212,1616 using Tina
- * Sources SRATEX = 1X rates: 44.1 kHz or 48 kHz
- * 0x00,0x00-0x1f: Silence
- * 0x01, 0x10-0x1f: 32 Elink channels from Audio Dock
- * 0x01, 0x00: Dock Mic A
- * 0x01, 0x04: Dock Mic B
- * 0x01, 0x08: Dock ADC 1 Left
- * 0x01, 0x0c: Dock ADC 1 Right
- * 0x01, 0x10: Dock ADC 2 Left
- * 0x01, 0x12: Dock S/PDIF Left
- * 0x01, 0x14: Dock ADC 2 Right
- * 0x01, 0x16: Dock S/PDIF Right
- * 0x01, 0x18-0x1f: Dock ADAT 0-7
- * 0x01, 0x18: Dock ADC 3 Left
- * 0x01, 0x1c: Dock ADC 3 Right
- * 0x02, 0x00: Hanoa ADC Left
- * 0x02, 0x01: Hanoa ADC Right
+ * Hana3, rev2 1010,1212m,1616[m] using Tina
+ * 0x00, 0x00-0x1f: Silence
+ * 0x01, 0x00-0x1f: 32 EDI channels from Micro Dock
+ * 0x00: Dock Mic A
+ * 0x04: Dock Mic B
+ * 0x08: Dock ADC 1 Left
+ * 0x0c: Dock ADC 1 Right
+ * 0x10: Dock ADC 2 Left
+ * 0x12: Dock S/PDIF Left
+ * 0x14: Dock ADC 2 Right
+ * 0x16: Dock S/PDIF Right
+ * 0x18-0x1f: Dock ADAT 0-7
+ * 0x02, 0x00: Hamoa ADC Left
+ * 0x02, 0x01: Hamoa ADC Right
* 0x03, 0x00-0x0f: 16 inputs from Tina Emu32A output
* 0x03, 0x10-0x1f: 16 inputs from Tina Emu32B output
* 0x04, 0x00-0x07: Hana3 ADAT
* 0x06-0x07: Not used
*
* HanaLite, rev1 0404 using Alice2
- * Sources SRATEX = 1X rates: 44.1 kHz or 48 kHz
- * 0x00,0x00-0x1f: Silence
- * 0x01: Not used
- * 0x02, 0x00: ADC Left
- * 0x02, 0x01: ADC Right
- * 0x03, 0x00-0x0f: 16 inputs from Alice2 Emu32A output
- * 0x03, 0x10-0x1f: 16 inputs from Alice2 Emu32B output
- * 0x04: Not used
- * 0x05, 0x00: S/PDIF Left
- * 0x05, 0x01: S/PDIF Right
- * 0x06-0x07: Not used
- *
- * HanaLiteLite, rev2 0404 using Alice2
- * Sources SRATEX = 1X rates: 44.1 kHz or 48 kHz
- * 0x00,0x00-0x1f: Silence
+ * HanaLiteLite, rev2 0404 using Tina
+ * 0x00, 0x00-0x1f: Silence
* 0x01: Not used
* 0x02, 0x00: ADC Left
* 0x02, 0x01: ADC Right
- * 0x03, 0x00-0x0f: 16 inputs from Alice2 Emu32A output
- * 0x03, 0x10-0x1f: 16 inputs from Alice2 Emu32B output
+ * 0x03, 0x00-0x0f: 16 inputs from Alice2/Tina Emu32A output
+ * 0x03, 0x10-0x1f: 16 inputs from Alice2/Tina Emu32B output
* 0x04: Not used
* 0x05, 0x00: S/PDIF Left
* 0x05, 0x01: S/PDIF Right
* 0x06-0x07: Not used
*
* Mana, Cardbus 1616 using Tina2
- * Sources SRATEX = 1X rates: 44.1 kHz or 48 kHz
- * 0x00,0x00-0x1f: Silence
- * 0x01, 0x10-0x1f: 32 Elink channels from Audio Dock
- * 0x01, 0x00: Dock Mic A
- * 0x01, 0x04: Dock Mic B
- * 0x01, 0x08: Dock ADC 1 Left
- * 0x01, 0x0c: Dock ADC 1 Right
- * 0x01, 0x10: Dock ADC 2 Left
- * 0x01, 0x12: Dock S/PDIF Left
- * 0x01, 0x14: Dock ADC 2 Right
- * 0x01, 0x16: Dock S/PDIF Right
- * 0x01, 0x18-0x1f: Dock ADAT 0-7
- * 0x01, 0x18: Dock ADC 3 Left
- * 0x01, 0x1c: Dock ADC 3 Right
+ * 0x00, 0x00-0x1f: Silence
+ * 0x01, 0x00-0x1f: 32 EDI channels from Micro Dock
+ * (same as rev2 1010)
* 0x02: Not used
- * 0x03, 0x00-0x0f: 16 inputs from Tina Emu32A output
- * 0x03, 0x10-0x1f: 16 inputs from Tina Emu32B output
+ * 0x03, 0x00-0x0f: 16 inputs from Tina2 Emu32A output
+ * 0x03, 0x10-0x1f: 16 inputs from Tina2 Emu32B output
* 0x04-0x07: Not used
- *
*/
/* 32-bit sources of signal in the Hana FPGA. The sources are routed to
- * destinations using mixer control for each destination - see emumixer.c
- * Sources are either physical inputs of FPGA,
- * or outputs from Alice (audigy) - 16 x EMU_SRC_ALICE_EMU32A +
- * 16 x EMU_SRC_ALICE_EMU32B
+ * destinations using a mixer control for each destination - see emumixer.c.
+ * Sources are either physical inputs of Hana, or inputs from Alice2/Tina -
+ * 16 x EMU_SRC_ALICE_EMU32A + 16 x EMU_SRC_ALICE_EMU32B.
*/
#define EMU_SRC_SILENCE 0x0000 /* Silence */
#define EMU_SRC_DOCK_MIC_A1 0x0100 /* Audio Dock Mic A, 1st or 48kHz only */
#define EMU_SRC_HAMOA_ADC_RIGHT4 0x0207 /* Hamoa ADC Right, 4th or 192kHz */
#define EMU_SRC_ALICE_EMU32A 0x0300 /* Alice2 EMU32a 16 outputs. +0 to +0xf */
#define EMU_SRC_ALICE_EMU32B 0x0310 /* Alice2 EMU32b 16 outputs. +0 to +0xf */
+// In S/MUX mode, the samples of one channel are adjacent.
#define EMU_SRC_HANA_ADAT 0x0400 /* Hana ADAT 8 channel in +0 to +7 */
#define EMU_SRC_HANA_SPDIF_LEFT1 0x0500 /* Hana SPDIF Left, 1st or 48kHz only */
#define EMU_SRC_HANA_SPDIF_LEFT2 0x0502 /* Hana SPDIF Left, 2nd or 96kHz */
+#define EMU_SRC_HANA_SPDIF_LEFT3 0x0504 /* Hana SPDIF Left, 3rd or 192kHz */
+#define EMU_SRC_HANA_SPDIF_LEFT4 0x0506 /* Hana SPDIF Left, 4th or 192kHz */
#define EMU_SRC_HANA_SPDIF_RIGHT1 0x0501 /* Hana SPDIF Right, 1st or 48kHz only */
#define EMU_SRC_HANA_SPDIF_RIGHT2 0x0503 /* Hana SPDIF Right, 2nd or 96kHz */
+#define EMU_SRC_HANA_SPDIF_RIGHT3 0x0505 /* Hana SPDIF Right, 3rd or 192kHz */
+#define EMU_SRC_HANA_SPDIF_RIGHT4 0x0507 /* Hana SPDIF Right, 4th or 192kHz */
/* Additional inputs for 1616(M)/Microdock */
-/* Microdock S/PDIF Left, 1st or 48kHz only */
-#define EMU_SRC_MDOCK_SPDIF_LEFT1 0x0112
-/* Microdock S/PDIF Left, 2nd or 96kHz */
-#define EMU_SRC_MDOCK_SPDIF_LEFT2 0x0113
-/* Microdock S/PDIF Right, 1st or 48kHz only */
-#define EMU_SRC_MDOCK_SPDIF_RIGHT1 0x0116
-/* Microdock S/PDIF Right, 2nd or 96kHz */
-#define EMU_SRC_MDOCK_SPDIF_RIGHT2 0x0117
-/* Microdock ADAT 8 channel in +8 to +f */
-#define EMU_SRC_MDOCK_ADAT 0x0118
+
+#define EMU_SRC_MDOCK_SPDIF_LEFT1 0x0112 /* Microdock S/PDIF Left, 1st or 48kHz only */
+#define EMU_SRC_MDOCK_SPDIF_LEFT2 0x0113 /* Microdock S/PDIF Left, 2nd or 96kHz */
+#define EMU_SRC_MDOCK_SPDIF_RIGHT1 0x0116 /* Microdock S/PDIF Right, 1st or 48kHz only */
+#define EMU_SRC_MDOCK_SPDIF_RIGHT2 0x0117 /* Microdock S/PDIF Right, 2nd or 96kHz */
+#define EMU_SRC_MDOCK_ADAT 0x0118 /* Microdock ADAT 8 channel in +8 to +f */
/* 0x600 and 0x700 no used */
struct snd_emu10k1;
struct snd_emu10k1_voice {
- struct snd_emu10k1 *emu;
int number;
unsigned int use: 1,
pcm: 1,
};
struct snd_emu10k1_fx8010 {
- unsigned short fxbus_mask; /* used FX buses (bitmask) */
- unsigned short extin_mask; /* used external inputs (bitmask) */
- unsigned short extout_mask; /* used external outputs (bitmask) */
- unsigned short pad1;
+ unsigned short extin_mask; /* used external inputs (bitmask); not used for Audigy */
+ unsigned short extout_mask; /* used external outputs (bitmask); not used for Audigy */
unsigned int itram_size; /* internal TRAM size in samples */
struct snd_dma_buffer etram_pages; /* external TRAM pages and size */
unsigned int dbg; /* FX debugger register */
EMU_MODEL_EMU0404,
};
+// Chip-o-logy:
+// - All SB Live! cards use EMU10K1 chips
+// - All SB Audigy cards use CA* chips, termed "emu10k2" by the driver
+// - Original Audigy uses CA0100 "Alice"
+// - Audigy 2 uses CA0102/CA10200 "Alice2"
+// - Has an interface for CA0151 (P16V) "Alice3"
+// - Audigy 2 Value uses CA0108/CA10300 "Tina"
+// - Approximately a CA0102 with an on-chip CA0151 (P17V)
+// - Audigy 2 ZS NB uses CA0109 "Tina2"
+// - Cardbus version of CA0108
struct snd_emu_chip_details {
u32 vendor;
u32 device;
u32 subsystem;
unsigned char revision;
unsigned char emu10k1_chip; /* Original SB Live. Not SB Live 24bit. */
+ /* Redundant with emu10k2_chip being unset. */
unsigned char emu10k2_chip; /* Audigy 1 or Audigy 2. */
unsigned char ca0102_chip; /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */
+ /* Redundant with ca0108_chip being unset. */
unsigned char ca0108_chip; /* Audigy 2 Value */
unsigned char ca_cardbus_chip; /* Audigy 2 ZS Notebook */
unsigned char ca0151_chip; /* P16V */
unsigned char ac97_chip; /* Has an AC97 chip: 1 = mandatory, 2 = optional */
unsigned char ecard; /* APS EEPROM */
unsigned char emu_model; /* EMU model type */
- unsigned char spi_dac; /* SPI interface for DAC */
- unsigned char i2c_adc; /* I2C interface for ADC */
+ unsigned char spi_dac; /* SPI interface for DAC; requires ca0108_chip */
+ unsigned char i2c_adc; /* I2C interface for ADC; requires ca0108_chip */
unsigned char adc_1361t; /* Use Philips 1361T ADC */
unsigned char invert_shared_spdif; /* analog/digital switch inverted */
const char *driver;
unsigned int revision; /* chip revision */
unsigned int serial; /* serial number */
unsigned short model; /* subsystem id */
- unsigned int card_type; /* EMU10K1_CARD_* */
unsigned int ecard_ctrl; /* ecard control bits */
unsigned int address_mode; /* address mode */
unsigned long dma_mask; /* PCI DMA mask */
void *synth;
int (*get_synth_voice)(struct snd_emu10k1 *emu);
- spinlock_t reg_lock;
- spinlock_t emu_lock;
- spinlock_t voice_lock;
+ spinlock_t reg_lock; // high-level driver lock
+ spinlock_t emu_lock; // low-level i/o lock
+ spinlock_t voice_lock; // voice allocator lock
spinlock_t spi_lock; /* serialises access to spi port */
spinlock_t i2c_lock; /* serialises access to i2c port */
struct snd_emu10k1_voice voices[NUM_G];
- struct snd_emu10k1_voice p16v_voices[4];
- struct snd_emu10k1_voice p16v_capture_voice;
int p16v_device_offset;
u32 p16v_capture_source;
u32 p16v_capture_channel;
void (*capture_efx_interrupt)(struct snd_emu10k1 *emu, unsigned int status);
void (*spdif_interrupt)(struct snd_emu10k1 *emu, unsigned int status);
void (*dsp_interrupt)(struct snd_emu10k1 *emu);
+ void (*p16v_interrupt)(struct snd_emu10k1 *emu);
struct snd_pcm_substream *pcm_capture_substream;
struct snd_pcm_substream *pcm_capture_mic_substream;
struct snd_pcm_substream *pcm_capture_efx_substream;
- struct snd_pcm_substream *pcm_playback_efx_substream;
struct snd_timer *timer;
void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data);
int snd_emu10k1_spi_write(struct snd_emu10k1 * emu, unsigned int data);
int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu, u32 reg, u32 value);
-int snd_emu1010_fpga_write(struct snd_emu10k1 * emu, u32 reg, u32 value);
-int snd_emu1010_fpga_read(struct snd_emu10k1 * emu, u32 reg, u32 *value);
-int snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 * emu, u32 dst, u32 src);
+void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value);
+void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value);
+void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src);
unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc);
void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb);
void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb);
--- /dev/null
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2022-2023 Intel Corporation. All rights reserved.
+ */
+
+struct hdac_bus;
+struct hdac_ext_link;
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
+
+int hda_bus_ml_init(struct hdac_bus *bus);
+void hda_bus_ml_free(struct hdac_bus *bus);
+
+int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid);
+void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable);
+bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid);
+
+int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd);
+int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd);
+
+int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid);
+int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus);
+
+void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink);
+void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink);
+
+int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid);
+int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus);
+
+bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid);
+bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus);
+
+int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink);
+int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink);
+
+int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink);
+int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink);
+
+int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink);
+int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink);
+
+int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num);
+
+void hda_bus_ml_put_all(struct hdac_bus *bus);
+void hda_bus_ml_reset_losidv(struct hdac_bus *bus);
+int hda_bus_ml_resume(struct hdac_bus *bus);
+int hda_bus_ml_suspend(struct hdac_bus *bus);
+
+struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus);
+struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus);
+
+struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid);
+
+int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable);
+
+#else
+
+static inline int
+hda_bus_ml_init(struct hdac_bus *bus) { return 0; }
+
+static inline void hda_bus_ml_free(struct hdac_bus *bus) { }
+
+static inline int
+hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid) { return 0; }
+
+static inline void
+hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable) { }
+
+static inline bool
+hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid) { return false; }
+
+static inline int
+hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
+{
+ return 0;
+}
+
+static inline int
+hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
+{
+ return 0;
+}
+
+static inline int
+hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
+{
+ return 0;
+}
+
+static inline int
+hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus) { return 0; }
+
+static inline void
+hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink) { }
+
+static inline void
+hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink) { }
+
+static inline int
+hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid) { return 0; }
+
+static inline int
+hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus) { return 0; }
+
+static inline bool
+hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid) { return false; }
+
+static inline bool
+hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus) { return false; }
+
+static inline int
+hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return 0;
+}
+
+static inline int
+hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return 0;
+}
+
+static inline int
+hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return 0;
+}
+
+static inline int
+hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return 0;
+}
+
+static inline int
+hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink) { return 0; }
+
+static inline int
+hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink) { return 0; }
+
+static inline int
+hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num) { return 0; }
+
+static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { }
+static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { }
+static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; }
+static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; }
+
+static inline struct hdac_ext_link *
+hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; }
+
+static inline struct hdac_ext_link *
+hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus) { return NULL; }
+
+static inline struct mutex *
+hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid) { return NULL; }
+
+static inline int
+hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
+{
+ return 0;
+}
+#endif /* CONFIG_SND_SOC_SOF_HDA */
#define AZX_ML_BASE 0x40
#define AZX_ML_INTERVAL 0x40
+/* HDaudio registers valid for HDaudio and HDaudio extended links */
#define AZX_REG_ML_LCAP 0x00
-#define AZX_REG_ML_LCTL 0x04
+#define AZX_ML_HDA_LCAP_ALT BIT(28)
+#define AZX_ML_HDA_LCAP_ALT_HDA 0x0
+#define AZX_ML_HDA_LCAP_ALT_HDA_EXT 0x1
+
+#define AZX_ML_HDA_LCAP_INTC BIT(27) /* only used if ALT == 1 */
+#define AZX_ML_HDA_LCAP_OFLS BIT(26) /* only used if ALT == 1 */
+#define AZX_ML_HDA_LCAP_LSS BIT(23) /* only used if ALT == 1 */
+#define AZX_ML_HDA_LCAP_SLCOUNT GENMASK(22, 20) /* only used if ALT == 1 */
+
+#define AZX_REG_ML_LCTL 0x04
+#define AZX_ML_LCTL_INTSTS BIT(31) /* only used if ALT == 1 */
#define AZX_ML_LCTL_CPA BIT(23)
#define AZX_ML_LCTL_CPA_SHIFT 23
#define AZX_ML_LCTL_SPA BIT(16)
#define AZX_ML_LCTL_SPA_SHIFT 16
-#define AZX_ML_LCTL_SCF GENMASK(3, 0)
+#define AZX_ML_LCTL_INTEN BIT(5) /* only used if ALT == 1 */
+#define AZX_ML_LCTL_OFLEN BIT(4) /* only used if ALT == 1 */
+#define AZX_ML_LCTL_SCF GENMASK(3, 0) /* only used if ALT == 0 */
#define AZX_REG_ML_LOSIDV 0x08
#define AZX_ML_LOSIDV_STREAM_MASK 0xFFFE
#define AZX_REG_ML_LSDIID 0x0C
+#define AZX_REG_ML_LSDIID_OFFSET(x) (0x0C + (x) * 0x02) /* only used if ALT == 1 */
+
+/* HDaudio registers only valid if LCAP.ALT == 0 */
#define AZX_REG_ML_LPSOO 0x10
#define AZX_REG_ML_LPSIO 0x12
#define AZX_REG_ML_LWALFC 0x18
#define AZX_REG_ML_LOUTPAY 0x20
#define AZX_REG_ML_LINPAY 0x30
+/* HDaudio Extended link registers only valid if LCAP.ALT == 1 */
+#define AZX_REG_ML_LSYNC 0x1C
+
+#define AZX_REG_ML_LSYNC_CMDSYNC BIT(24)
+#define AZX_REG_ML_LSYNC_CMDSYNC_SHIFT 24
+#define AZX_REG_ML_LSYNC_SYNCGO BIT(23)
+#define AZX_REG_ML_LSYNC_SYNCPU BIT(20)
+#define AZX_REG_ML_LSYNC_SYNCPRD GENMASK(19, 0)
+
+#define AZX_REG_ML_LEPTR 0x20
+
+#define AZX_REG_ML_LEPTR_ID GENMASK(31, 24)
+#define AZX_REG_ML_LEPTR_ID_SHIFT 24
+#define AZX_REG_ML_LEPTR_ID_SDW 0x00
+#define AZX_REG_ML_LEPTR_ID_INTEL_SSP 0xC0
+#define AZX_REG_ML_LEPTR_ID_INTEL_DMIC 0xC1
+#define AZX_REG_ML_LEPTR_ID_INTEL_UAOL 0xC2
+#define AZX_REG_ML_LEPTR_VER GENMASK(23, 20)
+#define AZX_REG_ML_LEPTR_PTR GENMASK(19, 0)
+
/* registers for DMA Resume Capability Structure */
#define AZX_DRSM_CAP_ID 0x5
#define AZX_REG_DRSM_CTL 0x4
if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
diff += runtime->boundary;
if (diff < 0)
- return -EINVAL;
+ return -EPIPE;
rec->sw_ready += (int)frames_to_bytes(runtime, diff);
rec->appl_ptr = appl_ptr;
}
struct snd_pcm_indirect *rec, unsigned int ptr)
{
int bytes = ptr - rec->hw_io;
+ int err;
+
if (bytes < 0)
bytes += rec->hw_buffer_size;
rec->hw_io = ptr;
rec->sw_io += bytes;
if (rec->sw_io >= rec->sw_buffer_size)
rec->sw_io -= rec->sw_buffer_size;
- if (substream->ops->ack)
- substream->ops->ack(substream);
+ if (substream->ops->ack) {
+ err = substream->ops->ack(substream);
+ if (err == -EPIPE)
+ return SNDRV_PCM_POS_XRUN;
+ }
return bytes_to_frames(substream->runtime, rec->sw_io);
}
if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
diff += runtime->boundary;
if (diff < 0)
- return -EINVAL;
+ return -EPIPE;
rec->sw_ready -= frames_to_bytes(runtime, diff);
rec->appl_ptr = appl_ptr;
}
{
int qsize;
int bytes = ptr - rec->hw_io;
+ int err;
+
if (bytes < 0)
bytes += rec->hw_buffer_size;
rec->hw_io = ptr;
rec->sw_io += bytes;
if (rec->sw_io >= rec->sw_buffer_size)
rec->sw_io -= rec->sw_buffer_size;
- if (substream->ops->ack)
- substream->ops->ack(substream);
+ if (substream->ops->ack) {
+ err = substream->ops->ack(substream);
+ if (err == -EPIPE)
+ return SNDRV_PCM_POS_XRUN;
+ }
return bytes_to_frames(substream->runtime, rec->sw_io);
}
unsigned int rate_den;
unsigned int no_period_wakeup: 1;
- /* -- SW params -- */
- int tstamp_mode; /* mmap timestamp is updated */
+ /* -- SW params; see struct snd_pcm_sw_params for comments -- */
+ int tstamp_mode;
unsigned int period_step;
snd_pcm_uframes_t start_threshold;
snd_pcm_uframes_t stop_threshold;
- snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
- noise is nearest than this */
- snd_pcm_uframes_t silence_size; /* Silence filling size */
- snd_pcm_uframes_t boundary; /* pointers wrap point */
+ snd_pcm_uframes_t silence_threshold;
+ snd_pcm_uframes_t silence_size;
+ snd_pcm_uframes_t boundary;
+ /* internal data of auto-silencer */
snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
- snd_pcm_uframes_t silence_filled; /* size filled with silence */
+ snd_pcm_uframes_t silence_filled; /* already filled part of silence area */
union snd_pcm_sync_id sync; /* hardware synchronization ID */
#define __SOC_CARD_H
enum snd_soc_card_subclass {
- SND_SOC_CARD_CLASS_INIT = 0,
+ SND_SOC_CARD_CLASS_ROOT = 0,
SND_SOC_CARD_CLASS_RUNTIME = 1,
};
+static inline void snd_soc_card_mutex_lock_root(struct snd_soc_card *card)
+{
+ mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_ROOT);
+}
+
+static inline void snd_soc_card_mutex_lock(struct snd_soc_card *card)
+{
+ mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
+}
+
+static inline void snd_soc_card_mutex_unlock(struct snd_soc_card *card)
+{
+ mutex_unlock(&card->mutex);
+}
+
struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card,
const char *name);
int snd_soc_card_jack_new(struct snd_soc_card *card, const char *id, int type,
SND_SOC_DAPM_TYPE_COUNT
};
-enum snd_soc_dapm_subclass {
- SND_SOC_DAPM_CLASS_INIT = 0,
- SND_SOC_DAPM_CLASS_RUNTIME = 1,
-};
-
/*
* DAPM audio route definition.
*
struct list_head fe_clients;
int users;
- struct snd_pcm_runtime *runtime;
struct snd_pcm_hw_params hw_params;
/* state and update */
int id; /* optional ID for machine driver link identification */
- const struct snd_soc_pcm_stream *params;
- unsigned int num_params;
+ /*
+ * for Codec2Codec
+ */
+ const struct snd_soc_pcm_stream *c2c_params;
+ unsigned int num_c2c_params;
unsigned int dai_fmt; /* format to set on init */
struct snd_soc_dai_link *dai_link;
struct snd_pcm_ops ops;
- unsigned int params_select; /* currently selected param for dai link */
+ unsigned int c2c_params_select; /* currently selected c2c_param for dai link */
/* Dynamic PCM BE runtime data */
struct snd_soc_dpcm_runtime dpcm[SNDRV_PCM_STREAM_LAST + 1];
struct snd_soc_dai_link *dai_link);
void snd_soc_of_put_dai_link_cpus(struct snd_soc_dai_link *dai_link);
-int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
- struct snd_soc_dai_link *dai_link);
+int snd_soc_add_pcm_runtimes(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link,
+ int num_dai_link);
void snd_soc_remove_pcm_runtime(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd);
extern const struct dev_pm_ops snd_soc_pm_ops;
-/* Helper functions */
-static inline void snd_soc_dapm_mutex_lock(struct snd_soc_dapm_context *dapm)
+/*
+ * DAPM helper functions
+ */
+enum snd_soc_dapm_subclass {
+ SND_SOC_DAPM_CLASS_ROOT = 0,
+ SND_SOC_DAPM_CLASS_RUNTIME = 1,
+};
+
+static inline void _snd_soc_dapm_mutex_lock_root_c(struct snd_soc_card *card)
+{
+ mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_ROOT);
+}
+
+static inline void _snd_soc_dapm_mutex_lock_c(struct snd_soc_card *card)
+{
+ mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+}
+
+static inline void _snd_soc_dapm_mutex_unlock_c(struct snd_soc_card *card)
+{
+ mutex_unlock(&card->dapm_mutex);
+}
+
+static inline void _snd_soc_dapm_mutex_assert_held_c(struct snd_soc_card *card)
+{
+ lockdep_assert_held(&card->dapm_mutex);
+}
+
+static inline void _snd_soc_dapm_mutex_lock_root_d(struct snd_soc_dapm_context *dapm)
+{
+ _snd_soc_dapm_mutex_lock_root_c(dapm->card);
+}
+
+static inline void _snd_soc_dapm_mutex_lock_d(struct snd_soc_dapm_context *dapm)
+{
+ _snd_soc_dapm_mutex_lock_c(dapm->card);
+}
+
+static inline void _snd_soc_dapm_mutex_unlock_d(struct snd_soc_dapm_context *dapm)
{
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ _snd_soc_dapm_mutex_unlock_c(dapm->card);
}
-static inline void snd_soc_dapm_mutex_unlock(struct snd_soc_dapm_context *dapm)
+static inline void _snd_soc_dapm_mutex_assert_held_d(struct snd_soc_dapm_context *dapm)
{
- mutex_unlock(&dapm->card->dapm_mutex);
+ _snd_soc_dapm_mutex_assert_held_c(dapm->card);
}
+#define snd_soc_dapm_mutex_lock_root(x) _Generic((x), \
+ struct snd_soc_card * : _snd_soc_dapm_mutex_lock_root_c, \
+ struct snd_soc_dapm_context * : _snd_soc_dapm_mutex_lock_root_d)(x)
+#define snd_soc_dapm_mutex_lock(x) _Generic((x), \
+ struct snd_soc_card * : _snd_soc_dapm_mutex_lock_c, \
+ struct snd_soc_dapm_context * : _snd_soc_dapm_mutex_lock_d)(x)
+#define snd_soc_dapm_mutex_unlock(x) _Generic((x), \
+ struct snd_soc_card * : _snd_soc_dapm_mutex_unlock_c, \
+ struct snd_soc_dapm_context * : _snd_soc_dapm_mutex_unlock_d)(x)
+#define snd_soc_dapm_mutex_assert_held(x) _Generic((x), \
+ struct snd_soc_card * : _snd_soc_dapm_mutex_assert_held_c, \
+ struct snd_soc_dapm_context * : _snd_soc_dapm_mutex_assert_held_d)(x)
+
+/*
+ * PCM helper functions
+ */
+static inline void _snd_soc_dpcm_mutex_lock_c(struct snd_soc_card *card)
+{
+ mutex_lock_nested(&card->pcm_mutex, card->pcm_subclass);
+}
+
+static inline void _snd_soc_dpcm_mutex_unlock_c(struct snd_soc_card *card)
+{
+ mutex_unlock(&card->pcm_mutex);
+}
+
+static inline void _snd_soc_dpcm_mutex_assert_held_c(struct snd_soc_card *card)
+{
+ lockdep_assert_held(&card->pcm_mutex);
+}
+
+static inline void _snd_soc_dpcm_mutex_lock_r(struct snd_soc_pcm_runtime *rtd)
+{
+ _snd_soc_dpcm_mutex_lock_c(rtd->card);
+}
+
+static inline void _snd_soc_dpcm_mutex_unlock_r(struct snd_soc_pcm_runtime *rtd)
+{
+ _snd_soc_dpcm_mutex_unlock_c(rtd->card);
+}
+
+static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime *rtd)
+{
+ _snd_soc_dpcm_mutex_assert_held_c(rtd->card);
+}
+
+#define snd_soc_dpcm_mutex_lock(x) _Generic((x), \
+ struct snd_soc_card * : _snd_soc_dpcm_mutex_lock_c, \
+ struct snd_soc_pcm_runtime * : _snd_soc_dpcm_mutex_lock_r)(x)
+
+#define snd_soc_dpcm_mutex_unlock(x) _Generic((x), \
+ struct snd_soc_card * : _snd_soc_dpcm_mutex_unlock_c, \
+ struct snd_soc_pcm_runtime * : _snd_soc_dpcm_mutex_unlock_r)(x)
+
+#define snd_soc_dpcm_mutex_assert_held(x) _Generic((x), \
+ struct snd_soc_card * : _snd_soc_dpcm_mutex_assert_held_c, \
+ struct snd_soc_pcm_runtime * : _snd_soc_dpcm_mutex_assert_held_r)(x)
+
#include <sound/soc-component.h>
#include <sound/soc-card.h>
#include <sound/soc-jack.h>
/**
* enum sof_fw_state - DSP firmware state definitions
* @SOF_FW_BOOT_NOT_STARTED: firmware boot is not yet started
+ * @SOF_DSPLESS_MODE: DSP is not used
* @SOF_FW_BOOT_PREPARE: preparing for boot (firmware loading for exaqmple)
* @SOF_FW_BOOT_IN_PROGRESS: firmware boot is in progress
* @SOF_FW_BOOT_FAILED: firmware boot failed
*/
enum sof_fw_state {
SOF_FW_BOOT_NOT_STARTED = 0,
+ SOF_DSPLESS_MODE,
SOF_FW_BOOT_PREPARE,
SOF_FW_BOOT_IN_PROGRESS,
SOF_FW_BOOT_FAILED,
unsigned int ipc_supported_mask;
enum sof_ipc_type ipc_default;
+ /* The platform supports DSPless mode */
+ bool dspless_mode_supported;
+
/* defaults paths for firmware, library and topology files */
const char *default_fw_path[SOF_IPC_TYPE_COUNT];
const char *default_lib_path[SOF_IPC_TYPE_COUNT];
#define SOF_IPC4_GLB_PIPE_EXT_LP_MASK BIT(0)
#define SOF_IPC4_GLB_PIPE_EXT_LP(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_LP_SHIFT)
+#define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT 20
+#define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_MASK GENMASK(23, 20)
+#define SOF_IPC4_GLB_PIPE_EXT_CORE_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT)
+
/* pipeline set state ipc msg */
#define SOF_IPC4_GLB_PIPE_STATE_ID_SHIFT 16
#define SOF_IPC4_GLB_PIPE_STATE_ID_MASK GENMASK(23, 16)
#define SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID_SHIFT 16
#define SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(x) ((x) << SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID_SHIFT)
+/* chain dma ipc message */
+#define SOF_IPC4_GLB_CHAIN_DMA_HOST_ID_SHIFT 0
+#define SOF_IPC4_GLB_CHAIN_DMA_HOST_ID_MASK GENMASK(4, 0)
+#define SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(x) (((x) << SOF_IPC4_GLB_CHAIN_DMA_HOST_ID_SHIFT) & \
+ SOF_IPC4_GLB_CHAIN_DMA_HOST_ID_MASK)
+
+#define SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_SHIFT 8
+#define SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK GENMASK(12, 8)
+#define SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(x) (((x) << SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_SHIFT) & \
+ SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK)
+
+#define SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_SHIFT 16
+#define SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK BIT(16)
+#define SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE(x) (((x) & 1) << SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_SHIFT)
+
+#define SOF_IPC4_GLB_CHAIN_DMA_ENABLE_SHIFT 17
+#define SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK BIT(17)
+#define SOF_IPC4_GLB_CHAIN_DMA_ENABLE(x) (((x) & 1) << SOF_IPC4_GLB_CHAIN_DMA_ENABLE_SHIFT)
+
+#define SOF_IPC4_GLB_CHAIN_DMA_SCS_SHIFT 18
+#define SOF_IPC4_GLB_CHAIN_DMA_SCS_MASK BIT(18)
+#define SOF_IPC4_GLB_CHAIN_DMA_SCS(x) (((x) & 1) << SOF_IPC4_GLB_CHAIN_DMA_SCS_SHIFT)
+
+#define SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE_SHIFT 0
+#define SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE_MASK GENMASK(24, 0)
+#define SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(x) (((x) << \
+ SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE_SHIFT) & \
+ SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE_MASK)
+
enum sof_ipc4_channel_config {
/* one channel only. */
SOF_IPC4_CHANNEL_CONFIG_MONO,
snd_pcm_uframes_t avail_min; /* min avail frames for wakeup */
snd_pcm_uframes_t xfer_align; /* obsolete: xfer size need to be a multiple */
snd_pcm_uframes_t start_threshold; /* min hw_avail frames for automatic start */
- snd_pcm_uframes_t stop_threshold; /* min avail frames for automatic stop */
- snd_pcm_uframes_t silence_threshold; /* min distance from noise for silence filling */
- snd_pcm_uframes_t silence_size; /* silence block size */
+ /*
+ * The following two thresholds alleviate playback buffer underruns; when
+ * hw_avail drops below the threshold, the respective action is triggered:
+ */
+ snd_pcm_uframes_t stop_threshold; /* - stop playback */
+ snd_pcm_uframes_t silence_threshold; /* - pre-fill buffer with silence */
+ snd_pcm_uframes_t silence_size; /* max size of silence pre-fill; when >= boundary,
+ * fill played area with silence immediately */
snd_pcm_uframes_t boundary; /* pointers wrap point */
unsigned int proto; /* protocol version */
unsigned int tstamp_type; /* timestamp type (req. proto >= 2.0.12) */
struct __snd_pcm_mmap_control64 {
__pad_before_uframe __pad1;
snd_pcm_uframes_t appl_ptr; /* RW: appl ptr (0...boundary-1) */
- __pad_before_uframe __pad2;
+ __pad_before_uframe __pad2; // This should be __pad_after_uframe, but binary
+ // backwards compatibility constraints prevent a fix.
__pad_before_uframe __pad3;
snd_pcm_uframes_t avail_min; /* RW: min available frames for wakeup */
* ---- FX8010 ----
*/
-#define EMU10K1_CARD_CREATIVE 0x00000000
-#define EMU10K1_CARD_EMUAPS 0x00000001
-
#define EMU10K1_FX8010_PCM_COUNT 8
/*
#define iINTERP 0x0e /* R = A + (X * (Y - A) >> 31) ; saturation */
#define iSKIP 0x0f /* R = A (cc_reg), X (count), Y (cc_test) */
+#define LOWORD_OPX_MASK 0x000ffc00 /* Instruction operand X */
+#define LOWORD_OPY_MASK 0x000003ff /* Instruction operand Y */
+#define HIWORD_OPCODE_MASK 0x00f00000 /* Instruction opcode */
+#define HIWORD_RESULT_MASK 0x000ffc00 /* Instruction result */
+#define HIWORD_OPA_MASK 0x000003ff /* Instruction operand A */
+
+/* Audigy Soundcards have a different instruction format */
+#define A_LOWORD_OPX_MASK 0x007ff000
+#define A_LOWORD_OPY_MASK 0x000007ff
+#define A_HIWORD_OPCODE_MASK 0x0f000000
+#define A_HIWORD_RESULT_MASK 0x007ff000
+#define A_HIWORD_OPA_MASK 0x000007ff
+
/* GPRs */
#define FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x0f */
#define EXTIN(x) (0x10 + (x)) /* x = 0x00 - 0x0f */
#define FXBUS2(x) (0x30 + (x)) /* x = 0x00 - 0x0f copies of fx buses for capture -> FXWC high 16 bits */
/* NB: 0x31 and 0x32 are shared with Center/LFE on SB live 5.1 */
+#define A_FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x3f FX buses */
+#define A_EXTIN(x) (0x40 + (x)) /* x = 0x00 - 0x0f physical ins */
+#define A_P16VIN(x) (0x50 + (x)) /* x = 0x00 - 0x0f p16v ins (A2 only) "EMU32 inputs" */
+#define A_EXTOUT(x) (0x60 + (x)) /* x = 0x00 - 0x1f physical outs -> A_FXWC1 0x79-7f unknown */
+#define A_FXBUS2(x) (0x80 + (x)) /* x = 0x00 - 0x1f extra outs used for EFX capture -> A_FXWC2 */
+#define A_EMU32OUTH(x) (0xa0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_10 - _1F" */
+#define A_EMU32OUTL(x) (0xb0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_01 - _0F" */
+#define A3_EMU32IN(x) (0x160 + (x)) /* x = 0x00 - 0x1f "EMU32_IN_00 - _1F" - Only when .device = 0x0008 */
+#define A3_EMU32OUT(x) (0x1E0 + (x)) /* x = 0x00 - 0x1f "EMU32_OUT_00 - _1F" - Only when .device = 0x0008 */
+
#define C_00000000 0x40
#define C_00000001 0x41
#define C_00000002 0x42
#define GPR_NOISE1 0x59 /* noise source */
#define GPR_IRQ 0x5a /* IRQ register */
#define GPR_DBAC 0x5b /* TRAM Delay Base Address Counter */
+
+/* Audigy constants */
+#define A_C_00000000 0xc0
+#define A_C_00000001 0xc1
+#define A_C_00000002 0xc2
+#define A_C_00000003 0xc3
+#define A_C_00000004 0xc4
+#define A_C_00000008 0xc5
+#define A_C_00000010 0xc6
+#define A_C_00000020 0xc7
+#define A_C_00000100 0xc8
+#define A_C_00010000 0xc9
+#define A_C_00000800 0xca
+#define A_C_10000000 0xcb
+#define A_C_20000000 0xcc
+#define A_C_40000000 0xcd
+#define A_C_80000000 0xce
+#define A_C_7fffffff 0xcf
+#define A_C_ffffffff 0xd0
+#define A_C_fffffffe 0xd1
+#define A_C_c0000000 0xd2
+#define A_C_4f1bbcdc 0xd3
+#define A_C_5a7ef9db 0xd4
+#define A_C_00100000 0xd5
+#define A_GPR_ACCU 0xd6 /* ACCUM, accumulator */
+#define A_GPR_COND 0xd7 /* CCR, condition register */
+#define A_GPR_NOISE0 0xd8 /* noise source */
+#define A_GPR_NOISE1 0xd9 /* noise source */
+#define A_GPR_IRQ 0xda /* IRQ register */
+#define A_GPR_DBAC 0xdb /* TRAM Delay Base Address Counter - internal */
+#define A_GPR_DBACE 0xde /* TRAM Delay Base Address Counter - external */
+
+/* Each FX general purpose register is 32 bits in length, all bits are used */
+#define FXGPREGBASE 0x100 /* FX general purpose registers base */
+#define A_FXGPREGBASE 0x400 /* Audigy GPRs, 0x400 to 0x5ff */
+
+#define A_TANKMEMCTLREGBASE 0x100 /* Tank memory control registers base - only for Audigy */
+#define A_TANKMEMCTLREG_MASK 0x1f /* only 5 bits used - only for Audigy */
+
+/* Tank audio data is logarithmically compressed down to 16 bits before writing to TRAM and is */
+/* decompressed back to 20 bits on a read. There are a total of 160 locations, the last 32 */
+/* locations are for external TRAM. */
+#define TANKMEMDATAREGBASE 0x200 /* Tank memory data registers base */
+#define TANKMEMDATAREG_MASK 0x000fffff /* 20 bit tank audio data field */
+
+/* Combined address field and memory opcode or flag field. 160 locations, last 32 are external */
+#define TANKMEMADDRREGBASE 0x300 /* Tank memory address registers base */
+#define TANKMEMADDRREG_ADDR_MASK 0x000fffff /* 20 bit tank address field */
+#define TANKMEMADDRREG_CLEAR 0x00800000 /* Clear tank memory */
+#define TANKMEMADDRREG_ALIGN 0x00400000 /* Align read or write relative to tank access */
+#define TANKMEMADDRREG_WRITE 0x00200000 /* Write to tank memory */
+#define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */
+
#define GPR(x) (FXGPREGBASE + (x)) /* free GPRs: x = 0x00 - 0xff */
#define ITRAM_DATA(x) (TANKMEMDATAREGBASE + 0x00 + (x)) /* x = 0x00 - 0x7f */
#define ETRAM_DATA(x) (TANKMEMDATAREGBASE + 0x80 + (x)) /* x = 0x00 - 0x1f */
#define ITRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x00 + (x)) /* x = 0x00 - 0x7f */
#define ETRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x80 + (x)) /* x = 0x00 - 0x1f */
+#define A_GPR(x) (A_FXGPREGBASE + (x))
#define A_ITRAM_DATA(x) (TANKMEMDATAREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */
#define A_ETRAM_DATA(x) (TANKMEMDATAREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */
#define A_ITRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */
#define A_ITRAM_CTL(x) (A_TANKMEMCTLREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */
#define A_ETRAM_CTL(x) (A_TANKMEMCTLREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */
-#define A_FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x3f FX buses */
-#define A_EXTIN(x) (0x40 + (x)) /* x = 0x00 - 0x0f physical ins */
-#define A_P16VIN(x) (0x50 + (x)) /* x = 0x00 - 0x0f p16v ins (A2 only) "EMU32 inputs" */
-#define A_EXTOUT(x) (0x60 + (x)) /* x = 0x00 - 0x1f physical outs -> A_FXWC1 0x79-7f unknown */
-#define A_FXBUS2(x) (0x80 + (x)) /* x = 0x00 - 0x1f extra outs used for EFX capture -> A_FXWC2 */
-#define A_EMU32OUTH(x) (0xa0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_10 - _1F" - ??? */
-#define A_EMU32OUTL(x) (0xb0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_1 - _F" - ??? */
-#define A3_EMU32IN(x) (0x160 + (x)) /* x = 0x00 - 0x3f "EMU32_IN_00 - _3F" - Only when .device = 0x0008 */
-#define A3_EMU32OUT(x) (0x1E0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_00 - _3F" - Only when .device = 0x0008 */
-#define A_GPR(x) (A_FXGPREGBASE + (x))
-
/* cc_reg constants */
#define CC_REG_NORMALIZED C_00000001
#define CC_REG_BORROW C_00000002
#define CC_REG_SATURATE C_00000010
#define CC_REG_NONZERO C_00000100
+#define A_CC_REG_NORMALIZED A_C_00000001
+#define A_CC_REG_BORROW A_C_00000002
+#define A_CC_REG_MINUS A_C_00000004
+#define A_CC_REG_ZERO A_C_00000008
+#define A_CC_REG_SATURATE A_C_00000010
+#define A_CC_REG_NONZERO A_C_00000100
+
/* FX buses */
+// These are arbitrary mappings; our DSP code simply expects
+// the config files to route the channels this way.
+// The numbers are documented in {audigy,sb-live}-mixer.rst.
#define FXBUS_PCM_LEFT 0x00
#define FXBUS_PCM_RIGHT 0x01
#define FXBUS_PCM_LEFT_REAR 0x02
#define A_EXTOUT_ADC_CAP_R 0x17 /* right */
#define A_EXTOUT_MIC_CAP 0x18 /* Mic capture buffer */
-/* Audigy constants */
-#define A_C_00000000 0xc0
-#define A_C_00000001 0xc1
-#define A_C_00000002 0xc2
-#define A_C_00000003 0xc3
-#define A_C_00000004 0xc4
-#define A_C_00000008 0xc5
-#define A_C_00000010 0xc6
-#define A_C_00000020 0xc7
-#define A_C_00000100 0xc8
-#define A_C_00010000 0xc9
-#define A_C_00000800 0xca
-#define A_C_10000000 0xcb
-#define A_C_20000000 0xcc
-#define A_C_40000000 0xcd
-#define A_C_80000000 0xce
-#define A_C_7fffffff 0xcf
-#define A_C_ffffffff 0xd0
-#define A_C_fffffffe 0xd1
-#define A_C_c0000000 0xd2
-#define A_C_4f1bbcdc 0xd3
-#define A_C_5a7ef9db 0xd4
-#define A_C_00100000 0xd5
-#define A_GPR_ACCU 0xd6 /* ACCUM, accumulator */
-#define A_GPR_COND 0xd7 /* CCR, condition register */
-#define A_GPR_NOISE0 0xd8 /* noise source */
-#define A_GPR_NOISE1 0xd9 /* noise source */
-#define A_GPR_IRQ 0xda /* IRQ register */
-#define A_GPR_DBAC 0xdb /* TRAM Delay Base Address Counter - internal */
-#define A_GPR_DBACE 0xde /* TRAM Delay Base Address Counter - external */
-
-/* definitions for debug register */
+/* Definitions for debug register. Note that these are for emu10k1 ONLY. */
#define EMU10K1_DBG_ZC 0x80000000 /* zero tram counter */
#define EMU10K1_DBG_SATURATION_OCCURED 0x02000000 /* saturation control */
#define EMU10K1_DBG_SATURATION_ADDR 0x01ff0000 /* saturation address */
#define EMU10K1_DBG_CONDITION_CODE 0x00003e00 /* condition code */
#define EMU10K1_DBG_SINGLE_STEP_ADDR 0x000001ff /* single step address */
-/* tank memory address line */
-#ifndef __KERNEL__
-#define TANKMEMADDRREG_ADDR_MASK 0x000fffff /* 20 bit tank address field */
-#define TANKMEMADDRREG_CLEAR 0x00800000 /* Clear tank memory */
-#define TANKMEMADDRREG_ALIGN 0x00400000 /* Align read or write relative to tank access */
-#define TANKMEMADDRREG_WRITE 0x00200000 /* Write to tank memory */
-#define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */
-#endif
+/* Definitions for emu10k2 debug register. */
+#define A_DBG_ZC 0x40000000 /* zero tram counter */
+#define A_DBG_SATURATION_OCCURED 0x20000000
+#define A_DBG_SATURATION_ADDR 0x0ffc0000
+#define A_DBG_SINGLE_STEP 0x00020000 /* Set to zero to start dsp */
+#define A_DBG_STEP 0x00010000
+#define A_DBG_CONDITION_CODE 0x0000f800
+#define A_DBG_STEP_ADDR 0x000003ff
struct snd_emu10k1_fx8010_info {
unsigned int internal_tram_size; /* in samples */
/* SOF ABI magic number "SOF\0". */
#define SOF_ABI_MAGIC 0x00464F53
+/* SOF IPC4 ABI magic number "SOF4". */
+#define SOF_IPC4_ABI_MAGIC 0x34464F53
#endif
#include <linux/types.h>
-/*
- * Header for all non IPC ABI data.
+/**
+ * struct sof_abi_hdr - Header for all non IPC ABI data.
+ * @magic: Magic number for validation
+ * for IPC3 data: 0x00464F53 ('S', 'O', 'F', '\0')
+ * for IPC4 data: 0x34464F53 ('S', 'O', 'F', '4')
+ * @type: module specific parameter
+ * for IPC3: Component specific type
+ * for IPC4: parameter ID (param_id) of the data
+ * @size: The size in bytes of the data, excluding this struct
+ * @abi: SOF ABI version. The version is valid in scope of the 'magic', IPC3 and
+ * IPC4 ABI version numbers have no relationship.
+ * @reserved: Reserved for future use
+ * @data: Component data - opaque to core
*
* Identifies data type, size and ABI.
* Used by any bespoke component data structures or binary blobs.
*/
struct sof_abi_hdr {
- __u32 magic; /**< 'S', 'O', 'F', '\0' */
- __u32 type; /**< component specific type */
- __u32 size; /**< size in bytes of data excl. this struct */
- __u32 abi; /**< SOF ABI version */
- __u32 reserved[4]; /**< reserved for future use */
- __u32 data[]; /**< Component data - opaque to core */
+ __u32 magic;
+ __u32 type;
+ __u32 size;
+ __u32 abi;
+ __u32 reserved[4];
+ __u32 data[];
} __packed;
#define SOF_MANIFEST_DATA_TYPE_NHLT 1
#define SOF_TKN_SCHED_DYNAMIC_PIPELINE 206
#define SOF_TKN_SCHED_LP_MODE 207
#define SOF_TKN_SCHED_MEM_USAGE 208
+#define SOF_TKN_SCHED_USE_CHAIN_DMA 209
/* volume */
#define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250
#define SOF_TKN_COMP_CPC 406
#define SOF_TKN_COMP_IS_PAGES 409
#define SOF_TKN_COMP_NUM_AUDIO_FORMATS 410
-#define SOF_TKN_COMP_NUM_SINK_PINS 411
-#define SOF_TKN_COMP_NUM_SOURCE_PINS 412
+#define SOF_TKN_COMP_NUM_INPUT_PINS 411
+#define SOF_TKN_COMP_NUM_OUTPUT_PINS 412
/*
- * The token for sink/source pin binding, it specifies the widget
- * name that the sink/source pin is connected from/to.
+ * The token for input/output pin binding, it specifies the widget
+ * name that the input/output pin is connected from/to.
*/
-#define SOF_TKN_COMP_SINK_PIN_BINDING_WNAME 413
-#define SOF_TKN_COMP_SRC_PIN_BINDING_WNAME 414
+#define SOF_TKN_COMP_INPUT_PIN_BINDING_WNAME 413
+#define SOF_TKN_COMP_OUTPUT_PIN_BINDING_WNAME 414
+#define SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS 415
+#define SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS 416
/* SSP */
/* CAVS AUDIO FORMAT */
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_RATE 1900
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_BIT_DEPTH 1901
-#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_VALID_BIT 1902
+#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_VALID_BIT_DEPTH 1902
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_CHANNELS 1903
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_MAP 1904
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_CFG 1905
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_INTERLEAVING_STYLE 1906
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_FMT_CFG 1907
#define SOF_TKN_CAVS_AUDIO_FORMAT_IN_SAMPLE_TYPE 1908
+#define SOF_TKN_CAVS_AUDIO_FORMAT_PIN_INDEX 1909
/* intentional token numbering discontinuity, reserved for future use */
#define SOF_TKN_CAVS_AUDIO_FORMAT_OUT_RATE 1930
#define SOF_TKN_CAVS_AUDIO_FORMAT_OUT_BIT_DEPTH 1931
-#define SOF_TKN_CAVS_AUDIO_FORMAT_OUT_VALID_BIT 1932
+#define SOF_TKN_CAVS_AUDIO_FORMAT_OUT_VALID_BIT_DEPTH 1932
#define SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CHANNELS 1933
#define SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_MAP 1934
#define SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_CFG 1935
/* COPIER */
#define SOF_TKN_INTEL_COPIER_NODE_TYPE 1980
+#define SOF_TKN_INTEL_COPIER_DEEP_BUFFER_DMA_MS 1981
/* ACP I2S */
#define SOF_TKN_AMD_ACPI2S_RATE 1700
}
EXPORT_SYMBOL_GPL(snd_ac97_reset);
-/*
- * Let drivers decide whether they want to support given codec from their
- * probe method. Drivers have direct access to the struct snd_ac97
- * structure and may decide based on the id field amongst other things.
- */
-static int ac97_bus_match(struct device *dev, struct device_driver *drv)
-{
- return 1;
-}
-
struct bus_type ac97_bus_type = {
.name = "ac97",
- .match = ac97_bus_match,
};
static int __init ac97_bus_init(void)
return ret;
}
-static int pxa2xx_ac97_remove(struct platform_device *dev)
+static void pxa2xx_ac97_remove(struct platform_device *dev)
{
struct snd_card *card = platform_get_drvdata(dev);
snd_card_free(card);
pxa2xx_ac97_hw_remove(dev);
}
-
- return 0;
}
static struct platform_driver pxa2xx_ac97_driver = {
.probe = pxa2xx_ac97_probe,
- .remove = pxa2xx_ac97_remove,
+ .remove_new = pxa2xx_ac97_remove,
.driver = {
.name = "pxa2xx-ac97",
#ifdef CONFIG_PM_SLEEP
#define ATMEL_AC97C_PM_OPS NULL
#endif
-static int atmel_ac97c_remove(struct platform_device *pdev)
+static void atmel_ac97c_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
struct atmel_ac97c *chip = get_chip(card);
free_irq(chip->irq, chip);
snd_card_free(card);
-
- return 0;
}
static struct platform_driver atmel_ac97c_driver = {
.probe = atmel_ac97c_probe,
- .remove = atmel_ac97c_remove,
+ .remove_new = atmel_ac97c_remove,
.driver = {
.name = "atmel_ac97c",
.pm = ATMEL_AC97C_PM_OPS,
*
* when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
*/
-void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr)
+void snd_pcm_playback_silence(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
- snd_pcm_uframes_t frames, ofs, transfer;
+ snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
+ snd_pcm_sframes_t added, hw_avail, frames;
+ snd_pcm_uframes_t noise_dist, ofs, transfer;
int err;
+ added = appl_ptr - runtime->silence_start;
+ if (added) {
+ if (added < 0)
+ added += runtime->boundary;
+ if (added < runtime->silence_filled)
+ runtime->silence_filled -= added;
+ else
+ runtime->silence_filled = 0;
+ runtime->silence_start = appl_ptr;
+ }
+
+ // This will "legitimately" turn negative on underrun, and will be mangled
+ // into a huge number by the boundary crossing handling. The initial state
+ // might also be not quite sane. The code below MUST account for these cases.
+ hw_avail = appl_ptr - runtime->status->hw_ptr;
+ if (hw_avail < 0)
+ hw_avail += runtime->boundary;
+
+ noise_dist = hw_avail + runtime->silence_filled;
if (runtime->silence_size < runtime->boundary) {
- snd_pcm_sframes_t noise_dist, n;
- snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
- if (runtime->silence_start != appl_ptr) {
- n = appl_ptr - runtime->silence_start;
- if (n < 0)
- n += runtime->boundary;
- if ((snd_pcm_uframes_t)n < runtime->silence_filled)
- runtime->silence_filled -= n;
- else
- runtime->silence_filled = 0;
- runtime->silence_start = appl_ptr;
- }
- if (runtime->silence_filled >= runtime->buffer_size)
- return;
- noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled;
- if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold)
- return;
frames = runtime->silence_threshold - noise_dist;
+ if (frames <= 0)
+ return;
if (frames > runtime->silence_size)
frames = runtime->silence_size;
} else {
- if (new_hw_ptr == ULONG_MAX) { /* initialization */
- snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime);
- if (avail > runtime->buffer_size)
- avail = runtime->buffer_size;
- runtime->silence_filled = avail > 0 ? avail : 0;
- runtime->silence_start = (runtime->status->hw_ptr +
- runtime->silence_filled) %
- runtime->boundary;
- } else {
- ofs = runtime->status->hw_ptr;
- frames = new_hw_ptr - ofs;
- if ((snd_pcm_sframes_t)frames < 0)
- frames += runtime->boundary;
- runtime->silence_filled -= frames;
- if ((snd_pcm_sframes_t)runtime->silence_filled < 0) {
- runtime->silence_filled = 0;
- runtime->silence_start = new_hw_ptr;
- } else {
- runtime->silence_start = ofs;
- }
- }
- frames = runtime->buffer_size - runtime->silence_filled;
+ frames = runtime->buffer_size - noise_dist;
+ if (frames <= 0)
+ return;
}
+
if (snd_BUG_ON(frames > runtime->buffer_size))
return;
- if (frames == 0)
- return;
- ofs = runtime->silence_start % runtime->buffer_size;
- while (frames > 0) {
+ ofs = (runtime->silence_start + runtime->silence_filled) % runtime->buffer_size;
+ do {
transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames;
err = fill_silence_frames(substream, ofs, transfer);
snd_BUG_ON(err < 0);
runtime->silence_filled += transfer;
frames -= transfer;
ofs = 0;
- }
+ } while (frames > 0);
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
}
return 0;
}
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
- runtime->silence_size > 0)
- snd_pcm_playback_silence(substream, new_hw_ptr);
-
if (in_interrupt) {
delta = new_hw_ptr - runtime->hw_ptr_interrupt;
if (delta < 0)
runtime->hw_ptr_wrap += runtime->boundary;
}
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ runtime->silence_size > 0)
+ snd_pcm_playback_silence(substream);
+
update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
return snd_pcm_update_state(substream, runtime);
if (substream->wait_time) {
wait_time = substream->wait_time;
} else {
- wait_time = 10;
+ wait_time = 100;
if (runtime->rate) {
- long t = runtime->period_size * 2 /
- runtime->rate;
+ long t = runtime->buffer_size * 1100 / runtime->rate;
wait_time = max(t, wait_time);
}
- wait_time = msecs_to_jiffies(wait_time * 1000);
}
+ wait_time = msecs_to_jiffies(wait_time);
}
for (;;) {
}
if (!tout) {
pcm_dbg(substream->pcm,
- "%s write error (DMA or IRQ trouble?)\n",
- is_playback ? "playback" : "capture");
+ "%s timeout (DMA or IRQ trouble?)\n",
+ is_playback ? "playback write" : "capture read");
err = -EIO;
break;
}
struct snd_pcm_runtime *runtime);
int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream);
-void snd_pcm_playback_silence(struct snd_pcm_substream *substream,
- snd_pcm_uframes_t new_hw_ptr);
+void snd_pcm_playback_silence(struct snd_pcm_substream *substream);
static inline snd_pcm_uframes_t
snd_pcm_avail(struct snd_pcm_substream *substream)
if (snd_pcm_running(substream)) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
- snd_pcm_playback_silence(substream, ULONG_MAX);
+ snd_pcm_playback_silence(substream);
err = snd_pcm_update_state(substream, runtime);
}
snd_pcm_stream_unlock_irq(substream);
__snd_pcm_set_state(runtime, state);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
- snd_pcm_playback_silence(substream, ULONG_MAX);
+ snd_pcm_playback_silence(substream);
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);
}
runtime->control->appl_ptr = runtime->status->hw_ptr;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
- snd_pcm_playback_silence(substream, ULONG_MAX);
+ snd_pcm_playback_silence(substream);
snd_pcm_stream_unlock_irq(substream);
}
if (runtime->no_period_wakeup)
tout = MAX_SCHEDULE_TIMEOUT;
else {
- tout = 10;
+ tout = 100;
if (runtime->rate) {
- long t = runtime->period_size * 2 / runtime->rate;
+ long t = runtime->buffer_size * 1100 / runtime->rate;
tout = max(t, tout);
}
- tout = msecs_to_jiffies(tout * 1000);
+ tout = msecs_to_jiffies(tout);
}
tout = schedule_timeout(tout);
result = -ESTRPIPE;
else {
dev_dbg(substream->pcm->card->dev,
- "playback drain error (DMA or IRQ trouble?)\n");
+ "playback drain timeout (DMA or IRQ trouble?)\n");
snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
result = -EIO;
}
return err;
}
-static int snd_mts64_remove(struct platform_device *pdev)
+static void snd_mts64_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
if (card)
snd_card_free(card);
-
- return 0;
}
static struct platform_driver snd_mts64_driver = {
.probe = snd_mts64_probe,
- .remove = snd_mts64_remove,
+ .remove_new = snd_mts64_remove,
.driver = {
.name = PLATFORM_DRIVER,
}
parport_write_control(pm->pardev->port, value);
}
-static inline u8 portman_read_command(struct portman *pm)
-{
- return parport_read_control(pm->pardev->port);
-}
-
static inline u8 portman_read_status(struct portman *pm)
{
return parport_read_status(pm->pardev->port);
}
-static inline u8 portman_read_data(struct portman *pm)
-{
- return parport_read_data(pm->pardev->port);
-}
-
static inline void portman_write_data(struct portman *pm, u8 value)
{
parport_write_data(pm->pardev->port, value);
return err;
}
-static int snd_portman_remove(struct platform_device *pdev)
+static void snd_portman_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
if (card)
snd_card_free(card);
-
- return 0;
}
static struct platform_driver snd_portman_driver = {
.probe = snd_portman_probe,
- .remove = snd_portman_remove,
+ .remove_new = snd_portman_remove,
.driver = {
.name = PLATFORM_DRIVER,
}
return 0;
}
-static int hal2_remove(struct platform_device *pdev)
+static void hal2_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
snd_card_free(card);
- return 0;
}
static struct platform_driver hal2_driver = {
.probe = hal2_probe,
- .remove = hal2_remove,
+ .remove_new = hal2_remove,
.driver = {
.name = "sgihal2",
}
return 0;
}
-static int snd_sgio2audio_remove(struct platform_device *pdev)
+static void snd_sgio2audio_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
snd_card_free(card);
- return 0;
}
static struct platform_driver sgio2audio_driver = {
.probe = snd_sgio2audio_probe,
- .remove = snd_sgio2audio_remove,
+ .remove_new = snd_sgio2audio_remove,
.driver = {
.name = "sgio2audio",
}
will be called snd-indigodjx
config SND_EMU10K1
- tristate "Emu10k1 (SB Live!, Audigy, E-mu APS)"
+ tristate "Emu10k1 (SB Live!, Audigy, E-MU APS/0404/1010/1212/1616/1820)"
select FW_LOADER
select SND_HWDEP
select SND_RAWMIDI
depends on ZONE_DMA
help
Say Y to include support for Sound Blaster PCI 512, Live!,
- Audigy and E-mu APS (partially supported) soundcards.
+ Audigy and E-MU APS/0404/1010/1212/1616/1820 soundcards.
The confusing multitude of mixer controls is documented in
<file:Documentation/sound/cards/sb-live-mixer.rst> and
int local_count = count;
int xfer_size;
u32 *pdata = dest;
- u32 loop_count = 0;
while (local_count) {
if (local_count > c6711_burst_size)
pdata += xfer_size;
local_hpi_address += sizeof(u32) * xfer_size;
local_count -= xfer_size;
- loop_count++;
}
if (time_out)
{ 0, }
};
-/*
- * Audigy 2 Value notes:
- * A_IOCFG Input (GPIO)
- * 0x400 = Front analog jack plugged in. (Green socket)
- * 0x1000 = Read analog jack plugged in. (Black socket)
- * 0x2000 = Center/LFE analog jack plugged in. (Orange socket)
- * A_IOCFG Output (GPIO)
- * 0x60 = Sound out of front Left.
- * Win sets it to 0xXX61
- */
-
MODULE_DEVICE_TABLE(pci, snd_emu10k1_ids);
static int snd_card_emu10k1_probe(struct pci_dev *pci,
static void
set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp)
{
- unsigned int val;
- val = snd_emu10k1_ptr_read(hw, CCCA, vp->ch) & ~CCCA_RESONANCE;
- val |= (vp->reg.parm.filterQ << 28);
- snd_emu10k1_ptr_write(hw, CCCA, vp->ch, val);
+ snd_emu10k1_ptr_write(hw, CCCA_RESONANCE, vp->ch, vp->reg.parm.filterQ);
}
{ 0x15, ADC_MUX_2 }, /* ADC Mixer control. Mic for A2ZS Notebook */
};
-static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir, int resume)
+static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir)
{
unsigned int silent_page;
int ch;
outl(0, emu->port + INTE);
snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
+
+ /* disable stop on loop end */
snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);
snd_emu10k1_ptr_write(emu, SPCS1, 0, emu->spdif_bits[1]);
snd_emu10k1_ptr_write(emu, SPCS2, 0, emu->spdif_bits[2]);
- if (emu->card_capabilities->ca0151_chip) { /* audigy2 */
+ if (emu->card_capabilities->emu_model) {
+ } else if (emu->card_capabilities->ca0151_chip) { /* audigy2 */
/* Hacks for Alice3 to work independent of haP16V driver */
/* Setup SRCMulti_I2S SamplingRate */
- tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
- tmp &= 0xfffff1ff;
- tmp |= (0x2<<9);
- snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+ snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, 0, A_I2S_CAPTURE_96000);
/* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
snd_emu10k1_ptr20_write(emu, SRCSel, 0, 0x14);
outl(0x0201, emu->port + HCFG2);
/* Set playback routing. */
snd_emu10k1_ptr20_write(emu, CAPTURE_P16V_SOURCE, 0, 0x78e4);
- }
- if (emu->card_capabilities->ca0108_chip) { /* audigy2 Value */
+ } else if (emu->card_capabilities->ca0108_chip) { /* audigy2 Value */
/* Hacks for Alice3 to work independent of haP16V driver */
dev_info(emu->card->dev, "Audigy2 value: Special config.\n");
/* Setup SRCMulti_I2S SamplingRate */
- tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0);
- tmp &= 0xfffff1ff;
- tmp |= (0x2<<9);
- snd_emu10k1_ptr_write(emu, A_SPDIF_SAMPLERATE, 0, tmp);
+ snd_emu10k1_ptr_write(emu, A_I2S_CAPTURE_RATE, 0, A_I2S_CAPTURE_96000);
/* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
- outl(0x600000, emu->port + 0x20);
- outl(0x14, emu->port + 0x24);
+ snd_emu10k1_ptr20_write(emu, P17V_SRCSel, 0, 0x14);
/* Setup SRCMulti Input Audio Enable */
- outl(0x7b0000, emu->port + 0x20);
- outl(0xFF000000, emu->port + 0x24);
+ snd_emu10k1_ptr20_write(emu, P17V_MIXER_I2S_ENABLE, 0, 0xFF000000);
/* Setup SPDIF Out Audio Enable */
/* The Audigy 2 Value has a separate SPDIF out,
* so no need for a mixer switch
*/
- outl(0x7a0000, emu->port + 0x20);
- outl(0xFF000000, emu->port + 0x24);
- tmp = inl(emu->port + A_IOCFG) & ~0x8; /* Clear bit 3 */
- outl(tmp, emu->port + A_IOCFG);
+ snd_emu10k1_ptr20_write(emu, P17V_MIXER_SPDIF_ENABLE, 0, 0xFF000000);
+
+ tmp = inw(emu->port + A_IOCFG) & ~0x8; /* Clear bit 3 */
+ outw(tmp, emu->port + A_IOCFG);
}
if (emu->card_capabilities->spi_dac) { /* Audigy 2 ZS Notebook with DAC Wolfson WM8768/WM8568 */
int size, n;
* GPIO6: Unknown
* GPIO7: Unknown
*/
- outl(0x76, emu->port + A_IOCFG); /* Windows uses 0x3f76 */
+ outw(0x76, emu->port + A_IOCFG); /* Windows uses 0x3f76 */
}
if (emu->card_capabilities->i2c_adc) { /* Audigy 2 ZS Notebook with ADC Wolfson WM8775 */
int size, n;
snd_emu10k1_ptr20_write(emu, P17V_I2S_SRC_SEL, 0, 0x2020205f);
- tmp = inl(emu->port + A_IOCFG);
- outl(tmp | 0x4, emu->port + A_IOCFG); /* Set bit 2 for mic input */
- tmp = inl(emu->port + A_IOCFG);
+ tmp = inw(emu->port + A_IOCFG);
+ outw(tmp | 0x4, emu->port + A_IOCFG); /* Set bit 2 for mic input */
+ tmp = inw(emu->port + A_IOCFG);
size = ARRAY_SIZE(i2c_adc_init);
for (n = 0; n < size; n++)
snd_emu10k1_i2c_write(emu, i2c_adc_init[n][0], i2c_adc_init[n][1]);
} else if (emu->card_capabilities->i2c_adc) {
; /* Disable A_IOCFG for Audigy 2 ZS Notebook */
} else if (emu->audigy) {
- unsigned int reg = inl(emu->port + A_IOCFG);
- outl(reg | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+ u16 reg = inw(emu->port + A_IOCFG);
+ outw(reg | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
udelay(500);
- outl(reg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
+ outw(reg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, emu->port + A_IOCFG);
udelay(100);
- outl(reg, emu->port + A_IOCFG);
+ outw(reg, emu->port + A_IOCFG);
} else {
unsigned int reg = inl(emu->port + HCFG);
outl(reg | HCFG_GPOUT2, emu->port + HCFG);
} else if (emu->card_capabilities->i2c_adc) {
; /* Disable A_IOCFG for Audigy 2 ZS Notebook */
} else if (emu->audigy) { /* enable analog output */
- unsigned int reg = inl(emu->port + A_IOCFG);
- outl(reg | A_IOCFG_GPOUT0, emu->port + A_IOCFG);
+ u16 reg = inw(emu->port + A_IOCFG);
+ outw(reg | A_IOCFG_GPOUT0, emu->port + A_IOCFG);
}
if (emu->address_mode == 0) {
} else if (emu->card_capabilities->i2c_adc) {
; /* Disable A_IOCFG for Audigy 2 ZS Notebook */
} else if (emu->audigy) {
- outl(inl(emu->port + A_IOCFG) & ~0x44, emu->port + A_IOCFG);
+ outw(inw(emu->port + A_IOCFG) & ~0x44, emu->port + A_IOCFG);
if (emu->card_capabilities->ca0151_chip) { /* audigy2 */
/* Unmute Analog now. Set GPO6 to 1 for Apollo.
* This has to be done after init ALice3 I2SOut beyond 48KHz.
* So, sequence is important. */
- outl(inl(emu->port + A_IOCFG) | 0x0040, emu->port + A_IOCFG);
+ outw(inw(emu->port + A_IOCFG) | 0x0040, emu->port + A_IOCFG);
} else if (emu->card_capabilities->ca0108_chip) { /* audigy2 value */
/* Unmute Analog now. */
- outl(inl(emu->port + A_IOCFG) | 0x0060, emu->port + A_IOCFG);
+ outw(inw(emu->port + A_IOCFG) | 0x0060, emu->port + A_IOCFG);
} else {
/* Disable routing from AC97 line out to Front speakers */
- outl(inl(emu->port + A_IOCFG) | 0x0080, emu->port + A_IOCFG);
+ outw(inw(emu->port + A_IOCFG) | 0x0080, emu->port + A_IOCFG);
}
}
const struct firmware *fw_entry)
{
int n, i;
- int reg;
- int value;
- __always_unused unsigned int write_post;
+ u16 reg;
+ u8 value;
+ __always_unused u16 write_post;
unsigned long flags;
if (!fw_entry)
return -EIO;
/* The FPGA is a Xilinx Spartan IIE XC2S50E */
+ /* On E-MU 0404b it is a Xilinx Spartan III XC3S50 */
/* GPIO7 -> FPGA PGMN
* GPIO6 -> FPGA CCLK
* GPIO5 -> FPGA DIN
* FPGA CONFIG OFF -> FPGA PGMN
*/
spin_lock_irqsave(&emu->emu_lock, flags);
- outl(0x00, emu->port + A_IOCFG); /* Set PGMN low for 1uS. */
- write_post = inl(emu->port + A_IOCFG);
+ outw(0x00, emu->port + A_GPIO); /* Set PGMN low for 100uS. */
+ write_post = inw(emu->port + A_GPIO);
udelay(100);
- outl(0x80, emu->port + A_IOCFG); /* Leave bit 7 set during netlist setup. */
- write_post = inl(emu->port + A_IOCFG);
+ outw(0x80, emu->port + A_GPIO); /* Leave bit 7 set during netlist setup. */
+ write_post = inw(emu->port + A_GPIO);
udelay(100); /* Allow FPGA memory to clean */
for (n = 0; n < fw_entry->size; n++) {
value = fw_entry->data[n];
if (value & 0x1)
reg = reg | 0x20;
value = value >> 1;
- outl(reg, emu->port + A_IOCFG);
- write_post = inl(emu->port + A_IOCFG);
- outl(reg | 0x40, emu->port + A_IOCFG);
- write_post = inl(emu->port + A_IOCFG);
+ outw(reg, emu->port + A_GPIO);
+ write_post = inw(emu->port + A_GPIO);
+ outw(reg | 0x40, emu->port + A_GPIO);
+ write_post = inw(emu->port + A_GPIO);
}
}
/* After programming, set GPIO bit 4 high again. */
- outl(0x10, emu->port + A_IOCFG);
- write_post = inl(emu->port + A_IOCFG);
+ outw(0x10, emu->port + A_GPIO);
+ write_post = inw(emu->port + A_GPIO);
spin_unlock_irqrestore(&emu->emu_lock, flags);
return 0;
} else if (!reg && emu->emu1010.last_reg) {
/* Audio Dock removed */
dev_info(emu->card->dev, "emu1010: Audio Dock detached\n");
- /* Unmute all */
+ /* The hardware auto-mutes all, so we unmute again */
snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
}
}
/*
- * EMU-1010 - details found out from this driver, official MS Win drivers,
- * testing the card:
- *
- * Audigy2 (aka Alice2):
- * ---------------------
- * * communication over PCI
- * * conversion of 32-bit data coming over EMU32 links from HANA FPGA
- * to 2 x 16-bit, using internal DSP instructions
- * * slave mode, clock supplied by HANA
- * * linked to HANA using:
- * 32 x 32-bit serial EMU32 output channels
- * 16 x EMU32 input channels
- * (?) x I2S I/O channels (?)
- *
- * FPGA (aka HANA):
- * ---------------
- * * provides all (?) physical inputs and outputs of the card
- * (ADC, DAC, SPDIF I/O, ADAT I/O, etc.)
- * * provides clock signal for the card and Alice2
- * * two crystals - for 44.1kHz and 48kHz multiples
- * * provides internal routing of signal sources to signal destinations
- * * inputs/outputs to Alice2 - see above
- *
* Current status of the driver:
* ----------------------------
* * only 44.1/48kHz supported (the MS Win driver supports up to 192 kHz)
int err;
dev_info(emu->card->dev, "emu1010: Special config.\n");
- /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
- * Lock Sound Memory Cache, Lock Tank Memory Cache,
- * Mute all codecs.
- */
- outl(0x0005a00c, emu->port + HCFG);
- /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
- * Lock Tank Memory Cache,
- * Mute all codecs.
- */
- outl(0x0005a004, emu->port + HCFG);
- /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
- * Mute all codecs.
- */
- outl(0x0005a000, emu->port + HCFG);
- /* AC97 2.1, Any 16Meg of 4Gig address, Auto-Mute, EMU32 Slave,
- * Mute all codecs.
- */
- outl(0x0005a000, emu->port + HCFG);
+
+ /* Mute, and disable audio and lock cache, just in case.
+ * Proper init follows in snd_emu10k1_init(). */
+ outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK, emu->port + HCFG);
/* Disable 48Volt power to Audio Dock */
snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_PWR, 0);
snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®);
dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg);
- snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®);
- dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg);
- snd_emu1010_fpga_read(emu, EMU_HANA_OPTICAL_TYPE, &tmp);
/* Optical -> ADAT I/O */
- /* 0 : SPDIF
- * 1 : ADAT
- */
emu->emu1010.optical_in = 1; /* IN_ADAT */
- emu->emu1010.optical_out = 1; /* IN_ADAT */
- tmp = 0;
+ emu->emu1010.optical_out = 1; /* OUT_ADAT */
tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : 0) |
(emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : 0);
snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp);
- snd_emu1010_fpga_read(emu, EMU_HANA_ADC_PADS, &tmp);
/* Set no attenuation on Audio Dock pads. */
snd_emu1010_fpga_write(emu, EMU_HANA_ADC_PADS, 0x00);
emu->emu1010.adc_pads = 0x00;
- snd_emu1010_fpga_read(emu, EMU_HANA_DOCK_MISC, &tmp);
/* Unmute Audio dock DACs, Headphone source DAC-4. */
snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_MISC, 0x30);
- snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12);
- snd_emu1010_fpga_read(emu, EMU_HANA_DAC_PADS, &tmp);
/* DAC PADs. */
snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, 0x0f);
emu->emu1010.dac_pads = 0x0f;
- snd_emu1010_fpga_read(emu, EMU_HANA_DOCK_MISC, &tmp);
- snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_MISC, 0x30);
- snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &tmp);
/* SPDIF Format. Set Consumer mode, 24bit, copy enable */
snd_emu1010_fpga_write(emu, EMU_HANA_SPDIF_MODE, 0x10);
/* MIDI routing */
/* IRQ Enable: All off */
snd_emu1010_fpga_write(emu, EMU_HANA_IRQ_ENABLE, 0x00);
- snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®);
- dev_info(emu->card->dev, "emu1010: Card options3 = 0x%x\n", reg);
+ emu->emu1010.internal_clock = 1; /* 48000 */
/* Default WCLK set to 48kHz. */
snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 0x00);
/* Word Clock source, Internal 48kHz x1 */
EMU_DST_ALICE_I2S2_RIGHT, EMU_SRC_DOCK_ADC3_RIGHT1);
snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x01); /* Unmute all */
- snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &tmp);
-
- /* AC97 1.03, Any 32Meg of 2Gig address, Auto-Mute, EMU32 Slave,
- * Lock Sound Memory Cache, Lock Tank Memory Cache,
- * Mute all codecs.
- */
- outl(0x0000a000, emu->port + HCFG);
- /* AC97 1.03, Any 32Meg of 2Gig address, Auto-Mute, EMU32 Slave,
- * Lock Sound Memory Cache, Lock Tank Memory Cache,
- * Un-Mute all codecs.
- */
- outl(0x0000a001, emu->port + HCFG);
-
- /* Initial boot complete. Now patches */
-
- snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, &tmp);
- snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19); /* MIDI Route */
- snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c); /* Unknown */
- snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_IN, 0x19); /* MIDI Route */
- snd_emu1010_fpga_write(emu, EMU_HANA_MIDI_OUT, 0x0c); /* Unknown */
- snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &tmp);
- snd_emu1010_fpga_write(emu, EMU_HANA_SPDIF_MODE, 0x10); /* SPDIF Format spdif (or 0x11 for aes/ebu) */
-
#if 0
snd_emu1010_fpga_link_dst_src_write(emu,
EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32B + 2); /* ALICE2 bus 0xa2 */
EMU_DST_HANA_ADAT + 7, EMU_SRC_ALICE_EMU32A + 7);
emu->emu1010.output_source[23] = 28;
}
- /* TEMP: Select SPDIF in/out */
- /* snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, 0x0); */ /* Output spdif */
-
- /* TEMP: Select 48kHz SPDIF out */
- snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x0); /* Mute all */
- snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 0x0); /* Default fallback clock 48kHz */
- /* Word Clock source, Internal 48kHz x1 */
- snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K);
- /* snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X); */
- emu->emu1010.internal_clock = 1; /* 48000 */
- snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, 0x12); /* Set LEDs on Audio Dock */
- snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, 0x1); /* Unmute all */
- /* snd_emu1010_fpga_write(emu, 0x7, 0x0); */ /* Mute all */
- /* snd_emu1010_fpga_write(emu, 0x7, 0x1); */ /* Unmute all */
- /* snd_emu1010_fpga_write(emu, 0xe, 0x12); */ /* Set LEDs on Audio Dock */
return 0;
}
* AC97: STAC9750
* CA0151: None
*/
+ /*
+ * A_IOCFG Input (GPIO)
+ * 0x400 = Front analog jack plugged in. (Green socket)
+ * 0x1000 = Rear analog jack plugged in. (Black socket)
+ * 0x2000 = Center/LFE analog jack plugged in. (Orange socket)
+ * A_IOCFG Output (GPIO)
+ * 0x60 = Sound out of front Left.
+ * Win sets it to 0xXX61
+ */
{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10011102,
.driver = "Audigy2", .name = "SB Audigy 2 Value [SB0400]",
.id = "Audigy2",
.spi_dac = 1,
.i2c_adc = 1,
.spk71 = 1} ,
+ /* This is MAEM8950 "Mana" */
+ /* Attach MicroDock[M] to make it an E-MU 1616[m]. */
+ /* Does NOT support sync daughter card (obviously). */
/* Tested by James@superbug.co.uk 4th Nov 2007. */
{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x42011102,
.driver = "Audigy2", .name = "E-mu 1010 Notebook [MAEM8950]",
.spk71 = 1 ,
.emu_model = EMU_MODEL_EMU1616},
/* Tested by James@superbug.co.uk 4th Nov 2007. */
- /* This is MAEM8960, 0202 is MAEM 8980 */
+ /* This is MAEM8960 "Hana3", 0202 is MAEM8980 */
+ /* Attach 0202 daughter card to make it an E-MU 1212m, OR a
+ * MicroDock[M] to make it an E-MU 1616[m]. */
+ /* Does NOT support sync daughter card. */
{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40041102,
.driver = "Audigy2", .name = "E-mu 1010b PCI [MAEM8960]",
.id = "EMU1010",
.emu_model = EMU_MODEL_EMU1010B}, /* EMU 1010 new revision */
/* Tested by Maxim Kachur <mcdebugger@duganet.ru> 17th Oct 2012. */
/* This is MAEM8986, 0202 is MAEM8980 */
+ /* Attach 0202 daughter card to make it an E-MU 1212m, OR a
+ * MicroDockM to make it an E-MU 1616m. The non-m
+ * version was never sold with this card, but should
+ * still work. */
+ /* Does NOT support sync daughter card. */
{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40071102,
.driver = "Audigy2", .name = "E-mu 1010 PCIe [MAEM8986]",
.id = "EMU1010",
.spk71 = 1,
.emu_model = EMU_MODEL_EMU1010B}, /* EMU 1010 PCIe */
/* Tested by James@superbug.co.uk 8th July 2005. */
- /* This is MAEM8810, 0202 is MAEM8820 */
+ /* This is MAEM8810 "Hana", 0202 is MAEM8820 "Hamoa" */
+ /* Attach 0202 daughter card to make it an E-MU 1212m, OR an
+ * AudioDock[M] to make it an E-MU 1820[m]. */
+ /* Supports sync daughter card. */
{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40011102,
.driver = "Audigy2", .name = "E-mu 1010 [MAEM8810]",
.id = "EMU1010",
.ca0102_chip = 1,
.spk71 = 1,
.emu_model = EMU_MODEL_EMU1010}, /* EMU 1010 old revision */
- /* EMU0404b */
+ /* This is MAEM8852 "HanaLiteLite" */
+ /* Supports sync daughter card. */
+ /* Tested by oswald.buddenhagen@gmx.de Mar 2023. */
{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40021102,
.driver = "Audigy2", .name = "E-mu 0404b PCI [MAEM8852]",
.id = "EMU0404",
.ca0108_chip = 1,
.spk71 = 1,
.emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 new revision */
+ /* This is MAEM8850 "HanaLite" */
+ /* Supports sync daughter card. */
/* Tested by James@superbug.co.uk 20-3-2007. */
{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40021102,
.driver = "Audigy2", .name = "E-mu 0404 [MAEM8850]",
.spk71 = 1,
.emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 */
/* EMU0404 PCIe */
+ /* Does NOT support sync daughter card. */
{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40051102,
.driver = "Audigy2", .name = "E-mu 0404 PCIe [MAEM8984]",
.id = "EMU0404",
.ca0108_chip = 1,
.spk71 = 1,
.emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 PCIe ver_03 */
- /* Note that all E-mu cards require kernel 2.6 or newer. */
{.vendor = 0x1102, .device = 0x0008,
.driver = "Audigy2", .name = "SB Audigy 2 Value [Unknown]",
.id = "Audigy2",
.spdif_bug = 1,
.adc_1361t = 1, /* 24 bit capture instead of 16bit */
.ac97_chip = 1} ,
+ /* Audigy 2 Platinum EX */
+ /* Win driver sets A_IOCFG output to 0x1c00 */
{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10051102,
.driver = "Audigy2", .name = "Audigy 2 Platinum EX [SB0280]",
.id = "Audigy2",
.spdif_bug = 1,
.invert_shared_spdif = 1, /* digital/analog switch swapped */
.ac97_chip = 1} ,
+ /* Audigy 2 Platinum */
+ /* Win driver sets A_IOCFG output to 0xa00 */
{.vendor = 0x1102, .device = 0x0004, .subsystem = 0x10021102,
.driver = "Audigy2", .name = "SB Audigy 2 Platinum [SB0240P]",
.id = "Audigy2",
.emu10k1_chip = 1,
.ac97_chip = 1,
.sblive51 = 1} ,
+ /* SB Live! Platinum */
+ /* Win driver sets A_IOCFG output to 0 */
+ /* Tested by Jonathan Dowland <jon@dow.land> Apr 2023. */
{.vendor = 0x1102, .device = 0x0002, .subsystem = 0x80401102,
.driver = "EMU10K1", .name = "SB Live! Platinum [CT4760P]",
.id = "Live",
pci_set_master(pci);
- emu->fx8010.fxbus_mask = 0x303f;
+ // The masks are not used for Audigy.
+ // FIXME: these should come from the card_capabilites table.
if (extin_mask == 0)
- extin_mask = 0x3fcf;
+ extin_mask = 0x3fcf; // EXTIN_*
if (extout_mask == 0)
- extout_mask = 0x7fff;
+ extout_mask = 0x7fff; // EXTOUT_*
emu->fx8010.extin_mask = extin_mask;
emu->fx8010.extout_mask = extout_mask;
emu->enable_ir = enable_ir;
pgtbl[idx] = cpu_to_le32(silent_page | idx);
/* set up voice indices */
- for (idx = 0; idx < NUM_G; idx++) {
- emu->voices[idx].emu = emu;
+ for (idx = 0; idx < NUM_G; idx++)
emu->voices[idx].number = idx;
- }
- err = snd_emu10k1_init(emu, enable_ir, 0);
+ err = snd_emu10k1_init(emu, enable_ir);
if (err < 0)
return err;
#ifdef CONFIG_PM_SLEEP
0xff /* end */
};
static const unsigned char saved_regs_audigy[] = {
- A_ADCIDX, A_MICIDX, A_FXWC1, A_FXWC2, A_SAMPLE_RATE,
+ A_ADCIDX, A_MICIDX, A_FXWC1, A_FXWC2, A_EHC,
A_FXRT2, A_SENDAMOUNTS, A_FXRT1,
0xff /* end */
};
*val = snd_emu10k1_ptr_read(emu, *reg, i);
}
if (emu->audigy)
- emu->saved_a_iocfg = inl(emu->port + A_IOCFG);
+ emu->saved_a_iocfg = inw(emu->port + A_IOCFG);
emu->saved_hcfg = inl(emu->port + HCFG);
}
snd_emu10k1_emu1010_init(emu);
else
snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE);
- snd_emu10k1_init(emu, emu->enable_ir, 1);
+ snd_emu10k1_init(emu, emu->enable_ir);
}
void snd_emu10k1_resume_regs(struct snd_emu10k1 *emu)
/* resore for spdif */
if (emu->audigy)
- outl(emu->saved_a_iocfg, emu->port + A_IOCFG);
+ outw(emu->saved_a_iocfg, emu->port + A_IOCFG);
outl(emu->saved_hcfg, emu->port + HCFG);
val = emu->saved_ptr;
list_for_each_entry(ctl, &emu->fx8010.gpr_ctl, list) {
kcontrol = ctl->kcontrol;
if (kcontrol->id.iface == id->iface &&
- !strcmp(kcontrol->id.name, id->name) &&
- kcontrol->id.index == id->index)
+ kcontrol->id.index == id->index &&
+ !strcmp(kcontrol->id.name, id->name))
return ctl;
}
return NULL;
}
/*
- * Used for emu1010 - conversion from 32-bit capture inputs from HANA
- * to 2 x 16-bit registers in audigy - their values are read via DMA.
+ * Used for emu1010 - conversion from 32-bit capture inputs from the FPGA
+ * to 2 x 16-bit registers in Audigy - their values are read via DMA.
* Conversion is performed by Audigy DSP instructions of FX8010.
*/
static int snd_emu10k1_audigy_dsp_convert_32_to_2x16(
static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu)
{
- int err, i, z, gpr, nctl;
+ int err, z, gpr, nctl;
int bit_shifter16;
const int playback = 10;
const int capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); /* we reserve 10 voices */
icode->code = icode->tram_addr_map + 256;
/* clear free GPRs */
- for (i = 0; i < 512; i++)
- set_bit(i, icode->gpr_valid);
+ memset(icode->gpr_valid, 0xff, 512 / 8);
/* clear TRAM data & address lines */
- for (i = 0; i < 256; i++)
- set_bit(i, icode->tram_valid);
+ memset(icode->tram_valid, 0xff, 256 / 8);
strcpy(icode->name, "Audigy DSP code for ALSA");
ptr = 0;
gpr_map[gpr++] = 0x0000ffff;
bit_shifter16 = gpr;
- /* stop FX processor */
- snd_emu10k1_ptr_write(emu, A_DBG, 0, (emu->fx8010.dbg = 0) | A_DBG_SINGLE_STEP);
-
#if 1
/* PCM front Playback Volume (independent from stereo mix)
* playback = 0 + ( gpr * FXBUS_PCM_LEFT_FRONT >> 31)
#define A_ADD_VOLUME_IN(var,vol,input) \
A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input))
- /* emu1212 DSP 0 and DSP 1 Capture */
if (emu->card_capabilities->emu_model) {
+ /* EMU1010 DSP 0 and DSP 1 Capture */
+ // The 24 MSB hold the actual value. We implicitly discard the 16 LSB.
if (emu->card_capabilities->ca0108_chip) {
/* Note:JCD:No longer bit shift lower 16bits to upper 16bits of 32bit value. */
A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x0), A_C_00000001);
#endif
if (emu->card_capabilities->emu_model) {
+ /* Capture 16 channels of S32_LE sound. */
if (emu->card_capabilities->ca0108_chip) {
dev_info(emu->card->dev, "EMU2 inputs on\n");
+ /* Note that the Tina[2] DSPs have 16 more EMU32 inputs which we don't use. */
+
for (z = 0; z < 0x10; z++) {
snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp,
bit_shifter16,
}
} else {
dev_info(emu->card->dev, "EMU inputs on\n");
- /* Capture 16 (originally 8) channels of S32_LE sound */
+ /* Note that the Alice2 DSPs have 6 I2S inputs which we don't use. */
/*
dev_dbg(emu->card->dev, "emufx.c: gpr=0x%x, tmp=0x%x\n",
icode->code = icode->tram_addr_map + 160;
/* clear free GPRs */
- for (i = 0; i < 256; i++)
- set_bit(i, icode->gpr_valid);
+ memset(icode->gpr_valid, 0xff, 256 / 8);
/* clear TRAM data & address lines */
- for (i = 0; i < 160; i++)
- set_bit(i, icode->tram_valid);
+ memset(icode->tram_valid, 0xff, 160 / 8);
strcpy(icode->name, "SB Live! FX8010 code for ALSA v1.2 by Jaroslav Kysela");
ptr = 0; i = 0;
tmp = 0x88; /* we need 4 temporary GPR */
/* from 0x8c to 0xff is the area for tone control */
- /* stop FX processor */
- snd_emu10k1_ptr_write(emu, DBG, 0, (emu->fx8010.dbg = 0) | EMU10K1_DBG_SINGLE_STEP);
-
/*
* Process FX Buses
*/
struct snd_emu10k1_fx8010_info *info)
{
const char * const *fxbus, * const *extin, * const *extout;
- unsigned short fxbus_mask, extin_mask, extout_mask;
+ unsigned short extin_mask, extout_mask;
int res;
info->internal_tram_size = emu->fx8010.itram_size;
fxbus = fxbuses;
extin = emu->audigy ? audigy_ins : creative_ins;
extout = emu->audigy ? audigy_outs : creative_outs;
- fxbus_mask = emu->fx8010.fxbus_mask;
- extin_mask = emu->fx8010.extin_mask;
- extout_mask = emu->fx8010.extout_mask;
+ extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask;
+ extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask;
for (res = 0; res < 16; res++, fxbus++, extin++, extout++) {
- copy_string(info->fxbus_names[res], fxbus_mask & (1 << res) ? *fxbus : NULL, "FXBUS", res);
+ copy_string(info->fxbus_names[res], *fxbus, "FXBUS", res);
copy_string(info->extin_names[res], extin_mask & (1 << res) ? *extin : NULL, "Unused", res);
copy_string(info->extout_names[res], extout_mask & (1 << res) ? *extout : NULL, "Unused", res);
}
return -EPERM;
if (get_user(addr, (unsigned int __user *)argp))
return -EFAULT;
- if (addr > 0x1ff)
- return -EINVAL;
- if (emu->audigy)
- snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | addr);
- else
- snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | addr);
- udelay(10);
- if (emu->audigy)
- snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP | A_DBG_STEP_ADDR | addr);
- else
- snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP | EMU10K1_DBG_STEP | addr);
+ if (emu->audigy) {
+ if (addr > A_DBG_STEP_ADDR)
+ return -EINVAL;
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg |= A_DBG_SINGLE_STEP);
+ udelay(10);
+ snd_emu10k1_ptr_write(emu, A_DBG, 0, emu->fx8010.dbg | A_DBG_STEP | addr);
+ } else {
+ if (addr > EMU10K1_DBG_SINGLE_STEP_ADDR)
+ return -EINVAL;
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg |= EMU10K1_DBG_SINGLE_STEP);
+ udelay(10);
+ snd_emu10k1_ptr_write(emu, DBG, 0, emu->fx8010.dbg | EMU10K1_DBG_STEP | addr);
+ }
return 0;
case SNDRV_EMU10K1_IOCTL_DBG_READ:
if (emu->audigy)
};
/*
- * Data destinations - HANA outputs going to Alice2 (audigy) for
+ * Data destinations - FPGA outputs going to Alice2 (Audigy) for
* capture (EMU32 + I2S links)
* Each destination has an enum mixer control to choose a data source
*/
EMU_DST_ALICE2_EMU32_D,
EMU_DST_ALICE2_EMU32_E,
EMU_DST_ALICE2_EMU32_F,
+ /* These exist only on rev1 EMU1010 cards. */
EMU_DST_ALICE_I2S0_LEFT,
EMU_DST_ALICE_I2S0_RIGHT,
EMU_DST_ALICE_I2S1_LEFT,
/* 44100 */
/* Mute all */
snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE );
- /* Default fallback clock 48kHz */
+ /* Default fallback clock 44.1kHz */
snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_44_1K );
/* Word Clock source, Internal 44.1kHz x1 */
snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK,
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
unsigned int source_id;
unsigned int ngain, ogain;
- u32 gpio;
+ u16 gpio;
int change = 0;
unsigned long flags;
u32 source;
if (change) {
snd_emu10k1_i2c_write(emu, ADC_MUX, 0); /* Mute input */
spin_lock_irqsave(&emu->emu_lock, flags);
- gpio = inl(emu->port + A_IOCFG);
+ gpio = inw(emu->port + A_IOCFG);
if (source_id==0)
- outl(gpio | 0x4, emu->port + A_IOCFG);
+ outw(gpio | 0x4, emu->port + A_IOCFG);
else
- outl(gpio & ~0x4, emu->port + A_IOCFG);
+ outw(gpio & ~0x4, emu->port + A_IOCFG);
spin_unlock_irqrestore(&emu->emu_lock, flags);
ngain = emu->i2c_capture_volume[source_id][0]; /* Left */
{
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
unsigned int ogain;
- unsigned int ngain;
+ unsigned int ngain0, ngain1;
unsigned int source_id;
int change = 0;
/* capture_source: uinfo->value.enumerated.items = 2 */
if (source_id >= 2)
return -EINVAL;
+ ngain0 = ucontrol->value.integer.value[0];
+ ngain1 = ucontrol->value.integer.value[1];
+ if (ngain0 > 0xff)
+ return -EINVAL;
+ if (ngain1 > 0xff)
+ return -EINVAL;
ogain = emu->i2c_capture_volume[source_id][0]; /* Left */
- ngain = ucontrol->value.integer.value[0];
- if (ngain > 0xff)
- return 0;
- if (ogain != ngain) {
+ if (ogain != ngain0) {
if (emu->i2c_capture_source == source_id)
- snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCL, ((ngain) & 0xff) );
- emu->i2c_capture_volume[source_id][0] = ngain;
+ snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCL, ngain0);
+ emu->i2c_capture_volume[source_id][0] = ngain0;
change = 1;
}
ogain = emu->i2c_capture_volume[source_id][1]; /* Right */
- ngain = ucontrol->value.integer.value[1];
- if (ngain > 0xff)
- return 0;
- if (ogain != ngain) {
+ if (ogain != ngain1) {
if (emu->i2c_capture_source == source_id)
- snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCR, ((ngain) & 0xff));
- emu->i2c_capture_volume[source_id][1] = ngain;
+ snd_emu10k1_i2c_write(emu, ADC_ATTEN_ADCR, ngain1);
+ emu->i2c_capture_volume[source_id][1] = ngain1;
change = 1;
}
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
if (emu->audigy)
- ucontrol->value.integer.value[0] = inl(emu->port + A_IOCFG) & A_IOCFG_GPOUT0 ? 1 : 0;
+ ucontrol->value.integer.value[0] = inw(emu->port + A_IOCFG) & A_IOCFG_GPOUT0 ? 1 : 0;
else
ucontrol->value.integer.value[0] = inl(emu->port + HCFG) & HCFG_GPOUT0 ? 1 : 0;
if (emu->card_capabilities->invert_shared_spdif)
if ( emu->card_capabilities->i2c_adc) {
/* Do nothing for Audigy 2 ZS Notebook */
} else if (emu->audigy) {
- reg = inl(emu->port + A_IOCFG);
+ reg = inw(emu->port + A_IOCFG);
val = sw ? A_IOCFG_GPOUT0 : 0;
change = (reg & A_IOCFG_GPOUT0) != val;
if (change) {
reg &= ~A_IOCFG_GPOUT0;
reg |= val;
- outl(reg | val, emu->port + A_IOCFG);
+ outw(reg | val, emu->port + A_IOCFG);
}
}
reg = inl(emu->port + HCFG);
snd_pcm_period_elapsed(emu->pcm_capture_efx_substream);
}
-static snd_pcm_uframes_t snd_emu10k1_efx_playback_pointer(struct snd_pcm_substream *substream)
-{
- struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_emu10k1_pcm *epcm = runtime->private_data;
- unsigned int ptr;
-
- if (!epcm->running)
- return 0;
- ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff;
- ptr += runtime->buffer_size;
- ptr -= epcm->ccca_start_addr;
- ptr %= runtime->buffer_size;
-
- return ptr;
-}
-
static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices)
{
int err, i;
} else
snd_emu10k1_ptr_write(emu, FXRT, voice,
snd_emu10k1_compose_send_routing(send_routing));
- /* Stop CA */
/* Assumption that PT is already 0 so no harm overwriting */
snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]);
snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24));
}
static int snd_emu10k1_playback_hw_free(struct snd_pcm_substream *substream)
-{
- struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_emu10k1_pcm *epcm;
-
- if (runtime->private_data == NULL)
- return 0;
- epcm = runtime->private_data;
- if (epcm->extra) {
- snd_emu10k1_voice_free(epcm->emu, epcm->extra);
- epcm->extra = NULL;
- }
- if (epcm->voices[1]) {
- snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
- epcm->voices[1] = NULL;
- }
- if (epcm->voices[0]) {
- snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
- epcm->voices[0] = NULL;
- }
- if (epcm->memblk) {
- snd_emu10k1_free_pages(emu, epcm->memblk);
- epcm->memblk = NULL;
- epcm->start_addr = 0;
- }
- snd_pcm_lib_free_pages(substream);
- return 0;
-}
-
-static int snd_emu10k1_efx_playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
start_addr = epcm->start_addr;
end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream);
- /*
- * the kX driver leaves some space between voices
- */
channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK;
snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra,
epcm->type = PLAYBACK_EFX;
epcm->substream = substream;
- emu->pcm_playback_efx_substream = substream;
-
runtime->private_data = epcm;
runtime->private_free = snd_emu10k1_pcm_free_substream;
runtime->hw = snd_emu10k1_efx_playback;
runtime->hw.rate_min = runtime->hw.rate_max = 48000;
spin_lock_irq(&emu->reg_lock);
if (emu->card_capabilities->emu_model) {
- /* Nb. of channels has been increased to 16 */
/* TODO
- * SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE
* SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
* SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
* SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000
* Need to add mixer control to fix sample rate
*
* There are 32 mono channels of 16bits each.
- * 24bit Audio uses 2x channels over 16bit
- * 96kHz uses 2x channels over 48kHz
- * 192kHz uses 4x channels over 48kHz
- * So, for 48kHz 24bit, one has 16 channels
- * for 96kHz 24bit, one has 8 channels
- * for 192kHz 24bit, one has 4 channels
- *
+ * 24bit Audio uses 2x channels over 16bit,
+ * 96kHz uses 2x channels over 48kHz,
+ * 192kHz uses 4x channels over 48kHz.
+ * So, for 48kHz 24bit, one has 16 channels,
+ * for 96kHz 24bit, one has 8 channels,
+ * for 192kHz 24bit, one has 4 channels.
+ * 1010rev2 and 1616(m) cards have double that,
+ * but we don't exceed 16 channels anyway.
*/
#if 1
switch (emu->emu1010.internal_clock) {
.open = snd_emu10k1_efx_playback_open,
.close = snd_emu10k1_efx_playback_close,
.hw_params = snd_emu10k1_playback_hw_params,
- .hw_free = snd_emu10k1_efx_playback_hw_free,
+ .hw_free = snd_emu10k1_playback_hw_free,
.prepare = snd_emu10k1_efx_playback_prepare,
.trigger = snd_emu10k1_efx_playback_trigger,
- .pointer = snd_emu10k1_efx_playback_pointer,
+ .pointer = snd_emu10k1_playback_pointer,
};
int snd_emu10k1_pcm(struct snd_emu10k1 *emu, int device)
nval[idx / 32] |= 1 << (idx % 32);
bits++;
}
-
+
+ // Check that the number of requested channels is a power of two
+ // not bigger than the number of available channels.
for (idx = 0; idx < nefxb; idx++)
if (1 << idx == bits)
break;
-
if (idx >= nefxb)
return -EINVAL;
* to these
*/
- /* emu->efx_voices_mask[0] = FXWC_DEFAULTROUTE_C | FXWC_DEFAULTROUTE_A; */
if (emu->audigy) {
emu->efx_voices_mask[0] = 0;
if (emu->card_capabilities->emu_model)
for(i = offset; i < offset+length; i++) {
snd_iprintf(buffer, "%02X: ",i);
for (j = 0; j < voices; j++) {
- if(iobase == 0)
- value = snd_ptr_read(emu, 0, i, j);
- else
- value = snd_ptr_read(emu, 0x20, i, j);
+ value = snd_ptr_read(emu, iobase, i, j);
snd_iprintf(buffer, "%08lX ", value);
}
snd_iprintf(buffer, "\n");
return err;
}
-int snd_emu1010_fpga_write(struct snd_emu10k1 * emu, u32 reg, u32 value)
+void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value)
{
unsigned long flags;
- if (reg > 0x3f)
- return 1;
+ if (snd_BUG_ON(reg > 0x3f))
+ return;
reg += 0x40; /* 0x40 upwards are registers. */
- if (value > 0x3f) /* 0 to 0x3f are values */
- return 1;
+ if (snd_BUG_ON(value > 0x3f)) /* 0 to 0x3f are values */
+ return;
spin_lock_irqsave(&emu->emu_lock, flags);
- outl(reg, emu->port + A_IOCFG);
+ outw(reg, emu->port + A_GPIO);
udelay(10);
- outl(reg | 0x80, emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
+ outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */
udelay(10);
- outl(value, emu->port + A_IOCFG);
+ outw(value, emu->port + A_GPIO);
udelay(10);
- outl(value | 0x80 , emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
+ outw(value | 0x80 , emu->port + A_GPIO); /* High bit clocks the value into the fpga. */
spin_unlock_irqrestore(&emu->emu_lock, flags);
-
- return 0;
}
-int snd_emu1010_fpga_read(struct snd_emu10k1 * emu, u32 reg, u32 *value)
+void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value)
{
+ // The higest input pin is used as the designated interrupt trigger,
+ // so it needs to be masked out.
+ u32 mask = emu->card_capabilities->ca0108_chip ? 0x1f : 0x7f;
unsigned long flags;
- if (reg > 0x3f)
- return 1;
+ if (snd_BUG_ON(reg > 0x3f))
+ return;
reg += 0x40; /* 0x40 upwards are registers. */
spin_lock_irqsave(&emu->emu_lock, flags);
- outl(reg, emu->port + A_IOCFG);
+ outw(reg, emu->port + A_GPIO);
udelay(10);
- outl(reg | 0x80, emu->port + A_IOCFG); /* High bit clocks the value into the fpga. */
+ outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */
udelay(10);
- *value = ((inl(emu->port + A_IOCFG) >> 8) & 0x7f);
+ *value = ((inw(emu->port + A_GPIO) >> 8) & mask);
spin_unlock_irqrestore(&emu->emu_lock, flags);
-
- return 0;
}
/* Each Destination has one and only one Source,
* but one Source can feed any number of Destinations simultaneously.
*/
-int snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 * emu, u32 dst, u32 src)
+void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src)
{
- snd_emu1010_fpga_write(emu, 0x00, ((dst >> 8) & 0x3f) );
- snd_emu1010_fpga_write(emu, 0x01, (dst & 0x3f) );
- snd_emu1010_fpga_write(emu, 0x02, ((src >> 8) & 0x3f) );
- snd_emu1010_fpga_write(emu, 0x03, (src & 0x3f) );
-
- return 0;
+ if (snd_BUG_ON(dst & ~0x71f))
+ return;
+ if (snd_BUG_ON(src & ~0x71f))
+ return;
+ snd_emu1010_fpga_write(emu, 0x00, dst >> 8);
+ snd_emu1010_fpga_write(emu, 0x01, dst & 0x1f);
+ snd_emu1010_fpga_write(emu, 0x02, src >> 8);
+ snd_emu1010_fpga_write(emu, 0x03, src & 0x1f);
}
void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb)
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(CLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(CLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(CLIPH << 16, emu->port + PTR);
voicenum = 1 << (voicenum - 32);
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(HLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
unsigned int val;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(HLIEH << 16, emu->port + PTR);
val = inl(emu->port + DATA);
unsigned long flags;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(HLIPH << 16, emu->port + PTR);
voicenum = 1 << (voicenum - 32);
unsigned int sol;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(SOLEH << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
unsigned int sol;
spin_lock_irqsave(&emu->emu_lock, flags);
- /* voice interrupt */
if (voicenum >= 32) {
outl(SOLEH << 16, emu->port + PTR);
sol = inl(emu->port + DATA);
irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id)
{
struct snd_emu10k1 *emu = dev_id;
- unsigned int status, status2, orig_status, orig_status2;
+ unsigned int status, orig_status;
int handled = 0;
int timeout = 0;
status &= ~IPR_FXDSP;
}
if (status & IPR_P16V) {
- while ((status2 = inl(emu->port + IPR2)) != 0) {
- u32 mask = INTE2_PLAYBACK_CH_0_LOOP; /* Full Loop */
- struct snd_emu10k1_voice *pvoice = &(emu->p16v_voices[0]);
- struct snd_emu10k1_voice *cvoice = &(emu->p16v_capture_voice);
-
- /* dev_dbg(emu->card->dev, "status2=0x%x\n", status2); */
- orig_status2 = status2;
- if(status2 & mask) {
- if(pvoice->use) {
- snd_pcm_period_elapsed(pvoice->epcm->substream);
- } else {
- dev_err(emu->card->dev,
- "p16v: status: 0x%08x, mask=0x%08x, pvoice=%p, use=%d\n",
- status2, mask, pvoice,
- pvoice->use);
- }
- }
- if(status2 & 0x110000) {
- /* dev_info(emu->card->dev, "capture int found\n"); */
- if(cvoice->use) {
- /* dev_info(emu->card->dev, "capture period_elapsed\n"); */
- snd_pcm_period_elapsed(cvoice->epcm->substream);
- }
- }
- outl(orig_status2, emu->port + IPR2); /* ack all */
- }
+ if (emu->p16v_interrupt)
+ emu->p16v_interrupt(emu);
+ else
+ outl(0, emu->port + INTE2);
status &= ~IPR_P16V;
}
.fifo_size = 0,
};
-static void snd_p16v_pcm_free_substream(struct snd_pcm_runtime *runtime)
-{
- struct snd_emu10k1_pcm *epcm = runtime->private_data;
-
- kfree(epcm);
-}
-
/* open_playback callback */
static int snd_p16v_pcm_open_playback_channel(struct snd_pcm_substream *substream, int channel_id)
{
- struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
- struct snd_emu10k1_voice *channel = &(emu->p16v_voices[channel_id]);
- struct snd_emu10k1_pcm *epcm;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
- epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
- /* dev_dbg(emu->card->dev, "epcm kcalloc: %p\n", epcm); */
-
- if (epcm == NULL)
- return -ENOMEM;
- epcm->emu = emu;
- epcm->substream = substream;
/*
dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n",
substream->pcm->device, channel_id);
*/
- runtime->private_data = epcm;
- runtime->private_free = snd_p16v_pcm_free_substream;
runtime->hw = snd_p16v_playback_hw;
- channel->emu = emu;
- channel->number = channel_id;
-
- channel->use=1;
#if 0 /* debug */
dev_dbg(emu->card->dev,
"p16v: open channel_id=%d, channel=%p, use=0x%x\n",
channel_id, chip, channel);
#endif /* debug */
/* channel->interrupt = snd_p16v_pcm_channel_interrupt; */
- channel->epcm = epcm;
err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0)
return err;
return 0;
}
+
/* open_capture callback */
static int snd_p16v_pcm_open_capture_channel(struct snd_pcm_substream *substream, int channel_id)
{
- struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
- struct snd_emu10k1_voice *channel = &(emu->p16v_capture_voice);
- struct snd_emu10k1_pcm *epcm;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
- epcm = kzalloc(sizeof(*epcm), GFP_KERNEL);
- /* dev_dbg(emu->card->dev, "epcm kcalloc: %p\n", epcm); */
-
- if (epcm == NULL)
- return -ENOMEM;
- epcm->emu = emu;
- epcm->substream = substream;
/*
dev_dbg(emu->card->dev, "epcm device=%d, channel_id=%d\n",
substream->pcm->device, channel_id);
*/
- runtime->private_data = epcm;
- runtime->private_free = snd_p16v_pcm_free_substream;
runtime->hw = snd_p16v_capture_hw;
- channel->emu = emu;
- channel->number = channel_id;
-
- channel->use=1;
-#if 0 /* debug */
- dev_dbg(emu->card->dev,
- "p16v: open channel_id=%d, channel=%p, use=0x%x\n",
- channel_id, channel, channel->use);
- dev_dbg(emu->card->dev, "open:channel_id=%d, chip=%p, channel=%p\n",
- channel_id, chip, channel);
-#endif /* debug */
- /* channel->interrupt = snd_p16v_pcm_channel_interrupt; */
- channel->epcm = epcm;
err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0)
return err;
/* close callback */
static int snd_p16v_pcm_close_playback(struct snd_pcm_substream *substream)
{
- struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
- //struct snd_pcm_runtime *runtime = substream->runtime;
- //struct snd_emu10k1_pcm *epcm = runtime->private_data;
- emu->p16v_voices[substream->pcm->device - emu->p16v_device_offset].use = 0;
- /* FIXME: maybe zero others */
return 0;
}
/* close callback */
static int snd_p16v_pcm_close_capture(struct snd_pcm_substream *substream)
{
- struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
- //struct snd_pcm_runtime *runtime = substream->runtime;
- //struct snd_emu10k1_pcm *epcm = runtime->private_data;
- emu->p16v_capture_voice.use = 0;
- /* FIXME: maybe zero others */
return 0;
}
spin_unlock_irqrestore(&emu->emu_lock, flags);
}
+static void snd_p16v_interrupt(struct snd_emu10k1 *emu)
+{
+ unsigned int status;
+
+ while ((status = inl(emu->port + IPR2)) != 0) {
+ u32 mask = INTE2_PLAYBACK_CH_0_LOOP; /* Full Loop */
+
+ /* dev_dbg(emu->card->dev, "p16v status=0x%x\n", status); */
+ if (status & mask) {
+ struct snd_pcm_substream *substream =
+ emu->pcm_p16v->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if (runtime && runtime->private_data) {
+ snd_pcm_period_elapsed(substream);
+ } else {
+ dev_err(emu->card->dev,
+ "p16v: status: 0x%08x, mask=0x%08x\n",
+ status, mask);
+ }
+ }
+ if (status & 0x110000) {
+ struct snd_pcm_substream *substream =
+ emu->pcm_p16v->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /* dev_info(emu->card->dev, "capture int found\n"); */
+ if (runtime && runtime->private_data) {
+ /* dev_info(emu->card->dev, "capture period_elapsed\n"); */
+ snd_pcm_period_elapsed(substream);
+ }
+ }
+ outl(status, emu->port + IPR2); /* ack all */
+ }
+}
+
/* trigger_playback callback */
static int snd_p16v_pcm_trigger_playback(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime;
- struct snd_emu10k1_pcm *epcm;
int channel;
int result = 0;
struct snd_pcm_substream *s;
s->stream != SNDRV_PCM_STREAM_PLAYBACK)
continue;
runtime = s->runtime;
- epcm = runtime->private_data;
channel = substream->pcm->device-emu->p16v_device_offset;
/* dev_dbg(emu->card->dev, "p16v channel=%d\n", channel); */
- epcm->running = running;
+ runtime->private_data = (void *)(ptrdiff_t)running;
basic |= (0x1<<channel);
inte |= (INTE2_PLAYBACK_CH_0_LOOP<<channel);
snd_pcm_trigger_done(s, substream);
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_emu10k1_pcm *epcm = runtime->private_data;
int channel = 0;
int result = 0;
u32 inte = INTE2_CAPTURE_CH_0_LOOP | INTE2_CAPTURE_CH_0_HALF_LOOP;
case SNDRV_PCM_TRIGGER_START:
snd_p16v_intr_enable(emu, inte);
snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0)|(0x100<<channel));
- epcm->running = 1;
+ runtime->private_data = (void *)1;
break;
case SNDRV_PCM_TRIGGER_STOP:
snd_emu10k1_ptr20_write(emu, BASIC_INTERRUPT, 0, snd_emu10k1_ptr20_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<<channel));
snd_p16v_intr_disable(emu, inte);
//snd_emu10k1_ptr20_write(emu, EXTENDED_INT_MASK, 0, snd_emu10k1_ptr20_read(emu, EXTENDED_INT_MASK, 0) & ~(0x110000<<channel));
- epcm->running = 0;
+ runtime->private_data = NULL;
break;
default:
result = -EINVAL;
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_emu10k1_pcm *epcm = runtime->private_data;
snd_pcm_uframes_t ptr, ptr1, ptr2,ptr3,ptr4 = 0;
int channel = substream->pcm->device - emu->p16v_device_offset;
- if (!epcm->running)
+
+ if (!runtime->private_data)
return 0;
ptr3 = snd_emu10k1_ptr20_read(emu, PLAYBACK_LIST_PTR, channel);
{
struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_emu10k1_pcm *epcm = runtime->private_data;
snd_pcm_uframes_t ptr, ptr1, ptr2 = 0;
int channel = 0;
- if (!epcm->running)
+ if (!runtime->private_data)
return 0;
ptr1 = snd_emu10k1_ptr20_read(emu, CAPTURE_POINTER, channel);
pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
strcpy(pcm->name, "p16v");
emu->pcm_p16v = pcm;
+ emu->p16v_interrupt = snd_p16v_interrupt;
for(substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
substream;
*/
/********************************************************************************************************/
-/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */
+/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */
/********************************************************************************************************/
/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE.
*/
/******************************************************************************/
-/* Audigy2Value Tina (P17V) pointer-offset register set,
- * accessed through the PTR20 and DATA24 registers */
+/* Audigy2Value Tina (P17V) pointer-offset register set, */
+/* accessed through the PTR2 and DATA2 registers */
/******************************************************************************/
/* 00 - 07: Not used */
break;
case HDA_GEN_PCM_ACT_PREPARE:
mutex_lock(&cs35l41->fw_mutex);
- ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1);
+ ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 1, NULL);
mutex_unlock(&cs35l41->fw_mutex);
break;
case HDA_GEN_PCM_ACT_CLEANUP:
mutex_lock(&cs35l41->fw_mutex);
regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute));
- ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0);
+ ret = cs35l41_global_enable(reg, cs35l41->hw_cfg.bst_type, 0, NULL);
mutex_unlock(&cs35l41->fw_mutex);
break;
case HDA_GEN_PCM_ACT_CLOSE:
if (cs35l41->playback_started) {
regmap_multi_reg_write(cs35l41->regmap, cs35l41_hda_mute,
ARRAY_SIZE(cs35l41_hda_mute));
- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0);
+ cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0, NULL);
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT);
if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST)
/* Meteorlake-P */
{ PCI_DEVICE(0x8086, 0x7e28),
.driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
+ /* Lunarlake-P */
+ { PCI_DEVICE(0x8086, 0xa828),
+ .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE},
/* Broxton-P(Apollolake) */
{ PCI_DEVICE(0x8086, 0x5a98),
.driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON },
return; /* no error return from async probe */
}
-static int hda_tegra_remove(struct platform_device *pdev)
+static void hda_tegra_remove(struct platform_device *pdev)
{
snd_card_free(dev_get_drvdata(&pdev->dev));
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static void hda_tegra_shutdown(struct platform_device *pdev)
.of_match_table = hda_tegra_match,
},
.probe = hda_tegra_probe,
- .remove = hda_tegra_remove,
+ .remove_new = hda_tegra_remove,
.shutdown = hda_tegra_shutdown,
};
module_platform_driver(tegra_platform_hda);
goto unlock;
}
- if (snd_BUG_ON(pin_idx < 0)) {
- err = -EINVAL;
- goto unlock;
- }
per_pin = get_pin(spec, pin_idx);
/* Verify pin:cvt selections to avoid silent audio after S3.
snd_hda_spdif_ctls_unassign(codec, pcm_idx);
clear_bit(pcm_idx, &spec->pcm_in_use);
pin_idx = hinfo_to_pin_index(codec, hinfo);
+ /*
+ * In such a case, return 0 to match the behavior in
+ * hdmi_pcm_open()
+ */
if (pin_idx < 0)
goto unlock;
- if (snd_BUG_ON(pin_idx < 0)) {
- err = -EINVAL;
- goto unlock;
- }
per_pin = get_pin(spec, pin_idx);
if (spec->dyn_pin_out) {
return 0;
}
-static inline int copy_u32_le(void __user *dest, void __iomem *src)
-{
- u32 val = readl(src);
- return copy_to_user(dest, &val, 4);
-}
-
static int snd_hdspm_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
unsigned int cmd, unsigned long arg)
{
case 0x204: legacy_ctrl2 |= 2 << 6; break;
case 0x205: legacy_ctrl2 |= 3 << 6; break;
default:
- dev_err(chip->card->dev,
- "invalid joystick port %#x", io_port);
+ if (io_port > 0)
+ dev_err(chip->card->dev,
+ "The %s does not support arbitrary IO ports for the game port (requested 0x%x)\n",
+ chip->card->shortname, (unsigned int)io_port);
return -EINVAL;
}
}
default: model = str = "???"; break;
}
+ strcpy(card->driver, str);
+ sprintf(card->shortname, "Yamaha %s (%s)", model, str);
+ sprintf(card->longname, "%s at 0x%lx, irq %i",
+ card->shortname,
+ chip->reg_area_phys,
+ chip->irq);
+
legacy_ctrl = 0;
legacy_ctrl2 = 0x0800; /* SBEN = 0, SMOD = 01, LAD = 0 */
case 0x398: legacy_ctrl2 |= 1; break;
case 0x3a0: legacy_ctrl2 |= 2; break;
case 0x3a8: legacy_ctrl2 |= 3; break;
- default: fm_port[dev] = 0; break;
+ default:
+ if (fm_port[dev] > 0)
+ dev_err(card->dev,
+ "The %s does not support arbitrary IO ports for FM (requested 0x%x)\n",
+ card->shortname, (unsigned int)fm_port[dev]);
+ fm_port[dev] = 0;
+ break;
}
if (fm_port[dev] > 0)
fm_res = devm_request_region(&pci->dev, fm_port[dev],
case 0x300: legacy_ctrl2 |= 1 << 4; break;
case 0x332: legacy_ctrl2 |= 2 << 4; break;
case 0x334: legacy_ctrl2 |= 3 << 4; break;
- default: mpu_port[dev] = 0; break;
+ default:
+ if (mpu_port[dev] > 0)
+ dev_err(card->dev,
+ "The %s does not support arbitrary IO ports for MPU-401 (requested 0x%x)\n",
+ card->shortname, (unsigned int)mpu_port[dev]);
+ mpu_port[dev] = 0;
+ break;
}
if (mpu_port[dev] > 0)
mpu_res = devm_request_region(&pci->dev, mpu_port[dev],
if (err < 0)
return err;
- strcpy(card->driver, str);
- sprintf(card->shortname, "Yamaha %s (%s)", model, str);
- sprintf(card->longname, "%s at 0x%lx, irq %i",
- card->shortname,
- chip->reg_area_phys,
- chip->irq);
err = snd_ymfpci_pcm(chip, 0);
if (err < 0)
return err;
.name = KBUILD_MODNAME,
.id_table = snd_ymfpci_ids,
.probe = snd_card_ymfpci_probe,
-#ifdef CONFIG_PM_SLEEP
.driver = {
- .pm = &snd_ymfpci_pm,
+ .pm = pm_sleep_ptr(&snd_ymfpci_pm),
},
-#endif
};
module_pci_driver(ymfpci_driver);
u32 shift;
};
+static const int saved_regs_index[] = {
+ /* spdif */
+ YDSXGR_SPDIFOUTCTRL,
+ YDSXGR_SPDIFOUTSTATUS,
+ YDSXGR_SPDIFINCTRL,
+ /* volumes */
+ YDSXGR_PRIADCLOOPVOL,
+ YDSXGR_NATIVEDACINVOL,
+ YDSXGR_NATIVEDACOUTVOL,
+ YDSXGR_BUF441OUTVOL,
+ YDSXGR_NATIVEADCINVOL,
+ YDSXGR_SPDIFLOOPVOL,
+ YDSXGR_SPDIFOUTVOL,
+ YDSXGR_ZVOUTVOL,
+ YDSXGR_LEGACYOUTVOL,
+ /* address bases */
+ YDSXGR_PLAYCTRLBASE,
+ YDSXGR_RECCTRLBASE,
+ YDSXGR_EFFCTRLBASE,
+ YDSXGR_WORKBASE,
+ /* capture set up */
+ YDSXGR_MAPOFREC,
+ YDSXGR_RECFORMAT,
+ YDSXGR_RECSLOTSR,
+ YDSXGR_ADCFORMAT,
+ YDSXGR_ADCSLOTSR,
+};
+#define YDSXGR_NUM_SAVED_REGS ARRAY_SIZE(saved_regs_index)
+
+static const int pci_saved_regs_index[] = {
+ /* All Chips */
+ PCIR_DSXG_LEGACY,
+ PCIR_DSXG_ELEGACY,
+ /* YMF 744/754 */
+ PCIR_DSXG_FMBASE,
+ PCIR_DSXG_SBBASE,
+ PCIR_DSXG_MPU401BASE,
+ PCIR_DSXG_JOYBASE,
+};
+#define DSXG_PCI_NUM_SAVED_REGS ARRAY_SIZE(pci_saved_regs_index)
+#define DSXG_PCI_NUM_SAVED_LEGACY_REGS 2
+static_assert(DSXG_PCI_NUM_SAVED_LEGACY_REGS <= DSXG_PCI_NUM_SAVED_REGS);
+
struct snd_ymfpci {
int irq;
unsigned long reg_area_phys;
void __iomem *reg_area_virt;
- unsigned short old_legacy_ctrl;
+ u16 old_legacy_ctrl;
#ifdef SUPPORT_JOYSTICK
struct gameport *gameport;
#endif
const struct firmware *dsp_microcode;
const struct firmware *controller_microcode;
-#ifdef CONFIG_PM_SLEEP
- u32 *saved_regs;
+ u32 saved_regs[YDSXGR_NUM_SAVED_REGS];
u32 saved_ydsxgr_mode;
- u16 saved_dsxg_legacy;
- u16 saved_dsxg_elegacy;
-#endif
+ u16 saved_dsxg_pci_regs[DSXG_PCI_NUM_SAVED_REGS];
};
int snd_ymfpci_create(struct snd_card *card,
struct pci_dev *pci,
- unsigned short old_legacy_ctrl);
+ u16 old_legacy_ctrl);
void snd_ymfpci_free_gameport(struct snd_ymfpci *chip);
extern const struct dev_pm_ops snd_ymfpci_pm;
static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip);
-static inline u8 snd_ymfpci_readb(struct snd_ymfpci *chip, u32 offset)
-{
- return readb(chip->reg_area_virt + offset);
-}
-
static inline void snd_ymfpci_writeb(struct snd_ymfpci *chip, u32 offset, u8 val)
{
writeb(val, chip->reg_area_virt + offset);
snd_ymfpci_free_gameport(chip);
- pci_write_config_word(chip->pci, 0x40, chip->old_legacy_ctrl);
+ pci_write_config_word(chip->pci, PCIR_DSXG_LEGACY, chip->old_legacy_ctrl);
release_firmware(chip->dsp_microcode);
release_firmware(chip->controller_microcode);
}
-#ifdef CONFIG_PM_SLEEP
-static const int saved_regs_index[] = {
- /* spdif */
- YDSXGR_SPDIFOUTCTRL,
- YDSXGR_SPDIFOUTSTATUS,
- YDSXGR_SPDIFINCTRL,
- /* volumes */
- YDSXGR_PRIADCLOOPVOL,
- YDSXGR_NATIVEDACINVOL,
- YDSXGR_NATIVEDACOUTVOL,
- YDSXGR_BUF441OUTVOL,
- YDSXGR_NATIVEADCINVOL,
- YDSXGR_SPDIFLOOPVOL,
- YDSXGR_SPDIFOUTVOL,
- YDSXGR_ZVOUTVOL,
- YDSXGR_LEGACYOUTVOL,
- /* address bases */
- YDSXGR_PLAYCTRLBASE,
- YDSXGR_RECCTRLBASE,
- YDSXGR_EFFCTRLBASE,
- YDSXGR_WORKBASE,
- /* capture set up */
- YDSXGR_MAPOFREC,
- YDSXGR_RECFORMAT,
- YDSXGR_RECSLOTSR,
- YDSXGR_ADCFORMAT,
- YDSXGR_ADCSLOTSR,
-};
-#define YDSXGR_NUM_SAVED_REGS ARRAY_SIZE(saved_regs_index)
-
static int snd_ymfpci_suspend(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
struct snd_ymfpci *chip = card->private_data;
- unsigned int i;
-
+ unsigned int i, legacy_reg_count = DSXG_PCI_NUM_SAVED_LEGACY_REGS;
+
+ if (chip->pci->device >= 0x0010) /* YMF 744/754 */
+ legacy_reg_count = DSXG_PCI_NUM_SAVED_REGS;
+
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
snd_ac97_suspend(chip->ac97);
+
for (i = 0; i < YDSXGR_NUM_SAVED_REGS; i++)
chip->saved_regs[i] = snd_ymfpci_readl(chip, saved_regs_index[i]);
+
chip->saved_ydsxgr_mode = snd_ymfpci_readl(chip, YDSXGR_MODE);
- pci_read_config_word(chip->pci, PCIR_DSXG_LEGACY,
- &chip->saved_dsxg_legacy);
- pci_read_config_word(chip->pci, PCIR_DSXG_ELEGACY,
- &chip->saved_dsxg_elegacy);
+
+ for (i = 0; i < legacy_reg_count; i++)
+ pci_read_config_word(chip->pci, pci_saved_regs_index[i],
+ chip->saved_dsxg_pci_regs + i);
+
snd_ymfpci_writel(chip, YDSXGR_NATIVEDACOUTVOL, 0);
snd_ymfpci_writel(chip, YDSXGR_BUF441OUTVOL, 0);
snd_ymfpci_disable_dsp(chip);
struct pci_dev *pci = to_pci_dev(dev);
struct snd_card *card = dev_get_drvdata(dev);
struct snd_ymfpci *chip = card->private_data;
- unsigned int i;
+ unsigned int i, legacy_reg_count = DSXG_PCI_NUM_SAVED_LEGACY_REGS;
+
+ if (chip->pci->device >= 0x0010) /* YMF 744/754 */
+ legacy_reg_count = DSXG_PCI_NUM_SAVED_REGS;
snd_ymfpci_aclink_reset(pci);
snd_ymfpci_codec_ready(chip, 0);
snd_ac97_resume(chip->ac97);
- pci_write_config_word(chip->pci, PCIR_DSXG_LEGACY,
- chip->saved_dsxg_legacy);
- pci_write_config_word(chip->pci, PCIR_DSXG_ELEGACY,
- chip->saved_dsxg_elegacy);
+ for (i = 0; i < legacy_reg_count; i++)
+ pci_write_config_word(chip->pci, pci_saved_regs_index[i],
+ chip->saved_dsxg_pci_regs[i]);
/* start hw again */
if (chip->start_count > 0) {
return 0;
}
-SIMPLE_DEV_PM_OPS(snd_ymfpci_pm, snd_ymfpci_suspend, snd_ymfpci_resume);
-#endif /* CONFIG_PM_SLEEP */
+DEFINE_SIMPLE_DEV_PM_OPS(snd_ymfpci_pm, snd_ymfpci_suspend, snd_ymfpci_resume);
int snd_ymfpci_create(struct snd_card *card,
struct pci_dev *pci,
- unsigned short old_legacy_ctrl)
+ u16 old_legacy_ctrl)
{
struct snd_ymfpci *chip = card->private_data;
int err;
if (err < 0)
return err;
-#ifdef CONFIG_PM_SLEEP
- chip->saved_regs = devm_kmalloc_array(&pci->dev, YDSXGR_NUM_SAVED_REGS,
- sizeof(u32), GFP_KERNEL);
- if (!chip->saved_regs)
- return -ENOMEM;
-#endif
-
snd_ymfpci_proc_init(card, chip);
return 0;
}
-static int snd_pmac_remove(struct platform_device *devptr)
+static void snd_pmac_remove(struct platform_device *devptr)
{
snd_card_free(platform_get_drvdata(devptr));
- return 0;
}
#ifdef CONFIG_PM_SLEEP
static struct platform_driver snd_pmac_driver = {
.probe = snd_pmac_probe,
- .remove = snd_pmac_remove,
+ .remove_new = snd_pmac_remove,
.driver = {
.name = SND_PMAC_DRIVER,
.pm = SND_PMAC_PM_OPS,
for_each_child_of_node(chip->node, np) {
if (of_node_name_eq(np, "sound")) {
- if (of_get_property(np, "has-anded-reset", NULL))
+ if (of_property_read_bool(np, "has-anded-reset"))
mix->anded_reset = 1;
- if (of_get_property(np, "layout-id", NULL))
+ if (of_property_present(np, "layout-id"))
mix->reset_on_sleep = 0;
of_node_put(np);
break;
return 0;
}
-static int snd_aica_remove(struct platform_device *devptr)
+static void snd_aica_remove(struct platform_device *devptr)
{
struct snd_card_aica *dreamcastcard;
dreamcastcard = platform_get_drvdata(devptr);
- if (unlikely(!dreamcastcard))
- return -ENODEV;
snd_card_free(dreamcastcard->card);
kfree(dreamcastcard);
- return 0;
}
static int snd_aica_probe(struct platform_device *devptr)
static struct platform_driver snd_aica_driver = {
.probe = snd_aica_probe,
- .remove = snd_aica_remove,
+ .remove_new = snd_aica_remove,
.driver = {
.name = SND_AICA_DRIVER,
},
/* driver .remove -- destructor */
-static int snd_sh_dac_remove(struct platform_device *devptr)
+static void snd_sh_dac_remove(struct platform_device *devptr)
{
snd_card_free(platform_get_drvdata(devptr));
- return 0;
}
/* free -- it has been defined by create */
*/
static struct platform_driver sh_dac_driver = {
.probe = snd_sh_dac_probe,
- .remove = snd_sh_dac_remove,
+ .remove_new = snd_sh_dac_remove,
.driver = {
.name = "dac_audio",
},
return ret;
}
-static int axi_i2s_dev_remove(struct platform_device *pdev)
+static void axi_i2s_dev_remove(struct platform_device *pdev)
{
struct axi_i2s *i2s = platform_get_drvdata(pdev);
clk_disable_unprepare(i2s->clk);
-
- return 0;
}
static const struct of_device_id axi_i2s_of_match[] = {
.of_match_table = axi_i2s_of_match,
},
.probe = axi_i2s_probe,
- .remove = axi_i2s_dev_remove,
+ .remove_new = axi_i2s_dev_remove,
};
module_platform_driver(axi_i2s_driver);
return ret;
}
-static int axi_spdif_dev_remove(struct platform_device *pdev)
+static void axi_spdif_dev_remove(struct platform_device *pdev)
{
struct axi_spdif *spdif = platform_get_drvdata(pdev);
clk_disable_unprepare(spdif->clk);
-
- return 0;
}
static const struct of_device_id axi_spdif_of_match[] = {
.of_match_table = axi_spdif_of_match,
},
.probe = axi_spdif_probe,
- .remove = axi_spdif_dev_remove,
+ .remove_new = axi_spdif_dev_remove,
};
module_platform_driver(axi_spdif_driver);
config SND_SOC_AMD_ACP6x
tristate "AMD Audio Coprocessor-v6.x Yellow Carp support"
+ select SND_AMD_ACP_CONFIG
depends on X86 && PCI
help
This option enables Audio Coprocessor i.e ACP v6.x support on
config SND_SOC_AMD_PS
tristate "AMD Audio Coprocessor-v6.3 Pink Sardine support"
+ select SND_AMD_ACP_CONFIG
depends on X86 && PCI && ACPI
help
This option enables Audio Coprocessor i.e ACP v6.3 support on
return status;
}
-static int acp_audio_remove(struct platform_device *pdev)
+static void acp_audio_remove(struct platform_device *pdev)
{
int status;
struct audio_drv_data *adata = dev_get_drvdata(&pdev->dev);
if (status)
dev_err(&pdev->dev, "ACP Deinit failed status:%d\n", status);
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static int acp_pcm_resume(struct device *dev)
static struct platform_driver acp_dma_driver = {
.probe = acp_audio_probe,
- .remove = acp_audio_remove,
+ .remove_new = acp_audio_remove,
.driver = {
.name = DRV_NAME,
.pm = &acp_pm_ops,
return 0;
}
-static int rembrandt_audio_remove(struct platform_device *pdev)
+static void rembrandt_audio_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
- struct acp_chip_info *chip;
-
- chip = dev_get_platdata(&pdev->dev);
- if (!chip || !chip->base) {
- dev_err(&pdev->dev, "ACP chip data is NULL\n");
- return -ENODEV;
- }
+ struct acp_chip_info *chip = dev_get_platdata(dev);
rmb_acp_deinit(chip->base);
acp6x_disable_interrupts(adata);
acp_platform_unregister(dev);
- return 0;
}
static struct platform_driver rembrandt_driver = {
.probe = rembrandt_audio_probe,
- .remove = rembrandt_audio_remove,
+ .remove_new = rembrandt_audio_remove,
.driver = {
.name = "acp_asoc_rembrandt",
},
return 0;
}
-static int renoir_audio_remove(struct platform_device *pdev)
+static void renoir_audio_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct acp_dev_data *adata = dev_get_drvdata(dev);
dev_err(&pdev->dev, "ACP de-init Failed (%pe)\n", ERR_PTR(ret));
acp_platform_unregister(dev);
- return 0;
}
static struct platform_driver renoir_driver = {
.probe = renoir_audio_probe,
- .remove = renoir_audio_remove,
+ .remove_new = renoir_audio_remove,
.driver = {
.name = "acp_asoc_renoir",
},
u16 pdev_count;
u16 pdm_dev_index;
};
+
+int snd_amd_acp_find_config(struct pci_dev *pci);
dev_err(dev, "ACP reset failed\n");
return ret;
}
- acp63_writel(0x03, acp_base + ACP_CLKMUX_SEL);
acp63_enable_interrupts(acp_base);
return 0;
}
dev_err(dev, "ACP reset failed\n");
return ret;
}
- acp63_writel(0, acp_base + ACP_CLKMUX_SEL);
acp63_writel(0, acp_base + ACP_CONTROL);
return 0;
}
{
struct acp63_dev_data *adata;
u32 addr;
- u32 irqflags;
+ u32 irqflags, flag;
int val;
int ret;
irqflags = IRQF_SHARED;
+
+ /* Return if acp config flag is defined */
+ flag = snd_amd_acp_find_config(pci);
+ if (flag)
+ return -ENODEV;
+
/* Pink Sardine device check */
switch (pci->revision) {
case 0x63:
return 0;
}
-static int acp63_pdm_audio_remove(struct platform_device *pdev)
+static void acp63_pdm_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
static int __maybe_unused acp63_pdm_resume(struct device *dev)
static struct platform_driver acp63_pdm_dma_driver = {
.probe = acp63_pdm_audio_probe,
- .remove = acp63_pdm_audio_remove,
+ .remove_new = acp63_pdm_audio_remove,
.driver = {
.name = "acp_ps_pdm_dma",
.pm = &acp63_pdm_pm_ops,
return 0;
}
-static int acp3x_audio_remove(struct platform_device *pdev)
+static void acp3x_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
static int acp3x_resume(struct device *dev)
static struct platform_driver acp3x_dma_driver = {
.probe = acp3x_audio_probe,
- .remove = acp3x_audio_remove,
+ .remove_new = acp3x_audio_remove,
.driver = {
.name = "acp3x_rv_i2s_dma",
.pm = &acp3x_pm_ops,
return 0;
}
-static int acp_pdm_audio_remove(struct platform_device *pdev)
+static void acp_pdm_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
static int acp_pdm_resume(struct device *dev)
static struct platform_driver acp_pdm_dma_driver = {
.probe = acp_pdm_audio_probe,
- .remove = acp_pdm_audio_remove,
+ .remove_new = acp_pdm_audio_remove,
.driver = {
.name = "acp_rn_pdm_dma",
.pm = &acp_pdm_pm_ops,
* Copyright 2021 Advanced Micro Devices, Inc.
*/
-#include <sound/soc.h>
-#include <sound/soc-dapm.h>
-#include <linux/module.h>
-#include <linux/io.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-
-#include <sound/jack.h>
-#include <linux/clk.h>
-#include <linux/gpio.h>
-#include <linux/gpio/consumer.h>
-#include <linux/i2c.h>
-#include <linux/input.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input-event-codes.h>
+#include <linux/module.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
#include "../../codecs/nau8821.h"
-#include "../../codecs/cs35l41.h"
-
#include "acp5x.h"
-#define DRV_NAME "acp5x_mach"
-#define DUAL_CHANNEL 2
-#define ACP5X_NUVOTON_CODEC_DAI "nau8821-hifi"
-#define VG_JUPITER 1
-#define ACP5X_NUVOTON_BCLK 3072000
-#define ACP5X_NAU8821_FREQ_OUT 12288000
+#define DRV_NAME "acp5x_mach"
+#define DUAL_CHANNEL 2
+#define VG_JUPITER 1
+#define ACP5X_NAU8821_BCLK 3072000
+#define ACP5X_NAU8821_FREQ_OUT 12288000
+#define ACP5X_NAU8821_COMP_NAME "i2c-NVTN2020:00"
+#define ACP5X_NAU8821_DAI_NAME "nau8821-hifi"
+#define ACP5X_CS35L41_COMP_LNAME "spi-VLV1776:00"
+#define ACP5X_CS35L41_COMP_RNAME "spi-VLV1776:01"
+#define ACP5X_CS35L41_DAI_NAME "cs35l41-pcm"
static unsigned long acp5x_machine_id;
static struct snd_soc_jack vg_headset;
+SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0")));
+SND_SOC_DAILINK_DEF(acp5x_i2s, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0")));
+SND_SOC_DAILINK_DEF(acp5x_bt, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1")));
+SND_SOC_DAILINK_DEF(nau8821, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_NAU8821_COMP_NAME,
+ ACP5X_NAU8821_DAI_NAME)));
+
static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = {
{
.pin = "Headphone",
},
};
+static const struct snd_kcontrol_new acp5x_8821_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+};
+
+static int platform_clock_control(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct snd_soc_dai *dai;
+ int ret = 0;
+
+ dai = snd_soc_card_get_codec_dai(card, ACP5X_NAU8821_DAI_NAME);
+ if (!dai) {
+ dev_err(card->dev, "Codec dai not found\n");
+ return -EIO;
+ }
+
+ if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "set sysclk err = %d\n", ret);
+ return -EIO;
+ }
+ } else {
+ ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_FLL_BLK, 0, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(dai->dev, "can't set BLK clock %d\n", ret);
+ ret = snd_soc_dai_set_pll(dai, 0, 0, ACP5X_NAU8821_BCLK, ACP5X_NAU8821_FREQ_OUT);
+ if (ret < 0)
+ dev_err(dai->dev, "can't set FLL: %d\n", ret);
+ }
+
+ return ret;
+}
+
static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd)
{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
int ret;
- struct snd_soc_card *card = rtd->card;
- struct snd_soc_component *component =
- asoc_rtd_to_codec(rtd, 0)->component;
/*
* Headset buttons map to the google Reference headset.
* These can be configured by userspace.
*/
- ret = snd_soc_card_jack_new_pins(card, "Headset Jack",
+ ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_BTN_0,
&vg_headset, acp5x_nau8821_jack_pins,
ARRAY_SIZE(acp5x_nau8821_jack_pins));
snd_jack_set_key(vg_headset.jack, SND_JACK_BTN_0, KEY_MEDIA);
nau8821_enable_jack_detect(component, &vg_headset);
- return ret;
-}
-static int acp5x_cs35l41_init(struct snd_soc_pcm_runtime *rtd)
-{
- return 0;
+ return ret;
}
static const unsigned int rates[] = {
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_card *card = rtd->card;
- struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card);
+ struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card);
machine->play_i2s_instance = I2S_SP_INSTANCE;
machine->cap_i2s_instance = I2S_SP_INSTANCE;
snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
&constraints_sample_bits);
+
return 0;
}
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_card *card = rtd->card;
- struct snd_soc_dai *codec_dai =
- snd_soc_card_get_codec_dai(card,
- ACP5X_NUVOTON_CODEC_DAI);
- int ret;
+ struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(card, ACP5X_NAU8821_DAI_NAME);
+ int ret, bclk;
- ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0,
- SND_SOC_CLOCK_IN);
+ ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_FLL_BLK, 0, SND_SOC_CLOCK_IN);
if (ret < 0)
dev_err(card->dev, "can't set FS clock %d\n", ret);
- ret = snd_soc_dai_set_pll(codec_dai, 0, 0, snd_soc_params_to_bclk(params),
- params_rate(params) * 256);
+
+ bclk = snd_soc_params_to_bclk(params);
+ if (bclk < 0) {
+ dev_err(dai->dev, "Fail to get BCLK rate: %d\n", bclk);
+ return bclk;
+ }
+
+ ret = snd_soc_dai_set_pll(dai, 0, 0, bclk, params_rate(params) * 256);
if (ret < 0)
dev_err(card->dev, "can't set FLL: %d\n", ret);
return ret;
}
+static const struct snd_soc_ops acp5x_8821_ops = {
+ .startup = acp5x_8821_startup,
+ .hw_params = acp5x_nau8821_hw_params,
+};
+
static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream)
{
- struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_card *card = rtd->card;
- struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card);
+ struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_pcm_runtime *runtime = substream->runtime;
machine->play_i2s_instance = I2S_HS_INSTANCE;
&constraints_channels);
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);
+
return 0;
}
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_card *card = rtd->card;
- struct snd_soc_dai *codec_dai;
+ unsigned int bclk, rate = params_rate(params);
+ struct snd_soc_component *comp;
int ret, i;
- unsigned int num_codecs = rtd->dai_link->num_codecs;
- unsigned int bclk_val;
-
- ret = 0;
- for (i = 0; i < num_codecs; i++) {
- codec_dai = asoc_rtd_to_codec(rtd, i);
- if (strcmp(codec_dai->name, "cs35l41-pcm") == 0) {
- switch (params_rate(params)) {
- case 48000:
- bclk_val = 1536000;
- break;
- default:
- dev_err(card->dev, "Invalid Samplerate:0x%x\n",
- params_rate(params));
+
+ switch (rate) {
+ case 48000:
+ bclk = 1536000;
+ break;
+ default:
+ bclk = 0;
+ break;
+ }
+
+ for_each_rtd_components(rtd, i, comp) {
+ if (!(strcmp(comp->name, ACP5X_CS35L41_COMP_LNAME)) ||
+ !(strcmp(comp->name, ACP5X_CS35L41_COMP_RNAME))) {
+ if (!bclk) {
+ dev_err(comp->dev, "Invalid sample rate: 0x%x\n", rate);
return -EINVAL;
}
- ret = snd_soc_component_set_sysclk(codec_dai->component,
- 0, 0, bclk_val, SND_SOC_CLOCK_IN);
- if (ret < 0) {
- dev_err(card->dev, "failed to set sysclk for CS35l41 dai\n");
+
+ ret = snd_soc_component_set_sysclk(comp, 0, 0, bclk, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(comp->dev, "failed to set SYSCLK: %d\n", ret);
return ret;
}
}
}
- return ret;
-}
+ return 0;
-static const struct snd_soc_ops acp5x_8821_ops = {
- .startup = acp5x_8821_startup,
- .hw_params = acp5x_nau8821_hw_params,
-};
+}
static const struct snd_soc_ops acp5x_cs35l41_play_ops = {
.startup = acp5x_cs35l41_startup,
.hw_params = acp5x_cs35l41_hw_params,
};
-static struct snd_soc_codec_conf cs35l41_conf[] = {
+static struct snd_soc_codec_conf acp5x_cs35l41_conf[] = {
{
- .dlc = COMP_CODEC_CONF("spi-VLV1776:00"),
+ .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_LNAME),
.name_prefix = "Left",
},
{
- .dlc = COMP_CODEC_CONF("spi-VLV1776:01"),
+ .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_RNAME),
.name_prefix = "Right",
},
};
-SND_SOC_DAILINK_DEF(acp5x_i2s,
- DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0")));
+SND_SOC_DAILINK_DEF(cs35l41, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_CS35L41_COMP_LNAME,
+ ACP5X_CS35L41_DAI_NAME),
+ COMP_CODEC(ACP5X_CS35L41_COMP_RNAME,
+ ACP5X_CS35L41_DAI_NAME)));
-SND_SOC_DAILINK_DEF(acp5x_bt,
- DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1")));
-
-SND_SOC_DAILINK_DEF(nau8821,
- DAILINK_COMP_ARRAY(COMP_CODEC("i2c-NVTN2020:00",
- "nau8821-hifi")));
-
-SND_SOC_DAILINK_DEF(cs35l41,
- DAILINK_COMP_ARRAY(COMP_CODEC("spi-VLV1776:00", "cs35l41-pcm"),
- COMP_CODEC("spi-VLV1776:01", "cs35l41-pcm")));
-
-SND_SOC_DAILINK_DEF(platform,
- DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0")));
-
-static struct snd_soc_dai_link acp5x_dai[] = {
+static struct snd_soc_dai_link acp5x_8821_35l41_dai[] = {
{
.name = "acp5x-8821-play",
.stream_name = "Playback/Capture",
- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
.dpcm_playback = 1,
.dpcm_capture = 1,
{
.name = "acp5x-CS35L41-Stereo",
.stream_name = "CS35L41 Stereo Playback",
- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
.dpcm_playback = 1,
.playback_only = 1,
.ops = &acp5x_cs35l41_play_ops,
- .init = acp5x_cs35l41_init,
SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform),
},
};
-static int platform_clock_control(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *k, int event)
-{
- struct snd_soc_dapm_context *dapm = w->dapm;
- struct snd_soc_card *card = dapm->card;
- struct snd_soc_dai *codec_dai;
- int ret = 0;
- codec_dai = snd_soc_card_get_codec_dai(card, ACP5X_NUVOTON_CODEC_DAI);
- if (!codec_dai) {
- dev_err(card->dev, "Codec dai not found\n");
- return -EIO;
- }
- if (SND_SOC_DAPM_EVENT_OFF(event)) {
- ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_INTERNAL,
- 0, SND_SOC_CLOCK_IN);
- if (ret < 0) {
- dev_err(card->dev, "set sysclk err = %d\n", ret);
- return -EIO;
- }
- } else {
- ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0,
- SND_SOC_CLOCK_IN);
- if (ret < 0)
- dev_err(codec_dai->dev, "can't set BLK clock %d\n", ret);
- ret = snd_soc_dai_set_pll(codec_dai, 0, 0, ACP5X_NUVOTON_BCLK,
- ACP5X_NAU8821_FREQ_OUT);
- if (ret < 0)
- dev_err(codec_dai->dev, "can't set FLL: %d\n", ret);
- }
- return ret;
-}
-
-static const struct snd_kcontrol_new acp5x_8821_controls[] = {
- SOC_DAPM_PIN_SWITCH("Headphone"),
- SOC_DAPM_PIN_SWITCH("Headset Mic"),
- SOC_DAPM_PIN_SWITCH("Int Mic"),
-};
-
-static const struct snd_soc_dapm_widget acp5x_8821_widgets[] = {
+static const struct snd_soc_dapm_widget acp5x_8821_35l41_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Int Mic", NULL),
SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0,
- platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ platform_clock_control,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
};
-static const struct snd_soc_dapm_route acp5x_8821_audio_route[] = {
+static const struct snd_soc_dapm_route acp5x_8821_35l41_audio_route[] = {
/* HP jack connectors - unknown if we have jack detection */
{ "Headphone", NULL, "HPOL" },
{ "Headphone", NULL, "HPOR" },
{ "Int Mic", NULL, "Platform Clock" },
};
-static struct snd_soc_card acp5x_card = {
+static struct snd_soc_card acp5x_8821_35l41_card = {
.name = "acp5x",
.owner = THIS_MODULE,
- .dai_link = acp5x_dai,
- .num_links = ARRAY_SIZE(acp5x_dai),
- .dapm_widgets = acp5x_8821_widgets,
- .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_widgets),
- .dapm_routes = acp5x_8821_audio_route,
- .num_dapm_routes = ARRAY_SIZE(acp5x_8821_audio_route),
- .codec_conf = cs35l41_conf,
- .num_configs = ARRAY_SIZE(cs35l41_conf),
+ .dai_link = acp5x_8821_35l41_dai,
+ .num_links = ARRAY_SIZE(acp5x_8821_35l41_dai),
+ .dapm_widgets = acp5x_8821_35l41_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_35l41_widgets),
+ .dapm_routes = acp5x_8821_35l41_audio_route,
+ .num_dapm_routes = ARRAY_SIZE(acp5x_8821_35l41_audio_route),
+ .codec_conf = acp5x_cs35l41_conf,
+ .num_configs = ARRAY_SIZE(acp5x_cs35l41_conf),
.controls = acp5x_8821_controls,
.num_controls = ARRAY_SIZE(acp5x_8821_controls),
};
static int acp5x_vg_quirk_cb(const struct dmi_system_id *id)
{
acp5x_machine_id = VG_JUPITER;
+
return 1;
}
static int acp5x_probe(struct platform_device *pdev)
{
- int ret;
struct acp5x_platform_info *machine;
+ struct device *dev = &pdev->dev;
struct snd_soc_card *card;
+ int ret;
- machine = devm_kzalloc(&pdev->dev, sizeof(struct acp5x_platform_info),
- GFP_KERNEL);
+ machine = devm_kzalloc(dev, sizeof(*machine), GFP_KERNEL);
if (!machine)
return -ENOMEM;
dmi_check_system(acp5x_vg_quirk_table);
switch (acp5x_machine_id) {
case VG_JUPITER:
- card = &acp5x_card;
- acp5x_card.dev = &pdev->dev;
+ card = &acp5x_8821_35l41_card;
break;
default:
return -ENODEV;
}
+ card->dev = dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
- ret = devm_snd_soc_register_card(&pdev->dev, card);
- if (ret) {
- return dev_err_probe(&pdev->dev, ret,
- "snd_soc_register_card(%s) failed\n",
- acp5x_card.name);
- }
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret)
+ return dev_err_probe(dev, ret, "Register card (%s) failed\n", card->name);
+
return 0;
}
return 0;
}
-static int acp5x_audio_remove(struct platform_device *pdev)
+static void acp5x_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
static int __maybe_unused acp5x_pcm_resume(struct device *dev)
static struct platform_driver acp5x_dma_driver = {
.probe = acp5x_audio_probe,
- .remove = acp5x_audio_remove,
+ .remove_new = acp5x_audio_remove,
.driver = {
.name = "acp5x_i2s_dma",
.pm = &acp5x_pm_ops,
};
static const struct dmi_system_id yc_acp_quirk_table[] = {
+ {
+ .driver_data = &acp6x_card,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5525"),
+ }
+ },
{
.driver_data = &acp6x_card,
.matches = {
DMI_MATCH(DMI_PRODUCT_NAME, "21EN"),
}
},
+ {
+ .driver_data = &acp6x_card,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "21HY"),
+ }
+ },
{
.driver_data = &acp6x_card,
.matches = {
DMI_MATCH(DMI_PRODUCT_NAME, "OMEN by HP Gaming Laptop 16z-n000"),
}
},
+ {
+ .driver_data = &acp6x_card,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
+ DMI_MATCH(DMI_BOARD_NAME, "8A42"),
+ }
+ },
{
.driver_data = &acp6x_card,
.matches = {
return 0;
}
-static int acp6x_pdm_audio_remove(struct platform_device *pdev)
+static void acp6x_pdm_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
static int __maybe_unused acp6x_pdm_resume(struct device *dev)
static struct platform_driver acp6x_pdm_dma_driver = {
.probe = acp6x_pdm_audio_probe,
- .remove = acp6x_pdm_audio_remove,
+ .remove_new = acp6x_pdm_audio_remove,
.driver = {
.name = "acp_yc_pdm_dma",
.pm = &acp6x_pdm_pm_ops,
{
writel(val, base_addr - ACP6x_PHY_BASE_ADDRESS);
}
+
+int snd_amd_acp_find_config(struct pci_dev *pci);
+
int index = 0;
int val = 0x00;
u32 addr;
- unsigned int irqflags;
+ unsigned int irqflags, flag;
int ret;
irqflags = IRQF_SHARED;
+
+ /* Return if acp config flag is defined */
+ flag = snd_amd_acp_find_config(pci);
+ if (flag)
+ return -ENODEV;
+
/* Yellow Carp device check */
switch (pci->revision) {
case 0x60:
return ret;
}
-static int apple_mca_remove(struct platform_device *pdev)
+static void apple_mca_remove(struct platform_device *pdev)
{
struct mca_data *mca = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
apple_mca_release(mca);
- return 0;
}
static const struct of_device_id apple_mca_of_match[] = {
.of_match_table = apple_mca_of_match,
},
.probe = apple_mca_probe,
- .remove = apple_mca_remove,
+ .remove_new = apple_mca_remove,
};
module_platform_driver(apple_mca_driver);
if (!dai_link)
return -ENOMEM;
- comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL);
+ comp = devm_kzalloc(dev, 2 * sizeof(*comp), GFP_KERNEL);
if (!comp)
return -ENOMEM;
dai_link->cpus = &comp[0];
dai_link->codecs = &comp[1];
- dai_link->platforms = &comp[2];
dai_link->num_cpus = 1;
dai_link->num_codecs = 1;
- dai_link->num_platforms = 1;
dai_link->name = "CLASSD";
dai_link->stream_name = "CLASSD PCM";
dai_link->codecs->dai_name = "snd-soc-dummy-dai";
dai_link->cpus->dai_name = dev_name(dev);
dai_link->codecs->name = "snd-soc-dummy";
- dai_link->platforms->name = dev_name(dev);
card->dai_link = dai_link;
card->num_links = 1;
return 0;
}
-static int atmel_i2s_remove(struct platform_device *pdev)
+static void atmel_i2s_remove(struct platform_device *pdev)
{
struct atmel_i2s_dev *dev = platform_get_drvdata(pdev);
clk_disable_unprepare(dev->pclk);
-
- return 0;
}
static struct platform_driver atmel_i2s_driver = {
.of_match_table = of_match_ptr(atmel_i2s_dt_ids),
},
.probe = atmel_i2s_probe,
- .remove = atmel_i2s_remove,
+ .remove_new = atmel_i2s_remove,
};
module_platform_driver(atmel_i2s_driver);
if (!dai_link)
return -ENOMEM;
- comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL);
+ comp = devm_kzalloc(dev, 2 * sizeof(*comp), GFP_KERNEL);
if (!comp)
return -ENOMEM;
dai_link->cpus = &comp[0];
dai_link->codecs = &comp[1];
- dai_link->platforms = &comp[2];
dai_link->num_cpus = 1;
dai_link->num_codecs = 1;
- dai_link->num_platforms = 1;
dai_link->name = "PDMIC";
dai_link->stream_name = "PDMIC PCM";
dai_link->codecs->dai_name = "snd-soc-dummy-dai";
dai_link->cpus->dai_name = dev_name(dev);
dai_link->codecs->name = "snd-soc-dummy";
- dai_link->platforms->name = dev_name(dev);
card->dai_link = dai_link;
card->num_links = 1;
return ret;
}
-static int atmel_asoc_wm8904_remove(struct platform_device *pdev)
+static void atmel_asoc_wm8904_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
snd_soc_unregister_card(card);
atmel_ssc_put_audio(id);
-
- return 0;
}
#ifdef CONFIG_OF
.pm = &snd_soc_pm_ops,
},
.probe = atmel_asoc_wm8904_probe,
- .remove = atmel_asoc_wm8904_remove,
+ .remove_new = atmel_asoc_wm8904_remove,
};
module_platform_driver(atmel_asoc_wm8904_driver);
return 0;
}
-static int mchp_i2s_mcc_remove(struct platform_device *pdev)
+static void mchp_i2s_mcc_remove(struct platform_device *pdev)
{
struct mchp_i2s_mcc_dev *dev = platform_get_drvdata(pdev);
clk_disable_unprepare(dev->pclk);
-
- return 0;
}
static struct platform_driver mchp_i2s_mcc_driver = {
.of_match_table = of_match_ptr(mchp_i2s_mcc_dt_ids),
},
.probe = mchp_i2s_mcc_probe,
- .remove = mchp_i2s_mcc_remove,
+ .remove_new = mchp_i2s_mcc_remove,
};
module_platform_driver(mchp_i2s_mcc_driver);
#include <dt-bindings/sound/microchip,pdmc.h>
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of.h>
#define MCHP_PDMC_MR_OSR256 (3 << 16)
#define MCHP_PDMC_MR_SINCORDER_MASK GENMASK(23, 20)
-#define MCHP_PDMC_MR_SINCORDER(order) (((order) << 20) & \
- MCHP_PDMC_MR_SINCORDER_MASK)
#define MCHP_PDMC_MR_SINC_OSR_MASK GENMASK(27, 24)
#define MCHP_PDMC_MR_SINC_OSR_DIS (0 << 24)
#define MCHP_PDMC_MR_SINC_OSR_256 (6 << 24)
#define MCHP_PDMC_MR_CHUNK_MASK GENMASK(31, 28)
-#define MCHP_PDMC_MR_CHUNK(chunk) (((chunk) << 28) & \
- MCHP_PDMC_MR_CHUNK_MASK)
/*
* ---- Configuration Register (Read/Write) ----
mr_val |= mchp_pdmc_mr_set_osr(dd->audio_filter_en, osr);
- mr_val |= MCHP_PDMC_MR_SINCORDER(dd->sinc_order);
+ mr_val |= FIELD_PREP(MCHP_PDMC_MR_SINCORDER_MASK, dd->sinc_order);
dd->addr.maxburst = mchp_pdmc_period_to_maxburst(snd_pcm_lib_period_bytes(substream));
- mr_val |= MCHP_PDMC_MR_CHUNK(dd->addr.maxburst);
+ mr_val |= FIELD_PREP(MCHP_PDMC_MR_CHUNK_MASK, dd->addr.maxburst);
dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst);
snd_soc_component_update_bits(comp, MCHP_PDMC_MR,
int ret;
ret = mchp_pdmc_add_chmap_ctls(rtd->pcm, dd);
- if (ret < 0) {
+ if (ret < 0)
dev_err(dd->dev, "failed to add channel map controls: %d\n", ret);
- return ret;
- }
- return 0;
+ return ret;
}
static struct snd_soc_dai_driver mchp_pdmc_dai = {
/* PDMC interrupt handler */
static irqreturn_t mchp_pdmc_interrupt(int irq, void *dev_id)
{
- struct mchp_pdmc *dd = (struct mchp_pdmc *)dev_id;
+ struct mchp_pdmc *dd = dev_id;
u32 isr, msr, pending;
irqreturn_t ret = IRQ_NONE;
}
ret = devm_request_irq(dev, irq, mchp_pdmc_interrupt, 0,
- dev_name(&pdev->dev), (void *)dd);
+ dev_name(&pdev->dev), dd);
if (ret < 0) {
dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
irq, ret);
return ret;
}
-static int mchp_pdmc_remove(struct platform_device *pdev)
+static void mchp_pdmc_remove(struct platform_device *pdev)
{
struct mchp_pdmc *dd = platform_get_drvdata(pdev);
mchp_pdmc_runtime_suspend(dd->dev);
pm_runtime_disable(dd->dev);
-
- return 0;
}
static const struct of_device_id mchp_pdmc_of_match[] = {
.pm = pm_ptr(&mchp_pdmc_pm_ops),
},
.probe = mchp_pdmc_probe,
- .remove = mchp_pdmc_remove,
+ .remove_new = mchp_pdmc_remove,
};
module_platform_driver(mchp_pdmc_driver);
return err;
}
-static int mchp_spdifrx_remove(struct platform_device *pdev)
+static void mchp_spdifrx_remove(struct platform_device *pdev)
{
struct mchp_spdifrx_dev *dev = platform_get_drvdata(pdev);
pm_runtime_disable(dev->dev);
if (!pm_runtime_status_suspended(dev->dev))
mchp_spdifrx_runtime_suspend(dev->dev);
-
- return 0;
}
static struct platform_driver mchp_spdifrx_driver = {
.probe = mchp_spdifrx_probe,
- .remove = mchp_spdifrx_remove,
+ .remove_new = mchp_spdifrx_remove,
.driver = {
.name = "mchp_spdifrx",
.of_match_table = of_match_ptr(mchp_spdifrx_dt_ids),
/* Valid Bits per Sample */
#define SPDIFTX_MR_VBPS_MASK GENMASK(13, 8)
-#define SPDIFTX_MR_VBPS(bps) FIELD_PREP(SPDIFTX_MR_VBPS_MASK, bps)
/* Chunk Size */
#define SPDIFTX_MR_CHUNK_MASK GENMASK(19, 16)
-#define SPDIFTX_MR_CHUNK(size) FIELD_PREP(SPDIFTX_MR_CHUNK_MASK, size)
/* Validity Bits for Channels 1 and 2 */
#define SPDIFTX_MR_VALID1 BIT(24)
/* Bytes per Sample */
#define SPDIFTX_MR_BPS_MASK GENMASK(29, 28)
-#define SPDIFTX_MR_BPS(bytes) FIELD_PREP(SPDIFTX_MR_BPS_MASK, (bytes - 1))
/*
* ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ----
{
struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
- u32 mr;
- int running;
int ret;
/* do not start/stop while channel status or user data is updated */
spin_lock(&ctrl->lock);
- regmap_read(dev->regmap, SPDIFTX_MR, &mr);
- running = !!(mr & SPDIFTX_MR_TXEN_ENABLE);
-
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
dev->suspend_irq = 0;
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (!running) {
- mr &= ~SPDIFTX_MR_TXEN_MASK;
- mr |= SPDIFTX_MR_TXEN_ENABLE;
- }
+ ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
+ SPDIFTX_MR_TXEN_ENABLE);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
regmap_read(dev->regmap, SPDIFTX_IMR, &dev->suspend_irq);
SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR);
fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (running) {
- mr &= ~SPDIFTX_MR_TXEN_MASK;
- mr |= SPDIFTX_MR_TXEN_DISABLE;
- }
+ ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
+ SPDIFTX_MR_TXEN_DISABLE);
break;
default:
- spin_unlock(&ctrl->lock);
- return -EINVAL;
+ ret = -EINVAL;
}
-
- ret = regmap_write(dev->regmap, SPDIFTX_MR, mr);
spin_unlock(&ctrl->lock);
if (ret)
- dev_err(dev->dev, "unable to disable TX: %d\n", ret);
+ dev_err(dev->dev, "unable to start/stop TX: %d\n", ret);
return ret;
}
params_channels(params));
return -EINVAL;
}
- mr |= SPDIFTX_MR_CHUNK(dev->playback.maxburst);
+ mr |= FIELD_PREP(SPDIFTX_MR_CHUNK_MASK, dev->playback.maxburst);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
- mr |= SPDIFTX_MR_VBPS(8);
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 8);
break;
case SNDRV_PCM_FORMAT_S16_BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S16_LE:
- mr |= SPDIFTX_MR_VBPS(16);
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 16);
break;
case SNDRV_PCM_FORMAT_S18_3BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S18_3LE:
- mr |= SPDIFTX_MR_VBPS(18);
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 18);
break;
case SNDRV_PCM_FORMAT_S20_3BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S20_3LE:
- mr |= SPDIFTX_MR_VBPS(20);
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 20);
break;
case SNDRV_PCM_FORMAT_S24_3BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S24_3LE:
- mr |= SPDIFTX_MR_VBPS(24);
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
break;
case SNDRV_PCM_FORMAT_S24_BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S24_LE:
- mr |= SPDIFTX_MR_VBPS(24);
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
break;
case SNDRV_PCM_FORMAT_S32_BE:
mr |= SPDIFTX_MR_ENDIAN_BIG;
fallthrough;
case SNDRV_PCM_FORMAT_S32_LE:
- mr |= SPDIFTX_MR_VBPS(32);
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 32);
break;
default:
dev_err(dev->dev, "unsupported PCM format: %d\n",
return -EINVAL;
}
- mr |= SPDIFTX_MR_BPS(bps);
+ mr |= FIELD_PREP(SPDIFTX_MR_BPS_MASK, bps - 1);
switch (params_rate(params)) {
case 22050:
return err;
}
-static int mchp_spdiftx_remove(struct platform_device *pdev)
+static void mchp_spdiftx_remove(struct platform_device *pdev)
{
struct mchp_spdiftx_dev *dev = platform_get_drvdata(pdev);
mchp_spdiftx_runtime_suspend(dev->dev);
pm_runtime_disable(dev->dev);
-
- return 0;
}
static struct platform_driver mchp_spdiftx_driver = {
.probe = mchp_spdiftx_probe,
- .remove = mchp_spdiftx_remove,
+ .remove_new = mchp_spdiftx_remove,
.driver = {
.name = "mchp_spdiftx",
.of_match_table = of_match_ptr(mchp_spdiftx_dt_ids),
return ret;
}
-static int snd_proto_remove(struct platform_device *pdev)
+static void snd_proto_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&snd_proto);
-
- return 0;
}
static const struct of_device_id snd_proto_of_match[] = {
.of_match_table = snd_proto_of_match,
},
.probe = snd_proto_probe,
- .remove = snd_proto_remove,
+ .remove_new = snd_proto_remove,
};
module_platform_driver(snd_proto_driver);
return ret;
}
-static int at91sam9g20ek_audio_remove(struct platform_device *pdev)
+static void at91sam9g20ek_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
atmel_ssc_put_audio(0);
-
- return 0;
}
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
},
.probe = at91sam9g20ek_audio_probe,
- .remove = at91sam9g20ek_audio_remove,
+ .remove_new = at91sam9g20ek_audio_remove,
};
module_platform_driver(at91sam9g20ek_audio_driver);
return ret;
}
-static int sam9x5_wm8731_driver_remove(struct platform_device *pdev)
+static void sam9x5_wm8731_driver_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct sam9x5_drvdata *priv = card->drvdata;
atmel_ssc_put_audio(priv->ssc_id);
-
- return 0;
}
static const struct of_device_id sam9x5_wm8731_of_match[] = {
.of_match_table = of_match_ptr(sam9x5_wm8731_of_match),
},
.probe = sam9x5_wm8731_driver_probe,
- .remove = sam9x5_wm8731_driver_remove,
+ .remove_new = sam9x5_wm8731_driver_remove,
};
module_platform_driver(sam9x5_wm8731_driver);
return ret;
}
-static int tse850_remove(struct platform_device *pdev)
+static void tse850_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
regulator_disable(tse850->ana);
-
- return 0;
}
static const struct of_device_id tse850_dt_ids[] = {
.of_match_table = of_match_ptr(tse850_dt_ids),
},
.probe = tse850_probe,
- .remove = tse850_remove,
+ .remove_new = tse850_remove,
};
module_platform_driver(tse850_driver);
return 0;
}
-static int au1xac97c_drvremove(struct platform_device *pdev)
+static void au1xac97c_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */
ac97c_workdata = NULL; /* MDEV */
-
- return 0;
}
#ifdef CONFIG_PM
.pm = AU1XPSCAC97_PMOPS,
},
.probe = au1xac97c_drvprobe,
- .remove = au1xac97c_drvremove,
+ .remove_new = au1xac97c_drvremove,
};
module_platform_driver(au1xac97c_driver);
&au1xi2s_dai_driver, 1);
}
-static int au1xi2s_drvremove(struct platform_device *pdev)
+static void au1xi2s_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
snd_soc_unregister_component(&pdev->dev);
WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */
-
- return 0;
}
#ifdef CONFIG_PM
.pm = AU1XI2SC_PMOPS,
},
.probe = au1xi2s_drvprobe,
- .remove = au1xi2s_drvremove,
+ .remove_new = au1xi2s_drvremove,
};
module_platform_driver(au1xi2s_driver);
return 0;
}
-static int au1xpsc_ac97_drvremove(struct platform_device *pdev)
+static void au1xpsc_ac97_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
wmb(); /* drain writebuffer */
au1xpsc_ac97_workdata = NULL; /* MDEV */
-
- return 0;
}
#ifdef CONFIG_PM
.pm = AU1XPSCAC97_PMOPS,
},
.probe = au1xpsc_ac97_drvprobe,
- .remove = au1xpsc_ac97_drvremove,
+ .remove_new = au1xpsc_ac97_drvremove,
};
module_platform_driver(au1xpsc_ac97_driver);
&au1xpsc_i2s_component, &wd->dai_drv, 1);
}
-static int au1xpsc_i2s_drvremove(struct platform_device *pdev)
+static void au1xpsc_i2s_drvremove(struct platform_device *pdev)
{
struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
wmb(); /* drain writebuffer */
__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
wmb(); /* drain writebuffer */
-
- return 0;
}
#ifdef CONFIG_PM
.pm = AU1XPSCI2S_PMOPS,
},
.probe = au1xpsc_i2s_drvprobe,
- .remove = au1xpsc_i2s_drvremove,
+ .remove_new = au1xpsc_i2s_drvremove,
};
module_platform_driver(au1xpsc_i2s_driver);
return ret;
}
-static int bcm63xx_i2s_dev_remove(struct platform_device *pdev)
+static void bcm63xx_i2s_dev_remove(struct platform_device *pdev)
{
bcm63xx_soc_platform_remove(pdev);
- return 0;
}
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(snd_soc_bcm_audio_match),
},
.probe = bcm63xx_i2s_dev_probe,
- .remove = bcm63xx_i2s_dev_remove,
+ .remove_new = bcm63xx_i2s_dev_remove,
};
module_platform_driver(bcm63xx_i2s_driver);
return 0;
}
-static int cygnus_ssp_remove(struct platform_device *pdev)
+static void cygnus_ssp_remove(struct platform_device *pdev)
{
cygnus_soc_platform_unregister(&pdev->dev);
-
- return 0;
}
static const struct of_device_id cygnus_ssp_of_match[] = {
static struct platform_driver cygnus_ssp_driver = {
.probe = cygnus_ssp_probe,
- .remove = cygnus_ssp_remove,
+ .remove_new = cygnus_ssp_remove,
.driver = {
.name = "cygnus-ssp",
.of_match_table = cygnus_ssp_of_match,
the EP93xx I2S or AC97 interfaces.
config SND_EP93XX_SOC_I2S
- tristate
+ tristate "I2S controller support for the Cirrus Logic EP93xx series"
+ depends on SND_EP93XX_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the EP93xx I2S interface.
if SND_EP93XX_SOC_I2S
return ret;
}
-static int edb93xx_remove(struct platform_device *pdev)
+static void edb93xx_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
ep93xx_i2s_release();
-
- return 0;
}
static struct platform_driver edb93xx_driver = {
.name = "edb93xx-audio",
},
.probe = edb93xx_probe,
- .remove = edb93xx_remove,
+ .remove_new = edb93xx_remove,
};
module_platform_driver(edb93xx_driver);
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/io.h>
+#include <linux/of.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
return 0;
}
+static int ep93xx_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
+
+ ep93xx_i2s_enable(info, substream->stream);
+
+ return 0;
+}
+
static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
if (err)
return err;
- ep93xx_i2s_enable(info, substream->stream);
return 0;
}
#endif
static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = {
+ .startup = ep93xx_i2s_startup,
.shutdown = ep93xx_i2s_shutdown,
.hw_params = ep93xx_i2s_hw_params,
.set_sysclk = ep93xx_i2s_set_sysclk,
return err;
}
-static int ep93xx_i2s_remove(struct platform_device *pdev)
+static void ep93xx_i2s_remove(struct platform_device *pdev)
{
struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev);
clk_put(info->lrclk);
clk_put(info->sclk);
clk_put(info->mclk);
- return 0;
}
+static const struct of_device_id ep93xx_i2s_of_ids[] = {
+ { .compatible = "cirrus,ep9301-i2s" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ep93xx_i2s_of_ids);
+
static struct platform_driver ep93xx_i2s_driver = {
.probe = ep93xx_i2s_probe,
- .remove = ep93xx_i2s_remove,
+ .remove_new = ep93xx_i2s_remove,
.driver = {
.name = "ep93xx-i2s",
+ .of_match_table = ep93xx_i2s_of_ids,
},
};
imply SND_SOC_CS35L41_I2C
imply SND_SOC_CS35L45_I2C
imply SND_SOC_CS35L45_SPI
+ imply SND_SOC_CS35L56_I2C
+ imply SND_SOC_CS35L56_SPI
+ imply SND_SOC_CS35L56_SDW
imply SND_SOC_CS42L42
imply SND_SOC_CS42L42_SDW
imply SND_SOC_CS42L51_I2C
imply SND_SOC_MAX98925
imply SND_SOC_MAX98926
imply SND_SOC_MAX98927
+ imply SND_SOC_MAX98363
imply SND_SOC_MAX98373_I2C
imply SND_SOC_MAX98373_SDW
imply SND_SOC_MAX98390
imply SND_SOC_RT711_SDW
imply SND_SOC_RT711_SDCA_SDW
imply SND_SOC_RT712_SDCA_SDW
+ imply SND_SOC_RT712_SDCA_DMIC_SDW
imply SND_SOC_RT715_SDW
imply SND_SOC_RT715_SDCA_SDW
imply SND_SOC_RT1308_SDW
default y if SND_SOC_WM2200=y
default y if SND_SOC_CS35L41_SPI=y
default y if SND_SOC_CS35L41_I2C=y
+ default y if SND_SOC_CS35L45_SPI=y
+ default y if SND_SOC_CS35L45_I2C=y
+ default y if SND_SOC_CS35L56=y
default m if SND_SOC_MADERA=m
default m if SND_SOC_CS47L24=m
default m if SND_SOC_WM5102=m
default m if SND_SOC_WM2200=m
default m if SND_SOC_CS35L41_SPI=m
default m if SND_SOC_CS35L41_I2C=m
+ default m if SND_SOC_CS35L45_SPI=m
+ default m if SND_SOC_CS35L45_I2C=m
+ default m if SND_SOC_CS35L56=m
config SND_SOC_AB8500_CODEC
tristate
Enable support for Cirrus Logic CS35L45 smart speaker amplifier
with I2C control.
+config SND_SOC_CS35L56
+ tristate
+
+config SND_SOC_CS35L56_SHARED
+ tristate
+
+config SND_SOC_CS35L56_I2C
+ tristate "Cirrus Logic CS35L56 CODEC (I2C)"
+ depends on I2C
+ depends on SOUNDWIRE || !SOUNDWIRE
+ select REGMAP_I2C
+ select SND_SOC_CS35L56
+ select SND_SOC_CS35L56_SHARED
+ help
+ Enable support for Cirrus Logic CS35L56 boosted amplifier with I2C control
+
+config SND_SOC_CS35L56_SPI
+ tristate "Cirrus Logic CS35L56 CODEC (SPI)"
+ depends on SPI_MASTER
+ depends on SOUNDWIRE || !SOUNDWIRE
+ select REGMAP_SPI
+ select SND_SOC_CS35L56
+ select SND_SOC_CS35L56_SHARED
+ help
+ Enable support for Cirrus Logic CS35L56 boosted amplifier with SPI control
+
+config SND_SOC_CS35L56_SDW
+ tristate "Cirrus Logic CS35L56 CODEC (SDW)"
+ depends on SOUNDWIRE
+ select REGMAP
+ select SND_SOC_CS35L56
+ select SND_SOC_CS35L56_SHARED
+ help
+ Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control
+
config SND_SOC_CS42L42_CORE
tristate
To compile this driver as a module, choose M here.
+config SND_SOC_MAX98363
+ tristate "Analog Devices MAX98363 Soundwire Speaker Amplifier"
+ depends on SOUNDWIRE
+ select REGMAP_SOUNDWIRE
+ help
+ Enable support for Analog Devices MAX98363 Soundwire
+ amplifier. MAX98363 supports the MIPI SoundWire v1.2
+ compatible interface for audio and control data.
+ This amplifier does not support I2C and I2S.
+
config SND_SOC_MAX98373
tristate
select REGMAP_SOUNDWIRE
select REGMAP_SOUNDWIRE_MBQ
+config SND_SOC_RT712_SDCA_DMIC_SDW
+ tristate "Realtek RT712 SDCA DMIC Codec - SDW"
+ depends on SOUNDWIRE
+ select REGMAP_SOUNDWIRE
+ select REGMAP_SOUNDWIRE_MBQ
+
config SND_SOC_RT715
tristate
snd-soc-cs35l45-objs := cs35l45.o cs35l45-tables.o
snd-soc-cs35l45-spi-objs := cs35l45-spi.o
snd-soc-cs35l45-i2c-objs := cs35l45-i2c.o
+snd-soc-cs35l56-objs := cs35l56.o
+snd-soc-cs35l56-shared-objs := cs35l56-shared.o
+snd-soc-cs35l56-i2c-objs := cs35l56-i2c.o
+snd-soc-cs35l56-spi-objs := cs35l56-spi.o
+snd-soc-cs35l56-sdw-objs := cs35l56-sdw.o
snd-soc-cs42l42-objs := cs42l42.o
snd-soc-cs42l42-i2c-objs := cs42l42-i2c.o
snd-soc-cs42l42-sdw-objs := cs42l42-sdw.o
snd-soc-max98926-objs := max98926.o
snd-soc-max98927-objs := max98927.o
snd-soc-max98520-objs := max98520.o
+snd-soc-max98363-objs := max98363.o
snd-soc-max98373-objs := max98373.o
snd-soc-max98373-i2c-objs := max98373-i2c.o
snd-soc-max98373-sdw-objs := max98373-sdw.o
snd-soc-rt711-objs := rt711.o rt711-sdw.o
snd-soc-rt711-sdca-objs := rt711-sdca.o rt711-sdca-sdw.o
snd-soc-rt712-sdca-objs := rt712-sdca.o rt712-sdca-sdw.o
+snd-soc-rt712-sdca-dmic-objs := rt712-sdca-dmic.o
snd-soc-rt715-objs := rt715.o rt715-sdw.o
snd-soc-rt715-sdca-objs := rt715-sdca.o rt715-sdca-sdw.o
snd-soc-rt9120-objs := rt9120.o
obj-$(CONFIG_SND_SOC_CS35L45) += snd-soc-cs35l45.o
obj-$(CONFIG_SND_SOC_CS35L45_SPI) += snd-soc-cs35l45-spi.o
obj-$(CONFIG_SND_SOC_CS35L45_I2C) += snd-soc-cs35l45-i2c.o
+obj-$(CONFIG_SND_SOC_CS35L56) += snd-soc-cs35l56.o
+obj-$(CONFIG_SND_SOC_CS35L56_SHARED) += snd-soc-cs35l56-shared.o
+obj-$(CONFIG_SND_SOC_CS35L56_I2C) += snd-soc-cs35l56-i2c.o
+obj-$(CONFIG_SND_SOC_CS35L56_SPI) += snd-soc-cs35l56-spi.o
+obj-$(CONFIG_SND_SOC_CS35L56_SDW) += snd-soc-cs35l56-sdw.o
obj-$(CONFIG_SND_SOC_CS42L42_CORE) += snd-soc-cs42l42.o
obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42-i2c.o
obj-$(CONFIG_SND_SOC_CS42L42_SDW) += snd-soc-cs42l42-sdw.o
obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o
obj-$(CONFIG_SND_SOC_MAX98927) += snd-soc-max98927.o
obj-$(CONFIG_SND_SOC_MAX98520) += snd-soc-max98520.o
+obj-$(CONFIG_SND_SOC_MAX98363) += snd-soc-max98363.o
obj-$(CONFIG_SND_SOC_MAX98373) += snd-soc-max98373.o
obj-$(CONFIG_SND_SOC_MAX98373_I2C) += snd-soc-max98373-i2c.o
obj-$(CONFIG_SND_SOC_MAX98373_SDW) += snd-soc-max98373-sdw.o
obj-$(CONFIG_SND_SOC_RT711) += snd-soc-rt711.o
obj-$(CONFIG_SND_SOC_RT711_SDCA_SDW) += snd-soc-rt711-sdca.o
obj-$(CONFIG_SND_SOC_RT712_SDCA_SDW) += snd-soc-rt712-sdca.o
+obj-$(CONFIG_SND_SOC_RT712_SDCA_DMIC_SDW) += snd-soc-rt712-sdca-dmic.o
obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o
obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o
obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o
};
MODULE_DEVICE_TABLE(spi, adau1977_spi_ids);
-static const struct of_device_id adau1977_spi_of_match[] = {
+static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = {
{ .compatible = "adi,adau1977" },
{ .compatible = "adi,adau1978" },
{ .compatible = "adi,adau1979" },
{ 0x00000040, 0x00000033 },
};
+static const struct reg_sequence cs35l41_actv_seq[] = {
+ /* SYNC_BST_CTL_RX_EN = 1; SYNC_BST_CTL_TX_EN = 1 */
+ {CS35L41_MDSYNC_EN, 0x00003000},
+ /* BST_CTL_SEL = MDSYNC */
+ {CS35L41_BSTCVRT_VCTRL2, 0x00000002},
+};
+
+static const struct reg_sequence cs35l41_pass_seq[] = {
+ /* SYNC_BST_CTL_RX_EN = 0; SYNC_BST_CTL_TX_EN = 1 */
+ {CS35L41_MDSYNC_EN, 0x00001000},
+ /* BST_EN = 0 */
+ {CS35L41_PWR_CTRL2, 0x00003300},
+ /* BST_CTL_SEL = MDSYNC */
+ {CS35L41_BSTCVRT_VCTRL2, 0x00000002},
+};
+
int cs35l41_init_boost(struct device *dev, struct regmap *regmap,
struct cs35l41_hw_cfg *hw_cfg)
{
int ret;
switch (hw_cfg->bst_type) {
+ case CS35L41_SHD_BOOST_ACTV:
+ regmap_multi_reg_write(regmap, cs35l41_actv_seq, ARRAY_SIZE(cs35l41_actv_seq));
+ fallthrough;
case CS35L41_INT_BOOST:
ret = cs35l41_boost_config(dev, regmap, hw_cfg->bst_ind,
hw_cfg->bst_cap, hw_cfg->bst_ipk);
ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
CS35L41_BST_DIS_FET_OFF << CS35L41_BST_EN_SHIFT);
break;
+ case CS35L41_SHD_BOOST_PASS:
+ ret = regmap_multi_reg_write(regmap, cs35l41_pass_seq,
+ ARRAY_SIZE(cs35l41_pass_seq));
+ break;
default:
dev_err(dev, "Boost type %d not supported\n", hw_cfg->bst_type);
ret = -EINVAL;
}
EXPORT_SYMBOL_GPL(cs35l41_safe_reset);
-int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable)
+int cs35l41_global_enable(struct regmap *regmap, enum cs35l41_boost_type b_type, int enable,
+ struct completion *pll_lock)
{
int ret;
+ unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3;
+ struct reg_sequence cs35l41_mdsync_down_seq[] = {
+ {CS35L41_PWR_CTRL3, 0},
+ {CS35L41_GPIO_PAD_CONTROL, 0},
+ {CS35L41_PWR_CTRL1, 0, 3000},
+ };
+ struct reg_sequence cs35l41_mdsync_up_seq[] = {
+ {CS35L41_PWR_CTRL3, 0},
+ {CS35L41_PWR_CTRL1, 0x00000000, 3000},
+ {CS35L41_PWR_CTRL1, 0x00000001, 3000},
+ };
switch (b_type) {
+ case CS35L41_SHD_BOOST_ACTV:
+ case CS35L41_SHD_BOOST_PASS:
+ regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3);
+ regmap_read(regmap, CS35L41_GPIO_PAD_CONTROL, &pad_control);
+
+ pwr_ctrl3 &= ~CS35L41_SYNC_EN_MASK;
+ pwr_ctrl1 = enable << CS35L41_GLOBAL_EN_SHIFT;
+
+ gpio1_func = enable ? CS35L41_GPIO1_MDSYNC : CS35L41_GPIO1_HIZ;
+ gpio1_func <<= CS35L41_GPIO1_CTRL_SHIFT;
+
+ pad_control &= ~CS35L41_GPIO1_CTRL_MASK;
+ pad_control |= gpio1_func & CS35L41_GPIO1_CTRL_MASK;
+
+ cs35l41_mdsync_down_seq[0].def = pwr_ctrl3;
+ cs35l41_mdsync_down_seq[1].def = pad_control;
+ cs35l41_mdsync_down_seq[2].def = pwr_ctrl1;
+ ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_down_seq,
+ ARRAY_SIZE(cs35l41_mdsync_down_seq));
+ if (!enable)
+ break;
+
+ if (!pll_lock)
+ return -EINVAL;
+
+ ret = wait_for_completion_timeout(pll_lock, msecs_to_jiffies(1000));
+ if (ret == 0) {
+ ret = -ETIMEDOUT;
+ } else {
+ regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3);
+ pwr_ctrl3 |= CS35L41_SYNC_EN_MASK;
+ cs35l41_mdsync_up_seq[0].def = pwr_ctrl3;
+ ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq,
+ ARRAY_SIZE(cs35l41_mdsync_up_seq));
+ }
+ break;
case CS35L41_INT_BOOST:
ret = regmap_update_bits(regmap, CS35L41_PWR_CTRL1, CS35L41_GLOBAL_EN_MASK,
enable << CS35L41_GLOBAL_EN_SHIFT);
{ 5644800, 16, 24 },
{ 6000000, 16, 24 },
{ 6144000, 16, 24 },
+ { 12288000, 0, 0 },
};
static int cs35l41_get_fs_mon_config_index(int freq)
WM_ADSP_FW_CONTROL("DSP1", 0),
};
+static void cs35l41_boost_enable(struct cs35l41_private *cs35l41, unsigned int enable)
+{
+ switch (cs35l41->hw_cfg.bst_type) {
+ case CS35L41_INT_BOOST:
+ case CS35L41_SHD_BOOST_ACTV:
+ enable = enable ? CS35L41_BST_EN_DEFAULT : CS35L41_BST_DIS_FET_OFF;
+ regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
+ enable << CS35L41_BST_EN_SHIFT);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static void cs35l41_error_release(struct cs35l41_private *cs35l41, unsigned int irq_err_bit,
+ unsigned int rel_err_bit)
+{
+ regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, irq_err_bit);
+ regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
+ regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, rel_err_bit, rel_err_bit);
+ regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, rel_err_bit, 0);
+}
+
static irqreturn_t cs35l41_irq(int irq, void *data)
{
struct cs35l41_private *cs35l41 = data;
*/
if (status[0] & CS35L41_AMP_SHORT_ERR) {
dev_crit_ratelimited(cs35l41->dev, "Amp short error\n");
- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
- CS35L41_AMP_SHORT_ERR);
- regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_AMP_SHORT_ERR_RLS,
- CS35L41_AMP_SHORT_ERR_RLS);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_AMP_SHORT_ERR_RLS, 0);
+ cs35l41_error_release(cs35l41, CS35L41_AMP_SHORT_ERR, CS35L41_AMP_SHORT_ERR_RLS);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_TEMP_WARN) {
dev_crit_ratelimited(cs35l41->dev, "Over temperature warning\n");
- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
- CS35L41_TEMP_WARN);
- regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_TEMP_WARN_ERR_RLS,
- CS35L41_TEMP_WARN_ERR_RLS);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_TEMP_WARN_ERR_RLS, 0);
+ cs35l41_error_release(cs35l41, CS35L41_TEMP_WARN, CS35L41_TEMP_WARN_ERR_RLS);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_TEMP_ERR) {
dev_crit_ratelimited(cs35l41->dev, "Over temperature error\n");
- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
- CS35L41_TEMP_ERR);
- regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_TEMP_ERR_RLS,
- CS35L41_TEMP_ERR_RLS);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_TEMP_ERR_RLS, 0);
+ cs35l41_error_release(cs35l41, CS35L41_TEMP_ERR, CS35L41_TEMP_ERR_RLS);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_BST_OVP_ERR) {
dev_crit_ratelimited(cs35l41->dev, "VBST Over Voltage error\n");
- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
- CS35L41_BST_EN_MASK, 0);
- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
- CS35L41_BST_OVP_ERR);
- regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_BST_OVP_ERR_RLS,
- CS35L41_BST_OVP_ERR_RLS);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_BST_OVP_ERR_RLS, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
- CS35L41_BST_EN_MASK,
- CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
+ cs35l41_boost_enable(cs35l41, 0);
+ cs35l41_error_release(cs35l41, CS35L41_BST_OVP_ERR, CS35L41_BST_OVP_ERR_RLS);
+ cs35l41_boost_enable(cs35l41, 1);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_BST_DCM_UVP_ERR) {
dev_crit_ratelimited(cs35l41->dev, "DCM VBST Under Voltage Error\n");
- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
- CS35L41_BST_EN_MASK, 0);
- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
- CS35L41_BST_DCM_UVP_ERR);
- regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_BST_UVP_ERR_RLS,
- CS35L41_BST_UVP_ERR_RLS);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_BST_UVP_ERR_RLS, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
- CS35L41_BST_EN_MASK,
- CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
+ cs35l41_boost_enable(cs35l41, 0);
+ cs35l41_error_release(cs35l41, CS35L41_BST_DCM_UVP_ERR, CS35L41_BST_UVP_ERR_RLS);
+ cs35l41_boost_enable(cs35l41, 1);
ret = IRQ_HANDLED;
}
if (status[0] & CS35L41_BST_SHORT_ERR) {
dev_crit_ratelimited(cs35l41->dev, "LBST error: powering off!\n");
- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
- CS35L41_BST_EN_MASK, 0);
- regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
- CS35L41_BST_SHORT_ERR);
- regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_BST_SHORT_ERR_RLS,
- CS35L41_BST_SHORT_ERR_RLS);
- regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
- CS35L41_BST_SHORT_ERR_RLS, 0);
- regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
- CS35L41_BST_EN_MASK,
- CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
+ cs35l41_boost_enable(cs35l41, 0);
+ cs35l41_error_release(cs35l41, CS35L41_BST_SHORT_ERR, CS35L41_BST_SHORT_ERR_RLS);
+ cs35l41_boost_enable(cs35l41, 1);
+ ret = IRQ_HANDLED;
+ }
+
+ if (status[2] & CS35L41_PLL_LOCK) {
+ regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS3, CS35L41_PLL_LOCK);
+ complete(&cs35l41->pll_lock);
ret = IRQ_HANDLED;
}
cs35l41_pup_patch,
ARRAY_SIZE(cs35l41_pup_patch));
- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 1);
+ cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 1,
+ &cs35l41->pll_lock);
break;
case SND_SOC_DAPM_POST_PMD:
- cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0);
+ cs35l41_global_enable(cs35l41->regmap, cs35l41->hw_cfg.bst_type, 0,
+ &cs35l41->pll_lock);
ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
val, val & CS35L41_PDN_DONE_MASK,
static int cs35l41_pcm_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
+ struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component);
+
+ reinit_completion(&cs35l41->pll_lock);
+
if (substream->runtime)
return snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
unsigned int val;
int ret;
- ret = device_property_read_u32(dev, "cirrus,boost-type", &val);
- if (ret >= 0)
- hw_cfg->bst_type = val;
+ /* Some ACPI systems received the Shared Boost feature before the upstream driver,
+ * leaving those systems with deprecated _DSD properties.
+ * To correctly configure those systems add shared-boost-active and shared-boost-passive
+ * properties mapped to the correct value in boost-type.
+ * These two are not DT properties and should not be used in new systems designs.
+ */
+ if (device_property_read_bool(dev, "cirrus,shared-boost-active")) {
+ hw_cfg->bst_type = CS35L41_SHD_BOOST_ACTV;
+ } else if (device_property_read_bool(dev, "cirrus,shared-boost-passive")) {
+ hw_cfg->bst_type = CS35L41_SHD_BOOST_PASS;
+ } else {
+ ret = device_property_read_u32(dev, "cirrus,boost-type", &val);
+ if (ret >= 0)
+ hw_cfg->bst_type = val;
+ }
ret = device_property_read_u32(dev, "cirrus,boost-peak-milliamp", &val);
if (ret >= 0)
/* Set interrupt masks for critical errors */
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1,
CS35L41_INT1_MASK_DEFAULT);
+ if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS ||
+ cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV)
+ regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK,
+ 0 << CS35L41_INT3_PLL_LOCK_SHIFT);
ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL, cs35l41_irq,
IRQF_ONESHOT | IRQF_SHARED | irq_pol,
if (ret < 0)
goto err;
+ init_completion(&cs35l41->pll_lock);
+
pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000);
pm_runtime_use_autosuspend(cs35l41->dev);
pm_runtime_mark_last_busy(cs35l41->dev);
pm_runtime_disable(cs35l41->dev);
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF);
+ if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS ||
+ cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV)
+ regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK,
+ 1 << CS35L41_INT3_PLL_LOCK_SHIFT);
kfree(cs35l41->dsp.system_name);
wm_adsp2_remove(&cs35l41->dsp);
cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type);
int irq;
/* GPIO for /RST */
struct gpio_desc *reset_gpio;
+ struct completion pll_lock;
};
int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg);
}
cs35l45->dev = dev;
+ cs35l45->irq = client->irq;
+ cs35l45->bus_type = CONTROL_BUS_I2C;
+ cs35l45->i2c_addr = client->addr;
return cs35l45_probe(cs35l45);
}
if (cs35l45 == NULL)
return -ENOMEM;
+ spi->max_speed_hz = CS35L45_SPI_MAX_FREQ;
+ spi_setup(spi);
+
spi_set_drvdata(spi, cs35l45);
cs35l45->regmap = devm_regmap_init_spi(spi, &cs35l45_spi_regmap);
if (IS_ERR(cs35l45->regmap)) {
}
cs35l45->dev = dev;
+ cs35l45->irq = spi->irq;
+ cs35l45->bus_type = CONTROL_BUS_SPI;
return cs35l45_probe(cs35l45);
}
static const struct reg_default cs35l45_defaults[] = {
{ CS35L45_BLOCK_ENABLES, 0x00003323 },
{ CS35L45_BLOCK_ENABLES2, 0x00000010 },
+ { CS35L45_SYNC_GPIO1, 0x00000007 },
+ { CS35L45_INTB_GPIO2_MCLK_REF, 0x00000005 },
+ { CS35L45_GPIO3, 0x00000005 },
+ { CS35L45_PWRMGT_CTL, 0x00000000 },
+ { CS35L45_WAKESRC_CTL, 0x00000008 },
+ { CS35L45_WKI2C_CTL, 0x00000030 },
{ CS35L45_REFCLK_INPUT, 0x00000510 },
{ CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 },
{ CS35L45_ASP_ENABLES1, 0x00000000 },
{ CS35L45_ASPTX3_INPUT, 0x00000020 },
{ CS35L45_ASPTX4_INPUT, 0x00000028 },
{ CS35L45_ASPTX5_INPUT, 0x00000048 },
+ { CS35L45_DSP1_RX1_RATE, 0x00000001 },
+ { CS35L45_DSP1_RX2_RATE, 0x00000001 },
+ { CS35L45_DSP1_RX3_RATE, 0x00000001 },
+ { CS35L45_DSP1_RX4_RATE, 0x00000001 },
+ { CS35L45_DSP1_RX5_RATE, 0x00000001 },
+ { CS35L45_DSP1_RX6_RATE, 0x00000001 },
+ { CS35L45_DSP1_RX7_RATE, 0x00000001 },
+ { CS35L45_DSP1_RX8_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX1_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX2_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX3_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX4_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX5_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX6_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX7_RATE, 0x00000001 },
+ { CS35L45_DSP1_TX8_RATE, 0x00000001 },
+ { CS35L45_DSP1RX1_INPUT, 0x00000008 },
+ { CS35L45_DSP1RX2_INPUT, 0x00000009 },
+ { CS35L45_DSP1RX3_INPUT, 0x00000018 },
+ { CS35L45_DSP1RX4_INPUT, 0x00000019 },
+ { CS35L45_DSP1RX5_INPUT, 0x00000020 },
+ { CS35L45_DSP1RX6_INPUT, 0x00000028 },
+ { CS35L45_DSP1RX7_INPUT, 0x0000003A },
+ { CS35L45_DSP1RX8_INPUT, 0x00000028 },
{ CS35L45_AMP_PCM_CONTROL, 0x00100000 },
+ { CS35L45_IRQ1_CFG, 0x00000000 },
+ { CS35L45_IRQ1_MASK_1, 0xBFEFFFBF },
+ { CS35L45_IRQ1_MASK_2, 0xFFFFFFFF },
+ { CS35L45_IRQ1_MASK_3, 0xFFFF87FF },
+ { CS35L45_IRQ1_MASK_4, 0xF8FFFFFF },
+ { CS35L45_IRQ1_MASK_5, 0x0EF80000 },
+ { CS35L45_IRQ1_MASK_6, 0x00000000 },
+ { CS35L45_IRQ1_MASK_7, 0xFFFFFF78 },
+ { CS35L45_IRQ1_MASK_8, 0x00003FFF },
+ { CS35L45_IRQ1_MASK_9, 0x00000000 },
+ { CS35L45_IRQ1_MASK_10, 0x00000000 },
+ { CS35L45_IRQ1_MASK_11, 0x00000000 },
+ { CS35L45_IRQ1_MASK_12, 0x00000000 },
+ { CS35L45_IRQ1_MASK_13, 0x00000000 },
+ { CS35L45_IRQ1_MASK_14, 0x00000001 },
+ { CS35L45_IRQ1_MASK_15, 0x00000000 },
+ { CS35L45_IRQ1_MASK_16, 0x00000000 },
+ { CS35L45_IRQ1_MASK_17, 0x00000000 },
+ { CS35L45_IRQ1_MASK_18, 0x3FE5D0FF },
+ { CS35L45_GPIO1_CTRL1, 0x81000001 },
+ { CS35L45_GPIO2_CTRL1, 0x81000001 },
+ { CS35L45_GPIO3_CTRL1, 0x81000001 },
};
static bool cs35l45_readable_reg(struct device *dev, unsigned int reg)
case CS35L45_BLOCK_ENABLES:
case CS35L45_BLOCK_ENABLES2:
case CS35L45_ERROR_RELEASE:
+ case CS35L45_SYNC_GPIO1:
+ case CS35L45_INTB_GPIO2_MCLK_REF:
+ case CS35L45_GPIO3:
+ case CS35L45_PWRMGT_CTL:
+ case CS35L45_WAKESRC_CTL:
+ case CS35L45_WKI2C_CTL:
+ case CS35L45_PWRMGT_STS:
case CS35L45_REFCLK_INPUT:
case CS35L45_GLOBAL_SAMPLE_RATE:
case CS35L45_ASP_ENABLES1:
case CS35L45_ASPTX3_INPUT:
case CS35L45_ASPTX4_INPUT:
case CS35L45_ASPTX5_INPUT:
+ case CS35L45_DSP1RX1_INPUT:
+ case CS35L45_DSP1RX2_INPUT:
+ case CS35L45_DSP1RX3_INPUT:
+ case CS35L45_DSP1RX4_INPUT:
+ case CS35L45_DSP1RX5_INPUT:
+ case CS35L45_DSP1RX6_INPUT:
+ case CS35L45_DSP1RX7_INPUT:
+ case CS35L45_DSP1RX8_INPUT:
case CS35L45_AMP_PCM_CONTROL:
case CS35L45_AMP_PCM_HPF_TST:
- case CS35L45_IRQ1_EINT_4:
+ case CS35L45_IRQ1_CFG:
+ case CS35L45_IRQ1_STATUS:
+ case CS35L45_IRQ1_EINT_1 ... CS35L45_IRQ1_EINT_18:
+ case CS35L45_IRQ1_STS_1 ... CS35L45_IRQ1_STS_18:
+ case CS35L45_IRQ1_MASK_1 ... CS35L45_IRQ1_MASK_18:
+ case CS35L45_GPIO_STATUS1:
+ case CS35L45_GPIO1_CTRL1:
+ case CS35L45_GPIO2_CTRL1:
+ case CS35L45_GPIO3_CTRL1:
+ case CS35L45_DSP_MBOX_1:
+ case CS35L45_DSP_MBOX_2:
+ case CS35L45_DSP_VIRT1_MBOX_1 ... CS35L45_DSP_VIRT1_MBOX_4:
+ case CS35L45_DSP_VIRT2_MBOX_1 ... CS35L45_DSP_VIRT2_MBOX_4:
+ case CS35L45_DSP1_SYS_ID:
+ case CS35L45_DSP1_CLOCK_FREQ:
+ case CS35L45_DSP1_RX1_RATE:
+ case CS35L45_DSP1_RX2_RATE:
+ case CS35L45_DSP1_RX3_RATE:
+ case CS35L45_DSP1_RX4_RATE:
+ case CS35L45_DSP1_RX5_RATE:
+ case CS35L45_DSP1_RX6_RATE:
+ case CS35L45_DSP1_RX7_RATE:
+ case CS35L45_DSP1_RX8_RATE:
+ case CS35L45_DSP1_TX1_RATE:
+ case CS35L45_DSP1_TX2_RATE:
+ case CS35L45_DSP1_TX3_RATE:
+ case CS35L45_DSP1_TX4_RATE:
+ case CS35L45_DSP1_TX5_RATE:
+ case CS35L45_DSP1_TX6_RATE:
+ case CS35L45_DSP1_TX7_RATE:
+ case CS35L45_DSP1_TX8_RATE:
+ case CS35L45_DSP1_SCRATCH1:
+ case CS35L45_DSP1_SCRATCH2:
+ case CS35L45_DSP1_SCRATCH3:
+ case CS35L45_DSP1_SCRATCH4:
+ case CS35L45_DSP1_CCM_CORE_CONTROL:
+ case CS35L45_DSP1_XMEM_PACK_0 ... CS35L45_DSP1_XMEM_PACK_4607:
+ case CS35L45_DSP1_XMEM_UNPACK32_0 ... CS35L45_DSP1_XMEM_UNPACK32_3071:
+ case CS35L45_DSP1_XMEM_UNPACK24_0 ... CS35L45_DSP1_XMEM_UNPACK24_6143:
+ case CS35L45_DSP1_YMEM_PACK_0 ... CS35L45_DSP1_YMEM_PACK_1532:
+ case CS35L45_DSP1_YMEM_UNPACK32_0 ... CS35L45_DSP1_YMEM_UNPACK32_1022:
+ case CS35L45_DSP1_YMEM_UNPACK24_0 ... CS35L45_DSP1_YMEM_UNPACK24_2043:
+ case CS35L45_DSP1_PMEM_0 ... CS35L45_DSP1_PMEM_3834:
return true;
default:
return false;
case CS35L45_GLOBAL_ENABLES:
case CS35L45_ERROR_RELEASE:
case CS35L45_AMP_PCM_HPF_TST: /* not cachable */
- case CS35L45_IRQ1_EINT_4:
+ case CS35L45_PWRMGT_STS:
+ case CS35L45_IRQ1_STATUS:
+ case CS35L45_IRQ1_EINT_1 ... CS35L45_IRQ1_EINT_18:
+ case CS35L45_IRQ1_STS_1 ... CS35L45_IRQ1_STS_18:
+ case CS35L45_GPIO_STATUS1:
+ case CS35L45_DSP_MBOX_1:
+ case CS35L45_DSP_MBOX_2:
+ case CS35L45_DSP_VIRT1_MBOX_1 ... CS35L45_DSP_VIRT1_MBOX_4:
+ case CS35L45_DSP_VIRT2_MBOX_1 ... CS35L45_DSP_VIRT2_MBOX_4:
+ case CS35L45_DSP1_SYS_ID:
+ case CS35L45_DSP1_CLOCK_FREQ:
+ case CS35L45_DSP1_SCRATCH1:
+ case CS35L45_DSP1_SCRATCH2:
+ case CS35L45_DSP1_SCRATCH3:
+ case CS35L45_DSP1_SCRATCH4:
+ case CS35L45_DSP1_CCM_CORE_CONTROL:
+ case CS35L45_DSP1_XMEM_PACK_0 ... CS35L45_DSP1_XMEM_PACK_4607:
+ case CS35L45_DSP1_XMEM_UNPACK32_0 ... CS35L45_DSP1_XMEM_UNPACK32_3071:
+ case CS35L45_DSP1_XMEM_UNPACK24_0 ... CS35L45_DSP1_XMEM_UNPACK24_6143:
+ case CS35L45_DSP1_YMEM_PACK_0 ... CS35L45_DSP1_YMEM_PACK_1532:
+ case CS35L45_DSP1_YMEM_UNPACK32_0 ... CS35L45_DSP1_YMEM_UNPACK32_1022:
+ case CS35L45_DSP1_YMEM_UNPACK24_0 ... CS35L45_DSP1_YMEM_UNPACK24_2043:
+ case CS35L45_DSP1_PMEM_0 ... CS35L45_DSP1_PMEM_3834:
return true;
default:
return false;
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
+#include <linux/firmware.h>
#include <linux/regulator/consumer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "cs35l45.h"
+static bool cs35l45_check_cspl_mbox_sts(const enum cs35l45_cspl_mboxcmd cmd,
+ enum cs35l45_cspl_mboxstate sts)
+{
+ switch (cmd) {
+ case CSPL_MBOX_CMD_NONE:
+ case CSPL_MBOX_CMD_UNKNOWN_CMD:
+ return true;
+ case CSPL_MBOX_CMD_PAUSE:
+ case CSPL_MBOX_CMD_OUT_OF_HIBERNATE:
+ return (sts == CSPL_MBOX_STS_PAUSED);
+ case CSPL_MBOX_CMD_RESUME:
+ return (sts == CSPL_MBOX_STS_RUNNING);
+ case CSPL_MBOX_CMD_REINIT:
+ return (sts == CSPL_MBOX_STS_RUNNING);
+ case CSPL_MBOX_CMD_STOP_PRE_REINIT:
+ return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT);
+ case CSPL_MBOX_CMD_HIBERNATE:
+ return (sts == CSPL_MBOX_STS_HIBERNATE);
+ default:
+ return false;
+ }
+}
+
+static int cs35l45_set_cspl_mbox_cmd(struct cs35l45_private *cs35l45,
+ struct regmap *regmap,
+ const enum cs35l45_cspl_mboxcmd cmd)
+{
+ unsigned int sts = 0, i;
+ int ret;
+
+ if (!cs35l45->dsp.cs_dsp.running) {
+ dev_err(cs35l45->dev, "DSP not running\n");
+ return -EPERM;
+ }
+
+ // Set mailbox cmd
+ ret = regmap_write(regmap, CS35L45_DSP_VIRT1_MBOX_1, cmd);
+ if (ret < 0) {
+ if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE)
+ dev_err(cs35l45->dev, "Failed to write MBOX: %d\n", ret);
+ return ret;
+ }
+
+ // Read mailbox status and verify it is appropriate for the given cmd
+ for (i = 0; i < 5; i++) {
+ usleep_range(1000, 1100);
+
+ ret = regmap_read(regmap, CS35L45_DSP_MBOX_2, &sts);
+ if (ret < 0) {
+ dev_err(cs35l45->dev, "Failed to read MBOX STS: %d\n", ret);
+ continue;
+ }
+
+ if (!cs35l45_check_cspl_mbox_sts(cmd, sts))
+ dev_dbg(cs35l45->dev, "[%u] cmd %u returned invalid sts %u", i, cmd, sts);
+ else
+ return 0;
+ }
+
+ if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE)
+ dev_err(cs35l45->dev, "Failed to set mailbox cmd %u (status %u)\n", cmd, sts);
+
+ return -ENOMSG;
+}
+
static int cs35l45_global_en_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
return 0;
}
+static int cs35l45_dsp_preload_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 cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (cs35l45->dsp.cs_dsp.booted)
+ return 0;
+
+ return wm_adsp_early_event(w, kcontrol, event);
+ case SND_SOC_DAPM_POST_PMU:
+ if (cs35l45->dsp.cs_dsp.running)
+ return 0;
+
+ regmap_set_bits(cs35l45->regmap, CS35L45_PWRMGT_CTL,
+ CS35L45_MEM_RDY_MASK);
+
+ return wm_adsp_event(w, kcontrol, event);
+ case SND_SOC_DAPM_PRE_PMD:
+ if (cs35l45->dsp.preloaded)
+ return 0;
+
+ if (cs35l45->dsp.cs_dsp.running) {
+ ret = wm_adsp_event(w, kcontrol, event);
+ if (ret)
+ return ret;
+ }
+
+ return wm_adsp_early_event(w, kcontrol, event);
+ default:
+ return 0;
+ }
+}
+
+static int cs35l45_dsp_audio_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 cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ return cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap,
+ CSPL_MBOX_CMD_RESUME);
+ case SND_SOC_DAPM_PRE_PMD:
+ return cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap,
+ CSPL_MBOX_CMD_PAUSE);
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
static const char * const cs35l45_asp_tx_txt[] = {
"Zero", "ASP_RX1", "ASP_RX2",
"VMON", "IMON", "ERR_VOL",
"VDD_BATTMON", "VDD_BSTMON",
+ "DSP_TX1", "DSP_TX2",
"Interpolator", "IL_TARGET",
};
CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2,
CS35L45_PCM_SRC_VMON, CS35L45_PCM_SRC_IMON, CS35L45_PCM_SRC_ERR_VOL,
CS35L45_PCM_SRC_VDD_BATTMON, CS35L45_PCM_SRC_VDD_BSTMON,
+ CS35L45_PCM_SRC_DSP_TX1, CS35L45_PCM_SRC_DSP_TX2,
CS35L45_PCM_SRC_INTERPOLATOR, CS35L45_PCM_SRC_IL_TARGET,
};
cs35l45_asp_tx_val),
};
+static const char * const cs35l45_dsp_rx_txt[] = {
+ "Zero", "ASP_RX1", "ASP_RX2",
+ "VMON", "IMON", "ERR_VOL",
+ "CLASSH_TGT", "VDD_BATTMON",
+ "VDD_BSTMON", "TEMPMON",
+};
+
+static const unsigned int cs35l45_dsp_rx_val[] = {
+ CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2,
+ CS35L45_PCM_SRC_VMON, CS35L45_PCM_SRC_IMON, CS35L45_PCM_SRC_ERR_VOL,
+ CS35L45_PCM_SRC_CLASSH_TGT, CS35L45_PCM_SRC_VDD_BATTMON,
+ CS35L45_PCM_SRC_VDD_BSTMON, CS35L45_PCM_SRC_TEMPMON,
+};
+
+static const struct soc_enum cs35l45_dsp_rx_enums[] = {
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX1_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX2_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX3_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX4_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX5_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX6_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX7_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+ SOC_VALUE_ENUM_SINGLE(CS35L45_DSP1RX8_INPUT, 0, CS35L45_PCM_SRC_MASK,
+ ARRAY_SIZE(cs35l45_dsp_rx_txt), cs35l45_dsp_rx_txt,
+ cs35l45_dsp_rx_val),
+};
+
static const char * const cs35l45_dac_txt[] = {
- "Zero", "ASP_RX1", "ASP_RX2"
+ "Zero", "ASP_RX1", "ASP_RX2", "DSP_TX1", "DSP_TX2"
};
static const unsigned int cs35l45_dac_val[] = {
- CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2
+ CS35L45_PCM_SRC_ZERO, CS35L45_PCM_SRC_ASP_RX1, CS35L45_PCM_SRC_ASP_RX2,
+ CS35L45_PCM_SRC_DSP_TX1, CS35L45_PCM_SRC_DSP_TX2
};
static const struct soc_enum cs35l45_dacpcm_enums[] = {
SOC_DAPM_ENUM("ASP_TX5 Source", cs35l45_asp_tx_enums[4]),
};
+static const struct snd_kcontrol_new cs35l45_dsp_muxes[] = {
+ SOC_DAPM_ENUM("DSP_RX1 Source", cs35l45_dsp_rx_enums[0]),
+ SOC_DAPM_ENUM("DSP_RX2 Source", cs35l45_dsp_rx_enums[1]),
+ SOC_DAPM_ENUM("DSP_RX3 Source", cs35l45_dsp_rx_enums[2]),
+ SOC_DAPM_ENUM("DSP_RX4 Source", cs35l45_dsp_rx_enums[3]),
+ SOC_DAPM_ENUM("DSP_RX5 Source", cs35l45_dsp_rx_enums[4]),
+ SOC_DAPM_ENUM("DSP_RX6 Source", cs35l45_dsp_rx_enums[5]),
+ SOC_DAPM_ENUM("DSP_RX7 Source", cs35l45_dsp_rx_enums[6]),
+ SOC_DAPM_ENUM("DSP_RX8 Source", cs35l45_dsp_rx_enums[7]),
+};
+
static const struct snd_kcontrol_new cs35l45_dac_muxes[] = {
SOC_DAPM_ENUM("DACPCM1 Source", cs35l45_dacpcm_enums[0]),
};
static const struct snd_soc_dapm_widget cs35l45_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("DSP1 Preload", NULL),
+ SND_SOC_DAPM_SUPPLY_S("DSP1 Preloader", 100, SND_SOC_NOPM, 0, 0,
+ cs35l45_dsp_preload_ev,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0,
+ cs35l45_dsp_audio_ev,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_SUPPLY("GLOBAL_EN", SND_SOC_NOPM, 0, 0,
cs35l45_global_en_ev,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_MUX("ASP_TX4 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[3]),
SND_SOC_DAPM_MUX("ASP_TX5 Source", SND_SOC_NOPM, 0, 0, &cs35l45_asp_muxes[4]),
+ SND_SOC_DAPM_MUX("DSP_RX1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[0]),
+ SND_SOC_DAPM_MUX("DSP_RX2 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[1]),
+ SND_SOC_DAPM_MUX("DSP_RX3 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[2]),
+ SND_SOC_DAPM_MUX("DSP_RX4 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[3]),
+ SND_SOC_DAPM_MUX("DSP_RX5 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[4]),
+ SND_SOC_DAPM_MUX("DSP_RX6 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[5]),
+ SND_SOC_DAPM_MUX("DSP_RX7 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[6]),
+ SND_SOC_DAPM_MUX("DSP_RX8 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dsp_muxes[7]),
+
SND_SOC_DAPM_MUX("DACPCM1 Source", SND_SOC_NOPM, 0, 0, &cs35l45_dac_muxes[0]),
SND_SOC_DAPM_OUT_DRV("AMP", SND_SOC_NOPM, 0, 0, NULL, 0),
#define CS35L45_ASP_MUX_ROUTE(name) \
{ name" Source", "ASP_RX1", "ASP_RX1" }, \
{ name" Source", "ASP_RX2", "ASP_RX2" }, \
+ { name" Source", "DSP_TX1", "DSP1" }, \
+ { name" Source", "DSP_TX2", "DSP1" }, \
{ name" Source", "VMON", "VMON" }, \
{ name" Source", "IMON", "IMON" }, \
{ name" Source", "ERR_VOL", "ERR_VOL" }, \
{ name" Source", "Interpolator", "AMP_INTP" }, \
{ name" Source", "IL_TARGET", "IL_TARGET" }
-#define CS35L45_DAC_MUX_ROUTE(name) \
+#define CS35L45_DSP_MUX_ROUTE(name) \
{ name" Source", "ASP_RX1", "ASP_RX1" }, \
{ name" Source", "ASP_RX2", "ASP_RX2" }
+#define CS35L45_DAC_MUX_ROUTE(name) \
+ { name" Source", "ASP_RX1", "ASP_RX1" }, \
+ { name" Source", "ASP_RX2", "ASP_RX2" }, \
+ { name" Source", "DSP_TX1", "DSP1" }, \
+ { name" Source", "DSP_TX2", "DSP1" }
+
static const struct snd_soc_dapm_route cs35l45_dapm_routes[] = {
/* Feedback */
{ "VMON", NULL, "VMON_SRC" },
{ "AMP", NULL, "DACPCM1 Source"},
{ "AMP", NULL, "GLOBAL_EN"},
+ CS35L45_DSP_MUX_ROUTE("DSP_RX1"),
+ CS35L45_DSP_MUX_ROUTE("DSP_RX2"),
+ CS35L45_DSP_MUX_ROUTE("DSP_RX3"),
+ CS35L45_DSP_MUX_ROUTE("DSP_RX4"),
+ CS35L45_DSP_MUX_ROUTE("DSP_RX5"),
+ CS35L45_DSP_MUX_ROUTE("DSP_RX6"),
+ CS35L45_DSP_MUX_ROUTE("DSP_RX7"),
+ CS35L45_DSP_MUX_ROUTE("DSP_RX8"),
+
+ {"DSP1", NULL, "DSP_RX1 Source"},
+ {"DSP1", NULL, "DSP_RX2 Source"},
+ {"DSP1", NULL, "DSP_RX3 Source"},
+ {"DSP1", NULL, "DSP_RX4 Source"},
+ {"DSP1", NULL, "DSP_RX5 Source"},
+ {"DSP1", NULL, "DSP_RX6 Source"},
+ {"DSP1", NULL, "DSP_RX7 Source"},
+ {"DSP1", NULL, "DSP_RX8 Source"},
+
+ {"DSP1 Preload", NULL, "DSP1 Preloader"},
+ {"DSP1", NULL, "DSP1 Preloader"},
+
CS35L45_DAC_MUX_ROUTE("DACPCM1"),
{ "SPK", NULL, "AMP"},
-409, 48,
(CS35L45_AMP_VOL_PCM_WIDTH - 1) - 1,
0, cs35l45_dig_pcm_vol_tlv),
+ WM_ADSP2_PRELOAD_SWITCH("DSP1", 1),
+ WM_ADSP_FW_CONTROL("DSP1", 0),
};
static int cs35l45_set_pll(struct cs35l45_private *cs35l45, unsigned int freq)
},
};
+static int cs35l45_component_probe(struct snd_soc_component *component)
+{
+ struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
+
+ return wm_adsp2_component_probe(&cs35l45->dsp, component);
+}
+
+static void cs35l45_component_remove(struct snd_soc_component *component)
+{
+ struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
+
+ wm_adsp2_component_remove(&cs35l45->dsp, component);
+}
+
static const struct snd_soc_component_driver cs35l45_component = {
+ .probe = cs35l45_component_probe,
+ .remove = cs35l45_component_remove,
+
.dapm_widgets = cs35l45_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(cs35l45_dapm_widgets),
.endianness = 1,
};
+static void cs35l45_setup_hibernate(struct cs35l45_private *cs35l45)
+{
+ unsigned int wksrc;
+
+ if (cs35l45->bus_type == CONTROL_BUS_I2C)
+ wksrc = CS35L45_WKSRC_I2C;
+ else
+ wksrc = CS35L45_WKSRC_SPI;
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_WAKESRC_CTL,
+ CS35L45_WKSRC_EN_MASK,
+ wksrc << CS35L45_WKSRC_EN_SHIFT);
+
+ regmap_set_bits(cs35l45->regmap, CS35L45_WAKESRC_CTL,
+ CS35L45_UPDT_WKCTL_MASK);
+
+ regmap_update_bits(cs35l45->regmap, CS35L45_WKI2C_CTL,
+ CS35L45_WKI2C_ADDR_MASK, cs35l45->i2c_addr);
+
+ regmap_set_bits(cs35l45->regmap, CS35L45_WKI2C_CTL,
+ CS35L45_UPDT_WKI2C_MASK);
+}
+
+static int cs35l45_enter_hibernate(struct cs35l45_private *cs35l45)
+{
+ dev_dbg(cs35l45->dev, "Enter hibernate\n");
+
+ cs35l45_setup_hibernate(cs35l45);
+
+ // Don't wait for ACK since bus activity would wake the device
+ regmap_write(cs35l45->regmap, CS35L45_DSP_VIRT1_MBOX_1, CSPL_MBOX_CMD_HIBERNATE);
+
+ return 0;
+}
+
+static int cs35l45_exit_hibernate(struct cs35l45_private *cs35l45)
+{
+ const int wake_retries = 20;
+ const int sleep_retries = 5;
+ int ret, i, j;
+
+ for (i = 0; i < sleep_retries; i++) {
+ dev_dbg(cs35l45->dev, "Exit hibernate\n");
+
+ for (j = 0; j < wake_retries; j++) {
+ ret = cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap,
+ CSPL_MBOX_CMD_OUT_OF_HIBERNATE);
+ if (!ret) {
+ dev_dbg(cs35l45->dev, "Wake success at cycle: %d\n", j);
+ return 0;
+ }
+ usleep_range(100, 200);
+ }
+
+ dev_err(cs35l45->dev, "Wake failed, re-enter hibernate: %d\n", ret);
+
+ cs35l45_setup_hibernate(cs35l45);
+ }
+
+ dev_err(cs35l45->dev, "Timed out waking device\n");
+
+ return -ETIMEDOUT;
+}
+
static int __maybe_unused cs35l45_runtime_suspend(struct device *dev)
{
struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
+ if (!cs35l45->dsp.preloaded || !cs35l45->dsp.cs_dsp.running)
+ return 0;
+
+ cs35l45_enter_hibernate(cs35l45);
+
regcache_cache_only(cs35l45->regmap, true);
+ regcache_mark_dirty(cs35l45->regmap);
dev_dbg(cs35l45->dev, "Runtime suspended\n");
struct cs35l45_private *cs35l45 = dev_get_drvdata(dev);
int ret;
+ if (!cs35l45->dsp.preloaded || !cs35l45->dsp.cs_dsp.running)
+ return 0;
+
dev_dbg(cs35l45->dev, "Runtime resume\n");
regcache_cache_only(cs35l45->regmap, false);
+
+ ret = cs35l45_exit_hibernate(cs35l45);
+ if (ret)
+ return ret;
+
ret = regcache_sync(cs35l45->regmap);
if (ret != 0)
dev_warn(cs35l45->dev, "regcache_sync failed: %d\n", ret);
static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45)
{
+ struct device_node *node = cs35l45->dev->of_node;
+ unsigned int gpio_regs[] = {CS35L45_GPIO1_CTRL1, CS35L45_GPIO2_CTRL1,
+ CS35L45_GPIO3_CTRL1};
+ unsigned int pad_regs[] = {CS35L45_SYNC_GPIO1,
+ CS35L45_INTB_GPIO2_MCLK_REF, CS35L45_GPIO3};
+ struct device_node *child;
unsigned int val;
+ char of_name[32];
+ int ret, i;
+
+ if (!node)
+ return 0;
+
+ for (i = 0; i < CS35L45_NUM_GPIOS; i++) {
+ sprintf(of_name, "cirrus,gpio-ctrl%d", i + 1);
+ child = of_get_child_by_name(node, of_name);
+ if (!child)
+ continue;
+
+ ret = of_property_read_u32(child, "gpio-dir", &val);
+ if (!ret)
+ regmap_update_bits(cs35l45->regmap, gpio_regs[i],
+ CS35L45_GPIO_DIR_MASK,
+ val << CS35L45_GPIO_DIR_SHIFT);
+
+ ret = of_property_read_u32(child, "gpio-lvl", &val);
+ if (!ret)
+ regmap_update_bits(cs35l45->regmap, gpio_regs[i],
+ CS35L45_GPIO_LVL_MASK,
+ val << CS35L45_GPIO_LVL_SHIFT);
+
+ ret = of_property_read_u32(child, "gpio-op-cfg", &val);
+ if (!ret)
+ regmap_update_bits(cs35l45->regmap, gpio_regs[i],
+ CS35L45_GPIO_OP_CFG_MASK,
+ val << CS35L45_GPIO_OP_CFG_SHIFT);
+
+ ret = of_property_read_u32(child, "gpio-pol", &val);
+ if (!ret)
+ regmap_update_bits(cs35l45->regmap, gpio_regs[i],
+ CS35L45_GPIO_POL_MASK,
+ val << CS35L45_GPIO_POL_SHIFT);
+
+ ret = of_property_read_u32(child, "gpio-ctrl", &val);
+ if (!ret)
+ regmap_update_bits(cs35l45->regmap, pad_regs[i],
+ CS35L45_GPIO_CTRL_MASK,
+ val << CS35L45_GPIO_CTRL_SHIFT);
+
+ ret = of_property_read_u32(child, "gpio-invert", &val);
+ if (!ret) {
+ regmap_update_bits(cs35l45->regmap, pad_regs[i],
+ CS35L45_GPIO_INVERT_MASK,
+ val << CS35L45_GPIO_INVERT_SHIFT);
+ if (i == 1)
+ cs35l45->irq_invert = val;
+ }
+
+ of_node_put(child);
+ }
if (device_property_read_u32(cs35l45->dev,
"cirrus,asp-sdout-hiz-ctrl", &val) == 0) {
return 0;
}
+static int cs35l45_dsp_virt2_mbox3_irq_handle(struct cs35l45_private *cs35l45,
+ const unsigned int cmd,
+ unsigned int data)
+{
+ static char *speak_status = "Unknown";
+
+ switch (cmd) {
+ case EVENT_SPEAKER_STATUS:
+ switch (data) {
+ case 1:
+ speak_status = "All Clear";
+ break;
+ case 2:
+ speak_status = "Open Circuit";
+ break;
+ case 4:
+ speak_status = "Short Circuit";
+ break;
+ }
+
+ dev_info(cs35l45->dev, "MBOX event (SPEAKER_STATUS): %s\n",
+ speak_status);
+ break;
+ case EVENT_BOOT_DONE:
+ dev_dbg(cs35l45->dev, "MBOX event (BOOT_DONE)\n");
+ break;
+ default:
+ dev_err(cs35l45->dev, "MBOX event not supported %u\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static irqreturn_t cs35l45_dsp_virt2_mbox_cb(int irq, void *data)
+{
+ struct cs35l45_private *cs35l45 = data;
+ unsigned int mbox_val;
+ int ret = 0;
+
+ ret = regmap_read(cs35l45->regmap, CS35L45_DSP_VIRT2_MBOX_3, &mbox_val);
+ if (!ret && mbox_val)
+ ret = cs35l45_dsp_virt2_mbox3_irq_handle(cs35l45, mbox_val & CS35L45_MBOX3_CMD_MASK,
+ (mbox_val & CS35L45_MBOX3_DATA_MASK) >> CS35L45_MBOX3_DATA_SHIFT);
+
+ /* Handle DSP trace log IRQ */
+ ret = regmap_read(cs35l45->regmap, CS35L45_DSP_VIRT2_MBOX_4, &mbox_val);
+ if (!ret && mbox_val != 0) {
+ dev_err(cs35l45->dev, "Spurious DSP MBOX4 IRQ\n");
+ }
+
+ return IRQ_RETVAL(ret);
+}
+
+static irqreturn_t cs35l45_pll_unlock(int irq, void *data)
+{
+ struct cs35l45_private *cs35l45 = data;
+
+ dev_dbg(cs35l45->dev, "PLL unlock detected!");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cs35l45_pll_lock(int irq, void *data)
+{
+ struct cs35l45_private *cs35l45 = data;
+
+ dev_dbg(cs35l45->dev, "PLL lock detected!");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cs35l45_spk_safe_err(int irq, void *data);
+
+static const struct cs35l45_irq cs35l45_irqs[] = {
+ CS35L45_IRQ(AMP_SHORT_ERR, "Amplifier short error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(UVLO_VDDBATT_ERR, "VDDBATT undervoltage error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(BST_SHORT_ERR, "Boost inductor error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(BST_UVP_ERR, "Boost undervoltage error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(TEMP_ERR, "Overtemperature error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(AMP_CAL_ERR, "Amplifier calibration error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(UVLO_VDDLV_ERR, "LV threshold detector error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(GLOBAL_ERROR, "Global error", cs35l45_spk_safe_err),
+ CS35L45_IRQ(DSP_WDT_EXPIRE, "DSP Watchdog Timer", cs35l45_spk_safe_err),
+ CS35L45_IRQ(PLL_UNLOCK_FLAG_RISE, "PLL unlock", cs35l45_pll_unlock),
+ CS35L45_IRQ(PLL_LOCK_FLAG, "PLL lock", cs35l45_pll_lock),
+ CS35L45_IRQ(DSP_VIRT2_MBOX, "DSP virtual MBOX 2 write flag", cs35l45_dsp_virt2_mbox_cb),
+};
+
+static irqreturn_t cs35l45_spk_safe_err(int irq, void *data)
+{
+ struct cs35l45_private *cs35l45 = data;
+ int i;
+
+ i = irq - regmap_irq_get_virq(cs35l45->irq_data, 0);
+
+ dev_err(cs35l45->dev, "%s condition detected!\n", cs35l45_irqs[i].name);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_irq cs35l45_reg_irqs[] = {
+ CS35L45_REG_IRQ(IRQ1_EINT_1, AMP_SHORT_ERR),
+ CS35L45_REG_IRQ(IRQ1_EINT_1, UVLO_VDDBATT_ERR),
+ CS35L45_REG_IRQ(IRQ1_EINT_1, BST_SHORT_ERR),
+ CS35L45_REG_IRQ(IRQ1_EINT_1, BST_UVP_ERR),
+ CS35L45_REG_IRQ(IRQ1_EINT_1, TEMP_ERR),
+ CS35L45_REG_IRQ(IRQ1_EINT_3, AMP_CAL_ERR),
+ CS35L45_REG_IRQ(IRQ1_EINT_18, UVLO_VDDLV_ERR),
+ CS35L45_REG_IRQ(IRQ1_EINT_18, GLOBAL_ERROR),
+ CS35L45_REG_IRQ(IRQ1_EINT_2, DSP_WDT_EXPIRE),
+ CS35L45_REG_IRQ(IRQ1_EINT_3, PLL_UNLOCK_FLAG_RISE),
+ CS35L45_REG_IRQ(IRQ1_EINT_3, PLL_LOCK_FLAG),
+ CS35L45_REG_IRQ(IRQ1_EINT_2, DSP_VIRT2_MBOX),
+};
+
+static const struct regmap_irq_chip cs35l45_regmap_irq_chip = {
+ .name = "cs35l45 IRQ1 Controller",
+ .main_status = CS35L45_IRQ1_STATUS,
+ .status_base = CS35L45_IRQ1_EINT_1,
+ .mask_base = CS35L45_IRQ1_MASK_1,
+ .ack_base = CS35L45_IRQ1_EINT_1,
+ .num_regs = 18,
+ .irqs = cs35l45_reg_irqs,
+ .num_irqs = ARRAY_SIZE(cs35l45_reg_irqs),
+ .runtime_pm = true,
+};
+
static int cs35l45_initialize(struct cs35l45_private *cs35l45)
{
struct device *dev = cs35l45->dev;
if (ret < 0)
return ret;
- pm_runtime_set_autosuspend_delay(cs35l45->dev, 3000);
- pm_runtime_use_autosuspend(cs35l45->dev);
- pm_runtime_set_active(cs35l45->dev);
- pm_runtime_enable(cs35l45->dev);
-
return 0;
}
+static const struct reg_sequence cs35l45_fs_errata_patch[] = {
+ {0x02B80080, 0x00000001},
+ {0x02B80088, 0x00000001},
+ {0x02B80090, 0x00000001},
+ {0x02B80098, 0x00000001},
+ {0x02B800A0, 0x00000001},
+ {0x02B800A8, 0x00000001},
+ {0x02B800B0, 0x00000001},
+ {0x02B800B8, 0x00000001},
+ {0x02B80280, 0x00000001},
+ {0x02B80288, 0x00000001},
+ {0x02B80290, 0x00000001},
+ {0x02B80298, 0x00000001},
+ {0x02B802A0, 0x00000001},
+ {0x02B802A8, 0x00000001},
+ {0x02B802B0, 0x00000001},
+ {0x02B802B8, 0x00000001},
+};
+
+static const struct cs_dsp_region cs35l45_dsp1_regions[] = {
+ { .type = WMFW_HALO_PM_PACKED, .base = CS35L45_DSP1_PMEM_0 },
+ { .type = WMFW_HALO_XM_PACKED, .base = CS35L45_DSP1_XMEM_PACK_0 },
+ { .type = WMFW_HALO_YM_PACKED, .base = CS35L45_DSP1_YMEM_PACK_0 },
+ {. type = WMFW_ADSP2_XM, .base = CS35L45_DSP1_XMEM_UNPACK24_0},
+ {. type = WMFW_ADSP2_YM, .base = CS35L45_DSP1_YMEM_UNPACK24_0},
+};
+
+static int cs35l45_dsp_init(struct cs35l45_private *cs35l45)
+{
+ struct wm_adsp *dsp = &cs35l45->dsp;
+ int ret;
+
+ dsp->part = "cs35l45";
+ dsp->fw = 9; /* 9 is WM_ADSP_FW_SPK_PROT in wm_adsp.c */
+ dsp->toggle_preload = true;
+ dsp->cs_dsp.num = 1;
+ dsp->cs_dsp.type = WMFW_HALO;
+ dsp->cs_dsp.rev = 0;
+ dsp->cs_dsp.dev = cs35l45->dev;
+ dsp->cs_dsp.regmap = cs35l45->regmap;
+ dsp->cs_dsp.base = CS35L45_DSP1_CLOCK_FREQ;
+ dsp->cs_dsp.base_sysinfo = CS35L45_DSP1_SYS_ID;
+ dsp->cs_dsp.mem = cs35l45_dsp1_regions;
+ dsp->cs_dsp.num_mems = ARRAY_SIZE(cs35l45_dsp1_regions);
+ dsp->cs_dsp.lock_regions = 0xFFFFFFFF;
+
+ ret = wm_halo_init(dsp);
+
+ regmap_multi_reg_write(cs35l45->regmap, cs35l45_fs_errata_patch,
+ ARRAY_SIZE(cs35l45_fs_errata_patch));
+
+ return ret;
+}
+
int cs35l45_probe(struct cs35l45_private *cs35l45)
{
struct device *dev = cs35l45->dev;
- int ret;
+ unsigned long irq_pol = IRQF_ONESHOT | IRQF_SHARED;
+ int ret, i, irq;
cs35l45->vdd_batt = devm_regulator_get(dev, "vdd-batt");
if (IS_ERR(cs35l45->vdd_batt))
if (ret < 0)
goto err_reset;
+ ret = cs35l45_dsp_init(cs35l45);
+ if (ret < 0)
+ goto err_reset;
+
+ pm_runtime_set_autosuspend_delay(cs35l45->dev, 3000);
+ pm_runtime_use_autosuspend(cs35l45->dev);
+ pm_runtime_mark_last_busy(cs35l45->dev);
+ pm_runtime_set_active(cs35l45->dev);
+ pm_runtime_get_noresume(cs35l45->dev);
+ pm_runtime_enable(cs35l45->dev);
+
+ if (cs35l45->irq) {
+ if (cs35l45->irq_invert)
+ irq_pol |= IRQF_TRIGGER_HIGH;
+ else
+ irq_pol |= IRQF_TRIGGER_LOW;
+
+ ret = devm_regmap_add_irq_chip(dev, cs35l45->regmap, cs35l45->irq, irq_pol, 0,
+ &cs35l45_regmap_irq_chip, &cs35l45->irq_data);
+ if (ret) {
+ dev_err(dev, "Failed to register IRQ chip: %d\n", ret);
+ goto err_dsp;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cs35l45_irqs); i++) {
+ irq = regmap_irq_get_virq(cs35l45->irq_data, cs35l45_irqs[i].irq);
+ if (irq < 0) {
+ dev_err(dev, "Failed to get %s\n", cs35l45_irqs[i].name);
+ ret = irq;
+ goto err_dsp;
+ }
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, cs35l45_irqs[i].handler,
+ irq_pol, cs35l45_irqs[i].name, cs35l45);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ %s: %d\n",
+ cs35l45_irqs[i].name, ret);
+ goto err_dsp;
+ }
+ }
+ }
+
ret = devm_snd_soc_register_component(dev, &cs35l45_component,
cs35l45_dai,
ARRAY_SIZE(cs35l45_dai));
if (ret < 0)
- goto err_reset;
+ goto err_dsp;
+
+ pm_runtime_put_autosuspend(cs35l45->dev);
return 0;
+err_dsp:
+ pm_runtime_disable(cs35l45->dev);
+ pm_runtime_put_noidle(cs35l45->dev);
+ wm_adsp2_remove(&cs35l45->dsp);
+
err_reset:
gpiod_set_value_cansleep(cs35l45->reset_gpio, 0);
err:
void cs35l45_remove(struct cs35l45_private *cs35l45)
{
+ pm_runtime_get_sync(cs35l45->dev);
pm_runtime_disable(cs35l45->dev);
+ wm_adsp2_remove(&cs35l45->dsp);
gpiod_set_value_cansleep(cs35l45->reset_gpio, 0);
+
+ pm_runtime_put_noidle(cs35l45->dev);
regulator_disable(cs35l45->vdd_a);
/* VDD_BATT must be the last to power-off */
regulator_disable(cs35l45->vdd_batt);
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <dt-bindings/sound/cs35l45.h>
+#include "wm_adsp.h"
#define CS35L45_DEVID 0x00000000
#define CS35L45_REVID 0x00000004
#define CS35L45_BLOCK_ENABLES 0x00002018
#define CS35L45_BLOCK_ENABLES2 0x0000201C
#define CS35L45_ERROR_RELEASE 0x00002034
+#define CS35L45_SYNC_GPIO1 0x00002430
+#define CS35L45_INTB_GPIO2_MCLK_REF 0x00002434
+#define CS35L45_GPIO3 0x00002438
+#define CS35L45_PWRMGT_CTL 0x00002900
+#define CS35L45_WAKESRC_CTL 0x00002904
+#define CS35L45_WKI2C_CTL 0x00002908
+#define CS35L45_PWRMGT_STS 0x0000290C
#define CS35L45_REFCLK_INPUT 0x00002C04
#define CS35L45_GLOBAL_SAMPLE_RATE 0x00002C0C
#define CS35L45_BOOST_CCM_CFG 0x00003808
#define CS35L45_ASPTX3_INPUT 0x00004C28
#define CS35L45_ASPTX4_INPUT 0x00004C2C
#define CS35L45_ASPTX5_INPUT 0x00004C30
+#define CS35L45_DSP1RX1_INPUT 0x00004C40
+#define CS35L45_DSP1RX2_INPUT 0x00004C44
+#define CS35L45_DSP1RX3_INPUT 0x00004C48
+#define CS35L45_DSP1RX4_INPUT 0x00004C4C
+#define CS35L45_DSP1RX5_INPUT 0x00004C50
+#define CS35L45_DSP1RX6_INPUT 0x00004C54
+#define CS35L45_DSP1RX7_INPUT 0x00004C58
+#define CS35L45_DSP1RX8_INPUT 0x00004C5C
#define CS35L45_LDPM_CONFIG 0x00006404
#define CS35L45_AMP_PCM_CONTROL 0x00007000
#define CS35L45_AMP_PCM_HPF_TST 0x00007004
+#define CS35L45_IRQ1_CFG 0x0000E000
+#define CS35L45_IRQ1_STATUS 0x0000E004
+#define CS35L45_IRQ1_EINT_1 0x0000E010
+#define CS35L45_IRQ1_EINT_2 0x0000E014
+#define CS35L45_IRQ1_EINT_3 0x0000E018
#define CS35L45_IRQ1_EINT_4 0x0000E01C
-#define CS35L45_LASTREG 0x0000E01C
+#define CS35L45_IRQ1_EINT_5 0x0000E020
+#define CS35L45_IRQ1_EINT_7 0x0000E028
+#define CS35L45_IRQ1_EINT_8 0x0000E02C
+#define CS35L45_IRQ1_EINT_18 0x0000E054
+#define CS35L45_IRQ1_STS_1 0x0000E090
+#define CS35L45_IRQ1_STS_2 0x0000E094
+#define CS35L45_IRQ1_STS_3 0x0000E098
+#define CS35L45_IRQ1_STS_4 0x0000E09C
+#define CS35L45_IRQ1_STS_5 0x0000E0A0
+#define CS35L45_IRQ1_STS_7 0x0000E0A8
+#define CS35L45_IRQ1_STS_8 0x0000E0AC
+#define CS35L45_IRQ1_STS_18 0x0000E0D4
+#define CS35L45_IRQ1_MASK_1 0x0000E110
+#define CS35L45_IRQ1_MASK_2 0x0000E114
+#define CS35L45_IRQ1_MASK_3 0x0000E118
+#define CS35L45_IRQ1_MASK_4 0x0000E11C
+#define CS35L45_IRQ1_MASK_5 0x0000E120
+#define CS35L45_IRQ1_MASK_6 0x0000E124
+#define CS35L45_IRQ1_MASK_7 0x0000E128
+#define CS35L45_IRQ1_MASK_8 0x0000E12C
+#define CS35L45_IRQ1_MASK_9 0x0000E130
+#define CS35L45_IRQ1_MASK_10 0x0000E134
+#define CS35L45_IRQ1_MASK_11 0x0000E138
+#define CS35L45_IRQ1_MASK_12 0x0000E13C
+#define CS35L45_IRQ1_MASK_13 0x0000E140
+#define CS35L45_IRQ1_MASK_14 0x0000E144
+#define CS35L45_IRQ1_MASK_15 0x0000E148
+#define CS35L45_IRQ1_MASK_16 0x0000E14C
+#define CS35L45_IRQ1_MASK_17 0x0000E150
+#define CS35L45_IRQ1_MASK_18 0x0000E154
+#define CS35L45_GPIO_STATUS1 0x0000F000
+#define CS35L45_GPIO1_CTRL1 0x0000F008
+#define CS35L45_GPIO2_CTRL1 0x0000F00C
+#define CS35L45_GPIO3_CTRL1 0x0000F010
+#define CS35L45_DSP_MBOX_1 0x00011000
+#define CS35L45_DSP_MBOX_2 0x00011004
+#define CS35L45_DSP_VIRT1_MBOX_1 0x00011020
+#define CS35L45_DSP_VIRT1_MBOX_2 0x00011024
+#define CS35L45_DSP_VIRT1_MBOX_3 0x00011028
+#define CS35L45_DSP_VIRT1_MBOX_4 0x0001102C
+#define CS35L45_DSP_VIRT2_MBOX_1 0x00011040
+#define CS35L45_DSP_VIRT2_MBOX_2 0x00011044
+#define CS35L45_DSP_VIRT2_MBOX_3 0x00011048
+#define CS35L45_DSP_VIRT2_MBOX_4 0x0001104C
+#define CS35L45_DSP1_XMEM_PACK_0 0x02000000
+#define CS35L45_DSP1_XMEM_PACK_4607 0x020047FC
+#define CS35L45_DSP1_XMEM_UNPACK32_0 0x02400000
+#define CS35L45_DSP1_XMEM_UNPACK32_3071 0x02402FFC
+#define CS35L45_DSP1_SYS_ID 0x025E0000
+#define CS35L45_DSP1_XMEM_UNPACK24_0 0x02800000
+#define CS35L45_DSP1_XMEM_UNPACK24_6143 0x02805FFC
+#define CS35L45_DSP1_CLOCK_FREQ 0x02B80000
+#define CS35L45_DSP1_RX1_RATE 0x02B80080
+#define CS35L45_DSP1_RX2_RATE 0x02B80088
+#define CS35L45_DSP1_RX3_RATE 0x02B80090
+#define CS35L45_DSP1_RX4_RATE 0x02B80098
+#define CS35L45_DSP1_RX5_RATE 0x02B800A0
+#define CS35L45_DSP1_RX6_RATE 0x02B800A8
+#define CS35L45_DSP1_RX7_RATE 0x02B800B0
+#define CS35L45_DSP1_RX8_RATE 0x02B800B8
+#define CS35L45_DSP1_TX1_RATE 0x02B80280
+#define CS35L45_DSP1_TX2_RATE 0x02B80288
+#define CS35L45_DSP1_TX3_RATE 0x02B80290
+#define CS35L45_DSP1_TX4_RATE 0x02B80298
+#define CS35L45_DSP1_TX5_RATE 0x02B802A0
+#define CS35L45_DSP1_TX6_RATE 0x02B802A8
+#define CS35L45_DSP1_TX7_RATE 0x02B802B0
+#define CS35L45_DSP1_TX8_RATE 0x02B802B8
+#define CS35L45_DSP1_SCRATCH1 0x02B805C0
+#define CS35L45_DSP1_SCRATCH2 0x02B805C8
+#define CS35L45_DSP1_SCRATCH3 0x02B805D0
+#define CS35L45_DSP1_SCRATCH4 0x02B805D8
+#define CS35L45_DSP1_CCM_CORE_CONTROL 0x02BC1000
+#define CS35L45_DSP1_YMEM_PACK_0 0x02C00000
+#define CS35L45_DSP1_YMEM_PACK_1532 0x02C017F0
+#define CS35L45_DSP1_YMEM_UNPACK32_0 0x03000000
+#define CS35L45_DSP1_YMEM_UNPACK32_1022 0x03000FF8
+#define CS35L45_DSP1_YMEM_UNPACK24_0 0x03400000
+#define CS35L45_DSP1_YMEM_UNPACK24_2043 0x03401FEC
+#define CS35L45_DSP1_PMEM_0 0x03800000
+#define CS35L45_DSP1_PMEM_3834 0x03803BE8
+#define CS35L45_LASTREG 0x03C6EFE8
/* SFT_RESET */
#define CS35L45_SOFT_RESET_TRIGGER 0x5A000000
/* BLOCK_ENABLES2 */
#define CS35L45_ASP_EN_SHIFT 27
+#define CS35L45_MEM_RDY_SHIFT 1
+#define CS35L45_MEM_RDY_MASK BIT(1)
+
/* ERROR_RELEASE */
#define CS35L45_GLOBAL_ERR_RLS_MASK BIT(11)
+/* CCM_CORE */
+#define CS35L45_CCM_CORE_RESET_SHIFT 9
+#define CS35L45_CCM_CORE_RESET_MASK BIT(9)
+#define CS35L45_CCM_PM_REMAP_SHIFT 7
+#define CS35L45_CCM_PM_REMAP_MASK BIT(7)
+#define CS35L45_CCM_CORE_EN_SHIFT 0
+#define CS35L45_CCM_CORE_EN_MASK BIT(0)
+
/* REFCLK_INPUT */
#define CS35L45_PLL_FORCE_EN_SHIFT 16
#define CS35L45_PLL_FORCE_EN_MASK BIT(16)
#define CS35L45_OTP_BOOT_DONE_STS_MASK BIT(1)
#define CS35L45_OTP_BUSY_MASK BIT(0)
+/* GPIOX_CTRL1 */
+#define CS35L45_GPIO_DIR_SHIFT 31
+#define CS35L45_GPIO_DIR_MASK BIT(31)
+#define CS35L45_GPIO_LVL_SHIFT 15
+#define CS35L45_GPIO_LVL_MASK BIT(15)
+#define CS35L45_GPIO_OP_CFG_SHIFT 14
+#define CS35L45_GPIO_OP_CFG_MASK BIT(14)
+#define CS35L45_GPIO_POL_SHIFT 12
+#define CS35L45_GPIO_POL_MASK BIT(12)
+
+/* SYNC_GPIO1, INTB_GPIO2_MCLK_REF, GPIO3 */
+#define CS35L45_GPIO_CTRL_SHIFT 20
+#define CS35L45_GPIO_CTRL_MASK GENMASK(22, 20)
+#define CS35L45_GPIO_INVERT_SHIFT 19
+#define CS35L45_GPIO_INVERT_MASK BIT(19)
+
+/* CS35L45_IRQ1_EINT_1 */
+#define CS35L45_BST_UVP_ERR_SHIFT 7
+#define CS35L45_BST_UVP_ERR_MASK BIT(7)
+#define CS35L45_BST_SHORT_ERR_SHIFT 8
+#define CS35L45_BST_SHORT_ERR_MASK BIT(8)
+#define CS35L45_TEMP_ERR_SHIFT 17
+#define CS35L45_TEMP_ERR_MASK BIT(17)
+#define CS35L45_MSM_GLOBAL_EN_ASSERT_SHIFT 22
+#define CS35L45_MSM_GLOBAL_EN_ASSERT_MASK BIT(22)
+#define CS35L45_UVLO_VDDBATT_ERR_SHIFT 29
+#define CS35L45_UVLO_VDDBATT_ERR_MASK BIT(29)
+#define CS35L45_AMP_SHORT_ERR_SHIFT 31
+#define CS35L45_AMP_SHORT_ERR_MASK BIT(31)
+
+/* CS35L45_IRQ1_EINT_2 */
+#define CS35L45_DSP_WDT_EXPIRE_SHIFT 4
+#define CS35L45_DSP_WDT_EXPIRE_MASK BIT(4)
+#define CS35L45_DSP_VIRT2_MBOX_SHIFT 21
+#define CS35L45_DSP_VIRT2_MBOX_MASK BIT(21)
+
+/* CS35L45_IRQ1_EINT_3 */
+#define CS35L45_PLL_LOCK_FLAG_SHIFT 1
+#define CS35L45_PLL_LOCK_FLAG_MASK BIT(1)
+#define CS35L45_PLL_UNLOCK_FLAG_RISE_SHIFT 4
+#define CS35L45_PLL_UNLOCK_FLAG_RISE_MASK BIT(4)
+#define CS35L45_AMP_CAL_ERR_SHIFT 25
+#define CS35L45_AMP_CAL_ERR_MASK BIT(25)
+
+/* CS35L45_IRQ1_EINT_18 */
+#define CS35L45_GLOBAL_ERROR_SHIFT 15
+#define CS35L45_GLOBAL_ERROR_MASK BIT(15)
+#define CS35L45_UVLO_VDDLV_ERR_SHIFT 16
+#define CS35L45_UVLO_VDDLV_ERR_MASK BIT(16)
+
/* Mixer sources */
#define CS35L45_PCM_SRC_MASK 0x7F
#define CS35L45_PCM_SRC_ZERO 0x00
#define CS35L45_PCM_SRC_CLASSH_TGT 0x21
#define CS35L45_PCM_SRC_VDD_BATTMON 0x28
#define CS35L45_PCM_SRC_VDD_BSTMON 0x29
+#define CS35L45_PCM_SRC_DSP_TX1 0x32
+#define CS35L45_PCM_SRC_DSP_TX2 0x33
#define CS35L45_PCM_SRC_TEMPMON 0x3A
#define CS35L45_PCM_SRC_INTERPOLATOR 0x40
#define CS35L45_PCM_SRC_IL_TARGET 0x48
#define CS35L45_POST_GLOBAL_EN_US 5000
#define CS35L45_PRE_GLOBAL_DIS_US 3000
+/* WAKESRC_CTL */
+#define CS35L45_WKSRC_SYNC_GPIO1 BIT(0)
+#define CS35L45_WKSRC_INT_GPIO2 BIT(1)
+#define CS35L45_WKSRC_GPIO3 BIT(2)
+#define CS35L45_WKSRC_SPI BIT(3)
+#define CS35L45_WKSRC_I2C BIT(4)
+#define CS35L45_UPDT_WKCTL_SHIFT 15
+#define CS35L45_UPDT_WKCTL_MASK BIT(15)
+#define CS35L45_WKSRC_EN_SHIFT 8
+#define CS35L45_WKSRC_EN_MASK GENMASK(12, 8)
+#define CS35L45_WKSRC_POL_SHIFT 0
+#define CS35L45_WKSRC_POL_MASK GENMASK(3, 0)
+
+/* WAKEI2C_CTL */
+#define CS35L45_UPDT_WKI2C_SHIFT 15
+#define CS35L45_UPDT_WKI2C_MASK BIT(15)
+#define CS35L45_WKI2C_ADDR_SHIFT 0
+#define CS35L45_WKI2C_ADDR_MASK GENMASK(6, 0)
+
+#define CS35L45_SPI_MAX_FREQ 4000000
+
+enum cs35l45_cspl_mboxstate {
+ CSPL_MBOX_STS_RUNNING = 0,
+ CSPL_MBOX_STS_PAUSED = 1,
+ CSPL_MBOX_STS_RDY_FOR_REINIT = 2,
+ CSPL_MBOX_STS_HIBERNATE = 3,
+};
+
+enum cs35l45_cspl_mboxcmd {
+ CSPL_MBOX_CMD_NONE = 0,
+ CSPL_MBOX_CMD_PAUSE = 1,
+ CSPL_MBOX_CMD_RESUME = 2,
+ CSPL_MBOX_CMD_REINIT = 3,
+ CSPL_MBOX_CMD_STOP_PRE_REINIT = 4,
+ CSPL_MBOX_CMD_HIBERNATE = 5,
+ CSPL_MBOX_CMD_OUT_OF_HIBERNATE = 6,
+ CSPL_MBOX_CMD_UNKNOWN_CMD = -1,
+ CSPL_MBOX_CMD_INVALID_SEQUENCE = -2,
+};
+
+enum control_bus_type {
+ CONTROL_BUS_I2C = 0,
+ CONTROL_BUS_SPI = 1,
+};
+
#define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_3LE| \
SNDRV_PCM_FMTBIT_S24_LE)
SNDRV_PCM_RATE_88200 | \
SNDRV_PCM_RATE_96000)
+/*
+ * IRQs
+ */
+#define CS35L45_IRQ(_irq, _name, _hand) \
+ { \
+ .irq = CS35L45_ ## _irq ## _IRQ,\
+ .name = _name, \
+ .handler = _hand, \
+ }
+
+struct cs35l45_irq {
+ int irq;
+ const char *name;
+ irqreturn_t (*handler)(int irq, void *data);
+};
+
+#define CS35L45_REG_IRQ(_reg, _irq) \
+ [CS35L45_ ## _irq ## _IRQ] = { \
+ .reg_offset = (CS35L45_ ## _reg) - CS35L45_IRQ1_EINT_1, \
+ .mask = CS35L45_ ## _irq ## _MASK \
+ }
+
+enum cs35l45_irq_list {
+ CS35L45_AMP_SHORT_ERR_IRQ,
+ CS35L45_UVLO_VDDBATT_ERR_IRQ,
+ CS35L45_BST_SHORT_ERR_IRQ,
+ CS35L45_BST_UVP_ERR_IRQ,
+ CS35L45_TEMP_ERR_IRQ,
+ CS35L45_AMP_CAL_ERR_IRQ,
+ CS35L45_UVLO_VDDLV_ERR_IRQ,
+ CS35L45_GLOBAL_ERROR_IRQ,
+ CS35L45_DSP_WDT_EXPIRE_IRQ,
+ CS35L45_PLL_UNLOCK_FLAG_RISE_IRQ,
+ CS35L45_PLL_LOCK_FLAG_IRQ,
+ CS35L45_DSP_VIRT2_MBOX_IRQ,
+ CS35L45_NUM_IRQ
+};
+
+#define CS35L45_MBOX3_CMD_MASK 0xFF
+#define CS35L45_MBOX3_CMD_SHIFT 0
+#define CS35L45_MBOX3_DATA_MASK 0xFFFFFF00
+#define CS35L45_MBOX3_DATA_SHIFT 8
+
+enum mbox3_events {
+ EVENT_SPEAKER_STATUS = 0x66,
+ EVENT_BOOT_DONE = 0x67,
+};
+
struct cs35l45_private {
+ struct wm_adsp dsp; /* needs to be first member */
struct device *dev;
struct regmap *regmap;
struct gpio_desc *reset_gpio;
bool sysclk_set;
u8 slot_width;
u8 slot_count;
+ int irq_invert;
+ int irq;
+ unsigned int i2c_addr;
+ enum control_bus_type bus_type;
+ struct regmap_irq_chip_data *irq_data;
};
extern const struct dev_pm_ops cs35l45_pm_ops;
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// CS35L56 ALSA SoC audio driver I2C binding
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "cs35l56.h"
+
+static int cs35l56_i2c_probe(struct i2c_client *client)
+{
+ struct cs35l56_private *cs35l56;
+ struct device *dev = &client->dev;
+ const struct regmap_config *regmap_config = &cs35l56_regmap_i2c;
+ int ret;
+
+ cs35l56 = devm_kzalloc(dev, sizeof(struct cs35l56_private), GFP_KERNEL);
+ if (!cs35l56)
+ return -ENOMEM;
+
+ cs35l56->dev = dev;
+ cs35l56->can_hibernate = true;
+
+ i2c_set_clientdata(client, cs35l56);
+ cs35l56->regmap = devm_regmap_init_i2c(client, regmap_config);
+ if (IS_ERR(cs35l56->regmap)) {
+ ret = PTR_ERR(cs35l56->regmap);
+ return dev_err_probe(cs35l56->dev, ret, "Failed to allocate register map\n");
+ }
+
+ ret = cs35l56_common_probe(cs35l56);
+ if (ret != 0)
+ return ret;
+
+ ret = cs35l56_init(cs35l56);
+ if (ret == 0)
+ ret = cs35l56_irq_request(cs35l56, client->irq);
+ if (ret < 0)
+ cs35l56_remove(cs35l56);
+
+ return ret;
+}
+
+static void cs35l56_i2c_remove(struct i2c_client *client)
+{
+ struct cs35l56_private *cs35l56 = i2c_get_clientdata(client);
+
+ cs35l56_remove(cs35l56);
+}
+
+static const struct i2c_device_id cs35l56_id_i2c[] = {
+ { "cs35l56", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs35l56_id_i2c);
+
+static struct i2c_driver cs35l56_i2c_driver = {
+ .driver = {
+ .name = "cs35l56",
+ .pm = &cs35l56_pm_ops_i2c_spi,
+ },
+ .id_table = cs35l56_id_i2c,
+ .probe_new = cs35l56_i2c_probe,
+ .remove = cs35l56_i2c_remove,
+};
+
+module_i2c_driver(cs35l56_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC CS35L56 I2C driver");
+MODULE_IMPORT_NS(SND_SOC_CS35L56_CORE);
+MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// CS35L56 ALSA SoC audio driver SoundWire binding
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+#include <linux/swab.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "cs35l56.h"
+
+/* Register addresses are offset when sent over SoundWire */
+#define CS35L56_SDW_ADDR_OFFSET 0x8000
+
+static int cs35l56_sdw_read_one(struct sdw_slave *peripheral, unsigned int reg, void *buf)
+{
+ int ret;
+
+ ret = sdw_nread_no_pm(peripheral, reg, 4, (u8 *)buf);
+ if (ret != 0) {
+ dev_err(&peripheral->dev, "Read failed @%#x:%d\n", reg, ret);
+ return ret;
+ }
+
+ swab32s((u32 *)buf);
+
+ return 0;
+}
+
+static int cs35l56_sdw_read(void *context, const void *reg_buf,
+ const size_t reg_size, void *val_buf,
+ size_t val_size)
+{
+ struct sdw_slave *peripheral = context;
+ u8 *buf8 = val_buf;
+ unsigned int reg, bytes;
+ int ret;
+
+ reg = le32_to_cpu(*(const __le32 *)reg_buf);
+ reg += CS35L56_SDW_ADDR_OFFSET;
+
+ if (val_size == 4)
+ return cs35l56_sdw_read_one(peripheral, reg, val_buf);
+
+ while (val_size) {
+ bytes = SDW_REG_NO_PAGE - (reg & SDW_REGADDR); /* to end of page */
+ if (bytes > val_size)
+ bytes = val_size;
+
+ ret = sdw_nread_no_pm(peripheral, reg, bytes, buf8);
+ if (ret != 0) {
+ dev_err(&peripheral->dev, "Read failed @%#x..%#x:%d\n",
+ reg, reg + bytes - 1, ret);
+ return ret;
+ }
+
+ swab32_array((u32 *)buf8, bytes / 4);
+ val_size -= bytes;
+ reg += bytes;
+ buf8 += bytes;
+ }
+
+ return 0;
+}
+
+static inline void cs35l56_swab_copy(void *dest, const void *src, size_t nbytes)
+{
+ u32 *dest32 = dest;
+ const u32 *src32 = src;
+
+ for (; nbytes > 0; nbytes -= 4)
+ *dest32++ = swab32(*src32++);
+}
+
+static int cs35l56_sdw_write_one(struct sdw_slave *peripheral, unsigned int reg, const void *buf)
+{
+ u32 val_le = swab32(*(u32 *)buf);
+ int ret;
+
+ ret = sdw_nwrite_no_pm(peripheral, reg, 4, (u8 *)&val_le);
+ if (ret != 0) {
+ dev_err(&peripheral->dev, "Write failed @%#x:%d\n", reg, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cs35l56_sdw_gather_write(void *context,
+ const void *reg_buf, size_t reg_size,
+ const void *val_buf, size_t val_size)
+{
+ struct sdw_slave *peripheral = context;
+ const u8 *src_be = val_buf;
+ u32 val_le_buf[64]; /* Define u32 so it is 32-bit aligned */
+ unsigned int reg, bytes;
+ int ret;
+
+ reg = le32_to_cpu(*(const __le32 *)reg_buf);
+ reg += CS35L56_SDW_ADDR_OFFSET;
+
+ if (val_size == 4)
+ return cs35l56_sdw_write_one(peripheral, reg, src_be);
+
+ while (val_size) {
+ bytes = SDW_REG_NO_PAGE - (reg & SDW_REGADDR); /* to end of page */
+ if (bytes > val_size)
+ bytes = val_size;
+ if (bytes > sizeof(val_le_buf))
+ bytes = sizeof(val_le_buf);
+
+ cs35l56_swab_copy(val_le_buf, src_be, bytes);
+
+ ret = sdw_nwrite_no_pm(peripheral, reg, bytes, (u8 *)val_le_buf);
+ if (ret != 0) {
+ dev_err(&peripheral->dev, "Write failed @%#x..%#x:%d\n",
+ reg, reg + bytes - 1, ret);
+ return ret;
+ }
+
+ val_size -= bytes;
+ reg += bytes;
+ src_be += bytes;
+ }
+
+ return 0;
+}
+
+static int cs35l56_sdw_write(void *context, const void *val_buf, size_t val_size)
+{
+ const u8 *src_buf = val_buf;
+
+ /* First word of val_buf contains the destination address */
+ return cs35l56_sdw_gather_write(context, &src_buf[0], 4, &src_buf[4], val_size - 4);
+}
+
+/*
+ * Registers are big-endian on I2C and SPI but little-endian on SoundWire.
+ * Exported firmware controls are big-endian on I2C/SPI but little-endian on
+ * SoundWire. Firmware files are always big-endian and are opaque blobs.
+ * Present a big-endian regmap and hide the endianness swap, so that the ALSA
+ * byte controls always have the same byte order, and firmware file blobs
+ * can be written verbatim.
+ */
+static const struct regmap_bus cs35l56_regmap_bus_sdw = {
+ .read = cs35l56_sdw_read,
+ .write = cs35l56_sdw_write,
+ .gather_write = cs35l56_sdw_gather_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static void cs35l56_sdw_init(struct sdw_slave *peripheral)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
+ int ret;
+
+ pm_runtime_get_noresume(cs35l56->dev);
+
+ regcache_cache_only(cs35l56->regmap, false);
+
+ ret = cs35l56_init(cs35l56);
+ if (ret < 0) {
+ regcache_cache_only(cs35l56->regmap, true);
+ goto out;
+ }
+
+ /*
+ * cs35l56_init can return with !init_done if it triggered
+ * a soft reset.
+ */
+ if (cs35l56->init_done) {
+ /* Enable SoundWire interrupts */
+ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1,
+ CS35L56_SDW_INT_MASK_CODEC_IRQ);
+ }
+
+out:
+ pm_runtime_mark_last_busy(cs35l56->dev);
+ pm_runtime_put_autosuspend(cs35l56->dev);
+}
+
+static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral,
+ struct sdw_slave_intr_status *status)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
+
+ /* SoundWire core holds our pm_runtime when calling this function. */
+
+ dev_dbg(cs35l56->dev, "int control_port=%#x\n", status->control_port);
+
+ if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0)
+ return 0;
+
+ /*
+ * Prevent bus manager suspending and possibly issuing a
+ * bus-reset before the queued work has run.
+ */
+ pm_runtime_get_noresume(cs35l56->dev);
+
+ /*
+ * Mask and clear until it has been handled. The read of GEN_INT_STAT_1
+ * is required as per the SoundWire spec for interrupt status bits
+ * to clear. GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1.
+ * None of the interrupts are time-critical so use the
+ * power-efficient queue.
+ */
+ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0);
+ sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1);
+ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF);
+ queue_work(system_power_efficient_wq, &cs35l56->sdw_irq_work);
+
+ return 0;
+}
+
+static void cs35l56_sdw_irq_work(struct work_struct *work)
+{
+ struct cs35l56_private *cs35l56 = container_of(work,
+ struct cs35l56_private,
+ sdw_irq_work);
+
+ cs35l56_irq(-1, cs35l56);
+
+ /* unmask interrupts */
+ if (!cs35l56->sdw_irq_no_unmask)
+ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1,
+ CS35L56_SDW_INT_MASK_CODEC_IRQ);
+
+ pm_runtime_put_autosuspend(cs35l56->dev);
+}
+
+static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
+ struct sdw_slave_prop *prop = &peripheral->prop;
+ struct sdw_dpn_prop *ports;
+
+ ports = devm_kcalloc(cs35l56->dev, 2, sizeof(*ports), GFP_KERNEL);
+ if (!ports)
+ return -ENOMEM;
+
+ prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT);
+ prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT);
+ prop->paging_support = true;
+ prop->clk_stop_mode1 = false;
+ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY;
+ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF;
+
+ /* DP1 - playback */
+ ports[0].num = CS35L56_SDW1_PLAYBACK_PORT;
+ ports[0].type = SDW_DPN_FULL;
+ ports[0].ch_prep_timeout = 10;
+ prop->sink_dpn_prop = &ports[0];
+
+ /* DP3 - capture */
+ ports[1].num = CS35L56_SDW1_CAPTURE_PORT;
+ ports[1].type = SDW_DPN_FULL;
+ ports[1].ch_prep_timeout = 10;
+ prop->src_dpn_prop = &ports[1];
+
+ return 0;
+}
+
+static int cs35l56_sdw_update_status(struct sdw_slave *peripheral,
+ enum sdw_slave_status status)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
+
+ switch (status) {
+ case SDW_SLAVE_ATTACHED:
+ dev_dbg(cs35l56->dev, "%s: ATTACHED\n", __func__);
+ if (cs35l56->sdw_attached)
+ break;
+
+ if (!cs35l56->init_done || cs35l56->soft_resetting)
+ cs35l56_sdw_init(peripheral);
+
+ cs35l56->sdw_attached = true;
+ break;
+ case SDW_SLAVE_UNATTACHED:
+ dev_dbg(cs35l56->dev, "%s: UNATTACHED\n", __func__);
+ cs35l56->sdw_attached = false;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56,
+ struct sdw_slave *peripheral)
+{
+ unsigned int curr_scale_reg, next_scale_reg;
+ int curr_scale, next_scale, ret;
+
+ if (!cs35l56->init_done)
+ return 0;
+
+ if (peripheral->bus->params.curr_bank) {
+ curr_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B1;
+ next_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B0;
+ } else {
+ curr_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B0;
+ next_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B1;
+ }
+
+ /*
+ * Current clock scale value must be different to new value.
+ * Modify current to guarantee this. If next still has the dummy
+ * value we wrote when it was current, the core code has not set
+ * a new scale so restore its original good value
+ */
+ curr_scale = sdw_read_no_pm(peripheral, curr_scale_reg);
+ if (curr_scale < 0) {
+ dev_err(cs35l56->dev, "Failed to read current clock scale: %d\n", curr_scale);
+ return curr_scale;
+ }
+
+ next_scale = sdw_read_no_pm(peripheral, next_scale_reg);
+ if (next_scale < 0) {
+ dev_err(cs35l56->dev, "Failed to read next clock scale: %d\n", next_scale);
+ return next_scale;
+ }
+
+ if (next_scale == CS35L56_SDW_INVALID_BUS_SCALE) {
+ next_scale = cs35l56->old_sdw_clock_scale;
+ ret = sdw_write_no_pm(peripheral, next_scale_reg, next_scale);
+ if (ret < 0) {
+ dev_err(cs35l56->dev, "Failed to modify current clock scale: %d\n", ret);
+ return ret;
+ }
+ }
+
+ cs35l56->old_sdw_clock_scale = curr_scale;
+ ret = sdw_write_no_pm(peripheral, curr_scale_reg, CS35L56_SDW_INVALID_BUS_SCALE);
+ if (ret < 0) {
+ dev_err(cs35l56->dev, "Failed to modify current clock scale: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(cs35l56->dev, "Next bus scale: %#x\n", next_scale);
+
+ return 0;
+}
+
+static int cs35l56_sdw_bus_config(struct sdw_slave *peripheral,
+ struct sdw_bus_params *params)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
+ int sclk;
+
+ sclk = params->curr_dr_freq / 2;
+ dev_dbg(cs35l56->dev, "%s: sclk=%u c=%u r=%u\n", __func__, sclk, params->col, params->row);
+
+ if (cs35l56->rev < 0xb0)
+ return cs35l56_a1_kick_divider(cs35l56, peripheral);
+
+ return 0;
+}
+
+static int __maybe_unused cs35l56_sdw_clk_stop(struct sdw_slave *peripheral,
+ enum sdw_clk_stop_mode mode,
+ enum sdw_clk_stop_type type)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
+
+ dev_dbg(cs35l56->dev, "%s: mode:%d type:%d\n", __func__, mode, type);
+
+ return 0;
+}
+
+static const struct sdw_slave_ops cs35l56_sdw_ops = {
+ .read_prop = cs35l56_sdw_read_prop,
+ .interrupt_callback = cs35l56_sdw_interrupt,
+ .update_status = cs35l56_sdw_update_status,
+ .bus_config = cs35l56_sdw_bus_config,
+#ifdef DEBUG
+ .clk_stop = cs35l56_sdw_clk_stop,
+#endif
+};
+
+static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs35l56)
+{
+ struct sdw_slave *peripheral = cs35l56->sdw_peripheral;
+
+ if (peripheral->unattach_request) {
+ /* Cannot access registers until bus is re-initialized. */
+ dev_dbg(cs35l56->dev, "Wait for initialization_complete\n");
+ if (!wait_for_completion_timeout(&peripheral->initialization_complete,
+ msecs_to_jiffies(5000))) {
+ dev_err(cs35l56->dev, "initialization_complete timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ peripheral->unattach_request = 0;
+
+ /*
+ * Don't call regcache_mark_dirty(), we can't be sure that the
+ * Manager really did issue a Bus Reset.
+ */
+ }
+
+ return 0;
+}
+
+static int __maybe_unused cs35l56_sdw_runtime_suspend(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ if (!cs35l56->init_done)
+ return 0;
+
+ return cs35l56_runtime_suspend(dev);
+}
+
+static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "Runtime resume\n");
+
+ if (!cs35l56->init_done)
+ return 0;
+
+ ret = cs35l56_sdw_handle_unattach(cs35l56);
+ if (ret < 0)
+ return ret;
+
+ ret = cs35l56_runtime_resume_common(cs35l56);
+ if (ret)
+ return ret;
+
+ /* Re-enable SoundWire interrupts */
+ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1,
+ CS35L56_SDW_INT_MASK_CODEC_IRQ);
+
+ return 0;
+}
+
+static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ if (!cs35l56->init_done)
+ return 0;
+
+ /*
+ * Disable SoundWire interrupts.
+ * Flush - don't cancel because that could leave an unbalanced pm_runtime_get.
+ */
+ cs35l56->sdw_irq_no_unmask = true;
+ flush_work(&cs35l56->sdw_irq_work);
+
+ /* Mask interrupts and flush in case sdw_irq_work was queued again */
+ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0);
+ sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1);
+ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF);
+ flush_work(&cs35l56->sdw_irq_work);
+
+ return cs35l56_system_suspend(dev);
+}
+
+static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ cs35l56->sdw_irq_no_unmask = false;
+ /* runtime_resume re-enables the interrupt */
+
+ return cs35l56_system_resume(dev);
+}
+
+static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id)
+{
+ struct device *dev = &peripheral->dev;
+ struct cs35l56_private *cs35l56;
+ int ret;
+
+ cs35l56 = devm_kzalloc(dev, sizeof(*cs35l56), GFP_KERNEL);
+ if (!cs35l56)
+ return -ENOMEM;
+
+ cs35l56->dev = dev;
+ cs35l56->sdw_peripheral = peripheral;
+ INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work);
+
+ dev_set_drvdata(dev, cs35l56);
+
+ cs35l56->regmap = devm_regmap_init(dev, &cs35l56_regmap_bus_sdw,
+ peripheral, &cs35l56_regmap_sdw);
+ if (IS_ERR(cs35l56->regmap)) {
+ ret = PTR_ERR(cs35l56->regmap);
+ return dev_err_probe(dev, ret, "Failed to allocate register map\n");
+ }
+
+ /* Start in cache-only until device is enumerated */
+ regcache_cache_only(cs35l56->regmap, true);
+
+ ret = cs35l56_common_probe(cs35l56);
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static int cs35l56_sdw_remove(struct sdw_slave *peripheral)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev);
+
+ /* Disable SoundWire interrupts */
+ cs35l56->sdw_irq_no_unmask = true;
+ cancel_work_sync(&cs35l56->sdw_irq_work);
+ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0);
+ sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1);
+ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF);
+
+ cs35l56_remove(cs35l56);
+
+ return 0;
+}
+
+static const struct dev_pm_ops cs35l56_sdw_pm = {
+ SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume)
+ LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early)
+ /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */
+};
+
+static const struct sdw_device_id cs35l56_sdw_id[] = {
+ SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, cs35l56_sdw_id);
+
+static struct sdw_driver cs35l56_sdw_driver = {
+ .driver = {
+ .name = "cs35l56",
+ .pm = &cs35l56_sdw_pm,
+ },
+ .probe = cs35l56_sdw_probe,
+ .remove = cs35l56_sdw_remove,
+ .ops = &cs35l56_sdw_ops,
+ .id_table = cs35l56_sdw_id,
+};
+
+module_sdw_driver(cs35l56_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC CS35L56 SoundWire driver");
+MODULE_IMPORT_NS(SND_SOC_CS35L56_CORE);
+MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Components shared between ASoC and HDA CS35L56 drivers
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include "cs35l56.h"
+
+static const struct reg_default cs35l56_reg_defaults[] = {
+ { CS35L56_ASP1_ENABLES1, 0x00000000 },
+ { CS35L56_ASP1_CONTROL1, 0x00000028 },
+ { CS35L56_ASP1_CONTROL2, 0x18180200 },
+ { CS35L56_ASP1_CONTROL3, 0x00000002 },
+ { CS35L56_ASP1_FRAME_CONTROL1, 0x03020100 },
+ { CS35L56_ASP1_FRAME_CONTROL5, 0x00020100 },
+ { CS35L56_ASP1_DATA_CONTROL1, 0x00000018 },
+ { CS35L56_ASP1_DATA_CONTROL5, 0x00000018 },
+ { CS35L56_ASP1TX1_INPUT, 0x00000018 },
+ { CS35L56_ASP1TX2_INPUT, 0x00000019 },
+ { CS35L56_ASP1TX3_INPUT, 0x00000020 },
+ { CS35L56_ASP1TX4_INPUT, 0x00000028 },
+ { CS35L56_SWIRE_DP3_CH1_INPUT, 0x00000018 },
+ { CS35L56_SWIRE_DP3_CH2_INPUT, 0x00000019 },
+ { CS35L56_SWIRE_DP3_CH3_INPUT, 0x00000029 },
+ { CS35L56_SWIRE_DP3_CH4_INPUT, 0x00000028 },
+ { CS35L56_IRQ1_CFG, 0x00000000 },
+ { CS35L56_IRQ1_MASK_1, 0x83ffffff },
+ { CS35L56_IRQ1_MASK_2, 0xffff7fff },
+ { CS35L56_IRQ1_MASK_4, 0xe0ffffff },
+ { CS35L56_IRQ1_MASK_8, 0xfc000fff },
+ { CS35L56_IRQ1_MASK_18, 0x1f7df0ff },
+ { CS35L56_IRQ1_MASK_20, 0x15c00000 },
+ /* CS35L56_MAIN_RENDER_USER_MUTE - soft register, no default */
+ /* CS35L56_MAIN_RENDER_USER_VOLUME - soft register, no default */
+ /* CS35L56_MAIN_POSTURE_NUMBER - soft register, no default */
+};
+
+static bool cs35l56_is_dsp_memory(unsigned int reg)
+{
+ switch (reg) {
+ case CS35L56_DSP1_XMEM_PACKED_0 ... CS35L56_DSP1_XMEM_PACKED_6143:
+ case CS35L56_DSP1_XMEM_UNPACKED32_0 ... CS35L56_DSP1_XMEM_UNPACKED32_4095:
+ case CS35L56_DSP1_XMEM_UNPACKED24_0 ... CS35L56_DSP1_XMEM_UNPACKED24_8191:
+ case CS35L56_DSP1_YMEM_PACKED_0 ... CS35L56_DSP1_YMEM_PACKED_4604:
+ case CS35L56_DSP1_YMEM_UNPACKED32_0 ... CS35L56_DSP1_YMEM_UNPACKED32_3070:
+ case CS35L56_DSP1_YMEM_UNPACKED24_0 ... CS35L56_DSP1_YMEM_UNPACKED24_6141:
+ case CS35L56_DSP1_PMEM_0 ... CS35L56_DSP1_PMEM_5114:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs35l56_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS35L56_DEVID:
+ case CS35L56_REVID:
+ case CS35L56_RELID:
+ case CS35L56_OTPID:
+ case CS35L56_SFT_RESET:
+ case CS35L56_GLOBAL_ENABLES:
+ case CS35L56_BLOCK_ENABLES:
+ case CS35L56_BLOCK_ENABLES2:
+ case CS35L56_REFCLK_INPUT:
+ case CS35L56_GLOBAL_SAMPLE_RATE:
+ case CS35L56_ASP1_ENABLES1:
+ case CS35L56_ASP1_CONTROL1:
+ case CS35L56_ASP1_CONTROL2:
+ case CS35L56_ASP1_CONTROL3:
+ case CS35L56_ASP1_FRAME_CONTROL1:
+ case CS35L56_ASP1_FRAME_CONTROL5:
+ case CS35L56_ASP1_DATA_CONTROL1:
+ case CS35L56_ASP1_DATA_CONTROL5:
+ case CS35L56_DACPCM1_INPUT:
+ case CS35L56_DACPCM2_INPUT:
+ case CS35L56_ASP1TX1_INPUT:
+ case CS35L56_ASP1TX2_INPUT:
+ case CS35L56_ASP1TX3_INPUT:
+ case CS35L56_ASP1TX4_INPUT:
+ case CS35L56_DSP1RX1_INPUT:
+ case CS35L56_DSP1RX2_INPUT:
+ case CS35L56_SWIRE_DP3_CH1_INPUT:
+ case CS35L56_SWIRE_DP3_CH2_INPUT:
+ case CS35L56_SWIRE_DP3_CH3_INPUT:
+ case CS35L56_SWIRE_DP3_CH4_INPUT:
+ case CS35L56_IRQ1_CFG:
+ case CS35L56_IRQ1_STATUS:
+ case CS35L56_IRQ1_EINT_1 ... CS35L56_IRQ1_EINT_8:
+ case CS35L56_IRQ1_EINT_18:
+ case CS35L56_IRQ1_EINT_20:
+ case CS35L56_IRQ1_MASK_1:
+ case CS35L56_IRQ1_MASK_2:
+ case CS35L56_IRQ1_MASK_4:
+ case CS35L56_IRQ1_MASK_8:
+ case CS35L56_IRQ1_MASK_18:
+ case CS35L56_IRQ1_MASK_20:
+ case CS35L56_DSP_VIRTUAL1_MBOX_1:
+ case CS35L56_DSP_VIRTUAL1_MBOX_2:
+ case CS35L56_DSP_VIRTUAL1_MBOX_3:
+ case CS35L56_DSP_VIRTUAL1_MBOX_4:
+ case CS35L56_DSP_VIRTUAL1_MBOX_5:
+ case CS35L56_DSP_VIRTUAL1_MBOX_6:
+ case CS35L56_DSP_VIRTUAL1_MBOX_7:
+ case CS35L56_DSP_VIRTUAL1_MBOX_8:
+ case CS35L56_DSP_RESTRICT_STS1:
+ case CS35L56_DSP1_SYS_INFO_ID ... CS35L56_DSP1_SYS_INFO_END:
+ case CS35L56_DSP1_AHBM_WINDOW_DEBUG_0:
+ case CS35L56_DSP1_AHBM_WINDOW_DEBUG_1:
+ case CS35L56_DSP1_SCRATCH1:
+ case CS35L56_DSP1_SCRATCH2:
+ case CS35L56_DSP1_SCRATCH3:
+ case CS35L56_DSP1_SCRATCH4:
+ return true;
+ default:
+ return cs35l56_is_dsp_memory(reg);
+ }
+}
+
+static bool cs35l56_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS35L56_DSP1_XMEM_PACKED_0 ... CS35L56_DSP1_XMEM_PACKED_6143:
+ case CS35L56_DSP1_YMEM_PACKED_0 ... CS35L56_DSP1_YMEM_PACKED_4604:
+ case CS35L56_DSP1_PMEM_0 ... CS35L56_DSP1_PMEM_5114:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs35l56_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS35L56_DEVID:
+ case CS35L56_REVID:
+ case CS35L56_RELID:
+ case CS35L56_OTPID:
+ case CS35L56_SFT_RESET:
+ case CS35L56_GLOBAL_ENABLES: /* owned by firmware */
+ case CS35L56_BLOCK_ENABLES: /* owned by firmware */
+ case CS35L56_BLOCK_ENABLES2: /* owned by firmware */
+ case CS35L56_REFCLK_INPUT: /* owned by firmware */
+ case CS35L56_GLOBAL_SAMPLE_RATE: /* owned by firmware */
+ case CS35L56_DACPCM1_INPUT: /* owned by firmware */
+ case CS35L56_DACPCM2_INPUT: /* owned by firmware */
+ case CS35L56_DSP1RX1_INPUT: /* owned by firmware */
+ case CS35L56_DSP1RX2_INPUT: /* owned by firmware */
+ case CS35L56_IRQ1_STATUS:
+ case CS35L56_IRQ1_EINT_1 ... CS35L56_IRQ1_EINT_8:
+ case CS35L56_IRQ1_EINT_18:
+ case CS35L56_IRQ1_EINT_20:
+ case CS35L56_DSP_VIRTUAL1_MBOX_1:
+ case CS35L56_DSP_VIRTUAL1_MBOX_2:
+ case CS35L56_DSP_VIRTUAL1_MBOX_3:
+ case CS35L56_DSP_VIRTUAL1_MBOX_4:
+ case CS35L56_DSP_VIRTUAL1_MBOX_5:
+ case CS35L56_DSP_VIRTUAL1_MBOX_6:
+ case CS35L56_DSP_VIRTUAL1_MBOX_7:
+ case CS35L56_DSP_VIRTUAL1_MBOX_8:
+ case CS35L56_DSP_RESTRICT_STS1:
+ case CS35L56_DSP1_SYS_INFO_ID ... CS35L56_DSP1_SYS_INFO_END:
+ case CS35L56_DSP1_AHBM_WINDOW_DEBUG_0:
+ case CS35L56_DSP1_AHBM_WINDOW_DEBUG_1:
+ case CS35L56_DSP1_SCRATCH1:
+ case CS35L56_DSP1_SCRATCH2:
+ case CS35L56_DSP1_SCRATCH3:
+ case CS35L56_DSP1_SCRATCH4:
+ return true;
+ case CS35L56_MAIN_RENDER_USER_MUTE:
+ case CS35L56_MAIN_RENDER_USER_VOLUME:
+ case CS35L56_MAIN_POSTURE_NUMBER:
+ return false;
+ default:
+ return cs35l56_is_dsp_memory(reg);
+ }
+}
+
+static const u32 cs35l56_firmware_registers[] = {
+ CS35L56_MAIN_RENDER_USER_MUTE,
+ CS35L56_MAIN_RENDER_USER_VOLUME,
+ CS35L56_MAIN_POSTURE_NUMBER,
+};
+
+void cs35l56_reread_firmware_registers(struct device *dev, struct regmap *regmap)
+{
+ int i;
+ unsigned int val;
+
+ for (i = 0; i < ARRAY_SIZE(cs35l56_firmware_registers); i++) {
+ regmap_read(regmap, cs35l56_firmware_registers[i], &val);
+ dev_dbg(dev, "%s: %d: %#x: %#x\n", __func__,
+ i, cs35l56_firmware_registers[i], val);
+ }
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_reread_firmware_registers, SND_SOC_CS35L56_SHARED);
+
+const struct cs_dsp_region cs35l56_dsp1_regions[] = {
+ { .type = WMFW_HALO_PM_PACKED, .base = CS35L56_DSP1_PMEM_0 },
+ { .type = WMFW_HALO_XM_PACKED, .base = CS35L56_DSP1_XMEM_PACKED_0 },
+ { .type = WMFW_HALO_YM_PACKED, .base = CS35L56_DSP1_YMEM_PACKED_0 },
+ { .type = WMFW_ADSP2_XM, .base = CS35L56_DSP1_XMEM_UNPACKED24_0 },
+ { .type = WMFW_ADSP2_YM, .base = CS35L56_DSP1_YMEM_UNPACKED24_0 },
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_dsp1_regions, SND_SOC_CS35L56_SHARED);
+
+static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = {
+ [0x0C] = 128000,
+ [0x0F] = 256000,
+ [0x11] = 384000,
+ [0x12] = 512000,
+ [0x15] = 768000,
+ [0x17] = 1024000,
+ [0x1A] = 1500000,
+ [0x1B] = 1536000,
+ [0x1C] = 2000000,
+ [0x1D] = 2048000,
+ [0x1E] = 2400000,
+ [0x20] = 3000000,
+ [0x21] = 3072000,
+ [0x23] = 4000000,
+ [0x24] = 4096000,
+ [0x25] = 4800000,
+ [0x27] = 6000000,
+ [0x28] = 6144000,
+ [0x29] = 6250000,
+ [0x2A] = 6400000,
+ [0x2E] = 8000000,
+ [0x2F] = 8192000,
+ [0x30] = 9600000,
+ [0x32] = 12000000,
+ [0x33] = 12288000,
+ [0x37] = 13500000,
+ [0x38] = 19200000,
+ [0x39] = 22579200,
+ [0x3B] = 24576000,
+};
+
+int cs35l56_get_bclk_freq_id(unsigned int freq)
+{
+ int i;
+
+ if (freq == 0)
+ return -EINVAL;
+
+ /* The BCLK frequency must be a valid PLL REFCLK */
+ for (i = 0; i < ARRAY_SIZE(cs35l56_bclk_valid_for_pll_freq_table); ++i) {
+ if (cs35l56_bclk_valid_for_pll_freq_table[i] == freq)
+ return i;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_get_bclk_freq_id, SND_SOC_CS35L56_SHARED);
+
+static const char * const cs35l56_supplies[/* auto-sized */] = {
+ "VDD_P",
+ "VDD_IO",
+ "VDD_A",
+};
+
+void cs35l56_fill_supply_names(struct regulator_bulk_data *data)
+{
+ int i;
+
+ BUILD_BUG_ON(ARRAY_SIZE(cs35l56_supplies) != CS35L56_NUM_BULK_SUPPLIES);
+ for (i = 0; i < ARRAY_SIZE(cs35l56_supplies); i++)
+ data[i].supply = cs35l56_supplies[i];
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_fill_supply_names, SND_SOC_CS35L56_SHARED);
+
+const char * const cs35l56_tx_input_texts[] = {
+ "None", "ASP1RX1", "ASP1RX2", "VMON", "IMON", "ERRVOL", "CLASSH",
+ "VDDBMON", "VBSTMON", "DSP1TX1", "DSP1TX2", "DSP1TX3", "DSP1TX4",
+ "DSP1TX5", "DSP1TX6", "DSP1TX7", "DSP1TX8", "TEMPMON",
+ "INTERPOLATOR", "SDW1RX1", "SDW1RX2",
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_tx_input_texts, SND_SOC_CS35L56_SHARED);
+
+const unsigned int cs35l56_tx_input_values[] = {
+ CS35L56_INPUT_SRC_NONE,
+ CS35L56_INPUT_SRC_ASP1RX1,
+ CS35L56_INPUT_SRC_ASP1RX2,
+ CS35L56_INPUT_SRC_VMON,
+ CS35L56_INPUT_SRC_IMON,
+ CS35L56_INPUT_SRC_ERR_VOL,
+ CS35L56_INPUT_SRC_CLASSH,
+ CS35L56_INPUT_SRC_VDDBMON,
+ CS35L56_INPUT_SRC_VBSTMON,
+ CS35L56_INPUT_SRC_DSP1TX1,
+ CS35L56_INPUT_SRC_DSP1TX2,
+ CS35L56_INPUT_SRC_DSP1TX3,
+ CS35L56_INPUT_SRC_DSP1TX4,
+ CS35L56_INPUT_SRC_DSP1TX5,
+ CS35L56_INPUT_SRC_DSP1TX6,
+ CS35L56_INPUT_SRC_DSP1TX7,
+ CS35L56_INPUT_SRC_DSP1TX8,
+ CS35L56_INPUT_SRC_TEMPMON,
+ CS35L56_INPUT_SRC_INTERPOLATOR,
+ CS35L56_INPUT_SRC_SWIRE_DP1_CHANNEL1,
+ CS35L56_INPUT_SRC_SWIRE_DP1_CHANNEL2,
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_tx_input_values, SND_SOC_CS35L56_SHARED);
+
+struct regmap_config cs35l56_regmap_i2c = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = CS35L56_DSP1_PMEM_5114,
+ .reg_defaults = cs35l56_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(cs35l56_reg_defaults),
+ .volatile_reg = cs35l56_volatile_reg,
+ .readable_reg = cs35l56_readable_reg,
+ .precious_reg = cs35l56_precious_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_regmap_i2c, SND_SOC_CS35L56_SHARED);
+
+struct regmap_config cs35l56_regmap_spi = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .pad_bits = 16,
+ .reg_stride = 4,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = CS35L56_DSP1_PMEM_5114,
+ .reg_defaults = cs35l56_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(cs35l56_reg_defaults),
+ .volatile_reg = cs35l56_volatile_reg,
+ .readable_reg = cs35l56_readable_reg,
+ .precious_reg = cs35l56_precious_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_regmap_spi, SND_SOC_CS35L56_SHARED);
+
+struct regmap_config cs35l56_regmap_sdw = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .reg_format_endian = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = CS35L56_DSP1_PMEM_5114,
+ .reg_defaults = cs35l56_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(cs35l56_reg_defaults),
+ .volatile_reg = cs35l56_volatile_reg,
+ .readable_reg = cs35l56_readable_reg,
+ .precious_reg = cs35l56_precious_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_regmap_sdw, SND_SOC_CS35L56_SHARED);
+
+MODULE_DESCRIPTION("ASoC CS35L56 Shared");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// CS35L56 ALSA SoC audio driver SPI binding
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+#include "cs35l56.h"
+
+static int cs35l56_spi_probe(struct spi_device *spi)
+{
+ const struct regmap_config *regmap_config = &cs35l56_regmap_spi;
+ struct cs35l56_private *cs35l56;
+ int ret;
+
+ cs35l56 = devm_kzalloc(&spi->dev, sizeof(struct cs35l56_private), GFP_KERNEL);
+ if (!cs35l56)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, cs35l56);
+ cs35l56->regmap = devm_regmap_init_spi(spi, regmap_config);
+ if (IS_ERR(cs35l56->regmap)) {
+ ret = PTR_ERR(cs35l56->regmap);
+ return dev_err_probe(&spi->dev, ret, "Failed to allocate register map\n");
+ }
+
+ cs35l56->dev = &spi->dev;
+
+ ret = cs35l56_common_probe(cs35l56);
+ if (ret != 0)
+ return ret;
+
+ ret = cs35l56_init(cs35l56);
+ if (ret == 0)
+ ret = cs35l56_irq_request(cs35l56, spi->irq);
+ if (ret < 0)
+ cs35l56_remove(cs35l56);
+
+ return ret;
+}
+
+static void cs35l56_spi_remove(struct spi_device *spi)
+{
+ struct cs35l56_private *cs35l56 = spi_get_drvdata(spi);
+
+ cs35l56_remove(cs35l56);
+}
+
+static const struct spi_device_id cs35l56_id_spi[] = {
+ { "cs35l56", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, cs35l56_id_spi);
+
+static struct spi_driver cs35l56_spi_driver = {
+ .driver = {
+ .name = "cs35l56",
+ .pm = &cs35l56_pm_ops_i2c_spi,
+ },
+ .id_table = cs35l56_id_spi,
+ .probe = cs35l56_spi_probe,
+ .remove = cs35l56_spi_remove,
+};
+
+module_spi_driver(cs35l56_spi_driver);
+
+MODULE_DESCRIPTION("ASoC CS35L56 SPI driver");
+MODULE_IMPORT_NS(SND_SOC_CS35L56_CORE);
+MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Driver for Cirrus Logic CS35L56 smart amp
+//
+// Copyright (C) 2023 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <linux/acpi.h>
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+#include "wm_adsp.h"
+#include "cs35l56.h"
+
+static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+
+static int cs35l56_mbox_send(struct cs35l56_private *cs35l56, unsigned int command)
+{
+ unsigned int val;
+ int ret;
+
+ regmap_write(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, command);
+ ret = regmap_read_poll_timeout(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+ val, (val == 0),
+ CS35L56_MBOX_POLL_US, CS35L56_MBOX_TIMEOUT_US);
+ if (ret) {
+ dev_warn(cs35l56->dev, "MBOX command %#x failed: %d\n", command, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void cs35l56_wait_dsp_ready(struct cs35l56_private *cs35l56)
+{
+ /* Wait for patching to complete */
+ flush_work(&cs35l56->dsp_work);
+}
+
+static int cs35l56_dspwait_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+
+ cs35l56_wait_dsp_ready(cs35l56);
+ return snd_soc_get_volsw(kcontrol, ucontrol);
+}
+
+static int cs35l56_dspwait_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+
+ cs35l56_wait_dsp_ready(cs35l56);
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static DECLARE_TLV_DB_SCALE(vol_tlv, -10000, 25, 0);
+
+static const struct snd_kcontrol_new cs35l56_controls[] = {
+ SOC_SINGLE_EXT("Speaker Switch",
+ CS35L56_MAIN_RENDER_USER_MUTE, 0, 1, 1,
+ cs35l56_dspwait_get_volsw, cs35l56_dspwait_put_volsw),
+ SOC_SINGLE_S_EXT_TLV("Speaker Volume",
+ CS35L56_MAIN_RENDER_USER_VOLUME,
+ 6, -400, 400, 9, 0,
+ cs35l56_dspwait_get_volsw,
+ cs35l56_dspwait_put_volsw,
+ vol_tlv),
+ SOC_SINGLE_EXT("Posture Number", CS35L56_MAIN_POSTURE_NUMBER,
+ 0, 255, 0,
+ cs35l56_dspwait_get_volsw, cs35l56_dspwait_put_volsw),
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx1_enum,
+ CS35L56_ASP1TX1_INPUT,
+ 0, CS35L56_ASP_TXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new asp1_tx1_mux =
+ SOC_DAPM_ENUM("ASP1TX1 SRC", cs35l56_asp1tx1_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx2_enum,
+ CS35L56_ASP1TX2_INPUT,
+ 0, CS35L56_ASP_TXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new asp1_tx2_mux =
+ SOC_DAPM_ENUM("ASP1TX2 SRC", cs35l56_asp1tx2_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx3_enum,
+ CS35L56_ASP1TX3_INPUT,
+ 0, CS35L56_ASP_TXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new asp1_tx3_mux =
+ SOC_DAPM_ENUM("ASP1TX3 SRC", cs35l56_asp1tx3_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx4_enum,
+ CS35L56_ASP1TX4_INPUT,
+ 0, CS35L56_ASP_TXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new asp1_tx4_mux =
+ SOC_DAPM_ENUM("ASP1TX4 SRC", cs35l56_asp1tx4_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx1_enum,
+ CS35L56_SWIRE_DP3_CH1_INPUT,
+ 0, CS35L56_SWIRETXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new sdw1_tx1_mux =
+ SOC_DAPM_ENUM("SDW1TX1 SRC", cs35l56_sdw1tx1_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx2_enum,
+ CS35L56_SWIRE_DP3_CH2_INPUT,
+ 0, CS35L56_SWIRETXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new sdw1_tx2_mux =
+ SOC_DAPM_ENUM("SDW1TX2 SRC", cs35l56_sdw1tx2_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx3_enum,
+ CS35L56_SWIRE_DP3_CH3_INPUT,
+ 0, CS35L56_SWIRETXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new sdw1_tx3_mux =
+ SOC_DAPM_ENUM("SDW1TX3 SRC", cs35l56_sdw1tx3_enum);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx4_enum,
+ CS35L56_SWIRE_DP3_CH4_INPUT,
+ 0, CS35L56_SWIRETXn_SRC_MASK,
+ cs35l56_tx_input_texts,
+ cs35l56_tx_input_values);
+
+static const struct snd_kcontrol_new sdw1_tx4_mux =
+ SOC_DAPM_ENUM("SDW1TX4 SRC", cs35l56_sdw1tx4_enum);
+
+static int cs35l56_play_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+ unsigned int val;
+ int ret;
+
+ dev_dbg(cs35l56->dev, "play: %d\n", event);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Don't wait for ACK, we check in POST_PMU that it completed */
+ return regmap_write(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+ CS35L56_MBOX_CMD_AUDIO_PLAY);
+ case SND_SOC_DAPM_POST_PMU:
+ /* Wait for firmware to enter PS0 power state */
+ ret = regmap_read_poll_timeout(cs35l56->regmap,
+ CS35L56_TRANSDUCER_ACTUAL_PS,
+ val, (val == CS35L56_PS0),
+ CS35L56_PS0_POLL_US,
+ CS35L56_PS0_TIMEOUT_US);
+ if (ret)
+ dev_err(cs35l56->dev, "PS0 wait failed: %d\n", ret);
+ return ret;
+ case SND_SOC_DAPM_POST_PMD:
+ return cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_AUDIO_PAUSE);
+ default:
+ return 0;
+ }
+}
+
+static const struct snd_soc_dapm_widget cs35l56_dapm_widgets[] = {
+ SND_SOC_DAPM_REGULATOR_SUPPLY("VDD_B", 0, 0),
+ SND_SOC_DAPM_REGULATOR_SUPPLY("VDD_AMP", 0, 0),
+
+ SND_SOC_DAPM_SUPPLY("PLAY", SND_SOC_NOPM, 0, 0, cs35l56_play_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_OUT_DRV("AMP", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("SPK"),
+
+ SND_SOC_DAPM_PGA_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0, cs35l56_dsp_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_AIF_IN("ASP1RX1", NULL, 0, CS35L56_ASP1_ENABLES1,
+ CS35L56_ASP_RX1_EN_SHIFT, 0),
+ SND_SOC_DAPM_AIF_IN("ASP1RX2", NULL, 1, CS35L56_ASP1_ENABLES1,
+ CS35L56_ASP_RX2_EN_SHIFT, 0),
+ SND_SOC_DAPM_AIF_OUT("ASP1TX1", NULL, 0, CS35L56_ASP1_ENABLES1,
+ CS35L56_ASP_TX1_EN_SHIFT, 0),
+ SND_SOC_DAPM_AIF_OUT("ASP1TX2", NULL, 1, CS35L56_ASP1_ENABLES1,
+ CS35L56_ASP_TX2_EN_SHIFT, 0),
+ SND_SOC_DAPM_AIF_OUT("ASP1TX3", NULL, 2, CS35L56_ASP1_ENABLES1,
+ CS35L56_ASP_TX3_EN_SHIFT, 0),
+ SND_SOC_DAPM_AIF_OUT("ASP1TX4", NULL, 3, CS35L56_ASP1_ENABLES1,
+ CS35L56_ASP_TX4_EN_SHIFT, 0),
+
+ SND_SOC_DAPM_MUX("ASP1 TX1 Source", SND_SOC_NOPM, 0, 0, &asp1_tx1_mux),
+ SND_SOC_DAPM_MUX("ASP1 TX2 Source", SND_SOC_NOPM, 0, 0, &asp1_tx2_mux),
+ SND_SOC_DAPM_MUX("ASP1 TX3 Source", SND_SOC_NOPM, 0, 0, &asp1_tx3_mux),
+ SND_SOC_DAPM_MUX("ASP1 TX4 Source", SND_SOC_NOPM, 0, 0, &asp1_tx4_mux),
+
+ SND_SOC_DAPM_MUX("SDW1 TX1 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx1_mux),
+ SND_SOC_DAPM_MUX("SDW1 TX2 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx2_mux),
+ SND_SOC_DAPM_MUX("SDW1 TX3 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx3_mux),
+ SND_SOC_DAPM_MUX("SDW1 TX4 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx4_mux),
+
+ SND_SOC_DAPM_SIGGEN("VMON ADC"),
+ SND_SOC_DAPM_SIGGEN("IMON ADC"),
+ SND_SOC_DAPM_SIGGEN("ERRVOL ADC"),
+ SND_SOC_DAPM_SIGGEN("CLASSH ADC"),
+ SND_SOC_DAPM_SIGGEN("VDDBMON ADC"),
+ SND_SOC_DAPM_SIGGEN("VBSTMON ADC"),
+ SND_SOC_DAPM_SIGGEN("TEMPMON ADC"),
+};
+
+#define CS35L56_SRC_ROUTE(name) \
+ { name" Source", "ASP1RX1", "ASP1RX1" }, \
+ { name" Source", "ASP1RX2", "ASP1RX2" }, \
+ { name" Source", "VMON", "VMON ADC" }, \
+ { name" Source", "IMON", "IMON ADC" }, \
+ { name" Source", "ERRVOL", "ERRVOL ADC" }, \
+ { name" Source", "CLASSH", "CLASSH ADC" }, \
+ { name" Source", "VDDBMON", "VDDBMON ADC" }, \
+ { name" Source", "VBSTMON", "VBSTMON ADC" }, \
+ { name" Source", "DSP1TX1", "DSP1" }, \
+ { name" Source", "DSP1TX2", "DSP1" }, \
+ { name" Source", "DSP1TX3", "DSP1" }, \
+ { name" Source", "DSP1TX4", "DSP1" }, \
+ { name" Source", "DSP1TX5", "DSP1" }, \
+ { name" Source", "DSP1TX6", "DSP1" }, \
+ { name" Source", "DSP1TX7", "DSP1" }, \
+ { name" Source", "DSP1TX8", "DSP1" }, \
+ { name" Source", "TEMPMON", "TEMPMON ADC" }, \
+ { name" Source", "INTERPOLATOR", "AMP" }, \
+ { name" Source", "SDW1RX1", "SDW1 Playback" }, \
+ { name" Source", "SDW1RX2", "SDW1 Playback" },
+
+static const struct snd_soc_dapm_route cs35l56_audio_map[] = {
+ { "AMP", NULL, "VDD_B" },
+ { "AMP", NULL, "VDD_AMP" },
+
+ { "ASP1 Playback", NULL, "PLAY" },
+ { "SDW1 Playback", NULL, "PLAY" },
+
+ { "ASP1RX1", NULL, "ASP1 Playback" },
+ { "ASP1RX2", NULL, "ASP1 Playback" },
+ { "DSP1", NULL, "ASP1RX1" },
+ { "DSP1", NULL, "ASP1RX2" },
+ { "DSP1", NULL, "SDW1 Playback" },
+ { "AMP", NULL, "DSP1" },
+ { "SPK", NULL, "AMP" },
+
+ CS35L56_SRC_ROUTE("ASP1 TX1")
+ CS35L56_SRC_ROUTE("ASP1 TX2")
+ CS35L56_SRC_ROUTE("ASP1 TX3")
+ CS35L56_SRC_ROUTE("ASP1 TX4")
+
+ { "ASP1TX1", NULL, "ASP1 TX1 Source" },
+ { "ASP1TX2", NULL, "ASP1 TX2 Source" },
+ { "ASP1TX3", NULL, "ASP1 TX3 Source" },
+ { "ASP1TX4", NULL, "ASP1 TX4 Source" },
+ { "ASP1 Capture", NULL, "ASP1TX1" },
+ { "ASP1 Capture", NULL, "ASP1TX2" },
+ { "ASP1 Capture", NULL, "ASP1TX3" },
+ { "ASP1 Capture", NULL, "ASP1TX4" },
+
+ CS35L56_SRC_ROUTE("SDW1 TX1")
+ CS35L56_SRC_ROUTE("SDW1 TX2")
+ CS35L56_SRC_ROUTE("SDW1 TX3")
+ CS35L56_SRC_ROUTE("SDW1 TX4")
+ { "SDW1 Capture", NULL, "SDW1 TX1 Source" },
+ { "SDW1 Capture", NULL, "SDW1 TX2 Source" },
+ { "SDW1 Capture", NULL, "SDW1 TX3 Source" },
+ { "SDW1 Capture", NULL, "SDW1 TX4 Source" },
+};
+
+static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+
+ dev_dbg(cs35l56->dev, "%s: %d\n", __func__, event);
+
+ return wm_adsp_event(w, kcontrol, event);
+}
+
+irqreturn_t cs35l56_irq(int irq, void *data)
+{
+ struct cs35l56_private *cs35l56 = data;
+ unsigned int status1 = 0, status8 = 0, status20 = 0;
+ unsigned int mask1, mask8, mask20;
+ unsigned int val;
+ int rv;
+
+ irqreturn_t ret = IRQ_NONE;
+
+ if (!cs35l56->init_done)
+ return IRQ_NONE;
+
+ mutex_lock(&cs35l56->irq_lock);
+
+ rv = pm_runtime_resume_and_get(cs35l56->dev);
+ if (rv < 0) {
+ dev_err(cs35l56->dev, "irq: failed to get pm_runtime: %d\n", rv);
+ goto err_unlock;
+ }
+
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_STATUS, &val);
+ if ((val & CS35L56_IRQ1_STS_MASK) == 0) {
+ dev_dbg(cs35l56->dev, "Spurious IRQ: no pending interrupt\n");
+ goto err;
+ }
+
+ /* Ack interrupts */
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_1, &status1);
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_MASK_1, &mask1);
+ status1 &= ~mask1;
+ regmap_write(cs35l56->regmap, CS35L56_IRQ1_EINT_1, status1);
+
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_8, &status8);
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_MASK_8, &mask8);
+ status8 &= ~mask8;
+ regmap_write(cs35l56->regmap, CS35L56_IRQ1_EINT_8, status8);
+
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_20, &status20);
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_MASK_20, &mask20);
+ status20 &= ~mask20;
+ /* We don't want EINT20 but they default to unmasked: force mask */
+ regmap_write(cs35l56->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
+
+ dev_dbg(cs35l56->dev, "%s: %#x %#x\n", __func__, status1, status8);
+
+ /* Check to see if unmasked bits are active */
+ if (!status1 && !status8 && !status20)
+ goto err;
+
+ if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK)
+ dev_crit(cs35l56->dev, "Amp short error\n");
+
+ if (status8 & CS35L56_TEMP_ERR_EINT1_MASK)
+ dev_crit(cs35l56->dev, "Overtemp error\n");
+
+ ret = IRQ_HANDLED;
+
+err:
+ pm_runtime_put(cs35l56->dev);
+err_unlock:
+ mutex_unlock(&cs35l56->irq_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_irq, SND_SOC_CS35L56_CORE);
+
+int cs35l56_irq_request(struct cs35l56_private *cs35l56, int irq)
+{
+ int ret;
+
+ if (!irq)
+ return 0;
+
+ ret = devm_request_threaded_irq(cs35l56->dev, irq, NULL, cs35l56_irq,
+ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW,
+ "cs35l56", cs35l56);
+ if (!ret)
+ cs35l56->irq = irq;
+ else
+ dev_err(cs35l56->dev, "Failed to get IRQ: %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, SND_SOC_CS35L56_CORE);
+
+static int cs35l56_asp_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(codec_dai->component);
+ unsigned int val;
+
+ dev_dbg(cs35l56->dev, "%s: %#x\n", __func__, fmt);
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_CBC_CFC:
+ break;
+ default:
+ dev_err(cs35l56->dev, "Unsupported clock source mode\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ val = CS35L56_ASP_FMT_DSP_A << CS35L56_ASP_FMT_SHIFT;
+ cs35l56->tdm_mode = true;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val = CS35L56_ASP_FMT_I2S << CS35L56_ASP_FMT_SHIFT;
+ cs35l56->tdm_mode = false;
+ break;
+ default:
+ dev_err(cs35l56->dev, "Unsupported DAI format\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_IF:
+ val |= CS35L56_ASP_FSYNC_INV_MASK;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ val |= CS35L56_ASP_BCLK_INV_MASK;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ val |= CS35L56_ASP_BCLK_INV_MASK | CS35L56_ASP_FSYNC_INV_MASK;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ dev_err(cs35l56->dev, "Invalid clock invert\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(cs35l56->regmap,
+ CS35L56_ASP1_CONTROL2,
+ CS35L56_ASP_FMT_MASK |
+ CS35L56_ASP_BCLK_INV_MASK | CS35L56_ASP_FSYNC_INV_MASK,
+ val);
+
+ /* Hi-Z DOUT in unused slots and when all TX are disabled */
+ regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL3,
+ CS35L56_ASP1_DOUT_HIZ_CTRL_MASK,
+ CS35L56_ASP_UNUSED_HIZ_OFF_HIZ);
+
+ return 0;
+}
+
+static void cs35l56_set_asp_slot_positions(struct cs35l56_private *cs35l56,
+ unsigned int reg, unsigned long mask)
+{
+ unsigned int reg_val, channel_shift;
+ int bit_num;
+
+ /* Init all slots to 63 */
+ switch (reg) {
+ case CS35L56_ASP1_FRAME_CONTROL1:
+ reg_val = 0x3f3f3f3f;
+ break;
+ case CS35L56_ASP1_FRAME_CONTROL5:
+ reg_val = 0x3f3f3f;
+ break;
+ }
+
+ /* Enable consecutive TX1..TXn for each of the slots set in mask */
+ channel_shift = 0;
+ for_each_set_bit(bit_num, &mask, 32) {
+ reg_val &= ~(0x3f << channel_shift);
+ reg_val |= bit_num << channel_shift;
+ channel_shift += 8;
+ }
+
+ regmap_write(cs35l56->regmap, reg, reg_val);
+}
+
+static int cs35l56_asp_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
+
+ if ((slots == 0) || (slot_width == 0)) {
+ dev_dbg(cs35l56->dev, "tdm config cleared\n");
+ cs35l56->asp_slot_width = 0;
+ cs35l56->asp_slot_count = 0;
+ return 0;
+ }
+
+ if (slot_width > (CS35L56_ASP_RX_WIDTH_MASK >> CS35L56_ASP_RX_WIDTH_SHIFT)) {
+ dev_err(cs35l56->dev, "tdm invalid slot width %d\n", slot_width);
+ return -EINVAL;
+ }
+
+ /* More than 32 slots would give an unsupportable BCLK frequency */
+ if (slots > 32) {
+ dev_err(cs35l56->dev, "tdm invalid slot count %d\n", slots);
+ return -EINVAL;
+ }
+
+ cs35l56->asp_slot_width = (u8)slot_width;
+ cs35l56->asp_slot_count = (u8)slots;
+
+ // Note: rx/tx is from point of view of the CPU end
+ if (tx_mask == 0)
+ tx_mask = 0x3; // ASPRX1/RX2 in slots 0 and 1
+
+ if (rx_mask == 0)
+ rx_mask = 0xf; // ASPTX1..TX4 in slots 0..3
+
+ cs35l56_set_asp_slot_positions(cs35l56, CS35L56_ASP1_FRAME_CONTROL1, rx_mask);
+ cs35l56_set_asp_slot_positions(cs35l56, CS35L56_ASP1_FRAME_CONTROL5, tx_mask);
+
+ dev_dbg(cs35l56->dev, "tdm slot width: %u count: %u tx_mask: %#x rx_mask: %#x\n",
+ cs35l56->asp_slot_width, cs35l56->asp_slot_count, tx_mask, rx_mask);
+
+ return 0;
+}
+
+static int cs35l56_asp_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
+ unsigned int rate = params_rate(params);
+ u8 asp_width, asp_wl;
+
+ asp_wl = params_width(params);
+ if (cs35l56->asp_slot_width)
+ asp_width = cs35l56->asp_slot_width;
+ else
+ asp_width = asp_wl;
+
+ dev_dbg(cs35l56->dev, "%s: wl=%d, width=%d, rate=%d", __func__, asp_wl, asp_width, rate);
+
+ if (!cs35l56->sysclk_set) {
+ unsigned int slots = cs35l56->asp_slot_count;
+ unsigned int bclk_freq;
+ int freq_id;
+
+ if (slots == 0) {
+ slots = params_channels(params);
+
+ /* I2S always has an even number of slots */
+ if (!cs35l56->tdm_mode)
+ slots = round_up(slots, 2);
+ }
+
+ bclk_freq = asp_width * slots * rate;
+ freq_id = cs35l56_get_bclk_freq_id(bclk_freq);
+ if (freq_id < 0) {
+ dev_err(cs35l56->dev, "%s: Invalid BCLK %u\n", __func__, bclk_freq);
+ return -EINVAL;
+ }
+
+ regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL1,
+ CS35L56_ASP_BCLK_FREQ_MASK,
+ freq_id << CS35L56_ASP_BCLK_FREQ_SHIFT);
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL2,
+ CS35L56_ASP_RX_WIDTH_MASK, asp_width <<
+ CS35L56_ASP_RX_WIDTH_SHIFT);
+ regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_DATA_CONTROL5,
+ CS35L56_ASP_RX_WL_MASK, asp_wl);
+ } else {
+ regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL2,
+ CS35L56_ASP_TX_WIDTH_MASK, asp_width <<
+ CS35L56_ASP_TX_WIDTH_SHIFT);
+ regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_DATA_CONTROL1,
+ CS35L56_ASP_TX_WL_MASK, asp_wl);
+ }
+
+ return 0;
+}
+
+static int cs35l56_asp_dai_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
+ int freq_id;
+
+ if (freq == 0) {
+ cs35l56->sysclk_set = false;
+ return 0;
+ }
+
+ freq_id = cs35l56_get_bclk_freq_id(freq);
+ if (freq_id < 0)
+ return freq_id;
+
+ regmap_update_bits(cs35l56->regmap, CS35L56_ASP1_CONTROL1,
+ CS35L56_ASP_BCLK_FREQ_MASK,
+ freq_id << CS35L56_ASP_BCLK_FREQ_SHIFT);
+ cs35l56->sysclk_set = true;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs35l56_ops = {
+ .set_fmt = cs35l56_asp_dai_set_fmt,
+ .set_tdm_slot = cs35l56_asp_dai_set_tdm_slot,
+ .hw_params = cs35l56_asp_dai_hw_params,
+ .set_sysclk = cs35l56_asp_dai_set_sysclk,
+};
+
+static void cs35l56_sdw_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int cs35l56_sdw_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
+
+ /* rx/tx are from point of view of the CPU end so opposite to our rx/tx */
+ cs35l56->rx_mask = tx_mask;
+ cs35l56->tx_mask = rx_mask;
+
+ return 0;
+}
+
+static int cs35l56_sdw_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
+ struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+ struct sdw_stream_config sconfig;
+ struct sdw_port_config pconfig;
+ int ret;
+
+ dev_dbg(cs35l56->dev, "%s: rate %d\n", __func__, params_rate(params));
+
+ if (!cs35l56->init_done)
+ return -ENODEV;
+
+ if (!sdw_stream)
+ return -EINVAL;
+
+ memset(&sconfig, 0, sizeof(sconfig));
+ memset(&pconfig, 0, sizeof(pconfig));
+
+ sconfig.frame_rate = params_rate(params);
+ sconfig.bps = snd_pcm_format_width(params_format(params));
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ sconfig.direction = SDW_DATA_DIR_RX;
+ pconfig.num = CS35L56_SDW1_PLAYBACK_PORT;
+ pconfig.ch_mask = cs35l56->rx_mask;
+ } else {
+ sconfig.direction = SDW_DATA_DIR_TX;
+ pconfig.num = CS35L56_SDW1_CAPTURE_PORT;
+ pconfig.ch_mask = cs35l56->tx_mask;
+ }
+
+ if (pconfig.ch_mask == 0) {
+ sconfig.ch_count = params_channels(params);
+ pconfig.ch_mask = GENMASK(sconfig.ch_count - 1, 0);
+ } else {
+ sconfig.ch_count = hweight32(pconfig.ch_mask);
+ }
+
+ ret = sdw_stream_add_slave(cs35l56->sdw_peripheral, &sconfig, &pconfig,
+ 1, sdw_stream);
+ if (ret) {
+ dev_err(dai->dev, "Failed to add sdw stream: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cs35l56_sdw_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
+ struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!cs35l56->sdw_peripheral)
+ return -EINVAL;
+
+ sdw_stream_remove_slave(cs35l56->sdw_peripheral, sdw_stream);
+
+ return 0;
+}
+
+static int cs35l56_sdw_dai_set_stream(struct snd_soc_dai *dai,
+ void *sdw_stream, int direction)
+{
+ if (!sdw_stream)
+ return 0;
+
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs35l56_sdw_dai_ops = {
+ .set_tdm_slot = cs35l56_sdw_dai_set_tdm_slot,
+ .shutdown = cs35l56_sdw_dai_shutdown,
+ .hw_params = cs35l56_sdw_dai_hw_params,
+ .hw_free = cs35l56_sdw_dai_hw_free,
+ .set_stream = cs35l56_sdw_dai_set_stream,
+};
+
+static struct snd_soc_dai_driver cs35l56_dai[] = {
+ {
+ .name = "cs35l56-asp1",
+ .id = 0,
+ .playback = {
+ .stream_name = "ASP1 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CS35L56_RATES,
+ .formats = CS35L56_RX_FORMATS,
+ },
+ .capture = {
+ .stream_name = "ASP1 Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = CS35L56_RATES,
+ .formats = CS35L56_TX_FORMATS,
+ },
+ .ops = &cs35l56_ops,
+ .symmetric_rate = 1,
+ .symmetric_sample_bits = 1,
+ },
+ {
+ .name = "cs35l56-sdw1",
+ .id = 1,
+ .playback = {
+ .stream_name = "SDW1 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CS35L56_RATES,
+ .formats = CS35L56_RX_FORMATS,
+ },
+ .capture = {
+ .stream_name = "SDW1 Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = CS35L56_RATES,
+ .formats = CS35L56_TX_FORMATS,
+ },
+ .symmetric_rate = 1,
+ .ops = &cs35l56_sdw_dai_ops,
+ }
+};
+
+static int cs35l56_wait_for_firmware_boot(struct cs35l56_private *cs35l56)
+{
+ unsigned int reg;
+ unsigned int val;
+ int ret;
+
+ if (cs35l56->rev < CS35L56_REVID_B0)
+ reg = CS35L56_DSP1_HALO_STATE_A1;
+ else
+ reg = CS35L56_DSP1_HALO_STATE;
+
+ ret = regmap_read_poll_timeout(cs35l56->regmap, reg,
+ val,
+ (val < 0xFFFF) && (val >= CS35L56_HALO_STATE_BOOT_DONE),
+ CS35L56_HALO_STATE_POLL_US,
+ CS35L56_HALO_STATE_TIMEOUT_US);
+
+ if ((ret < 0) && (ret != -ETIMEDOUT)) {
+ dev_err(cs35l56->dev, "Failed to read HALO_STATE: %d\n", ret);
+ return ret;
+ }
+
+ if ((ret == -ETIMEDOUT) || (val != CS35L56_HALO_STATE_BOOT_DONE)) {
+ dev_err(cs35l56->dev, "Firmware boot fail: HALO_STATE=%#x\n", val);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static inline void cs35l56_wait_min_reset_pulse(void)
+{
+ /* Satisfy minimum reset pulse width spec */
+ usleep_range(CS35L56_RESET_PULSE_MIN_US, 2 * CS35L56_RESET_PULSE_MIN_US);
+}
+
+static const struct reg_sequence cs35l56_system_reset_seq[] = {
+ REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_SYSTEM_RESET),
+};
+
+static void cs35l56_system_reset(struct cs35l56_private *cs35l56)
+{
+ cs35l56->soft_resetting = true;
+
+ /*
+ * Must enter cache-only first so there can't be any more register
+ * accesses other than the controlled system reset sequence below.
+ */
+ regcache_cache_only(cs35l56->regmap, true);
+ regmap_multi_reg_write_bypassed(cs35l56->regmap,
+ cs35l56_system_reset_seq,
+ ARRAY_SIZE(cs35l56_system_reset_seq));
+
+ /* On SoundWire the registers won't be accessible until it re-enumerates. */
+ if (cs35l56->sdw_peripheral)
+ return;
+
+ usleep_range(CS35L56_CONTROL_PORT_READY_US, CS35L56_CONTROL_PORT_READY_US + 400);
+ regcache_cache_only(cs35l56->regmap, false);
+}
+
+static void cs35l56_dsp_work(struct work_struct *work)
+{
+ struct cs35l56_private *cs35l56 = container_of(work,
+ struct cs35l56_private,
+ dsp_work);
+ unsigned int reg;
+ unsigned int val;
+ int ret = 0;
+
+ if (!cs35l56->init_done)
+ return;
+
+ cs35l56->dsp.part = devm_kasprintf(cs35l56->dev, GFP_KERNEL, "cs35l56%s-%02x",
+ cs35l56->secured ? "s" : "", cs35l56->rev);
+
+ if (!cs35l56->dsp.part)
+ return;
+
+ pm_runtime_get_sync(cs35l56->dev);
+
+ /*
+ * Disable SoundWire interrupts to prevent race with IRQ work.
+ * Setting sdw_irq_no_unmask prevents the handler re-enabling
+ * the SoundWire interrupt.
+ */
+ if (cs35l56->sdw_peripheral) {
+ cs35l56->sdw_irq_no_unmask = true;
+ cancel_work_sync(&cs35l56->sdw_irq_work);
+ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0);
+ sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1);
+ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF);
+ }
+
+ ret = cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_SHUTDOWN);
+ if (ret)
+ goto err;
+
+ if (cs35l56->rev < CS35L56_REVID_B0)
+ reg = CS35L56_DSP1_PM_CUR_STATE_A1;
+ else
+ reg = CS35L56_DSP1_PM_CUR_STATE;
+
+ ret = regmap_read_poll_timeout(cs35l56->regmap, reg,
+ val, (val == CS35L56_HALO_STATE_SHUTDOWN),
+ CS35L56_HALO_STATE_POLL_US,
+ CS35L56_HALO_STATE_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(cs35l56->dev, "Failed to poll PM_CUR_STATE to 1 is %d (ret %d)\n",
+ val, ret);
+
+ /* Use wm_adsp to load and apply the firmware patch and coefficient files */
+ ret = wm_adsp_power_up(&cs35l56->dsp);
+ if (ret) {
+ dev_dbg(cs35l56->dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
+ goto err;
+ }
+
+ mutex_lock(&cs35l56->irq_lock);
+
+ init_completion(&cs35l56->init_completion);
+
+ cs35l56_system_reset(cs35l56);
+
+ if (cs35l56->sdw_peripheral) {
+ /*
+ * The system-reset causes the CS35L56 to detach from the bus.
+ * Wait for the manager to re-enumerate the CS35L56 and
+ * cs35l56_init() to run again.
+ */
+ if (!wait_for_completion_timeout(&cs35l56->init_completion,
+ msecs_to_jiffies(5000))) {
+ dev_err(cs35l56->dev, "%s: init_completion timed out (SDW)\n", __func__);
+ goto err_unlock;
+ }
+ } else if (cs35l56_init(cs35l56)) {
+ goto err_unlock;
+ }
+
+ regmap_clear_bits(cs35l56->regmap, CS35L56_PROTECTION_STATUS, CS35L56_FIRMWARE_MISSING);
+ cs35l56->fw_patched = true;
+
+err_unlock:
+ mutex_unlock(&cs35l56->irq_lock);
+err:
+ pm_runtime_mark_last_busy(cs35l56->dev);
+ pm_runtime_put_autosuspend(cs35l56->dev);
+
+ /* Re-enable SoundWire interrupts */
+ if (cs35l56->sdw_peripheral) {
+ cs35l56->sdw_irq_no_unmask = false;
+ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1,
+ CS35L56_SDW_INT_MASK_CODEC_IRQ);
+ }
+}
+
+static int cs35l56_component_probe(struct snd_soc_component *component)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+ struct dentry *debugfs_root = component->debugfs_root;
+
+ BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values));
+
+ if (!wait_for_completion_timeout(&cs35l56->init_completion,
+ msecs_to_jiffies(5000))) {
+ dev_err(cs35l56->dev, "%s: init_completion timed out\n", __func__);
+ return -ENODEV;
+ }
+
+ cs35l56->component = component;
+ wm_adsp2_component_probe(&cs35l56->dsp, component);
+
+ debugfs_create_bool("init_done", 0444, debugfs_root, &cs35l56->init_done);
+ debugfs_create_bool("can_hibernate", 0444, debugfs_root, &cs35l56->can_hibernate);
+ debugfs_create_bool("fw_patched", 0444, debugfs_root, &cs35l56->fw_patched);
+
+ queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
+
+ return 0;
+}
+
+static void cs35l56_component_remove(struct snd_soc_component *component)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+
+ cancel_work_sync(&cs35l56->dsp_work);
+}
+
+static int cs35l56_set_bias_level(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ /*
+ * Wait for patching to complete when transitioning from
+ * BIAS_OFF to BIAS_STANDBY
+ */
+ if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
+ cs35l56_wait_dsp_ready(cs35l56);
+
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_component_dev_cs35l56 = {
+ .probe = cs35l56_component_probe,
+ .remove = cs35l56_component_remove,
+
+ .dapm_widgets = cs35l56_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs35l56_dapm_widgets),
+ .dapm_routes = cs35l56_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cs35l56_audio_map),
+ .controls = cs35l56_controls,
+ .num_controls = ARRAY_SIZE(cs35l56_controls),
+
+ .set_bias_level = cs35l56_set_bias_level,
+
+ .suspend_bias_off = 1, /* see cs35l56_system_resume() */
+};
+
+static const struct reg_sequence cs35l56_hibernate_seq[] = {
+ /* This must be the last register access */
+ REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_HIBERNATE_NOW),
+};
+
+static const struct reg_sequence cs35l56_hibernate_wake_seq[] = {
+ REG_SEQ0(CS35L56_DSP_VIRTUAL1_MBOX_1, CS35L56_MBOX_CMD_WAKEUP),
+};
+
+int cs35l56_runtime_suspend(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ if (!cs35l56->init_done)
+ return 0;
+
+ /* Firmware must have entered a power-save state */
+ ret = regmap_read_poll_timeout(cs35l56->regmap,
+ CS35L56_TRANSDUCER_ACTUAL_PS,
+ val, (val >= CS35L56_PS3),
+ CS35L56_PS3_POLL_US,
+ CS35L56_PS3_TIMEOUT_US);
+ if (ret)
+ dev_warn(cs35l56->dev, "PS3 wait failed: %d\n", ret);
+
+ /* Clear BOOT_DONE so it can be used to detect a reboot */
+ regmap_write(cs35l56->regmap, CS35L56_IRQ1_EINT_4, CS35L56_OTP_BOOT_DONE_MASK);
+
+ if (!cs35l56->can_hibernate) {
+ regcache_cache_only(cs35l56->regmap, true);
+ dev_dbg(dev, "Suspended: no hibernate");
+
+ return 0;
+ }
+
+ /*
+ * Enable auto-hibernate. If it is woken by some other wake source
+ * it will automatically return to hibernate.
+ */
+ cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE);
+
+ /*
+ * Must enter cache-only first so there can't be any more register
+ * accesses other than the controlled hibernate sequence below.
+ */
+ regcache_cache_only(cs35l56->regmap, true);
+
+ regmap_multi_reg_write_bypassed(cs35l56->regmap,
+ cs35l56_hibernate_seq,
+ ARRAY_SIZE(cs35l56_hibernate_seq));
+
+ dev_dbg(dev, "Suspended: hibernate");
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_suspend, SND_SOC_CS35L56_CORE);
+
+static int __maybe_unused cs35l56_runtime_resume_i2c_spi(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ if (!cs35l56->init_done)
+ return 0;
+
+ return cs35l56_runtime_resume_common(cs35l56);
+}
+
+int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56)
+{
+ unsigned int val;
+ int ret;
+
+ if (!cs35l56->can_hibernate)
+ goto out_sync;
+
+ if (!cs35l56->sdw_peripheral) {
+ /*
+ * Dummy transaction to trigger I2C/SPI auto-wake. This will NAK on I2C.
+ * Must be done before releasing cache-only.
+ */
+ regmap_multi_reg_write_bypassed(cs35l56->regmap,
+ cs35l56_hibernate_wake_seq,
+ ARRAY_SIZE(cs35l56_hibernate_wake_seq));
+
+ usleep_range(CS35L56_CONTROL_PORT_READY_US,
+ CS35L56_CONTROL_PORT_READY_US + 400);
+ }
+
+out_sync:
+ regcache_cache_only(cs35l56->regmap, false);
+
+ ret = cs35l56_wait_for_firmware_boot(cs35l56);
+ if (ret) {
+ dev_err(cs35l56->dev, "Hibernate wake failed: %d\n", ret);
+ goto err;
+ }
+
+ ret = cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
+ if (ret)
+ goto err;
+
+ /* BOOT_DONE will be 1 if the amp reset */
+ regmap_read(cs35l56->regmap, CS35L56_IRQ1_EINT_4, &val);
+ if (val & CS35L56_OTP_BOOT_DONE_MASK) {
+ dev_dbg(cs35l56->dev, "Registers reset in suspend\n");
+ regcache_mark_dirty(cs35l56->regmap);
+ }
+
+ regcache_sync(cs35l56->regmap);
+
+ dev_dbg(cs35l56->dev, "Resumed");
+
+ return 0;
+
+err:
+ regmap_write(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
+ CS35L56_MBOX_CMD_HIBERNATE_NOW);
+
+ regcache_cache_only(cs35l56->regmap, true);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_runtime_resume_common, SND_SOC_CS35L56_CORE);
+
+static int cs35l56_is_fw_reload_needed(struct cs35l56_private *cs35l56)
+{
+ unsigned int val;
+ int ret;
+
+ /* Nothing to re-patch if we haven't done any patching yet. */
+ if (!cs35l56->fw_patched)
+ return false;
+
+ /*
+ * If we have control of RESET we will have asserted it so the firmware
+ * will need re-patching.
+ */
+ if (cs35l56->reset_gpio)
+ return true;
+
+ /*
+ * In secure mode FIRMWARE_MISSING is cleared by the BIOS loader so
+ * can't be used here to test for memory retention.
+ * Assume that tuning must be re-loaded.
+ */
+ if (cs35l56->secured)
+ return true;
+
+ ret = pm_runtime_resume_and_get(cs35l56->dev);
+ if (ret) {
+ dev_err(cs35l56->dev, "Failed to runtime_get: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(cs35l56->regmap, CS35L56_PROTECTION_STATUS, &val);
+ if (ret)
+ dev_err(cs35l56->dev, "Failed to read PROTECTION_STATUS: %d\n", ret);
+ else
+ ret = !!(val & CS35L56_FIRMWARE_MISSING);
+
+ pm_runtime_put_autosuspend(cs35l56->dev);
+
+ return ret;
+}
+
+int cs35l56_system_suspend(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "system_suspend\n");
+
+ if (cs35l56->component)
+ flush_work(&cs35l56->dsp_work);
+
+ /*
+ * The interrupt line is normally shared, but after we start suspending
+ * we can't check if our device is the source of an interrupt, and can't
+ * clear it. Prevent this race by temporarily disabling the parent irq
+ * until we reach _no_irq.
+ */
+ if (cs35l56->irq)
+ disable_irq(cs35l56->irq);
+
+ return pm_runtime_force_suspend(dev);
+}
+EXPORT_SYMBOL_GPL(cs35l56_system_suspend);
+
+int cs35l56_system_suspend_late(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "system_suspend_late\n");
+
+ /*
+ * Assert RESET before removing supplies.
+ * RESET is usually shared by all amps so it must not be asserted until
+ * all driver instances have done their suspend() stage.
+ */
+ if (cs35l56->reset_gpio) {
+ gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+ cs35l56_wait_min_reset_pulse();
+ }
+
+ regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs35l56_system_suspend_late);
+
+int cs35l56_system_suspend_no_irq(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "system_suspend_no_irq\n");
+
+ /* Handlers are now disabled so the parent IRQ can safely be re-enabled. */
+ if (cs35l56->irq)
+ enable_irq(cs35l56->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs35l56_system_suspend_no_irq);
+
+int cs35l56_system_resume_no_irq(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "system_resume_no_irq\n");
+
+ /*
+ * WAKE interrupts unmask if the CS35L56 hibernates, which can cause
+ * spurious interrupts, and the interrupt line is normally shared.
+ * We can't check if our device is the source of an interrupt, and can't
+ * clear it, until it has fully resumed. Prevent this race by temporarily
+ * disabling the parent irq until we complete resume().
+ */
+ if (cs35l56->irq)
+ disable_irq(cs35l56->irq);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs35l56_system_resume_no_irq);
+
+int cs35l56_system_resume_early(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "system_resume_early\n");
+
+ /* Ensure a spec-compliant RESET pulse. */
+ if (cs35l56->reset_gpio) {
+ gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+ cs35l56_wait_min_reset_pulse();
+ }
+
+ /* Enable supplies before releasing RESET. */
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
+ if (ret) {
+ dev_err(dev, "system_resume_early failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ /* Release shared RESET before drivers start resume(). */
+ gpiod_set_value_cansleep(cs35l56->reset_gpio, 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs35l56_system_resume_early);
+
+int cs35l56_system_resume(struct device *dev)
+{
+ struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "system_resume\n");
+
+ /* Undo pm_runtime_force_suspend() before re-enabling the irq */
+ ret = pm_runtime_force_resume(dev);
+ if (cs35l56->irq)
+ enable_irq(cs35l56->irq);
+
+ if (ret)
+ return ret;
+
+ /* Firmware won't have been loaded if the component hasn't probed */
+ if (!cs35l56->component)
+ return 0;
+
+ ret = cs35l56_is_fw_reload_needed(cs35l56);
+ dev_dbg(cs35l56->dev, "fw_reload_needed: %d\n", ret);
+ if (ret < 1)
+ return ret;
+
+ cs35l56->fw_patched = false;
+ queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
+
+ /*
+ * suspend_bias_off ensures we are now in BIAS_OFF so there will be
+ * a BIAS_OFF->BIAS_STANDBY transition to complete dsp patching.
+ */
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs35l56_system_resume);
+
+static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
+{
+ struct wm_adsp *dsp;
+ int ret;
+
+ cs35l56->dsp_wq = create_singlethread_workqueue("cs35l56-dsp");
+ if (!cs35l56->dsp_wq)
+ return -ENOMEM;
+
+ INIT_WORK(&cs35l56->dsp_work, cs35l56_dsp_work);
+
+ dsp = &cs35l56->dsp;
+ dsp->part = "cs35l56";
+ dsp->cs_dsp.num = 1;
+ dsp->cs_dsp.type = WMFW_HALO;
+ dsp->cs_dsp.rev = 0;
+ dsp->fw = 12;
+ dsp->cs_dsp.dev = cs35l56->dev;
+ dsp->cs_dsp.regmap = cs35l56->regmap;
+ dsp->cs_dsp.base = CS35L56_DSP1_CORE_BASE;
+ dsp->cs_dsp.base_sysinfo = CS35L56_DSP1_SYS_INFO_ID;
+ dsp->cs_dsp.mem = cs35l56_dsp1_regions;
+ dsp->cs_dsp.num_mems = ARRAY_SIZE(cs35l56_dsp1_regions);
+ dsp->cs_dsp.no_core_startstop = true;
+ dsp->wmfw_optional = true;
+
+ dev_dbg(cs35l56->dev, "DSP system name: '%s'\n", dsp->system_name);
+
+ ret = wm_halo_init(dsp);
+ if (ret != 0) {
+ dev_err(cs35l56->dev, "wm_halo_init failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cs35l56_acpi_get_name(struct cs35l56_private *cs35l56)
+{
+ acpi_handle handle = ACPI_HANDLE(cs35l56->dev);
+ const char *sub;
+
+ /* If there is no ACPI_HANDLE, there is no ACPI for this system, return 0 */
+ if (!handle)
+ return 0;
+
+ sub = acpi_get_subsystem_id(handle);
+ if (IS_ERR(sub)) {
+ /* If bad ACPI, return 0 and fallback to legacy firmware path, otherwise fail */
+ if (PTR_ERR(sub) == -ENODATA)
+ return 0;
+ else
+ return PTR_ERR(sub);
+ }
+
+ cs35l56->dsp.system_name = sub;
+ dev_dbg(cs35l56->dev, "Subsystem ID: %s\n", cs35l56->dsp.system_name);
+
+ return 0;
+}
+
+int cs35l56_common_probe(struct cs35l56_private *cs35l56)
+{
+ int ret;
+
+ init_completion(&cs35l56->init_completion);
+ mutex_init(&cs35l56->irq_lock);
+
+ dev_set_drvdata(cs35l56->dev, cs35l56);
+
+ cs35l56_fill_supply_names(cs35l56->supplies);
+ ret = devm_regulator_bulk_get(cs35l56->dev, ARRAY_SIZE(cs35l56->supplies),
+ cs35l56->supplies);
+ if (ret != 0)
+ return dev_err_probe(cs35l56->dev, ret, "Failed to request supplies\n");
+
+ /* Reset could be controlled by the BIOS or shared by multiple amps */
+ cs35l56->reset_gpio = devm_gpiod_get_optional(cs35l56->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(cs35l56->reset_gpio)) {
+ ret = PTR_ERR(cs35l56->reset_gpio);
+ /*
+ * If RESET is shared the first amp to probe will grab the reset
+ * line and reset all the amps
+ */
+ if (ret != -EBUSY)
+ return dev_err_probe(cs35l56->dev, ret, "Failed to get reset GPIO\n");
+
+ dev_info(cs35l56->dev, "Reset GPIO busy, assume shared reset\n");
+ cs35l56->reset_gpio = NULL;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
+ if (ret != 0)
+ return dev_err_probe(cs35l56->dev, ret, "Failed to enable supplies\n");
+
+ if (cs35l56->reset_gpio) {
+ cs35l56_wait_min_reset_pulse();
+ gpiod_set_value_cansleep(cs35l56->reset_gpio, 1);
+ }
+
+ ret = cs35l56_acpi_get_name(cs35l56);
+ if (ret != 0)
+ goto err;
+
+ ret = cs35l56_dsp_init(cs35l56);
+ if (ret < 0) {
+ dev_err_probe(cs35l56->dev, ret, "DSP init failed\n");
+ goto err;
+ }
+
+ ret = devm_snd_soc_register_component(cs35l56->dev,
+ &soc_component_dev_cs35l56,
+ cs35l56_dai, ARRAY_SIZE(cs35l56_dai));
+ if (ret < 0) {
+ dev_err_probe(cs35l56->dev, ret, "Register codec failed\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+ regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_common_probe, SND_SOC_CS35L56_CORE);
+
+int cs35l56_init(struct cs35l56_private *cs35l56)
+{
+ int ret;
+ unsigned int devid, revid, otpid, secured;
+
+ /*
+ * Check whether the actions associated with soft reset or one time
+ * init need to be performed.
+ */
+ if (cs35l56->soft_resetting)
+ goto post_soft_reset;
+
+ if (cs35l56->init_done)
+ return 0;
+
+ pm_runtime_set_autosuspend_delay(cs35l56->dev, 100);
+ pm_runtime_use_autosuspend(cs35l56->dev);
+ pm_runtime_set_active(cs35l56->dev);
+ pm_runtime_enable(cs35l56->dev);
+
+ /*
+ * If the system is not using a reset_gpio then issue a
+ * dummy read to force a wakeup.
+ */
+ if (!cs35l56->reset_gpio)
+ regmap_read(cs35l56->regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, &devid);
+
+ /* Wait for control port to be ready (datasheet tIRS). */
+ usleep_range(CS35L56_CONTROL_PORT_READY_US,
+ CS35L56_CONTROL_PORT_READY_US + 400);
+
+ /*
+ * The HALO_STATE register is in different locations on Ax and B0
+ * devices so the REVID needs to be determined before waiting for the
+ * firmware to boot.
+ */
+ ret = regmap_read(cs35l56->regmap, CS35L56_REVID, &revid);
+ if (ret < 0) {
+ dev_err(cs35l56->dev, "Get Revision ID failed\n");
+ return ret;
+ }
+ cs35l56->rev = revid & (CS35L56_AREVID_MASK | CS35L56_MTLREVID_MASK);
+
+ ret = cs35l56_wait_for_firmware_boot(cs35l56);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(cs35l56->regmap, CS35L56_DEVID, &devid);
+ if (ret < 0) {
+ dev_err(cs35l56->dev, "Get Device ID failed\n");
+ return ret;
+ }
+ devid &= CS35L56_DEVID_MASK;
+
+ switch (devid) {
+ case 0x35A56:
+ break;
+ default:
+ dev_err(cs35l56->dev, "Unknown device %x\n", devid);
+ return ret;
+ }
+
+ ret = regmap_read(cs35l56->regmap, CS35L56_DSP_RESTRICT_STS1, &secured);
+ if (ret) {
+ dev_err(cs35l56->dev, "Get Secure status failed\n");
+ return ret;
+ }
+
+ /* When any bus is restricted treat the device as secured */
+ if (secured & CS35L56_RESTRICTED_MASK)
+ cs35l56->secured = true;
+
+ ret = regmap_read(cs35l56->regmap, CS35L56_OTPID, &otpid);
+ if (ret < 0) {
+ dev_err(cs35l56->dev, "Get OTP ID failed\n");
+ return ret;
+ }
+
+ dev_info(cs35l56->dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n",
+ cs35l56->secured ? "s" : "", cs35l56->rev, otpid);
+
+ /* Wake source and *_BLOCKED interrupts default to unmasked, so mask them */
+ regmap_write(cs35l56->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff);
+ regmap_update_bits(cs35l56->regmap, CS35L56_IRQ1_MASK_1,
+ CS35L56_AMP_SHORT_ERR_EINT1_MASK,
+ 0);
+ regmap_update_bits(cs35l56->regmap, CS35L56_IRQ1_MASK_8,
+ CS35L56_TEMP_ERR_EINT1_MASK,
+ 0);
+
+ if (!cs35l56->reset_gpio) {
+ dev_dbg(cs35l56->dev, "No reset gpio: using soft reset\n");
+ cs35l56_system_reset(cs35l56);
+ if (cs35l56->sdw_peripheral) {
+ /* Keep alive while we wait for re-enumeration */
+ pm_runtime_get_noresume(cs35l56->dev);
+ return 0;
+ }
+ }
+
+post_soft_reset:
+ if (cs35l56->soft_resetting) {
+ cs35l56->soft_resetting = false;
+
+ /* Done re-enumerating after one-time init so release the keep-alive */
+ if (cs35l56->sdw_peripheral && !cs35l56->init_done)
+ pm_runtime_put_noidle(cs35l56->dev);
+
+ regcache_mark_dirty(cs35l56->regmap);
+ ret = cs35l56_wait_for_firmware_boot(cs35l56);
+ if (ret)
+ return ret;
+
+ dev_dbg(cs35l56->dev, "Firmware rebooted after soft reset\n");
+ }
+
+ /* Disable auto-hibernate so that runtime_pm has control */
+ ret = cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
+ if (ret)
+ return ret;
+
+ /* Populate soft registers in the regmap cache */
+ cs35l56_reread_firmware_registers(cs35l56->dev, cs35l56->regmap);
+
+ /* Registers could be dirty after soft reset or SoundWire enumeration */
+ regcache_sync(cs35l56->regmap);
+
+ cs35l56->init_done = true;
+ complete(&cs35l56->init_completion);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_init, SND_SOC_CS35L56_CORE);
+
+void cs35l56_remove(struct cs35l56_private *cs35l56)
+{
+ cs35l56->init_done = false;
+
+ /*
+ * WAKE IRQs unmask if CS35L56 hibernates so free the handler to
+ * prevent it racing with remove().
+ */
+ if (cs35l56->irq)
+ devm_free_irq(cs35l56->dev, cs35l56->irq, cs35l56);
+
+ flush_workqueue(cs35l56->dsp_wq);
+ destroy_workqueue(cs35l56->dsp_wq);
+
+ pm_runtime_suspend(cs35l56->dev);
+ pm_runtime_disable(cs35l56->dev);
+
+ regcache_cache_only(cs35l56->regmap, true);
+
+ kfree(cs35l56->dsp.system_name);
+
+ gpiod_set_value_cansleep(cs35l56->reset_gpio, 0);
+ regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
+}
+EXPORT_SYMBOL_NS_GPL(cs35l56_remove, SND_SOC_CS35L56_CORE);
+
+const struct dev_pm_ops cs35l56_pm_ops_i2c_spi = {
+ SET_RUNTIME_PM_OPS(cs35l56_runtime_suspend, cs35l56_runtime_resume_i2c_spi, NULL)
+ SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend, cs35l56_system_resume)
+ LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early)
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_no_irq, cs35l56_system_resume_no_irq)
+};
+EXPORT_SYMBOL_NS_GPL(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE);
+
+MODULE_DESCRIPTION("ASoC CS35L56 driver");
+MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Driver for Cirrus Logic CS35L56 smart amp
+ *
+ * Copyright (C) 2023 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef CS35L56_H
+#define CS35L56_H
+
+#include <linux/completion.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/workqueue.h>
+#include <sound/cs35l56.h>
+#include "wm_adsp.h"
+
+#define CS35L56_SDW_GEN_INT_STAT_1 0xc0
+#define CS35L56_SDW_GEN_INT_MASK_1 0xc1
+#define CS35L56_SDW_INT_MASK_CODEC_IRQ BIT(0)
+
+#define CS35L56_SDW_INVALID_BUS_SCALE 0xf
+
+#define CS35L56_RX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+#define CS35L56_TX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE \
+ | SNDRV_PCM_FMTBIT_S32_LE)
+
+#define CS35L56_RATES (SNDRV_PCM_RATE_48000)
+
+struct sdw_slave;
+
+struct cs35l56_private {
+ struct wm_adsp dsp; /* must be first member */
+ struct work_struct dsp_work;
+ struct workqueue_struct *dsp_wq;
+ struct mutex irq_lock;
+ struct snd_soc_component *component;
+ struct device *dev;
+ struct regmap *regmap;
+ struct regulator_bulk_data supplies[CS35L56_NUM_BULK_SUPPLIES];
+ int irq;
+ struct sdw_slave *sdw_peripheral;
+ u8 rev;
+ struct work_struct sdw_irq_work;
+ bool secured;
+ bool sdw_irq_no_unmask;
+ bool soft_resetting;
+ bool init_done;
+ bool sdw_attached;
+ bool fw_patched;
+ bool can_hibernate;
+ struct completion init_completion;
+ struct gpio_desc *reset_gpio;
+
+ u32 rx_mask;
+ u32 tx_mask;
+ u8 asp_slot_width;
+ u8 asp_slot_count;
+ bool tdm_mode;
+ bool sysclk_set;
+ u8 old_sdw_clock_scale;
+};
+
+extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi;
+
+int cs35l56_runtime_suspend(struct device *dev);
+int cs35l56_runtime_resume_common(struct cs35l56_private *cs35l56);
+int cs35l56_system_suspend(struct device *dev);
+int cs35l56_system_suspend_late(struct device *dev);
+int cs35l56_system_suspend_no_irq(struct device *dev);
+int cs35l56_system_resume_no_irq(struct device *dev);
+int cs35l56_system_resume_early(struct device *dev);
+int cs35l56_system_resume(struct device *dev);
+irqreturn_t cs35l56_irq(int irq, void *data);
+int cs35l56_irq_request(struct cs35l56_private *cs35l56, int irq);
+int cs35l56_common_probe(struct cs35l56_private *cs35l56);
+int cs35l56_init(struct cs35l56_private *cs35l56);
+void cs35l56_remove(struct cs35l56_private *cs35l56);
+
+#endif /* ifndef CS35L56_H */
config = cs4271_regmap_config;
config.reg_bits = 8;
- config.val_bits = 8;
return cs4271_probe(&client->dev,
devm_regmap_init_i2c(client, &config));
config = cs4271_regmap_config;
config.reg_bits = 16;
- config.val_bits = 8;
config.read_flag_mask = 0x21;
config.write_flag_mask = 0x20;
.reg_defaults = cs4271_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(cs4271_reg_defaults),
- .cache_type = REGCACHE_RBTREE,
-
+ .cache_type = REGCACHE_FLAT,
+ .val_bits = 8,
.volatile_reg = cs4271_volatile_reg,
};
EXPORT_SYMBOL_GPL(cs4271_regmap_config);
static int cs42l42_sdw_dai_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- if (!sdw_stream)
- return 0;
-
snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
return ret;
}
-static int cs47l15_remove(struct platform_device *pdev)
+static void cs47l15_remove(struct platform_device *pdev)
{
struct cs47l15 *cs47l15 = platform_get_drvdata(pdev);
madera_free_irq(cs47l15->core.madera, MADERA_IRQ_DSP_IRQ1, cs47l15);
madera_free_overheat(&cs47l15->core);
madera_core_free(&cs47l15->core);
-
- return 0;
}
static struct platform_driver cs47l15_codec_driver = {
.name = "cs47l15-codec",
},
.probe = &cs47l15_probe,
- .remove = &cs47l15_remove,
+ .remove_new = cs47l15_remove,
};
module_platform_driver(cs47l15_codec_driver);
return ret;
}
-static int cs47l24_remove(struct platform_device *pdev)
+static void cs47l24_remove(struct platform_device *pdev)
{
struct cs47l24_priv *cs47l24 = platform_get_drvdata(pdev);
struct arizona *arizona = cs47l24->core.arizona;
arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 0);
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, cs47l24);
-
- return 0;
}
static struct platform_driver cs47l24_codec_driver = {
.name = "cs47l24-codec",
},
.probe = cs47l24_probe,
- .remove = cs47l24_remove,
+ .remove_new = cs47l24_remove,
};
module_platform_driver(cs47l24_codec_driver);
return ret;
}
-static int cs47l35_remove(struct platform_device *pdev)
+static void cs47l35_remove(struct platform_device *pdev)
{
struct cs47l35 *cs47l35 = platform_get_drvdata(pdev);
int i;
madera_free_irq(cs47l35->core.madera, MADERA_IRQ_DSP_IRQ1, cs47l35);
madera_free_overheat(&cs47l35->core);
madera_core_free(&cs47l35->core);
-
- return 0;
}
static struct platform_driver cs47l35_codec_driver = {
.name = "cs47l35-codec",
},
.probe = &cs47l35_probe,
- .remove = &cs47l35_remove,
+ .remove_new = cs47l35_remove,
};
module_platform_driver(cs47l35_codec_driver);
return ret;
}
-static int cs47l85_remove(struct platform_device *pdev)
+static void cs47l85_remove(struct platform_device *pdev)
{
struct cs47l85 *cs47l85 = platform_get_drvdata(pdev);
int i;
madera_free_irq(cs47l85->core.madera, MADERA_IRQ_DSP_IRQ1, cs47l85);
madera_free_overheat(&cs47l85->core);
madera_core_free(&cs47l85->core);
-
- return 0;
}
static struct platform_driver cs47l85_codec_driver = {
.name = "cs47l85-codec",
},
.probe = &cs47l85_probe,
- .remove = &cs47l85_remove,
+ .remove_new = cs47l85_remove,
};
module_platform_driver(cs47l85_codec_driver);
return ret;
}
-static int cs47l90_remove(struct platform_device *pdev)
+static void cs47l90_remove(struct platform_device *pdev)
{
struct cs47l90 *cs47l90 = platform_get_drvdata(pdev);
int i;
madera_set_irq_wake(cs47l90->core.madera, MADERA_IRQ_DSP_IRQ1, 0);
madera_free_irq(cs47l90->core.madera, MADERA_IRQ_DSP_IRQ1, cs47l90);
madera_core_free(&cs47l90->core);
-
- return 0;
}
static struct platform_driver cs47l90_codec_driver = {
.name = "cs47l90-codec",
},
.probe = &cs47l90_probe,
- .remove = &cs47l90_remove,
+ .remove_new = cs47l90_remove,
};
module_platform_driver(cs47l90_codec_driver);
return ret;
}
-static int cs47l92_remove(struct platform_device *pdev)
+static void cs47l92_remove(struct platform_device *pdev)
{
struct cs47l92 *cs47l92 = platform_get_drvdata(pdev);
madera_free_irq(cs47l92->core.madera, MADERA_IRQ_DSP_IRQ1, cs47l92);
madera_core_free(&cs47l92->core);
-
- return 0;
}
static struct platform_driver cs47l92_codec_driver = {
.name = "cs47l92-codec",
},
.probe = &cs47l92_probe,
- .remove = &cs47l92_remove,
+ .remove_new = cs47l92_remove,
};
module_platform_driver(cs47l92_codec_driver);
da7218_handle_pdata(component);
/* Check if MCLK provided, if not the clock is NULL */
- da7218->mclk = devm_clk_get(component->dev, "mclk");
+ da7218->mclk = devm_clk_get_optional(component->dev, "mclk");
if (IS_ERR(da7218->mclk)) {
- if (PTR_ERR(da7218->mclk) != -ENOENT) {
- ret = PTR_ERR(da7218->mclk);
- goto err_disable_reg;
- } else {
- da7218->mclk = NULL;
- }
+ ret = PTR_ERR(da7218->mclk);
+ goto err_disable_reg;
}
/* Default PC to free-running */
static void da7219_aad_jack_det_work(struct work_struct *work)
{
struct da7219_aad_priv *da7219_aad =
- container_of(work, struct da7219_aad_priv, jack_det_work);
+ container_of(work, struct da7219_aad_priv, jack_det_work.work);
struct snd_soc_component *component = da7219_aad->component;
- u8 srm_st;
- mutex_lock(&da7219_aad->jack_det_mutex);
-
- srm_st = snd_soc_component_read(component, DA7219_PLL_SRM_STS) & DA7219_PLL_SRM_STS_MCLK;
- msleep(da7219_aad->gnd_switch_delay * ((srm_st == 0x0) ? 2 : 1) - 4);
/* Enable ground switch */
snd_soc_component_update_bits(component, 0xFB, 0x01, 0x01);
-
- mutex_unlock(&da7219_aad->jack_det_mutex);
}
-
/*
* IRQ
*/
-static irqreturn_t da7219_aad_pre_irq_thread(int irq, void *data)
-{
-
- struct da7219_aad_priv *da7219_aad = data;
-
- if (!da7219_aad->jack_inserted)
- schedule_work(&da7219_aad->jack_det_work);
-
- return IRQ_WAKE_THREAD;
-}
-
static irqreturn_t da7219_aad_irq_thread(int irq, void *data)
{
struct da7219_aad_priv *da7219_aad = data;
/* Read status register for jack insertion & type status */
statusa = snd_soc_component_read(component, DA7219_ACCDET_STATUS_A);
+ if (events[DA7219_AAD_IRQ_REG_A] & DA7219_E_JACK_INSERTED_MASK) {
+ u8 srm_st;
+ int delay = 0;
+
+ srm_st = snd_soc_component_read(component,
+ DA7219_PLL_SRM_STS) & DA7219_PLL_SRM_STS_MCLK;
+ delay = (da7219_aad->gnd_switch_delay * ((srm_st == 0x0) ? 2 : 1) - 2);
+ queue_delayed_work(da7219_aad->aad_wq,
+ &da7219_aad->jack_det_work,
+ msecs_to_jiffies(delay));
+ }
+
/* Clear events */
regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_EVENT_A,
events, DA7219_AAD_IRQ_REG_MAX);
events[DA7219_AAD_IRQ_REG_A], events[DA7219_AAD_IRQ_REG_B],
statusa);
- if (!da7219_aad->jack_inserted)
- cancel_work_sync(&da7219_aad->jack_det_work);
-
if (statusa & DA7219_JACK_INSERTION_STS_MASK) {
/* Jack Insertion */
if (events[DA7219_AAD_IRQ_REG_A] &
if (statusa & DA7219_JACK_TYPE_STS_MASK) {
report |= SND_JACK_HEADSET;
mask |= SND_JACK_HEADSET | SND_JACK_LINEOUT;
- schedule_work(&da7219_aad->btn_det_work);
+ queue_work(da7219_aad->aad_wq, &da7219_aad->btn_det_work);
} else {
- schedule_work(&da7219_aad->hptest_work);
+ queue_work(da7219_aad->aad_wq, &da7219_aad->hptest_work);
}
}
da7219_aad->jack_inserted = false;
/* Cancel any pending work */
+ cancel_delayed_work_sync(&da7219_aad->jack_det_work);
cancel_work_sync(&da7219_aad->btn_det_work);
cancel_work_sync(&da7219_aad->hptest_work);
snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1,
DA7219_BUTTON_CONFIG_MASK, 0);
+ da7219_aad_handle_gnd_switch_time(component);
+
+ da7219_aad->aad_wq = create_singlethread_workqueue("da7219-aad");
+ if (!da7219_aad->aad_wq) {
+ dev_err(component->dev, "Failed to create aad workqueue\n");
+ return -ENOMEM;
+ }
+
+ INIT_DELAYED_WORK(&da7219_aad->jack_det_work, da7219_aad_jack_det_work);
INIT_WORK(&da7219_aad->btn_det_work, da7219_aad_btn_det_work);
INIT_WORK(&da7219_aad->hptest_work, da7219_aad_hptest_work);
- INIT_WORK(&da7219_aad->jack_det_work, da7219_aad_jack_det_work);
-
- mutex_init(&da7219_aad->jack_det_mutex);
- ret = request_threaded_irq(da7219_aad->irq, da7219_aad_pre_irq_thread,
+ ret = request_threaded_irq(da7219_aad->irq, NULL,
da7219_aad_irq_thread,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"da7219-aad", da7219_aad);
regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_MASK_A,
&mask, DA7219_AAD_IRQ_REG_MAX);
- da7219_aad_handle_gnd_switch_time(component);
-
return 0;
}
free_irq(da7219_aad->irq, da7219_aad);
+ cancel_delayed_work_sync(&da7219_aad->jack_det_work);
cancel_work_sync(&da7219_aad->btn_det_work);
cancel_work_sync(&da7219_aad->hptest_work);
+ destroy_workqueue(da7219_aad->aad_wq);
}
/*
MODULE_DESCRIPTION("ASoC DA7219 AAD Driver");
MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>");
+MODULE_AUTHOR("David Rau <David.Rau.opensource@dm.renesas.com>");
MODULE_LICENSE("GPL");
struct work_struct btn_det_work;
struct work_struct hptest_work;
- struct work_struct jack_det_work;
-
- struct mutex jack_det_mutex;
+ struct delayed_work jack_det_work;
+ struct workqueue_struct *aad_wq;
struct snd_soc_jack *jack;
bool micbias_resume_enable;
.endianness = 1,
};
-static const struct regmap_range es8316_volatile_ranges[] = {
- regmap_reg_range(ES8316_GPIO_FLAG, ES8316_GPIO_FLAG),
-};
-
-static const struct regmap_access_table es8316_volatile_table = {
- .yes_ranges = es8316_volatile_ranges,
- .n_yes_ranges = ARRAY_SIZE(es8316_volatile_ranges),
-};
+static bool es8316_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ES8316_GPIO_FLAG:
+ return true;
+ default:
+ return false;
+ }
+}
static const struct regmap_config es8316_regmap = {
.reg_bits = 8,
.use_single_read = true,
.use_single_write = true,
.max_register = 0x53,
- .volatile_table = &es8316_volatile_table,
+ .volatile_reg = es8316_volatile_reg,
.cache_type = REGCACHE_RBTREE,
};
es8316->irq = i2c_client->irq;
mutex_init(&es8316->lock);
- ret = devm_request_threaded_irq(dev, es8316->irq, NULL, es8316_irq,
- IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQF_NO_AUTOEN,
- "es8316", es8316);
- if (ret) {
- dev_warn(dev, "Failed to get IRQ %d: %d\n", es8316->irq, ret);
- es8316->irq = -ENXIO;
+ if (es8316->irq > 0) {
+ ret = devm_request_threaded_irq(dev, es8316->irq, NULL, es8316_irq,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "es8316", es8316);
+ if (ret) {
+ dev_warn(dev, "Failed to get IRQ %d: %d\n", es8316->irq, ret);
+ es8316->irq = -ENXIO;
+ }
}
return devm_snd_soc_register_component(&i2c_client->dev,
return ret;
}
-static int rk3036_codec_platform_remove(struct platform_device *pdev)
+static void rk3036_codec_platform_remove(struct platform_device *pdev)
{
struct rk3036_codec_priv *priv = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(priv->pclk);
-
- return 0;
}
static const struct of_device_id rk3036_codec_of_match[] __maybe_unused = {
.of_match_table = of_match_ptr(rk3036_codec_of_match),
},
.probe = rk3036_codec_platform_probe,
- .remove = rk3036_codec_platform_remove,
+ .remove_new = rk3036_codec_platform_remove,
};
module_platform_driver(rk3036_codec_platform_driver);
struct lpass_macro *l_pds;
int ret;
- if (!of_find_property(dev->of_node, "power-domains", NULL))
+ if (!of_property_present(dev->of_node, "power-domains"))
return NULL;
l_pds = devm_kzalloc(dev, sizeof(*l_pds), GFP_KERNEL);
#ifndef __LPASS_MACRO_COMMON_H__
#define __LPASS_MACRO_COMMON_H__
+/* NPL clock is expected */
+#define LPASS_MACRO_FLAG_HAS_NPL_CLOCK BIT(0)
+
struct lpass_macro {
struct device *macro_pd;
struct device *dcodec_pd;
struct clk_init_data init;
int ret;
- parent_clk_name = __clk_get_name(rx->npl);
+ if (rx->npl)
+ parent_clk_name = __clk_get_name(rx->npl);
+ else
+ parent_clk_name = __clk_get_name(rx->mclk);
init.name = clk_name;
init.ops = &swclk_gate_ops;
static int rx_macro_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ kernel_ulong_t flags;
struct rx_macro *rx;
void __iomem *base;
int ret;
+ flags = (kernel_ulong_t)device_get_match_data(dev);
+
rx = devm_kzalloc(dev, sizeof(*rx), GFP_KERNEL);
if (!rx)
return -ENOMEM;
if (IS_ERR(rx->mclk))
return PTR_ERR(rx->mclk);
- rx->npl = devm_clk_get(dev, "npl");
- if (IS_ERR(rx->npl))
- return PTR_ERR(rx->npl);
+ if (flags & LPASS_MACRO_FLAG_HAS_NPL_CLOCK) {
+ rx->npl = devm_clk_get(dev, "npl");
+ if (IS_ERR(rx->npl))
+ return PTR_ERR(rx->npl);
+ }
rx->fsgen = devm_clk_get(dev, "fsgen");
if (IS_ERR(rx->fsgen))
return ret;
}
-static int rx_macro_remove(struct platform_device *pdev)
+static void rx_macro_remove(struct platform_device *pdev)
{
struct rx_macro *rx = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(rx->dcodec);
lpass_macro_pds_exit(rx->pds);
-
- return 0;
}
static const struct of_device_id rx_macro_dt_match[] = {
- { .compatible = "qcom,sc7280-lpass-rx-macro" },
- { .compatible = "qcom,sm8250-lpass-rx-macro" },
- { .compatible = "qcom,sm8450-lpass-rx-macro" },
- { .compatible = "qcom,sc8280xp-lpass-rx-macro" },
+ {
+ .compatible = "qcom,sc7280-lpass-rx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+
+ }, {
+ .compatible = "qcom,sm8250-lpass-rx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8450-lpass-rx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8550-lpass-rx-macro",
+ }, {
+ .compatible = "qcom,sc8280xp-lpass-rx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ },
{ }
};
MODULE_DEVICE_TABLE(of, rx_macro_dt_match);
.pm = &rx_macro_pm_ops,
},
.probe = rx_macro_probe,
- .remove = rx_macro_remove,
+ .remove_new = rx_macro_remove,
};
module_platform_driver(rx_macro_driver);
struct clk_init_data init;
int ret;
- parent_clk_name = __clk_get_name(tx->npl);
+ if (tx->npl)
+ parent_clk_name = __clk_get_name(tx->npl);
+ else
+ parent_clk_name = __clk_get_name(tx->mclk);
init.name = clk_name;
init.ops = &swclk_gate_ops;
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
+ kernel_ulong_t flags;
struct tx_macro *tx;
void __iomem *base;
int ret, reg;
+ flags = (kernel_ulong_t)device_get_match_data(dev);
+
tx = devm_kzalloc(dev, sizeof(*tx), GFP_KERNEL);
if (!tx)
return -ENOMEM;
if (IS_ERR(tx->mclk))
return PTR_ERR(tx->mclk);
- tx->npl = devm_clk_get(dev, "npl");
- if (IS_ERR(tx->npl))
- return PTR_ERR(tx->npl);
+ if (flags & LPASS_MACRO_FLAG_HAS_NPL_CLOCK) {
+ tx->npl = devm_clk_get(dev, "npl");
+ if (IS_ERR(tx->npl))
+ return PTR_ERR(tx->npl);
+ }
tx->fsgen = devm_clk_get(dev, "fsgen");
if (IS_ERR(tx->fsgen))
return ret;
}
-static int tx_macro_remove(struct platform_device *pdev)
+static void tx_macro_remove(struct platform_device *pdev)
{
struct tx_macro *tx = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(tx->fsgen);
lpass_macro_pds_exit(tx->pds);
-
- return 0;
}
static int __maybe_unused tx_macro_runtime_suspend(struct device *dev)
};
static const struct of_device_id tx_macro_dt_match[] = {
- { .compatible = "qcom,sc7280-lpass-tx-macro" },
- { .compatible = "qcom,sm8250-lpass-tx-macro" },
- { .compatible = "qcom,sm8450-lpass-tx-macro" },
- { .compatible = "qcom,sc8280xp-lpass-tx-macro" },
+ {
+ .compatible = "qcom,sc7280-lpass-tx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8250-lpass-tx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8450-lpass-tx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8550-lpass-tx-macro",
+ }, {
+ .compatible = "qcom,sc8280xp-lpass-tx-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ },
{ }
};
MODULE_DEVICE_TABLE(of, tx_macro_dt_match);
.pm = &tx_macro_pm_ops,
},
.probe = tx_macro_probe,
- .remove = tx_macro_remove,
+ .remove_new = tx_macro_remove,
};
module_platform_driver(tx_macro_driver);
return ret;
}
-static int va_macro_remove(struct platform_device *pdev)
+static void va_macro_remove(struct platform_device *pdev)
{
struct va_macro *va = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(va->macro);
lpass_macro_pds_exit(va->pds);
-
- return 0;
}
static int __maybe_unused va_macro_runtime_suspend(struct device *dev)
.pm = &va_macro_pm_ops,
},
.probe = va_macro_probe,
- .remove = va_macro_remove,
+ .remove_new = va_macro_remove,
};
module_platform_driver(va_macro_driver);
#include <linux/pm_runtime.h>
#include <linux/of_platform.h>
#include <sound/tlv.h>
+
+#include "lpass-macro-common.h"
#include "lpass-wsa-macro.h"
#define CDC_WSA_CLK_RST_CTRL_MCLK_CONTROL (0x0000)
struct clk_init_data init;
int ret;
- parent_clk_name = __clk_get_name(wsa->npl);
+ if (wsa->npl)
+ parent_clk_name = __clk_get_name(wsa->npl);
+ else
+ parent_clk_name = __clk_get_name(wsa->mclk);
init.name = "mclk";
of_property_read_string(dev_of_node(dev), "clock-output-names",
{
struct device *dev = &pdev->dev;
struct wsa_macro *wsa;
+ kernel_ulong_t flags;
void __iomem *base;
int ret;
+ flags = (kernel_ulong_t)device_get_match_data(dev);
+
wsa = devm_kzalloc(dev, sizeof(*wsa), GFP_KERNEL);
if (!wsa)
return -ENOMEM;
if (IS_ERR(wsa->mclk))
return PTR_ERR(wsa->mclk);
- wsa->npl = devm_clk_get(dev, "npl");
- if (IS_ERR(wsa->npl))
- return PTR_ERR(wsa->npl);
+ if (flags & LPASS_MACRO_FLAG_HAS_NPL_CLOCK) {
+ wsa->npl = devm_clk_get(dev, "npl");
+ if (IS_ERR(wsa->npl))
+ return PTR_ERR(wsa->npl);
+ }
wsa->fsgen = devm_clk_get(dev, "fsgen");
if (IS_ERR(wsa->fsgen))
}
-static int wsa_macro_remove(struct platform_device *pdev)
+static void wsa_macro_remove(struct platform_device *pdev)
{
struct wsa_macro *wsa = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(wsa->mclk);
clk_disable_unprepare(wsa->npl);
clk_disable_unprepare(wsa->fsgen);
-
- return 0;
}
static int __maybe_unused wsa_macro_runtime_suspend(struct device *dev)
};
static const struct of_device_id wsa_macro_dt_match[] = {
- {.compatible = "qcom,sc7280-lpass-wsa-macro"},
- {.compatible = "qcom,sm8250-lpass-wsa-macro"},
- {.compatible = "qcom,sm8450-lpass-wsa-macro"},
- {.compatible = "qcom,sc8280xp-lpass-wsa-macro" },
+ {
+ .compatible = "qcom,sc7280-lpass-wsa-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8250-lpass-wsa-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8450-lpass-wsa-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ }, {
+ .compatible = "qcom,sm8550-lpass-wsa-macro",
+ }, {
+ .compatible = "qcom,sc8280xp-lpass-wsa-macro",
+ .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK,
+ },
{}
};
MODULE_DEVICE_TABLE(of, wsa_macro_dt_match);
.pm = &wsa_macro_pm_ops,
},
.probe = wsa_macro_probe,
- .remove = wsa_macro_remove,
+ .remove_new = wsa_macro_remove,
};
module_platform_driver(wsa_macro_driver);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2022, Analog Devices Inc.
+
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "max98363.h"
+
+static struct reg_default max98363_reg[] = {
+ {MAX98363_R2001_INTR_RAW, 0x0},
+ {MAX98363_R2003_INTR_STATE, 0x0},
+ {MAX98363_R2005_INTR_FALG, 0x0},
+ {MAX98363_R2007_INTR_EN, 0x0},
+ {MAX98363_R2009_INTR_CLR, 0x0},
+ {MAX98363_R2021_ERR_MON_CTRL, 0x0},
+ {MAX98363_R2022_SPK_MON_THRESH, 0x0},
+ {MAX98363_R2023_SPK_MON_DURATION, 0x0},
+ {MAX98363_R2030_TONE_GEN_CFG, 0x0},
+ {MAX98363_R203F_TONE_GEN_EN, 0x0},
+ {MAX98363_R2040_AMP_VOL, 0x0},
+ {MAX98363_R2041_AMP_GAIN, 0x5},
+ {MAX98363_R2042_DSP_CFG, 0x0},
+ {MAX98363_R21FF_REV_ID, 0x0},
+};
+
+static bool max98363_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX98363_R2001_INTR_RAW:
+ case MAX98363_R2003_INTR_STATE:
+ case MAX98363_R2005_INTR_FALG:
+ case MAX98363_R2007_INTR_EN:
+ case MAX98363_R2009_INTR_CLR:
+ case MAX98363_R2021_ERR_MON_CTRL ... MAX98363_R2023_SPK_MON_DURATION:
+ case MAX98363_R2030_TONE_GEN_CFG:
+ case MAX98363_R203F_TONE_GEN_EN:
+ case MAX98363_R2040_AMP_VOL:
+ case MAX98363_R2041_AMP_GAIN:
+ case MAX98363_R2042_DSP_CFG:
+ case MAX98363_R21FF_REV_ID:
+ return true;
+ default:
+ return false;
+ }
+};
+
+static bool max98363_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX98363_R2001_INTR_RAW:
+ case MAX98363_R2003_INTR_STATE:
+ case MAX98363_R2005_INTR_FALG:
+ case MAX98363_R2007_INTR_EN:
+ case MAX98363_R2009_INTR_CLR:
+ case MAX98363_R21FF_REV_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config max98363_sdw_regmap = {
+ .reg_bits = 32,
+ .val_bits = 8,
+ .max_register = MAX98363_R21FF_REV_ID,
+ .reg_defaults = max98363_reg,
+ .num_reg_defaults = ARRAY_SIZE(max98363_reg),
+ .readable_reg = max98363_readable_register,
+ .volatile_reg = max98363_volatile_reg,
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int max98363_suspend(struct device *dev)
+{
+ struct max98363_priv *max98363 = dev_get_drvdata(dev);
+
+ regcache_cache_only(max98363->regmap, true);
+ regcache_mark_dirty(max98363->regmap);
+
+ return 0;
+}
+
+#define MAX98363_PROBE_TIMEOUT 5000
+
+static int max98363_resume(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct max98363_priv *max98363 = dev_get_drvdata(dev);
+ unsigned long time;
+
+ if (!max98363->first_hw_init)
+ return 0;
+
+ if (!slave->unattach_request)
+ goto regmap_sync;
+
+ time = wait_for_completion_timeout(&slave->initialization_complete,
+ msecs_to_jiffies(MAX98363_PROBE_TIMEOUT));
+ if (!time) {
+ dev_err(dev, "Initialization not complete, timed out\n");
+ return -ETIMEDOUT;
+ }
+
+regmap_sync:
+
+ slave->unattach_request = 0;
+ regcache_cache_only(max98363->regmap, false);
+ regcache_sync(max98363->regmap);
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(max98363_pm, max98363_suspend, max98363_resume, NULL);
+
+static int max98363_read_prop(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ int nval, i;
+ u32 bit;
+ unsigned long addr;
+ struct sdw_dpn_prop *dpn;
+
+ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY;
+
+ /* BITMAP: 00000010 Dataport 1 is active */
+ prop->sink_ports = BIT(1);
+ prop->paging_support = true;
+ prop->clk_stop_timeout = 20;
+ prop->simple_clk_stop_capable = true;
+ prop->clock_reg_supported = true;
+
+ nval = hweight32(prop->sink_ports);
+ prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->sink_dpn_prop),
+ GFP_KERNEL);
+ if (!prop->sink_dpn_prop)
+ return -ENOMEM;
+
+ i = 0;
+ dpn = prop->sink_dpn_prop;
+ addr = prop->sink_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+
+ return 0;
+}
+
+static int max98363_io_init(struct sdw_slave *slave)
+{
+ struct device *dev = &slave->dev;
+ struct max98363_priv *max98363 = dev_get_drvdata(dev);
+ int ret, reg;
+
+ if (max98363->first_hw_init) {
+ regcache_cache_only(max98363->regmap, false);
+ regcache_cache_bypass(max98363->regmap, true);
+ }
+
+ /*
+ * PM runtime is only enabled when a Slave reports as Attached
+ */
+ if (!max98363->first_hw_init) {
+ /* set autosuspend parameters */
+ pm_runtime_set_autosuspend_delay(dev, 3000);
+ pm_runtime_use_autosuspend(dev);
+
+ /* update count of parent 'active' children */
+ pm_runtime_set_active(dev);
+
+ /* make sure the device does not suspend immediately */
+ pm_runtime_mark_last_busy(dev);
+
+ pm_runtime_enable(dev);
+ }
+
+ pm_runtime_get_noresume(dev);
+
+ ret = regmap_read(max98363->regmap, MAX98363_R21FF_REV_ID, ®);
+ if (!ret) {
+ dev_info(dev, "Revision ID: %X\n", reg);
+ return ret;
+ }
+
+ if (max98363->first_hw_init) {
+ regcache_cache_bypass(max98363->regmap, false);
+ regcache_mark_dirty(max98363->regmap);
+ }
+
+ max98363->first_hw_init = true;
+ max98363->hw_init = true;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+#define MAX98363_RATES SNDRV_PCM_RATE_8000_192000
+#define MAX98363_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
+
+static int max98363_sdw_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct max98363_priv *max98363 =
+ snd_soc_component_get_drvdata(component);
+
+ struct sdw_stream_config stream_config;
+ struct sdw_port_config port_config;
+ enum sdw_data_direction direction;
+ struct sdw_stream_runtime *stream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ int ret;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!stream)
+ return -EINVAL;
+
+ if (!max98363->slave)
+ return -EINVAL;
+
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+
+ direction = SDW_DATA_DIR_RX;
+ port_config.num = 1;
+
+ stream_config.frame_rate = params_rate(params);
+ stream_config.bps = snd_pcm_format_width(params_format(params));
+ stream_config.direction = direction;
+ stream_config.ch_count = params_channels(params);
+
+ if (stream_config.ch_count > runtime->hw.channels_max) {
+ stream_config.ch_count = runtime->hw.channels_max;
+ dev_info(dai->dev, "Number of channels: %d (requested: %d)\n",
+ stream_config.ch_count, params_channels(params));
+ }
+ port_config.ch_mask = GENMASK((int)stream_config.ch_count - 1, 0);
+
+ ret = sdw_stream_add_slave(max98363->slave, &stream_config,
+ &port_config, 1, stream);
+ if (ret) {
+ dev_err(dai->dev, "Unable to configure port\n");
+ return ret;
+ }
+
+ dev_dbg(component->dev, "Format supported %d", params_format(params));
+
+ return 0;
+}
+
+static int max98363_pcm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct max98363_priv *max98363 =
+ snd_soc_component_get_drvdata(component);
+ struct sdw_stream_runtime *stream =
+ snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!max98363->slave)
+ return -EINVAL;
+
+ sdw_stream_remove_slave(max98363->slave, stream);
+
+ return 0;
+}
+
+static int max98363_set_sdw_stream(struct snd_soc_dai *dai,
+ void *sdw_stream, int direction)
+{
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops max98363_dai_sdw_ops = {
+ .hw_params = max98363_sdw_dai_hw_params,
+ .hw_free = max98363_pcm_hw_free,
+ .set_stream = max98363_set_sdw_stream,
+};
+
+static struct snd_soc_dai_driver max98363_dai[] = {
+ {
+ .name = "max98363-aif1",
+ .playback = {
+ .stream_name = "HiFi Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = MAX98363_RATES,
+ .formats = MAX98363_FORMATS,
+ },
+ .ops = &max98363_dai_sdw_ops,
+ }
+};
+
+static int max98363_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct max98363_priv *max98363 = dev_get_drvdata(&slave->dev);
+
+ if (status == SDW_SLAVE_UNATTACHED)
+ max98363->hw_init = false;
+
+ /*
+ * Perform initialization only if slave status is SDW_SLAVE_ATTACHED
+ */
+ if (max98363->hw_init || status != SDW_SLAVE_ATTACHED)
+ return 0;
+
+ /* perform I/O transfers required for Slave initialization */
+ return max98363_io_init(slave);
+}
+
+static struct sdw_slave_ops max98363_slave_ops = {
+ .read_prop = max98363_read_prop,
+ .update_status = max98363_update_status,
+};
+
+static DECLARE_TLV_DB_SCALE(max98363_digital_tlv, -6350, 50, 1);
+static const DECLARE_TLV_DB_RANGE(max98363_spk_tlv,
+ 0, 5, TLV_DB_SCALE_ITEM(-300, 300, 0),
+);
+
+static const char * const max98363_tone_cfg_text[] = {
+ "Reserved", "0", "+FS/2", "-FS/2", "1KHz",
+ "12KHz", "8KHz", "6KHz", "4KHz", "3KHz",
+ "2KHz", "1.5KHz", "Reserved", "500Hz", "250Hz"
+};
+
+static SOC_ENUM_SINGLE_DECL(max98363_tone_cfg_enum,
+ MAX98363_R2030_TONE_GEN_CFG, 0,
+ max98363_tone_cfg_text);
+
+static const char * const max98363_spkmon_duration_text[] = {
+ "8ms", "20ms", "40ms", "60ms",
+ "80ms", "160ms", "240ms", "320ms",
+ "400ms", "480ms", "560ms", "640ms",
+ "720ms", "800ms", "880ms", "960ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(max98363_spkmon_duration_enum,
+ MAX98363_R2023_SPK_MON_DURATION, 0,
+ max98363_spkmon_duration_text);
+
+static const struct snd_kcontrol_new max98363_snd_controls[] = {
+ SOC_SINGLE_TLV("Digital Volume", MAX98363_R2040_AMP_VOL,
+ 0, 0x7F, 1, max98363_digital_tlv),
+ SOC_SINGLE_TLV("Speaker Volume", MAX98363_R2041_AMP_GAIN,
+ 0, 10, 0, max98363_spk_tlv),
+ SOC_SINGLE("Tone Generator Switch", MAX98363_R203F_TONE_GEN_EN,
+ 0, 1, 0),
+ SOC_ENUM("Tone Config", max98363_tone_cfg_enum),
+ SOC_SINGLE("Ramp Switch", MAX98363_R2042_DSP_CFG,
+ MAX98363_AMP_DSP_CFG_RMP_SHIFT, 1, 0),
+ SOC_SINGLE("CLK Monitor Switch", MAX98363_R2021_ERR_MON_CTRL,
+ MAX98363_CLOCK_MON_SHIFT, 1, 0),
+ SOC_SINGLE("SPKMON Monitor Switch", MAX98363_R2021_ERR_MON_CTRL,
+ MAX98363_SPKMON_SHIFT, 1, 0),
+ SOC_SINGLE("SPKMON Thresh", MAX98363_R2022_SPK_MON_THRESH, 0, 0xFF, 0),
+ SOC_ENUM("SPKMON Duration", max98363_spkmon_duration_enum),
+};
+
+static const struct snd_soc_dapm_widget max98363_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("AIFIN", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_OUTPUT("BE_OUT"),
+};
+
+static const struct snd_soc_dapm_route max98363_audio_map[] = {
+ /* Plabyack */
+ {"BE_OUT", NULL, "AIFIN"},
+};
+
+static const struct snd_soc_component_driver soc_codec_dev_max98363 = {
+ .controls = max98363_snd_controls,
+ .num_controls = ARRAY_SIZE(max98363_snd_controls),
+ .dapm_widgets = max98363_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(max98363_dapm_widgets),
+ .dapm_routes = max98363_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(max98363_audio_map),
+ .use_pmdown_time = 1,
+ .endianness = 1,
+};
+
+static int max98363_init(struct sdw_slave *slave, struct regmap *regmap)
+{
+ struct max98363_priv *max98363;
+ int ret;
+ struct device *dev = &slave->dev;
+
+ /* Allocate and assign private driver data structure */
+ max98363 = devm_kzalloc(dev, sizeof(*max98363), GFP_KERNEL);
+ if (!max98363)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, max98363);
+ max98363->regmap = regmap;
+ max98363->slave = slave;
+
+ max98363->hw_init = false;
+ max98363->first_hw_init = false;
+
+ /* codec registration */
+ ret = devm_snd_soc_register_component(dev, &soc_codec_dev_max98363,
+ max98363_dai,
+ ARRAY_SIZE(max98363_dai));
+ if (ret < 0)
+ dev_err(dev, "Failed to register codec: %d\n", ret);
+
+ return ret;
+}
+
+static int max98363_sdw_probe(struct sdw_slave *slave,
+ const struct sdw_device_id *id)
+{
+ struct regmap *regmap;
+
+ /* Regmap Initialization */
+ regmap = devm_regmap_init_sdw(slave, &max98363_sdw_regmap);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return max98363_init(slave, regmap);
+}
+
+static const struct sdw_device_id max98363_id[] = {
+ SDW_SLAVE_ENTRY(0x019F, 0x8363, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, max98363_id);
+
+static struct sdw_driver max98363_sdw_driver = {
+ .driver = {
+ .name = "max98363",
+ .pm = pm_ptr(&max98363_pm),
+ },
+ .probe = max98363_sdw_probe,
+ .ops = &max98363_slave_ops,
+ .id_table = max98363_id,
+};
+
+module_sdw_driver(max98363_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC MAX98363 driver SDW");
+MODULE_AUTHOR("Ryan Lee <ryans.lee@analog.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Analog Devices Inc. */
+
+#ifndef _MAX98363_H
+#define _MAX98363_H
+
+#define MAX98363_R2000_SW_RESET 0x2000
+#define MAX98363_R2001_INTR_RAW 0x2001
+#define MAX98363_R2003_INTR_STATE 0x2003
+#define MAX98363_R2005_INTR_FALG 0x2005
+#define MAX98363_R2007_INTR_EN 0x2007
+#define MAX98363_R2009_INTR_CLR 0x2009
+#define MAX98363_R2021_ERR_MON_CTRL 0x2021
+#define MAX98363_R2022_SPK_MON_THRESH 0x2022
+#define MAX98363_R2023_SPK_MON_DURATION 0x2023
+#define MAX98363_R2030_TONE_GEN_CFG 0x2030
+#define MAX98363_R203F_TONE_GEN_EN 0x203F
+#define MAX98363_R2040_AMP_VOL 0x2040
+#define MAX98363_R2041_AMP_GAIN 0x2041
+#define MAX98363_R2042_DSP_CFG 0x2042
+#define MAX98363_R21FF_REV_ID 0x21FF
+
+/* MAX98363_R2021_ERR_MON_CTRL */
+#define MAX98363_SPKMON_SHIFT (3)
+#define MAX98363_CLOCK_MON_SHIFT (0)
+
+/* MAX98363_R2042_DSP_CFG */
+#define MAX98363_AMP_DSP_CFG_RMP_SHIFT (3)
+
+struct max98363_priv {
+ struct regmap *regmap;
+ struct sdw_slave *slave;
+ bool hw_init;
+ bool first_hw_init;
+};
+#endif
#include "max98373.h"
#include "max98373-sdw.h"
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
static const u32 max98373_sdw_cache_reg[] = {
MAX98373_R2054_MEAS_ADC_PVDD_CH_READBACK,
MAX98373_R2055_MEAS_ADC_THERM_CH_READBACK,
snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int ret, chan_sz, sampling_rate;
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!max98373->slave)
}
ret = sdw_stream_add_slave(max98373->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (ret) {
dev_err(dai->dev, "Unable to configure port\n");
return ret;
struct snd_soc_component *component = dai->component;
struct max98373_priv *max98373 =
snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!max98373->slave)
return -EINVAL;
- sdw_stream_remove_slave(max98373->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(max98373->slave, sdw_stream);
return 0;
}
static int max98373_set_sdw_stream(struct snd_soc_dai *dai,
void *sdw_stream, int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void max98373_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int max98373_sdw_set_tdm_slot(struct snd_soc_dai *dai,
// Copyright 2018 Ladislav Michl <ladis@linux-mips.org>
//
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include "max9867.h"
struct max9867_priv {
+ struct clk *mclk;
struct regmap *regmap;
const struct snd_pcm_hw_constraint_list *constraints;
unsigned int sysclk, pclk;
struct max9867_priv *max9867 = snd_soc_component_get_drvdata(component);
switch (level) {
+ case SND_SOC_BIAS_ON:
+ err = clk_prepare_enable(max9867->mclk);
+ if (err)
+ return err;
+ break;
case SND_SOC_BIAS_STANDBY:
if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
err = regcache_sync(max9867->regmap);
return err;
regcache_mark_dirty(max9867->regmap);
+ clk_disable_unprepare(max9867->mclk);
break;
default:
break;
dev_info(&i2c->dev, "device revision: %x\n", reg);
ret = devm_snd_soc_register_component(&i2c->dev, &max9867_component,
max9867_dai, ARRAY_SIZE(max9867_dai));
- if (ret < 0)
+ if (ret < 0) {
dev_err(&i2c->dev, "Failed to register component: %d\n", ret);
- return ret;
+ return ret;
+ }
+
+ max9867->mclk = devm_clk_get(&i2c->dev, NULL);
+ if (IS_ERR(max9867->mclk))
+ return PTR_ERR(max9867->mclk);
+
+ return 0;
}
static const struct i2c_device_id max9867_i2c_id[] = {
return ret;
}
-static int pm8916_wcd_analog_spmi_remove(struct platform_device *pdev)
+static void pm8916_wcd_analog_spmi_remove(struct platform_device *pdev)
{
struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(priv->mclk);
-
- return 0;
}
static const struct of_device_id pm8916_wcd_analog_spmi_match_table[] = {
.of_match_table = pm8916_wcd_analog_spmi_match_table,
},
.probe = pm8916_wcd_analog_spmi_probe,
- .remove = pm8916_wcd_analog_spmi_remove,
+ .remove_new = pm8916_wcd_analog_spmi_remove,
};
module_platform_driver(pm8916_wcd_analog_spmi_driver);
return ret;
}
-static int msm8916_wcd_digital_remove(struct platform_device *pdev)
+static void msm8916_wcd_digital_remove(struct platform_device *pdev)
{
struct msm8916_wcd_digital_priv *priv = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(priv->mclk);
clk_disable_unprepare(priv->ahbclk);
-
- return 0;
}
static const struct of_device_id msm8916_wcd_digital_match_table[] = {
.of_match_table = msm8916_wcd_digital_match_table,
},
.probe = msm8916_wcd_digital_probe,
- .remove = msm8916_wcd_digital_remove,
+ .remove_new = msm8916_wcd_digital_remove,
};
module_platform_driver(msm8916_wcd_digital_driver);
struct mt6358_priv *priv = snd_soc_component_get_drvdata(component);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg;
+ unsigned int reg = 0;
int ret;
ret = snd_soc_put_volsw(kcontrol, ucontrol);
struct mt6359_priv *priv = snd_soc_component_get_drvdata(component);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- unsigned int reg;
+ unsigned int reg = 0;
int index = ucontrol->value.integer.value[0];
int ret;
SOC_ENUM_SINGLE(NAU8821_R2C_DAC_CTRL1, NAU8821_DAC_OVERSAMPLE_SFT,
ARRAY_SIZE(nau8821_dac_oversampl), nau8821_dac_oversampl);
+static const char * const nau8821_adc_drc_noise_gate[] = {
+ "1:1", "2:1", "4:1", "8:1" };
+
+static const struct soc_enum nau8821_adc_drc_noise_gate_enum =
+ SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES, NAU8821_DRC_NG_SLP_ADC_SFT,
+ ARRAY_SIZE(nau8821_adc_drc_noise_gate),
+ nau8821_adc_drc_noise_gate);
+
+static const char * const nau8821_adc_drc_expansion_slope[] = {
+ "1:1", "2:1", "4:1" };
+
+static const struct soc_enum nau8821_adc_drc_expansion_slope_enum =
+ SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES, NAU8821_DRC_EXP_SLP_ADC_SFT,
+ ARRAY_SIZE(nau8821_adc_drc_expansion_slope),
+ nau8821_adc_drc_expansion_slope);
+
+static const char * const nau8821_adc_drc_lower_region[] = {
+ "0", "1:2", "1:4", "1:8", "1:16", "", "", "1:1" };
+
+static const struct soc_enum nau8821_adc_drc_lower_region_enum =
+ SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES,
+ NAU8821_DRC_CMP2_SLP_ADC_SFT,
+ ARRAY_SIZE(nau8821_adc_drc_lower_region),
+ nau8821_adc_drc_lower_region);
+
+static const char * const nau8821_higher_region[] = {
+ "0", "1:2", "1:4", "1:8", "1:16", "", "", "1:1" };
+
+static const struct soc_enum nau8821_higher_region_enum =
+ SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES,
+ NAU8821_DRC_CMP1_SLP_ADC_SFT,
+ ARRAY_SIZE(nau8821_higher_region),
+ nau8821_higher_region);
+
+static const char * const nau8821_limiter_slope[] = {
+ "0", "1:2", "1:4", "1:8", "1:16", "1:32", "1:64", "1:1" };
+
+static const struct soc_enum nau8821_limiter_slope_enum =
+ SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES,
+ NAU8821_DRC_LMT_SLP_ADC_SFT, ARRAY_SIZE(nau8821_limiter_slope),
+ nau8821_limiter_slope);
+
+static const char * const nau8821_detection_attack_time[] = {
+ "Ts", "3Ts", "7Ts", "15Ts", "31Ts", "63Ts", "127Ts", "255Ts",
+ "", "511Ts" };
+
+static const struct soc_enum nau8821_detection_attack_time_enum =
+ SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY,
+ NAU8821_DRC_PK_COEF1_ADC_SFT,
+ ARRAY_SIZE(nau8821_detection_attack_time),
+ nau8821_detection_attack_time);
+
+static const char * const nau8821_detection_release_time[] = {
+ "63Ts", "127Ts", "255Ts", "511Ts", "1023Ts", "2047Ts", "4095Ts",
+ "8191Ts", "", "16383Ts" };
+
+static const struct soc_enum nau8821_detection_release_time_enum =
+ SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY,
+ NAU8821_DRC_PK_COEF2_ADC_SFT,
+ ARRAY_SIZE(nau8821_detection_release_time),
+ nau8821_detection_release_time);
+
+static const char * const nau8821_attack_time[] = {
+ "Ts", "3Ts", "7Ts", "15Ts", "31Ts", "63Ts", "127Ts", "255Ts",
+ "511Ts", "1023Ts", "2047Ts", "4095Ts", "8191Ts" };
+
+static const struct soc_enum nau8821_attack_time_enum =
+ SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY, NAU8821_DRC_ATK_ADC_SFT,
+ ARRAY_SIZE(nau8821_attack_time), nau8821_attack_time);
+
+static const char * const nau8821_decay_time[] = {
+ "63Ts", "127Ts", "255Ts", "511Ts", "1023Ts", "2047Ts", "4095Ts",
+ "8191Ts", "16383Ts", "32757Ts", "65535Ts" };
+
+static const struct soc_enum nau8821_decay_time_enum =
+ SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY, NAU8821_DRC_DCY_ADC_SFT,
+ ARRAY_SIZE(nau8821_decay_time), nau8821_decay_time);
+
static const DECLARE_TLV_DB_MINMAX_MUTE(adc_vol_tlv, -6600, 2400);
static const DECLARE_TLV_DB_MINMAX_MUTE(sidetone_vol_tlv, -4200, 0);
static const DECLARE_TLV_DB_MINMAX(hp_vol_tlv, -900, 0);
static const DECLARE_TLV_DB_SCALE(playback_vol_tlv, -6600, 50, 1);
static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600);
static const DECLARE_TLV_DB_MINMAX_MUTE(crosstalk_vol_tlv, -7000, 2400);
+static const DECLARE_TLV_DB_MINMAX(drc_knee4_tlv, -9800, -3500);
+static const DECLARE_TLV_DB_MINMAX(drc_knee3_tlv, -8100, -1800);
static const struct snd_kcontrol_new nau8821_controls[] = {
SOC_DOUBLE_TLV("Mic Volume", NAU8821_R35_ADC_DGAIN_CTRL1,
SOC_DOUBLE_TLV("Headphone Crosstalk Volume",
NAU8821_R2F_DAC_DGAIN_CTRL,
0, 8, 0xff, 0, crosstalk_vol_tlv),
+ SOC_SINGLE_TLV("ADC DRC KNEE4", NAU8821_R37_ADC_DRC_KNEE_IP34,
+ NAU8821_DRC_KNEE4_IP_ADC_SFT, 0x3f, 1, drc_knee4_tlv),
+ SOC_SINGLE_TLV("ADC DRC KNEE3", NAU8821_R37_ADC_DRC_KNEE_IP34,
+ NAU8821_DRC_KNEE3_IP_ADC_SFT, 0x3f, 1, drc_knee3_tlv),
+
+ SOC_ENUM("ADC DRC Noise Gate", nau8821_adc_drc_noise_gate_enum),
+ SOC_ENUM("ADC DRC Expansion Slope", nau8821_adc_drc_expansion_slope_enum),
+ SOC_ENUM("ADC DRC Lower Region", nau8821_adc_drc_lower_region_enum),
+ SOC_ENUM("ADC DRC Higher Region", nau8821_higher_region_enum),
+ SOC_ENUM("ADC DRC Limiter Slope", nau8821_limiter_slope_enum),
+ SOC_ENUM("ADC DRC Peak Detection Attack Time", nau8821_detection_attack_time_enum),
+ SOC_ENUM("ADC DRC Peak Detection Release Time", nau8821_detection_release_time_enum),
+ SOC_ENUM("ADC DRC Attack Time", nau8821_attack_time_enum),
+ SOC_ENUM("ADC DRC Decay Time", nau8821_decay_time_enum),
+ SOC_SINGLE("DRC Enable Switch", NAU8821_R36_ADC_DRC_KNEE_IP12,
+ NAU8821_DRC_ENA_ADC_SFT, 1, 0),
SOC_ENUM("ADC Decimation Rate", nau8821_adc_decimation_enum),
SOC_ENUM("DAC Oversampling Rate", nau8821_dac_oversampl_enum),
#define NAU8821_ADCL_CH_VOL_SFT 0
#define NAU8821_ADCL_CH_VOL_MASK 0xff
+/* ADC_DRC_KNEE_IP12 (0x36) */
+#define NAU8821_DRC_ENA_ADC_SFT 15
+#define NAU8821_DRC_ENA_ADC_EN (0x1 << NAU8821_DRC_ENA_ADC_SFT)
+
+/* ADC_DRC_KNEE_IP34 (0x37) */
+#define NAU8821_DRC_KNEE4_IP_ADC_SFT 8
+#define NAU8821_DRC_KNEE4_IP_ADC_MASK (0xff << NAU8821_DRC_KNEE4_IP_ADC_SFT)
+#define NAU8821_DRC_KNEE3_IP_ADC_SFT 0
+#define NAU8821_DRC_KNEE3_IP_ADC_MASK 0xff
+
+/* ADC_DRC_SLOPES (0x38) */
+#define NAU8821_DRC_NG_SLP_ADC_SFT 12
+#define NAU8821_DRC_EXP_SLP_ADC_SFT 9
+#define NAU8821_DRC_CMP2_SLP_ADC_SFT 6
+#define NAU8821_DRC_CMP1_SLP_ADC_SFT 3
+#define NAU8821_DRC_LMT_SLP_ADC_SFT 0
+
+/* ADC_DRC_ATKDCY (0x39) */
+#define NAU8821_DRC_PK_COEF1_ADC_SFT 12
+#define NAU8821_DRC_PK_COEF2_ADC_SFT 8
+#define NAU8821_DRC_ATK_ADC_SFT 4
+#define NAU8821_DRC_DCY_ADC_SFT 0
+
/* BIQ1_COF10 (0x4a) */
#define NAU8821_BIQ1_DAC_EN_SFT 3
#define NAU8821_BIQ1_DAC_EN_EN (0x1 << NAU8821_BIQ1_DAC_EN_SFT)
switch (event) {
case SND_SOC_DAPM_POST_PMU:
- msleep(125);
+ msleep(nau8825->adc_delay);
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC);
break;
dev_dbg(dev, "crosstalk-enable: %d\n",
nau8825->xtalk_enable);
dev_dbg(dev, "adcout-drive-strong: %d\n", nau8825->adcout_ds);
+ dev_dbg(dev, "adc-delay-ms: %d\n", nau8825->adc_delay);
}
static int nau8825_read_device_properties(struct device *dev,
nau8825->xtalk_enable = device_property_read_bool(dev,
"nuvoton,crosstalk-enable");
nau8825->adcout_ds = device_property_read_bool(dev, "nuvoton,adcout-drive-strong");
+ ret = device_property_read_u32(dev, "nuvoton,adc-delay-ms", &nau8825->adc_delay);
+ if (ret)
+ nau8825->adc_delay = 125;
+ if (nau8825->adc_delay < 125 || nau8825->adc_delay > 500)
+ dev_warn(dev, "Please set the suitable delay time!\n");
nau8825->mclk = devm_clk_get(dev, "mclk");
if (PTR_ERR(nau8825->mclk) == -EPROBE_DEFER) {
int xtalk_enable;
bool xtalk_baktab_initialized; /* True if initialized. */
bool adcout_ds;
+ int adc_delay;
};
int nau8825_enable_jack_detect(struct snd_soc_component *component,
return pcm179x_common_init(&spi->dev, regmap);
}
-static const struct of_device_id pcm179x_of_match[] = {
+static const struct of_device_id pcm179x_of_match[] __maybe_unused = {
{ .compatible = "ti,pcm1792a", },
{ }
};
return ret;
}
-static int rk817_platform_remove(struct platform_device *pdev)
+static void rk817_platform_remove(struct platform_device *pdev)
{
struct rk817_codec_priv *rk817 = platform_get_drvdata(pdev);
clk_disable_unprepare(rk817->mclk);
-
- return 0;
}
static struct platform_driver rk817_codec_driver = {
.name = "rk817-codec",
},
.probe = rk817_platform_probe,
- .remove = rk817_platform_remove,
+ .remove_new = rk817_platform_remove,
};
module_platform_driver(rk817_codec_driver);
};
MODULE_DEVICE_TABLE(i2c, rt1019_i2c_id);
-static const struct of_device_id rt1019_of_match[] = {
+static const struct of_device_id rt1019_of_match[] __maybe_unused = {
{ .compatible = "realtek,rt1019", },
{},
};
static int rt1308_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt1308_sdw_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt1308_sdw_set_tdm_slot(struct snd_soc_dai *dai,
snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt1308->sdw_slave)
}
retval = sdw_stream_add_slave(rt1308->sdw_slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
struct snd_soc_component *component = dai->component;
struct rt1308_sdw_priv *rt1308 =
snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt1308->sdw_slave)
return -EINVAL;
- sdw_stream_remove_slave(rt1308->sdw_slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt1308->sdw_slave, sdw_stream);
return 0;
}
unsigned int bq_params_cnt;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
#endif /* __RT1308_SDW_H__ */
static int rt1316_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt1316_sdw_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt1316_sdw_hw_params(struct snd_pcm_substream *substream,
snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt1316->sdw_slave)
port_config.num = 2;
retval = sdw_stream_add_slave(rt1316->sdw_slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
struct snd_soc_component *component = dai->component;
struct rt1316_sdw_priv *rt1316 =
snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt1316->sdw_slave)
return -EINVAL;
- sdw_stream_remove_slave(rt1316->sdw_slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt1316->sdw_slave, sdw_stream);
return 0;
}
unsigned int bq_params_cnt;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
#endif /* __RT1316_SDW_H__ */
static int rt1318_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt1318_sdw_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream,
struct sdw_stream_config stream_config;
struct sdw_port_config port_config;
enum sdw_data_direction direction;
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval, port, num_channels, ch_mask;
unsigned int sampling_rate;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt1318->sdw_slave)
port_config.num = port;
retval = sdw_stream_add_slave(rt1318->sdw_slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
struct snd_soc_component *component = dai->component;
struct rt1318_sdw_priv *rt1318 =
snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt1318->sdw_slave)
return -EINVAL;
- sdw_stream_remove_slave(rt1318->sdw_slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt1318->sdw_slave, sdw_stream);
return 0;
}
bool first_hw_init;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
#endif /* __RT1318_SDW_H__ */
.reg_write = rt5682_sdw_write,
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
static int rt5682_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt5682_sdw_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt5682_sdw_hw_params(struct snd_pcm_substream *substream,
struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
unsigned int val_p = 0, val_c = 0, osr_p = 0, osr_c = 0;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (!sdw_stream)
return -ENOMEM;
if (!rt5682->slave)
port_config.num = 2;
retval = sdw_stream_add_slave(rt5682->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
{
struct snd_soc_component *component = dai->component;
struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt5682->slave)
return -EINVAL;
- sdw_stream_remove_slave(rt5682->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt5682->slave, sdw_stream);
return 0;
}
static int rt700_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt700_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt700_pcm_hw_params(struct snd_pcm_substream *substream,
struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
unsigned int val = 0;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt700->slave)
}
retval = sdw_stream_add_slave(rt700->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
{
struct snd_soc_component *component = dai->component;
struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt700->slave)
return -EINVAL;
- sdw_stream_remove_slave(rt700->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt700->slave, sdw_stream);
return 0;
}
bool disable_irq;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
/* NID */
#define RT700_AUDIO_FUNCTION_GROUP 0x01
#define RT700_DAC_OUT1 0x02
static int rt711_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt711_sdca_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt711_sdca_pcm_hw_params(struct snd_pcm_substream *substream,
struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
unsigned int sampling_rate;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt711->slave)
}
retval = sdw_stream_add_slave(rt711->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
{
struct snd_soc_component *component = dai->component;
struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt711->slave)
return -EINVAL;
- sdw_stream_remove_slave(rt711->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt711->slave, sdw_stream);
return 0;
}
bool fu1e_dapm_mute, fu1e_mixer_l_mute, fu1e_mixer_r_mute;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
/* NID */
#define RT711_AUDIO_FUNCTION_GROUP 0x01
#define RT711_DAC_OUT2 0x03
static int rt711_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt711_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt711_pcm_hw_params(struct snd_pcm_substream *substream,
struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
unsigned int val = 0;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt711->slave)
}
retval = sdw_stream_add_slave(rt711->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
{
struct snd_soc_component *component = dai->component;
struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt711->slave)
return -EINVAL;
- sdw_stream_remove_slave(rt711->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt711->slave, sdw_stream);
return 0;
}
bool disable_irq;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
/* NID */
#define RT711_AUDIO_FUNCTION_GROUP 0x01
#define RT711_DAC_OUT2 0x03
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// rt712-sdca-dmic.c -- rt712 SDCA DMIC ALSA SoC audio driver
+//
+// Copyright(c) 2023 Realtek Semiconductor Corp.
+//
+//
+
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "rt712-sdca.h"
+#include "rt712-sdca-dmic.h"
+
+static bool rt712_sdca_dmic_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x201a ... 0x201f:
+ case 0x2029 ... 0x202a:
+ case 0x202d ... 0x2034:
+ case 0x2230 ... 0x2232:
+ case 0x2f01 ... 0x2f0a:
+ case 0x2f35 ... 0x2f36:
+ case 0x2f52:
+ case 0x2f58 ... 0x2f59:
+ case 0x3201:
+ case 0x320c:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rt712_sdca_dmic_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x201b:
+ case 0x201c:
+ case 0x201d:
+ case 0x201f:
+ case 0x202d ... 0x202f:
+ case 0x2230:
+ case 0x2f01:
+ case 0x2f35:
+ case 0x320c:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rt712_sdca_dmic_mbq_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x2000000 ... 0x200008e:
+ case 0x5300000 ... 0x530000e:
+ case 0x5400000 ... 0x540000e:
+ case 0x5600000 ... 0x5600008:
+ case 0x5700000 ... 0x570000d:
+ case 0x5800000 ... 0x5800021:
+ case 0x5900000 ... 0x5900028:
+ case 0x5a00000 ... 0x5a00009:
+ case 0x5b00000 ... 0x5b00051:
+ case 0x5c00000 ... 0x5c0009a:
+ case 0x5d00000 ... 0x5d00009:
+ case 0x5f00000 ... 0x5f00030:
+ case 0x6100000 ... 0x6100068:
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_01):
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_02):
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_03):
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_04):
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_01):
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_02):
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_03):
+ case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_04):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rt712_sdca_dmic_mbq_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x2000000:
+ case 0x200001a:
+ case 0x2000024:
+ case 0x2000046:
+ case 0x200008a:
+ case 0x5800000:
+ case 0x5800001:
+ case 0x6100008:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config rt712_sdca_dmic_regmap = {
+ .reg_bits = 32,
+ .val_bits = 8,
+ .readable_reg = rt712_sdca_dmic_readable_register,
+ .volatile_reg = rt712_sdca_dmic_volatile_register,
+ .max_register = 0x40981300,
+ .reg_defaults = rt712_sdca_dmic_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(rt712_sdca_dmic_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static const struct regmap_config rt712_sdca_dmic_mbq_regmap = {
+ .name = "sdw-mbq",
+ .reg_bits = 32,
+ .val_bits = 16,
+ .readable_reg = rt712_sdca_dmic_mbq_readable_register,
+ .volatile_reg = rt712_sdca_dmic_mbq_volatile_register,
+ .max_register = 0x40800f14,
+ .reg_defaults = rt712_sdca_dmic_mbq_defaults,
+ .num_reg_defaults = ARRAY_SIZE(rt712_sdca_dmic_mbq_defaults),
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int rt712_sdca_dmic_index_write(struct rt712_sdca_dmic_priv *rt712,
+ unsigned int nid, unsigned int reg, unsigned int value)
+{
+ int ret;
+ struct regmap *regmap = rt712->mbq_regmap;
+ unsigned int addr = (nid << 20) | reg;
+
+ ret = regmap_write(regmap, addr, value);
+ if (ret < 0)
+ dev_err(&rt712->slave->dev,
+ "Failed to set private value: %06x <= %04x ret=%d\n",
+ addr, value, ret);
+
+ return ret;
+}
+
+static int rt712_sdca_dmic_index_read(struct rt712_sdca_dmic_priv *rt712,
+ unsigned int nid, unsigned int reg, unsigned int *value)
+{
+ int ret;
+ struct regmap *regmap = rt712->mbq_regmap;
+ unsigned int addr = (nid << 20) | reg;
+
+ ret = regmap_read(regmap, addr, value);
+ if (ret < 0)
+ dev_err(&rt712->slave->dev,
+ "Failed to get private value: %06x => %04x ret=%d\n",
+ addr, *value, ret);
+
+ return ret;
+}
+
+static int rt712_sdca_dmic_index_update_bits(struct rt712_sdca_dmic_priv *rt712,
+ unsigned int nid, unsigned int reg, unsigned int mask, unsigned int val)
+{
+ unsigned int tmp;
+ int ret;
+
+ ret = rt712_sdca_dmic_index_read(rt712, nid, reg, &tmp);
+ if (ret < 0)
+ return ret;
+
+ set_mask_bits(&tmp, mask, val);
+ return rt712_sdca_dmic_index_write(rt712, nid, reg, tmp);
+}
+
+static int rt712_sdca_dmic_io_init(struct device *dev, struct sdw_slave *slave)
+{
+ struct rt712_sdca_dmic_priv *rt712 = dev_get_drvdata(dev);
+
+ if (rt712->hw_init)
+ return 0;
+
+ if (rt712->first_hw_init) {
+ regcache_cache_only(rt712->regmap, false);
+ regcache_cache_bypass(rt712->regmap, true);
+ regcache_cache_only(rt712->mbq_regmap, false);
+ regcache_cache_bypass(rt712->mbq_regmap, true);
+ } else {
+ /*
+ * PM runtime is only enabled when a Slave reports as Attached
+ */
+
+ /* set autosuspend parameters */
+ pm_runtime_set_autosuspend_delay(&slave->dev, 3000);
+ pm_runtime_use_autosuspend(&slave->dev);
+
+ /* update count of parent 'active' children */
+ pm_runtime_set_active(&slave->dev);
+
+ /* make sure the device does not suspend immediately */
+ pm_runtime_mark_last_busy(&slave->dev);
+
+ pm_runtime_enable(&slave->dev);
+ }
+
+ pm_runtime_get_noresume(&slave->dev);
+
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_ADC0A_08_PDE_FLOAT_CTL, 0x1112);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_ADC0B_11_PDE_FLOAT_CTL, 0x1111);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_DMIC1_2_PDE_FLOAT_CTL, 0x1111);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_I2S_IN_OUT_PDE_FLOAT_CTL, 0x1155);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_DMIC_ENT_FLOAT_CTL, 0x2626);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_ADC_ENT_FLOAT_CTL, 0x1e19);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_DMIC_GAIN_ENT_FLOAT_CTL0, 0x1515);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_ADC_VOL_CH_FLOAT_CTL2, 0x0304);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_DMIC_GAIN_ENT_FLOAT_CTL2, 0x0304);
+ rt712_sdca_dmic_index_write(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_HDA_LEGACY_CONFIG_CTL0, 0x0050);
+ regmap_write(rt712->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_IT26, RT712_SDCA_CTL_VENDOR_DEF, 0), 0x01);
+ rt712_sdca_dmic_index_write(rt712, RT712_ULTRA_SOUND_DET,
+ RT712_ULTRA_SOUND_DETECTOR6, 0x3200);
+ regmap_write(rt712->regmap, RT712_RC_CAL, 0x23);
+ regmap_write(rt712->regmap, 0x2f52, 0x00);
+
+ if (rt712->first_hw_init) {
+ regcache_cache_bypass(rt712->regmap, false);
+ regcache_mark_dirty(rt712->regmap);
+ regcache_cache_bypass(rt712->mbq_regmap, false);
+ regcache_mark_dirty(rt712->mbq_regmap);
+ } else
+ rt712->first_hw_init = true;
+
+ /* Mark Slave initialization complete */
+ rt712->hw_init = true;
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put_autosuspend(&slave->dev);
+
+ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__);
+ return 0;
+}
+
+static int rt712_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component);
+ struct rt712_sdca_dmic_kctrl_priv *p =
+ (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+ unsigned int regvalue, ctl, i;
+ unsigned int adc_vol_flag = 0;
+ const unsigned int interval_offset = 0xc0;
+
+ if (strstr(ucontrol->id.name, "FU1E Capture Volume"))
+ adc_vol_flag = 1;
+
+ /* check all channels */
+ for (i = 0; i < p->count; i++) {
+ regmap_read(rt712->mbq_regmap, p->reg_base + i, ®value);
+
+ if (!adc_vol_flag) /* boost gain */
+ ctl = regvalue / 0x0a00;
+ else { /* ADC gain */
+ if (adc_vol_flag)
+ ctl = p->max - (((0x1e00 - regvalue) & 0xffff) / interval_offset);
+ else
+ ctl = p->max - (((0 - regvalue) & 0xffff) / interval_offset);
+ }
+
+ ucontrol->value.integer.value[i] = ctl;
+ }
+
+ return 0;
+}
+
+static int rt712_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt712_sdca_dmic_kctrl_priv *p =
+ (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+ struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component);
+ unsigned int gain_val[4];
+ unsigned int i, adc_vol_flag = 0, changed = 0;
+ unsigned int regvalue[4];
+ const unsigned int interval_offset = 0xc0;
+ int err;
+
+ if (strstr(ucontrol->id.name, "FU1E Capture Volume"))
+ adc_vol_flag = 1;
+
+ /* check all channels */
+ for (i = 0; i < p->count; i++) {
+ regmap_read(rt712->mbq_regmap, p->reg_base + i, ®value[i]);
+
+ gain_val[i] = ucontrol->value.integer.value[i];
+ if (gain_val[i] > p->max)
+ gain_val[i] = p->max;
+
+ if (!adc_vol_flag) /* boost gain */
+ gain_val[i] = gain_val[i] * 0x0a00;
+ else { /* ADC gain */
+ gain_val[i] = 0x1e00 - ((p->max - gain_val[i]) * interval_offset);
+ gain_val[i] &= 0xffff;
+ }
+
+ if (regvalue[i] != gain_val[i])
+ changed = 1;
+ }
+
+ if (!changed)
+ return 0;
+
+ for (i = 0; i < p->count; i++) {
+ err = regmap_write(rt712->mbq_regmap, p->reg_base + i, gain_val[i]);
+ if (err < 0)
+ dev_err(&rt712->slave->dev, "0x%08x can't be set\n", p->reg_base + i);
+ }
+
+ return changed;
+}
+
+static int rt712_sdca_set_fu1e_capture_ctl(struct rt712_sdca_dmic_priv *rt712)
+{
+ int err, i;
+ unsigned int ch_mute;
+
+ for (i = 0; i < ARRAY_SIZE(rt712->fu1e_mixer_mute); i++) {
+ ch_mute = (rt712->fu1e_dapm_mute || rt712->fu1e_mixer_mute[i]) ? 0x01 : 0x00;
+ err = regmap_write(rt712->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E,
+ RT712_SDCA_CTL_FU_MUTE, CH_01) + i, ch_mute);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int rt712_sdca_dmic_fu1e_capture_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+ struct rt712_sdca_dmic_kctrl_priv *p =
+ (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+ unsigned int i;
+
+ for (i = 0; i < p->count; i++)
+ ucontrol->value.integer.value[i] = !rt712->fu1e_mixer_mute[i];
+
+ return 0;
+}
+
+static int rt712_sdca_dmic_fu1e_capture_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+ struct rt712_sdca_dmic_kctrl_priv *p =
+ (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+ int err, changed = 0, i;
+
+ for (i = 0; i < p->count; i++) {
+ if (rt712->fu1e_mixer_mute[i] != !ucontrol->value.integer.value[i])
+ changed = 1;
+ rt712->fu1e_mixer_mute[i] = !ucontrol->value.integer.value[i];
+ }
+
+ err = rt712_sdca_set_fu1e_capture_ctl(rt712);
+ if (err < 0)
+ return err;
+
+ return changed;
+}
+
+static int rt712_sdca_fu_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct rt712_sdca_dmic_kctrl_priv *p =
+ (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+
+ if (p->max == 1)
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = p->count;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = p->max;
+ return 0;
+}
+
+#define RT712_SDCA_PR_VALUE(xreg_base, xcount, xmax, xinvert) \
+ ((unsigned long)&(struct rt712_sdca_dmic_kctrl_priv) \
+ {.reg_base = xreg_base, .count = xcount, .max = xmax, \
+ .invert = xinvert})
+
+#define RT712_SDCA_FU_CTRL(xname, reg_base, xmax, xinvert, xcount) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .info = rt712_sdca_fu_info, \
+ .get = rt712_sdca_dmic_fu1e_capture_get, \
+ .put = rt712_sdca_dmic_fu1e_capture_put, \
+ .private_value = RT712_SDCA_PR_VALUE(reg_base, xcount, xmax, xinvert)}
+
+#define RT712_SDCA_EXT_TLV(xname, reg_base, xhandler_get,\
+ xhandler_put, xcount, xmax, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = rt712_sdca_fu_info, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = RT712_SDCA_PR_VALUE(reg_base, xcount, xmax, 0) }
+
+static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0);
+
+static const struct snd_kcontrol_new rt712_sdca_dmic_snd_controls[] = {
+ RT712_SDCA_FU_CTRL("FU1E Capture Switch",
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_MUTE, CH_01),
+ 1, 1, 4),
+ RT712_SDCA_EXT_TLV("FU1E Capture Volume",
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_01),
+ rt712_sdca_dmic_set_gain_get, rt712_sdca_dmic_set_gain_put, 4, 0x3f, in_vol_tlv),
+ RT712_SDCA_EXT_TLV("FU15 Boost Volume",
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_01),
+ rt712_sdca_dmic_set_gain_get, rt712_sdca_dmic_set_gain_put, 4, 3, mic_vol_tlv),
+};
+
+static int rt712_sdca_dmic_mux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_kcontrol_component(kcontrol);
+ struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+ unsigned int val = 0, mask_sft;
+
+ if (strstr(ucontrol->id.name, "ADC 25 Mux"))
+ mask_sft = 8;
+ else if (strstr(ucontrol->id.name, "ADC 26 Mux"))
+ mask_sft = 4;
+ else
+ return -EINVAL;
+
+ rt712_sdca_dmic_index_read(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_HDA_LEGACY_MUX_CTL0, &val);
+
+ ucontrol->value.enumerated.item[0] = (val >> mask_sft) & 0x7;
+
+ return 0;
+}
+
+static int rt712_sdca_dmic_mux_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 rt712_sdca_dmic_priv *rt712 = 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, val2 = 0, change, mask_sft;
+
+ if (item[0] >= e->items)
+ return -EINVAL;
+
+ if (strstr(ucontrol->id.name, "ADC 25 Mux"))
+ mask_sft = 8;
+ else if (strstr(ucontrol->id.name, "ADC 26 Mux"))
+ mask_sft = 4;
+ else
+ return -EINVAL;
+
+ val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l;
+
+ rt712_sdca_dmic_index_read(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_HDA_LEGACY_MUX_CTL0, &val2);
+ val2 = (0x7 << mask_sft) & val2;
+
+ if (val == val2)
+ change = 0;
+ else
+ change = 1;
+
+ if (change)
+ rt712_sdca_dmic_index_update_bits(rt712, RT712_VENDOR_HDA_CTL,
+ RT712_HDA_LEGACY_MUX_CTL0, 0x7 << mask_sft,
+ val << mask_sft);
+
+ snd_soc_dapm_mux_update_power(dapm, kcontrol,
+ item[0], e, NULL);
+
+ return change;
+}
+
+static const char * const adc_mux_text[] = {
+ "DMIC1",
+ "DMIC2",
+};
+
+static SOC_ENUM_SINGLE_DECL(
+ rt712_adc25_enum, SND_SOC_NOPM, 0, adc_mux_text);
+
+static SOC_ENUM_SINGLE_DECL(
+ rt712_adc26_enum, SND_SOC_NOPM, 0, adc_mux_text);
+
+static const struct snd_kcontrol_new rt712_sdca_dmic_adc25_mux =
+ SOC_DAPM_ENUM_EXT("ADC 25 Mux", rt712_adc25_enum,
+ rt712_sdca_dmic_mux_get, rt712_sdca_dmic_mux_put);
+
+static const struct snd_kcontrol_new rt712_sdca_dmic_adc26_mux =
+ SOC_DAPM_ENUM_EXT("ADC 26 Mux", rt712_adc26_enum,
+ rt712_sdca_dmic_mux_get, rt712_sdca_dmic_mux_put);
+
+static int rt712_sdca_dmic_fu1e_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ rt712->fu1e_dapm_mute = false;
+ rt712_sdca_set_fu1e_capture_ctl(rt712);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ rt712->fu1e_dapm_mute = true;
+ rt712_sdca_set_fu1e_capture_ctl(rt712);
+ break;
+ }
+ return 0;
+}
+
+static int rt712_sdca_dmic_pde11_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+ unsigned char ps0 = 0x0, ps3 = 0x3;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt712->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PDE11,
+ RT712_SDCA_CTL_REQ_POWER_STATE, 0),
+ ps0);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_write(rt712->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PDE11,
+ RT712_SDCA_CTL_REQ_POWER_STATE, 0),
+ ps3);
+ break;
+ }
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget rt712_sdca_dmic_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("DMIC1"),
+ SND_SOC_DAPM_INPUT("DMIC2"),
+
+ SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0,
+ rt712_sdca_dmic_pde11_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_ADC_E("FU 1E", NULL, SND_SOC_NOPM, 0, 0,
+ rt712_sdca_dmic_fu1e_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MUX("ADC 25 Mux", SND_SOC_NOPM, 0, 0,
+ &rt712_sdca_dmic_adc25_mux),
+ SND_SOC_DAPM_MUX("ADC 26 Mux", SND_SOC_NOPM, 0, 0,
+ &rt712_sdca_dmic_adc26_mux),
+
+ SND_SOC_DAPM_AIF_OUT("DP2TX", "DP2 Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route rt712_sdca_dmic_audio_map[] = {
+ {"DP2TX", NULL, "FU 1E"},
+
+ {"FU 1E", NULL, "PDE 11"},
+ {"FU 1E", NULL, "ADC 25 Mux"},
+ {"FU 1E", NULL, "ADC 26 Mux"},
+ {"ADC 25 Mux", "DMIC1", "DMIC1"},
+ {"ADC 25 Mux", "DMIC2", "DMIC2"},
+ {"ADC 26 Mux", "DMIC1", "DMIC1"},
+ {"ADC 26 Mux", "DMIC2", "DMIC2"},
+};
+
+static int rt712_sdca_dmic_probe(struct snd_soc_component *component)
+{
+ struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ rt712->component = component;
+
+ ret = pm_runtime_resume(component->dev);
+ if (ret < 0 && ret != -EACCES)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_sdca_dev_rt712_dmic = {
+ .probe = rt712_sdca_dmic_probe,
+ .controls = rt712_sdca_dmic_snd_controls,
+ .num_controls = ARRAY_SIZE(rt712_sdca_dmic_snd_controls),
+ .dapm_widgets = rt712_sdca_dmic_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(rt712_sdca_dmic_dapm_widgets),
+ .dapm_routes = rt712_sdca_dmic_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(rt712_sdca_dmic_audio_map),
+ .endianness = 1,
+};
+
+static int rt712_sdca_dmic_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
+ int direction)
+{
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
+
+ return 0;
+}
+
+static void rt712_sdca_dmic_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int rt712_sdca_dmic_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 rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+ struct sdw_stream_config stream_config;
+ struct sdw_port_config port_config;
+ struct sdw_stream_runtime *sdw_stream;
+ int retval, num_channels;
+ unsigned int sampling_rate;
+
+ dev_dbg(dai->dev, "%s %s", __func__, dai->name);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!sdw_stream)
+ return -EINVAL;
+
+ if (!rt712->slave)
+ return -EINVAL;
+
+ stream_config.frame_rate = params_rate(params);
+ stream_config.ch_count = params_channels(params);
+ stream_config.bps = snd_pcm_format_width(params_format(params));
+ stream_config.direction = SDW_DATA_DIR_TX;
+
+ num_channels = params_channels(params);
+ port_config.ch_mask = GENMASK(num_channels - 1, 0);
+ port_config.num = 2;
+
+ retval = sdw_stream_add_slave(rt712->slave, &stream_config,
+ &port_config, 1, sdw_stream);
+ if (retval) {
+ dev_err(dai->dev, "Unable to configure port\n");
+ return retval;
+ }
+
+ if (params_channels(params) > 4) {
+ dev_err(component->dev, "Unsupported channels %d\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+
+ /* sampling rate configuration */
+ switch (params_rate(params)) {
+ case 16000:
+ sampling_rate = RT712_SDCA_RATE_16000HZ;
+ break;
+ case 32000:
+ sampling_rate = RT712_SDCA_RATE_32000HZ;
+ break;
+ case 44100:
+ sampling_rate = RT712_SDCA_RATE_44100HZ;
+ break;
+ case 48000:
+ sampling_rate = RT712_SDCA_RATE_48000HZ;
+ break;
+ case 96000:
+ sampling_rate = RT712_SDCA_RATE_96000HZ;
+ break;
+ case 192000:
+ sampling_rate = RT712_SDCA_RATE_192000HZ;
+ break;
+ default:
+ dev_err(component->dev, "Rate %d is not supported\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ /* set sampling frequency */
+ regmap_write(rt712->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_CS1F, RT712_SDCA_CTL_SAMPLE_FREQ_INDEX, 0),
+ sampling_rate);
+ regmap_write(rt712->regmap,
+ SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_CS1C, RT712_SDCA_CTL_SAMPLE_FREQ_INDEX, 0),
+ sampling_rate);
+
+ return 0;
+}
+
+static int rt712_sdca_dmic_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component);
+ struct sdw_stream_runtime *sdw_stream =
+ snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!rt712->slave)
+ return -EINVAL;
+
+ sdw_stream_remove_slave(rt712->slave, sdw_stream);
+ return 0;
+}
+
+#define RT712_STEREO_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000)
+#define RT712_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static const struct snd_soc_dai_ops rt712_sdca_dmic_ops = {
+ .hw_params = rt712_sdca_dmic_hw_params,
+ .hw_free = rt712_sdca_dmic_hw_free,
+ .set_stream = rt712_sdca_dmic_set_sdw_stream,
+ .shutdown = rt712_sdca_dmic_shutdown,
+};
+
+static struct snd_soc_dai_driver rt712_sdca_dmic_dai[] = {
+ {
+ .name = "rt712-sdca-dmic-aif1",
+ .id = RT712_AIF1,
+ .capture = {
+ .stream_name = "DP2 Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = RT712_STEREO_RATES,
+ .formats = RT712_FORMATS,
+ },
+ .ops = &rt712_sdca_dmic_ops,
+ },
+};
+
+static int rt712_sdca_dmic_init(struct device *dev, struct regmap *regmap,
+ struct regmap *mbq_regmap, struct sdw_slave *slave)
+{
+ struct rt712_sdca_dmic_priv *rt712;
+ int ret;
+
+ rt712 = devm_kzalloc(dev, sizeof(*rt712), GFP_KERNEL);
+ if (!rt712)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, rt712);
+ rt712->slave = slave;
+ rt712->regmap = regmap;
+ rt712->mbq_regmap = mbq_regmap;
+
+ /*
+ * Mark hw_init to false
+ * HW init will be performed when device reports present
+ */
+ rt712->hw_init = false;
+ rt712->first_hw_init = false;
+ rt712->fu1e_dapm_mute = true;
+ rt712->fu1e_mixer_mute[0] = rt712->fu1e_mixer_mute[1] =
+ rt712->fu1e_mixer_mute[2] = rt712->fu1e_mixer_mute[3] = true;
+
+ ret = devm_snd_soc_register_component(dev,
+ &soc_sdca_dev_rt712_dmic,
+ rt712_sdca_dmic_dai,
+ ARRAY_SIZE(rt712_sdca_dmic_dai));
+
+ dev_dbg(&slave->dev, "%s\n", __func__);
+
+ return ret;
+}
+
+
+static int rt712_sdca_dmic_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct rt712_sdca_dmic_priv *rt712 = dev_get_drvdata(&slave->dev);
+
+ /* Update the status */
+ rt712->status = status;
+
+ if (status == SDW_SLAVE_UNATTACHED)
+ rt712->hw_init = false;
+
+ /*
+ * Perform initialization only if slave status is present and
+ * hw_init flag is false
+ */
+ if (rt712->hw_init || rt712->status != SDW_SLAVE_ATTACHED)
+ return 0;
+
+ /* perform I/O transfers required for Slave initialization */
+ return rt712_sdca_dmic_io_init(&slave->dev, slave);
+}
+
+static int rt712_sdca_dmic_read_prop(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ int nval, i;
+ u32 bit;
+ unsigned long addr;
+ struct sdw_dpn_prop *dpn;
+
+ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY;
+ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY;
+
+ prop->paging_support = true;
+
+ /* first we need to allocate memory for set bits in port lists */
+ prop->source_ports = BIT(2); /* BITMAP: 00000100 */
+ prop->sink_ports = 0;
+
+ nval = hweight32(prop->source_ports);
+ prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->src_dpn_prop), GFP_KERNEL);
+ if (!prop->src_dpn_prop)
+ return -ENOMEM;
+
+ i = 0;
+ dpn = prop->src_dpn_prop;
+ addr = prop->source_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+
+ /* set the timeout values */
+ prop->clk_stop_timeout = 200;
+
+ /* wake-up event */
+ prop->wake_capable = 1;
+
+ return 0;
+}
+
+static const struct sdw_device_id rt712_sdca_dmic_id[] = {
+ SDW_SLAVE_ENTRY_EXT(0x025d, 0x1712, 0x3, 0x1, 0),
+ SDW_SLAVE_ENTRY_EXT(0x025d, 0x1713, 0x3, 0x1, 0),
+ SDW_SLAVE_ENTRY_EXT(0x025d, 0x1716, 0x3, 0x1, 0),
+ SDW_SLAVE_ENTRY_EXT(0x025d, 0x1717, 0x3, 0x1, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, rt712_sdca_dmic_id);
+
+static int __maybe_unused rt712_sdca_dmic_dev_suspend(struct device *dev)
+{
+ struct rt712_sdca_dmic_priv *rt712 = dev_get_drvdata(dev);
+
+ if (!rt712->hw_init)
+ return 0;
+
+ regcache_cache_only(rt712->regmap, true);
+ regcache_cache_only(rt712->mbq_regmap, true);
+
+ return 0;
+}
+
+static int __maybe_unused rt712_sdca_dmic_dev_system_suspend(struct device *dev)
+{
+ struct rt712_sdca_dmic_priv *rt712_sdca = dev_get_drvdata(dev);
+
+ if (!rt712_sdca->hw_init)
+ return 0;
+
+ return rt712_sdca_dmic_dev_suspend(dev);
+}
+
+#define RT712_PROBE_TIMEOUT 5000
+
+static int __maybe_unused rt712_sdca_dmic_dev_resume(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct rt712_sdca_dmic_priv *rt712 = dev_get_drvdata(dev);
+ unsigned long time;
+
+ if (!rt712->first_hw_init)
+ return 0;
+
+ if (!slave->unattach_request)
+ goto regmap_sync;
+
+ time = wait_for_completion_timeout(&slave->initialization_complete,
+ msecs_to_jiffies(RT712_PROBE_TIMEOUT));
+ if (!time) {
+ dev_err(&slave->dev, "Initialization not complete, timed out\n");
+ sdw_show_ping_status(slave->bus, true);
+
+ return -ETIMEDOUT;
+ }
+
+regmap_sync:
+ slave->unattach_request = 0;
+ regcache_cache_only(rt712->regmap, false);
+ regcache_sync(rt712->regmap);
+ regcache_cache_only(rt712->mbq_regmap, false);
+ regcache_sync(rt712->mbq_regmap);
+ return 0;
+}
+
+static const struct dev_pm_ops rt712_sdca_dmic_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(rt712_sdca_dmic_dev_system_suspend, rt712_sdca_dmic_dev_resume)
+ SET_RUNTIME_PM_OPS(rt712_sdca_dmic_dev_suspend, rt712_sdca_dmic_dev_resume, NULL)
+};
+
+
+static struct sdw_slave_ops rt712_sdca_dmic_slave_ops = {
+ .read_prop = rt712_sdca_dmic_read_prop,
+ .update_status = rt712_sdca_dmic_update_status,
+};
+
+static int rt712_sdca_dmic_sdw_probe(struct sdw_slave *slave,
+ const struct sdw_device_id *id)
+{
+ struct regmap *regmap, *mbq_regmap;
+
+ /* Regmap Initialization */
+ mbq_regmap = devm_regmap_init_sdw_mbq(slave, &rt712_sdca_dmic_mbq_regmap);
+ if (IS_ERR(mbq_regmap))
+ return PTR_ERR(mbq_regmap);
+
+ regmap = devm_regmap_init_sdw(slave, &rt712_sdca_dmic_regmap);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return rt712_sdca_dmic_init(&slave->dev, regmap, mbq_regmap, slave);
+}
+
+static int rt712_sdca_dmic_sdw_remove(struct sdw_slave *slave)
+{
+ struct rt712_sdca_dmic_priv *rt712 = dev_get_drvdata(&slave->dev);
+
+ if (rt712->first_hw_init)
+ pm_runtime_disable(&slave->dev);
+
+ return 0;
+}
+
+static struct sdw_driver rt712_sdca_dmic_sdw_driver = {
+ .driver = {
+ .name = "rt712-sdca-dmic",
+ .owner = THIS_MODULE,
+ .pm = &rt712_sdca_dmic_pm,
+ },
+ .probe = rt712_sdca_dmic_sdw_probe,
+ .remove = rt712_sdca_dmic_sdw_remove,
+ .ops = &rt712_sdca_dmic_slave_ops,
+ .id_table = rt712_sdca_dmic_id,
+};
+module_sdw_driver(rt712_sdca_dmic_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC RT712 SDCA DMIC SDW driver");
+MODULE_AUTHOR("Shuming Fan <shumingf@realtek.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * rt712-sdca-dmic.h -- RT712 SDCA DMIC ALSA SoC audio driver header
+ *
+ * Copyright(c) 2023 Realtek Semiconductor Corp.
+ */
+
+#ifndef __RT712_SDW_DMIC_H__
+#define __RT712_SDW_DMIC_H__
+
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw_registers.h>
+
+struct rt712_sdca_dmic_priv {
+ struct regmap *regmap;
+ struct regmap *mbq_regmap;
+ struct snd_soc_component *component;
+ struct sdw_slave *slave;
+ enum sdw_slave_status status;
+ struct sdw_bus_params params;
+ bool hw_init;
+ bool first_hw_init;
+ bool fu1e_dapm_mute;
+ bool fu1e_mixer_mute[4];
+};
+
+struct rt712_sdca_dmic_kctrl_priv {
+ unsigned int reg_base;
+ unsigned int count;
+ unsigned int max;
+ unsigned int invert;
+};
+
+/* SDCA (Channel) */
+#define CH_01 0x01
+#define CH_02 0x02
+#define CH_03 0x03
+#define CH_04 0x04
+
+static const struct reg_default rt712_sdca_dmic_reg_defaults[] = {
+ { 0x201a, 0x00 },
+ { 0x201b, 0x00 },
+ { 0x201c, 0x00 },
+ { 0x201d, 0x00 },
+ { 0x201e, 0x00 },
+ { 0x201f, 0x00 },
+ { 0x2029, 0x00 },
+ { 0x202a, 0x00 },
+ { 0x202d, 0x00 },
+ { 0x202e, 0x00 },
+ { 0x202f, 0x00 },
+ { 0x2030, 0x00 },
+ { 0x2031, 0x00 },
+ { 0x2032, 0x00 },
+ { 0x2033, 0x00 },
+ { 0x2034, 0x00 },
+ { 0x2230, 0x00 },
+ { 0x2231, 0x2f },
+ { 0x2232, 0x80 },
+ { 0x2f01, 0x00 },
+ { 0x2f02, 0x09 },
+ { 0x2f03, 0x00 },
+ { 0x2f04, 0x00 },
+ { 0x2f05, 0x0b },
+ { 0x2f06, 0x01 },
+ { 0x2f08, 0x00 },
+ { 0x2f09, 0x00 },
+ { 0x2f0a, 0x01 },
+ { 0x2f35, 0x02 },
+ { 0x2f36, 0xcf },
+ { 0x2f52, 0x08 },
+ { 0x2f58, 0x07 },
+ { 0x2f59, 0x07 },
+ { 0x3201, 0x01 },
+ { 0x320c, 0x00 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_IT26, RT712_SDCA_CTL_VENDOR_DEF, 0), 0x00 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_MUTE, CH_01), 0x01 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_MUTE, CH_02), 0x01 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_MUTE, CH_03), 0x01 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_MUTE, CH_04), 0x01 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_CS1F, RT712_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), 0x09 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_CS1C, RT712_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), 0x09 },
+};
+
+static const struct reg_default rt712_sdca_dmic_mbq_defaults[] = {
+ { 0x0590001e, 0x0020 },
+ { 0x06100000, 0x0010 },
+ { 0x06100006, 0x0055 },
+ { 0x06100010, 0x2630 },
+ { 0x06100011, 0x152f },
+ { 0x06100013, 0x0102 },
+ { 0x06100015, 0x2219 },
+ { 0x06100018, 0x0102 },
+ { 0x06100026, 0x2c29 },
+ { 0x06100027, 0x2d2b },
+ { 0x0610002b, 0x2a32 },
+ { 0x0610002f, 0x3355 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_01), 0x0000 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_02), 0x0000 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_03), 0x0000 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_PLATFORM_FU15, RT712_SDCA_CTL_FU_CH_GAIN, CH_04), 0x0000 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_01), 0x0000 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_02), 0x0000 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_03), 0x0000 },
+ { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT712_SDCA_ENT_USER_FU1E, RT712_SDCA_CTL_FU_VOLUME, CH_04), 0x0000 },
+};
+
+#endif /* __RT712_SDW_DMIC_H__ */
static int rt712_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void rt712_sdca_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream,
struct sdw_stream_config stream_config;
struct sdw_port_config port_config;
enum sdw_data_direction direction;
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval, port, num_channels;
unsigned int sampling_rate;
dev_dbg(dai->dev, "%s %s", __func__, dai->name);
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt712->slave)
port_config.num = port;
retval = sdw_stream_add_slave(rt712->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
{
struct snd_soc_component *component = dai->component;
struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt712->slave)
return -EINVAL;
- sdw_stream_remove_slave(rt712->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt712->slave, sdw_stream);
return 0;
}
bool fu0f_mixer_r_mute;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
/* NID */
#define RT712_VENDOR_REG 0x20
#define RT712_VENDOR_CALI 0x58
static int rt715_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct rt715_sdw_stream_data *stream;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
struct snd_soc_dai *dai)
{
- struct rt715_sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
- return;
-
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt715_sdca_pcm_hw_params(struct snd_pcm_substream *substream,
struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct rt715_sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
unsigned int val;
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt715->slave)
}
retval = sdw_stream_add_slave(rt715->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(component->dev, "Unable to configure port, retval:%d\n",
retval);
{
struct snd_soc_component *component = dai->component;
struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component);
- struct rt715_sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt715->slave)
return -EINVAL;
- sdw_stream_remove_slave(rt715->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt715->slave, sdw_stream);
return 0;
}
int kctl_8ch_orig[8];
};
-struct rt715_sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
struct rt715_sdca_kcontrol_private {
unsigned int reg_base;
unsigned int count;
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int rt715_pcm_hw_params(struct snd_pcm_substream *substream,
struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream;
int retval;
unsigned int val = 0;
- stream = snd_soc_dai_get_dma_data(dai, substream);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!rt715->slave)
}
retval = sdw_stream_add_slave(rt715->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (retval) {
dev_err(dai->dev, "Unable to configure port\n");
return retval;
{
struct snd_soc_component *component = dai->component;
struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
+ struct sdw_stream_runtime *sdw_stream =
snd_soc_dai_get_dma_data(dai, substream);
if (!rt715->slave)
return -EINVAL;
- sdw_stream_remove_slave(rt715->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(rt715->slave, sdw_stream);
return 0;
}
unsigned int kctl_8ch_vol_ori[8];
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
/* NID */
#define RT715_AUDIO_FUNCTION_GROUP 0x01
#define RT715_MIC_ADC 0x07
struct sdw_slave *slave;
};
-struct sdw_stream_data {
- struct sdw_stream_runtime *sdw_stream;
-};
-
static int sdw_mockup_component_probe(struct snd_soc_component *component)
{
return 0;
static int sdw_mockup_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
int direction)
{
- struct sdw_stream_data *stream;
-
- if (!sdw_stream)
- return 0;
-
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
- return -ENOMEM;
-
- stream->sdw_stream = sdw_stream;
-
- /* Use tx_mask or rx_mask to configure stream tag and set dma_data */
- snd_soc_dai_dma_data_set(dai, direction, stream);
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static void sdw_mockup_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- struct sdw_stream_data *stream;
-
- stream = snd_soc_dai_get_dma_data(dai, substream);
snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(stream);
}
static int sdw_mockup_pcm_hw_params(struct snd_pcm_substream *substream,
struct sdw_mockup_priv *sdw_mockup = snd_soc_component_get_drvdata(component);
struct sdw_stream_config stream_config = {0};
struct sdw_port_config port_config = {0};
- struct sdw_stream_data *stream;
+ struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
int ret;
- stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!stream)
+ if (!sdw_stream)
return -EINVAL;
if (!sdw_mockup->slave)
port_config.num = 8;
ret = sdw_stream_add_slave(sdw_mockup->slave, &stream_config,
- &port_config, 1, stream->sdw_stream);
+ &port_config, 1, sdw_stream);
if (ret)
dev_err(dai->dev, "Unable to configure port\n");
{
struct snd_soc_component *component = dai->component;
struct sdw_mockup_priv *sdw_mockup = snd_soc_component_get_drvdata(component);
- struct sdw_stream_data *stream =
- snd_soc_dai_get_dma_data(dai, substream);
+ struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
if (!sdw_mockup->slave)
return -EINVAL;
- sdw_stream_remove_slave(sdw_mockup->slave, stream->sdw_stream);
+ sdw_stream_remove_slave(sdw_mockup->slave, sdw_stream);
return 0;
}
.num_dapm_routes = ARRAY_SIZE(sma1303_audio_map),
};
-const struct regmap_config sma_i2c_regmap = {
+static const struct regmap_config sma_i2c_regmap = {
.reg_bits = 8,
.val_bits = 8,
};
MODULE_DEVICE_TABLE(i2c, src4xxx_i2c_ids);
-static const struct of_device_id src4xxx_of_match[] = {
+static const struct of_device_id src4xxx_of_match[] __maybe_unused = {
{ .compatible = "ti,src4392", },
{ }
};
int i;
for (i = 0; i < ARRAY_SIZE(ssm2602_coeff_table); i++) {
- if (ssm2602_coeff_table[i].rate == rate &&
- ssm2602_coeff_table[i].mclk == mclk)
- return ssm2602_coeff_table[i].srate;
+ if (ssm2602_coeff_table[i].rate == rate) {
+ if (ssm2602_coeff_table[i].mclk == mclk)
+ return ssm2602_coeff_table[i].srate;
+ if (ssm2602_coeff_table[i].mclk == mclk / 2)
+ return ssm2602_coeff_table[i].srate | SRATE_CORECLK_DIV2;
+ }
}
return -EINVAL;
}
switch (freq) {
case 12288000:
case 18432000:
+ case 24576000:
+ case 36864000:
ssm2602->sysclk_constraints = &ssm2602_constraints_12288000;
break;
case 11289600:
case 16934400:
+ case 22579200:
+ case 33868800:
ssm2602->sysclk_constraints = &ssm2602_constraints_11289600;
break;
case 12000000:
+ case 24000000:
ssm2602->sysclk_constraints = NULL;
break;
default:
return -EINVAL;
}
+
ssm2602->sysclk = freq;
} else {
unsigned int mask;
of_property_read_u8(np, "st,ch3-output-mapping",
&pdata->ch3_output_mapping);
- if (of_get_property(np, "st,fault-detect-recovery", NULL))
- pdata->fault_detect_recovery = 1;
- if (of_get_property(np, "st,thermal-warning-recovery", NULL))
- pdata->thermal_warning_recovery = 1;
- if (of_get_property(np, "st,thermal-warning-adjustment", NULL))
- pdata->thermal_warning_adjustment = 1;
- if (of_get_property(np, "st,needs_esd_watchdog", NULL))
- pdata->needs_esd_watchdog = 1;
+ pdata->fault_detect_recovery =
+ of_property_read_bool(np, "st,fault-detect-recovery");
+ pdata->thermal_warning_recovery =
+ of_property_read_bool(np, "st,thermal-warning-recovery");
+ pdata->thermal_warning_adjustment =
+ of_property_read_bool(np, "st,thermal-warning-adjustment");
+ pdata->needs_esd_watchdog =
+ of_property_read_bool(np, "st,needs_esd_watchdog");
tmp = 140;
of_property_read_u16(np, "st,drop-compensation-ns", &tmp);
pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20;
/* CONFE */
- if (of_get_property(np, "st,max-power-use-mpcc", NULL))
- pdata->max_power_use_mpcc = 1;
-
- if (of_get_property(np, "st,max-power-correction", NULL))
- pdata->max_power_correction = 1;
-
- if (of_get_property(np, "st,am-reduction-mode", NULL))
- pdata->am_reduction_mode = 1;
-
- if (of_get_property(np, "st,odd-pwm-speed-mode", NULL))
- pdata->odd_pwm_speed_mode = 1;
+ pdata->max_power_use_mpcc =
+ of_property_read_bool(np, "st,max-power-use-mpcc");
+ pdata->max_power_correction =
+ of_property_read_bool(np, "st,max-power-correction");
+ pdata->am_reduction_mode =
+ of_property_read_bool(np, "st,am-reduction-mode");
+ pdata->odd_pwm_speed_mode =
+ of_property_read_bool(np, "st,odd-pwm-speed-mode");
/* CONFF */
- if (of_get_property(np, "st,invalid-input-detect-mute", NULL))
- pdata->invalid_input_detect_mute = 1;
+ pdata->invalid_input_detect_mute =
+ of_property_read_bool(np, "st,invalid-input-detect-mute");
sta32x->pdata = pdata;
of_property_read_u8(np, "st,ch3-output-mapping",
&pdata->ch3_output_mapping);
- if (of_get_property(np, "st,thermal-warning-recovery", NULL))
- pdata->thermal_warning_recovery = 1;
- if (of_get_property(np, "st,thermal-warning-adjustment", NULL))
- pdata->thermal_warning_adjustment = 1;
- if (of_get_property(np, "st,fault-detect-recovery", NULL))
- pdata->fault_detect_recovery = 1;
+ pdata->thermal_warning_recovery =
+ of_property_read_bool(np, "st,thermal-warning-recovery");
+ pdata->thermal_warning_adjustment =
+ of_property_read_bool(np, "st,thermal-warning-adjustment");
+ pdata->fault_detect_recovery =
+ of_property_read_bool(np, "st,fault-detect-recovery");
pdata->ffx_power_output_mode = STA350_FFX_PM_VARIABLE_DROP_COMP;
if (!of_property_read_string(np, "st,ffx-power-output-mode",
of_property_read_u16(np, "st,drop-compensation-ns", &tmp);
pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20;
- if (of_get_property(np, "st,overcurrent-warning-adjustment", NULL))
- pdata->oc_warning_adjustment = 1;
+ pdata->oc_warning_adjustment =
+ of_property_read_bool(np, "st,overcurrent-warning-adjustment");
/* CONFE */
- if (of_get_property(np, "st,max-power-use-mpcc", NULL))
- pdata->max_power_use_mpcc = 1;
-
- if (of_get_property(np, "st,max-power-correction", NULL))
- pdata->max_power_correction = 1;
-
- if (of_get_property(np, "st,am-reduction-mode", NULL))
- pdata->am_reduction_mode = 1;
-
- if (of_get_property(np, "st,odd-pwm-speed-mode", NULL))
- pdata->odd_pwm_speed_mode = 1;
-
- if (of_get_property(np, "st,distortion-compensation", NULL))
- pdata->distortion_compensation = 1;
+ pdata->max_power_use_mpcc =
+ of_property_read_bool(np, "st,max-power-use-mpcc");
+ pdata->max_power_correction =
+ of_property_read_bool(np, "st,max-power-correction");
+ pdata->am_reduction_mode =
+ of_property_read_bool(np, "st,am-reduction-mode");
+ pdata->odd_pwm_speed_mode =
+ of_property_read_bool(np, "st,odd-pwm-speed-mode");
+ pdata->distortion_compensation =
+ of_property_read_bool(np, "st,distortion-compensation");
/* CONFF */
- if (of_get_property(np, "st,invalid-input-detect-mute", NULL))
- pdata->invalid_input_detect_mute = 1;
+ pdata->invalid_input_detect_mute =
+ of_property_read_bool(np, "st,invalid-input-detect-mute");
/* MISC */
- if (of_get_property(np, "st,activate-mute-output", NULL))
- pdata->activate_mute_output = 1;
-
- if (of_get_property(np, "st,bridge-immediate-off", NULL))
- pdata->bridge_immediate_off = 1;
-
- if (of_get_property(np, "st,noise-shape-dc-cut", NULL))
- pdata->noise_shape_dc_cut = 1;
-
- if (of_get_property(np, "st,powerdown-master-volume", NULL))
- pdata->powerdown_master_vol = 1;
+ pdata->activate_mute_output =
+ of_property_read_bool(np, "st,activate-mute-output");
+ pdata->bridge_immediate_off =
+ of_property_read_bool(np, "st,bridge-immediate-off");
+ pdata->noise_shape_dc_cut =
+ of_property_read_bool(np, "st,noise-shape-dc-cut");
+ pdata->powerdown_master_vol =
+ of_property_read_bool(np, "st,powerdown-master-volume");
if (!of_property_read_u8(np, "st,powerdown-delay-divider", &tmp8)) {
if (is_power_of_2(tmp8) && tmp8 >= 1 && tmp8 <= 128)
snprintf(name, sizeof(name),
"ti,mid-z-channel-%d", i + 1);
- if (of_get_property(of_node, name, NULL) != NULL)
+ if (of_property_read_bool(of_node, name))
priv->pwm_start_mid_z |= 1 << i;
}
}
.volatile_table = &tas571x_volatile_regs,
};
+static const char *const tas5733_supply_names[] = {
+ "AVDD",
+ "DVDD",
+ "PVDD",
+};
+
+static const struct reg_default tas5733_reg_defaults[] = {
+ {TAS571X_CLK_CTRL_REG, 0x6c},
+ {TAS571X_DEV_ID_REG, 0x00},
+ {TAS571X_ERR_STATUS_REG, 0x00},
+ {TAS571X_SYS_CTRL_1_REG, 0xa0},
+ {TAS571X_SDI_REG, 0x05},
+ {TAS571X_SYS_CTRL_2_REG, 0x40},
+ {TAS571X_SOFT_MUTE_REG, 0x07},
+ {TAS571X_MVOL_REG, 0x03ff},
+ {TAS571X_CH1_VOL_REG, 0x00c0},
+ {TAS571X_CH2_VOL_REG, 0x00c0},
+ {TAS571X_CH3_VOL_REG, 0x00c0},
+ {TAS571X_VOL_CFG_REG, 0xf0},
+ {TAS571X_MODULATION_LIMIT_REG, 0x07},
+ {TAS571X_IC_DELAY_CH1_REG, 0xb8},
+ {TAS571X_IC_DELAY_CH2_REG, 0x60},
+ {TAS571X_IC_DELAY_CH3_REG, 0xa0},
+ {TAS571X_IC_DELAY_CH4_REG, 0x48},
+ {TAS571X_PWM_CH_SDN_GROUP_REG, 0x30},
+ {TAS571X_START_STOP_PERIOD_REG, 0x68},
+ {TAS571X_OSC_TRIM_REG, 0x82},
+ {TAS571X_BKND_ERR_REG, 0x02},
+ {TAS571X_INPUT_MUX_REG, 0x00897772},
+ {TAS571X_PWM_MUX_REG, 0x01021345},
+ {TAS5717_CH1_RIGHT_CH_MIX_REG, 0x00},
+ {TAS5717_CH1_LEFT_CH_MIX_REG, 0x800000},
+ {TAS5717_CH2_LEFT_CH_MIX_REG, 0x00},
+ {TAS5717_CH2_RIGHT_CH_MIX_REG, 0x800000},
+};
+
+static const struct regmap_config tas5733_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .max_register = 0xff,
+ .reg_read = tas571x_reg_read,
+ .reg_write = tas571x_reg_write,
+ .reg_defaults = tas5733_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tas5733_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+ .wr_table = &tas571x_write_regs,
+ .volatile_table = &tas571x_volatile_regs,
+};
+
+static const struct tas571x_chip tas5733_chip = {
+ .supply_names = tas5733_supply_names,
+ .num_supply_names = ARRAY_SIZE(tas5733_supply_names),
+ .controls = tas5717_controls,
+ .num_controls = ARRAY_SIZE(tas5717_controls),
+ .regmap_config = &tas5733_regmap_config,
+ .vol_reg_size = 2,
+};
static const struct tas571x_chip tas5721_chip = {
.supply_names = tas5721_supply_names,
{ .compatible = "ti,tas5717", .data = &tas5717_chip, },
{ .compatible = "ti,tas5719", .data = &tas5717_chip, },
{ .compatible = "ti,tas5721", .data = &tas5721_chip, },
+ { .compatible = "ti,tas5733", .data = &tas5733_chip, },
{ }
};
MODULE_DEVICE_TABLE(of, tas571x_of_match);
{ "tas5717", (kernel_ulong_t) &tas5717_chip },
{ "tas5719", (kernel_ulong_t) &tas5717_chip },
{ "tas5721", (kernel_ulong_t) &tas5721_chip },
+ { "tas5733", (kernel_ulong_t) &tas5733_chip },
{ }
};
MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id);
break;
default:
dev_err(component->dev, "unexpected private driver data\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto probe_fail;
}
if (device_id != expected_device_id)
* INTR2 is a subset of first interrupt sources MAD, VBAT, and SVA
*/
wcd->intr1 = of_irq_get_byname(wcd->dev->of_node, "intr1");
- if (wcd->intr1 < 0) {
- if (wcd->intr1 != -EPROBE_DEFER)
- dev_err(wcd->dev, "Unable to configure IRQ\n");
-
- return wcd->intr1;
- }
+ if (wcd->intr1 < 0)
+ return dev_err_probe(wcd->dev, wcd->intr1,
+ "Unable to configure IRQ\n");
ret = devm_regmap_add_irq_chip(wcd->dev, wcd->regmap, wcd->intr1,
IRQF_TRIGGER_HIGH, 0,
&wcd9335_regmap_irq1_chip, &wcd->irq_data);
if (ret)
- dev_err(wcd->dev, "Failed to register IRQ chip: %d\n", ret);
+ return dev_err_probe(wcd->dev, ret, "Failed to register IRQ chip\n");
- return ret;
+ return 0;
}
static int wcd9335_slim_probe(struct slim_device *slim)
slim_get_logical_addr(wcd->slim_ifc_dev);
wcd->regmap = regmap_init_slimbus(sdev, &wcd9335_regmap_config);
- if (IS_ERR(wcd->regmap)) {
- dev_err(dev, "Failed to allocate slim register map\n");
- return PTR_ERR(wcd->regmap);
- }
+ if (IS_ERR(wcd->regmap))
+ return dev_err_probe(dev, PTR_ERR(wcd->regmap),
+ "Failed to allocate slim register map\n");
wcd->if_regmap = regmap_init_slimbus(wcd->slim_ifc_dev,
&wcd9335_ifc_regmap_config);
- if (IS_ERR(wcd->if_regmap)) {
- dev_err(dev, "Failed to allocate ifc register map\n");
- return PTR_ERR(wcd->if_regmap);
- }
+ if (IS_ERR(wcd->if_regmap))
+ return dev_err_probe(dev, PTR_ERR(wcd->if_regmap),
+ "Failed to allocate ifc register map\n");
ret = wcd9335_bring_up(wcd);
if (ret) {
slim_get_logical_addr(wcd->sidev);
wcd->if_regmap = regmap_init_slimbus(wcd->sidev,
&wcd934x_ifc_regmap_config);
- if (IS_ERR(wcd->if_regmap)) {
- dev_err(dev, "Failed to allocate ifc register map\n");
- return PTR_ERR(wcd->if_regmap);
- }
+ if (IS_ERR(wcd->if_regmap))
+ return dev_err_probe(dev, PTR_ERR(wcd->if_regmap),
+ "Failed to allocate ifc register map\n");
of_property_read_u32(dev->parent->of_node, "qcom,dmic-sample-rate",
&wcd->dmic_sample_rate);
static int wcd934x_codec_probe(struct platform_device *pdev)
{
- struct wcd934x_ddata *data = dev_get_drvdata(pdev->dev.parent);
- struct wcd934x_codec *wcd;
struct device *dev = &pdev->dev;
+ struct wcd934x_ddata *data = dev_get_drvdata(dev->parent);
+ struct wcd934x_codec *wcd;
int ret, irq;
- wcd = devm_kzalloc(&pdev->dev, sizeof(*wcd), GFP_KERNEL);
+ wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL);
if (!wcd)
return -ENOMEM;
memcpy(wcd->tx_chs, wcd934x_tx_chs, sizeof(wcd934x_tx_chs));
irq = regmap_irq_get_virq(data->irq_data, WCD934X_IRQ_SLIMBUS);
- if (irq < 0) {
- dev_err(wcd->dev, "Failed to get SLIM IRQ\n");
- return irq;
- }
+ if (irq < 0)
+ return dev_err_probe(wcd->dev, irq, "Failed to get SLIM IRQ\n");
ret = devm_request_threaded_irq(dev, irq, NULL,
wcd934x_slim_irq_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"slim", wcd);
- if (ret) {
- dev_err(dev, "Failed to request slimbus irq\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request slimbus irq\n");
wcd934x_register_mclk_output(wcd);
platform_set_drvdata(pdev, wcd);
int ret;
wcd938x->reset_gpio = of_get_named_gpio(dev->of_node, "reset-gpios", 0);
- if (wcd938x->reset_gpio < 0) {
- dev_err(dev, "Failed to get reset gpio: err = %d\n",
- wcd938x->reset_gpio);
- return wcd938x->reset_gpio;
- }
+ if (wcd938x->reset_gpio < 0)
+ return dev_err_probe(dev, wcd938x->reset_gpio,
+ "Failed to get reset gpio\n");
wcd938x->us_euro_gpio = devm_gpiod_get_optional(dev, "us-euro",
GPIOD_OUT_LOW);
- if (IS_ERR(wcd938x->us_euro_gpio)) {
- dev_err(dev, "us-euro swap Control GPIO not found\n");
- return PTR_ERR(wcd938x->us_euro_gpio);
- }
+ if (IS_ERR(wcd938x->us_euro_gpio))
+ return dev_err_probe(dev, PTR_ERR(wcd938x->us_euro_gpio),
+ "us-euro swap Control GPIO not found\n");
cfg->swap_gnd_mic = wcd938x_swap_gnd_mic;
wcd938x->supplies[3].supply = "vdd-mic-bias";
ret = regulator_bulk_get(dev, WCD938X_MAX_SUPPLY, wcd938x->supplies);
- if (ret) {
- dev_err(dev, "Failed to get supplies: err = %d\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get supplies\n");
ret = regulator_bulk_enable(WCD938X_MAX_SUPPLY, wcd938x->supplies);
- if (ret) {
- dev_err(dev, "Failed to enable supplies: err = %d\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable supplies\n");
wcd938x_dt_parse_micbias_info(dev, wcd938x);
return 0;
}
-static int wcd938x_remove(struct platform_device *pdev)
+static void wcd938x_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &wcd938x_comp_ops);
-
- return 0;
}
#if defined(CONFIG_OF)
static struct platform_driver wcd938x_codec_driver = {
.probe = wcd938x_probe,
- .remove = wcd938x_remove,
+ .remove_new = wcd938x_remove,
.driver = {
.name = "wcd938x_codec",
.of_match_table = of_match_ptr(wcd938x_dt_match),
return ret;
}
-static int wm5102_remove(struct platform_device *pdev)
+static void wm5102_remove(struct platform_device *pdev)
{
struct wm5102_priv *wm5102 = platform_get_drvdata(pdev);
struct arizona *arizona = wm5102->core.arizona;
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5102);
arizona_jack_codec_dev_remove(&wm5102->core);
-
- return 0;
}
static struct platform_driver wm5102_codec_driver = {
.name = "wm5102-codec",
},
.probe = wm5102_probe,
- .remove = wm5102_remove,
+ .remove_new = wm5102_remove,
};
module_platform_driver(wm5102_codec_driver);
return ret;
}
-static int wm5110_remove(struct platform_device *pdev)
+static void wm5110_remove(struct platform_device *pdev)
{
struct wm5110_priv *wm5110 = platform_get_drvdata(pdev);
struct arizona *arizona = wm5110->core.arizona;
arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5110);
arizona_jack_codec_dev_remove(&wm5110->core);
-
- return 0;
}
static struct platform_driver wm5110_codec_driver = {
.name = "wm5110-codec",
},
.probe = wm5110_probe,
- .remove = wm5110_remove,
+ .remove_new = wm5110_remove,
};
module_platform_driver(wm5110_codec_driver);
*
* TODO:
* - TDM mode configuration.
- * - Digital microphone support.
*/
#include <linux/module.h>
return ret;
}
-static int wm8994_remove(struct platform_device *pdev)
+static void wm8994_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &wm8994_pm_ops,
},
.probe = wm8994_probe,
- .remove = wm8994_remove,
+ .remove_new = wm8994_remove,
};
module_platform_driver(wm8994_codec_driver);
return ret;
}
-static int wm8997_remove(struct platform_device *pdev)
+static void wm8997_remove(struct platform_device *pdev)
{
struct wm8997_priv *wm8997 = platform_get_drvdata(pdev);
struct arizona *arizona = wm8997->core.arizona;
arizona_free_spk_irqs(arizona);
arizona_jack_codec_dev_remove(&wm8997->core);
-
- return 0;
}
static struct platform_driver wm8997_codec_driver = {
.name = "wm8997-codec",
},
.probe = wm8997_probe,
- .remove = wm8997_remove,
+ .remove_new = wm8997_remove,
};
module_platform_driver(wm8997_codec_driver);
return ret;
}
-static int wm8998_remove(struct platform_device *pdev)
+static void wm8998_remove(struct platform_device *pdev)
{
struct wm8998_priv *wm8998 = platform_get_drvdata(pdev);
struct arizona *arizona = wm8998->core.arizona;
arizona_free_spk_irqs(arizona);
arizona_jack_codec_dev_remove(&wm8998->core);
-
- return 0;
}
static struct platform_driver wm8998_codec_driver = {
.name = "wm8998-codec",
},
.probe = wm8998_probe,
- .remove = wm8998_remove,
+ .remove_new = wm8998_remove,
};
module_platform_driver(wm8998_codec_driver);
adsp_dbg(dsp, "Failed to request '%s'\n", *filename);
kfree(*filename);
*filename = NULL;
+ } else {
+ adsp_dbg(dsp, "Found '%s'\n", *filename);
}
return ret;
if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename,
cirrus_dir, system_name,
asoc_component_prefix, "wmfw")) {
- adsp_dbg(dsp, "Found '%s'\n", *wmfw_filename);
wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
cirrus_dir, system_name,
asoc_component_prefix, "bin");
if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename,
cirrus_dir, system_name,
NULL, "wmfw")) {
- adsp_dbg(dsp, "Found '%s'\n", *wmfw_filename);
if (asoc_component_prefix)
wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
cirrus_dir, system_name,
if (!wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename,
"", NULL, NULL, "wmfw")) {
- adsp_dbg(dsp, "Found '%s'\n", *wmfw_filename);
wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
"", NULL, NULL, "bin");
return 0;
ret = wm_adsp_request_firmware_file(dsp, wmfw_firmware, wmfw_filename,
cirrus_dir, NULL, NULL, "wmfw");
if (!ret) {
- adsp_dbg(dsp, "Found '%s'\n", *wmfw_filename);
wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
cirrus_dir, NULL, NULL, "bin");
return 0;
}
+ if (dsp->wmfw_optional) {
+ if (system_name) {
+ if (asoc_component_prefix)
+ wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
+ cirrus_dir, system_name,
+ asoc_component_prefix, "bin");
+
+ if (!*coeff_firmware)
+ wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
+ cirrus_dir, system_name,
+ NULL, "bin");
+ }
+
+ if (!*coeff_firmware)
+ wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
+ "", NULL, NULL, "bin");
+
+ if (!*coeff_firmware)
+ wm_adsp_request_firmware_file(dsp, coeff_firmware, coeff_filename,
+ cirrus_dir, NULL, NULL, "bin");
+
+ return 0;
+ }
+
adsp_err(dsp, "Failed to request firmware <%s>%s-%s-%s<-%s<%s>>.wmfw\n",
cirrus_dir, dsp->part, dsp->fwf_name, wm_adsp_fw[dsp->fw].file,
system_name, asoc_component_prefix);
}
EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put);
-static void wm_adsp_boot_work(struct work_struct *work)
+int wm_adsp_power_up(struct wm_adsp *dsp)
{
- struct wm_adsp *dsp = container_of(work,
- struct wm_adsp,
- boot_work);
int ret = 0;
char *wmfw_filename = NULL;
const struct firmware *wmfw_firmware = NULL;
&wmfw_firmware, &wmfw_filename,
&coeff_firmware, &coeff_filename);
if (ret)
- return;
+ return ret;
- cs_dsp_power_up(&dsp->cs_dsp,
- wmfw_firmware, wmfw_filename,
- coeff_firmware, coeff_filename,
- wm_adsp_fw_text[dsp->fw]);
+ ret = cs_dsp_power_up(&dsp->cs_dsp,
+ wmfw_firmware, wmfw_filename,
+ coeff_firmware, coeff_filename,
+ wm_adsp_fw_text[dsp->fw]);
wm_adsp_release_firmware_files(dsp,
wmfw_firmware, wmfw_filename,
coeff_firmware, coeff_filename);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm_adsp_power_up);
+
+static void wm_adsp_boot_work(struct work_struct *work)
+{
+ struct wm_adsp *dsp = container_of(work,
+ struct wm_adsp,
+ boot_work);
+
+ wm_adsp_power_up(dsp);
}
int wm_adsp_early_event(struct snd_soc_dapm_widget *w,
{
char preload[32];
- snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->cs_dsp.name);
- snd_soc_component_disable_pin(component, preload);
+ if (!dsp->cs_dsp.no_core_startstop) {
+ snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->cs_dsp.name);
+ snd_soc_component_disable_pin(component, preload);
+ }
cs_dsp_init_debugfs(&dsp->cs_dsp, component->debugfs_root);
unsigned int sys_config_size;
int fw;
+ bool wmfw_optional;
struct work_struct boot_work;
int (*pre_run)(struct wm_adsp *dsp);
int wm_adsp_early_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
+int wm_adsp_power_up(struct wm_adsp *dsp);
+
irqreturn_t wm_adsp2_bus_error(int irq, void *data);
irqreturn_t wm_halo_bus_error(int irq, void *data);
irqreturn_t wm_halo_wdt_expire(int irq, void *data);
&zl38_dai, 1);
}
-static const struct of_device_id zl38_dt_ids[] = {
+static const struct of_device_id zl38_dt_ids[] __maybe_unused = {
{ .compatible = "mscc,zl38060", },
{ /* sentinel */ }
};
return ret;
}
-static int dw_i2s_remove(struct platform_device *pdev)
+static void dw_i2s_remove(struct platform_device *pdev)
{
struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(dev->clk);
pm_runtime_disable(&pdev->dev);
- return 0;
}
#ifdef CONFIG_OF
static struct platform_driver dw_i2s_driver = {
.probe = dw_i2s_probe,
- .remove = dw_i2s_remove,
+ .remove_new = dw_i2s_remove,
.driver = {
.name = "designware-i2s",
.of_match_table = of_match_ptr(dw_i2s_of_match),
config SND_SOC_POWERPC_DMA
tristate
+config SND_SOC_POWERPC_QMC_AUDIO
+ tristate "QMC ALSA SoC support"
+ depends on CPM_QMC
+ help
+ ALSA SoC Audio support using the Freescale QUICC Multichannel
+ Controller (QMC).
+ Say Y or M if you want to add support for SoC audio using Freescale
+ QMC.
+
comment "SoC Audio support for Freescale PPC boards:"
config SND_SOC_MPC8610_HPCD
snd-soc-fsl-xcvr-objs := fsl_xcvr.o
snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o
snd-soc-fsl-rpmsg-objs := fsl_rpmsg.o
+snd-soc-fsl-qmc-audio-objs := fsl_qmc_audio.o
obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o
obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o
obj-$(CONFIG_SND_SOC_FSL_RPMSG) += snd-soc-fsl-rpmsg.o
+obj-$(CONFIG_SND_SOC_POWERPC_QMC_AUDIO) += snd-soc-fsl-qmc-audio.o
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
return ret;
}
-static int eukrea_tlv320_remove(struct platform_device *pdev)
+static void eukrea_tlv320_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&eukrea_tlv320);
-
- return 0;
}
static const struct of_device_id imx_tlv320_dt_ids[] = {
.of_match_table = imx_tlv320_dt_ids,
},
.probe = eukrea_tlv320_probe,
- .remove = eukrea_tlv320_remove,
+ .remove_new = eukrea_tlv320_remove,
};
module_platform_driver(eukrea_tlv320_driver);
#include "../codecs/wm8994.h"
#include "../codecs/tlv320aic31xx.h"
+#define DRIVER_NAME "fsl-asoc-card"
+
#define CS427x_SYSCLK_MCLK 0
#define RX 0
priv->card.dapm_routes = audio_map;
priv->card.num_dapm_routes = ARRAY_SIZE(audio_map);
+ priv->card.driver_name = DRIVER_NAME;
/* Diversify the card configurations */
if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) {
codec_dai_name = "cs42888";
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
if (ret) {
- dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed: %d\n", ret);
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
goto asrc_fail;
}
static struct platform_driver fsl_asoc_card_driver = {
.probe = fsl_asoc_card_probe,
.driver = {
- .name = "fsl-asoc-card",
+ .name = DRIVER_NAME,
.pm = &snd_soc_pm_ops,
.of_match_table = fsl_asoc_card_dt_ids,
},
MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC");
MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
-MODULE_ALIAS("platform:fsl-asoc-card");
+MODULE_ALIAS("platform:" DRIVER_NAME);
MODULE_LICENSE("GPL");
return ret;
}
-static int fsl_asrc_remove(struct platform_device *pdev)
+static void fsl_asrc_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
fsl_asrc_runtime_suspend(&pdev->dev);
-
- return 0;
}
static int fsl_asrc_runtime_resume(struct device *dev)
static struct platform_driver fsl_asrc_driver = {
.probe = fsl_asrc_probe,
- .remove = fsl_asrc_remove,
+ .remove_new = fsl_asrc_remove,
.driver = {
.name = "fsl-asrc",
.of_match_table = fsl_asrc_ids,
return ret;
}
-static int fsl_aud2htx_remove(struct platform_device *pdev)
+static void fsl_aud2htx_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static int __maybe_unused fsl_aud2htx_runtime_suspend(struct device *dev)
static struct platform_driver fsl_aud2htx_driver = {
.probe = fsl_aud2htx_probe,
- .remove = fsl_aud2htx_remove,
+ .remove_new = fsl_aud2htx_remove,
.driver = {
.name = "fsl-aud2htx",
.pm = &fsl_aud2htx_pm_ops,
return ret;
}
-static int fsl_audmix_remove(struct platform_device *pdev)
+static void fsl_audmix_remove(struct platform_device *pdev)
{
struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev);
if (priv->pdev)
platform_device_unregister(priv->pdev);
-
- return 0;
}
#ifdef CONFIG_PM
static struct platform_driver fsl_audmix_driver = {
.probe = fsl_audmix_probe,
- .remove = fsl_audmix_remove,
+ .remove_new = fsl_audmix_remove,
.driver = {
.name = "fsl-audmix",
.of_match_table = fsl_audmix_ids,
return 0;
}
-static int fsl_soc_dma_remove(struct platform_device *pdev)
+static void fsl_soc_dma_remove(struct platform_device *pdev)
{
struct dma_object *dma = dev_get_drvdata(&pdev->dev);
iounmap(dma->channel);
irq_dispose_mapping(dma->irq);
kfree(dma);
-
- return 0;
}
static const struct of_device_id fsl_soc_dma_ids[] = {
.of_match_table = fsl_soc_dma_ids,
},
.probe = fsl_soc_dma_probe,
- .remove = fsl_soc_dma_remove,
+ .remove_new = fsl_soc_dma_remove,
};
module_platform_driver(fsl_soc_dma_driver);
return 0;
}
-static int fsl_easrc_remove(struct platform_device *pdev)
+static void fsl_easrc_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static __maybe_unused int fsl_easrc_runtime_suspend(struct device *dev)
static struct platform_driver fsl_easrc_driver = {
.probe = fsl_easrc_probe,
- .remove = fsl_easrc_remove,
+ .remove_new = fsl_easrc_remove,
.driver = {
.name = "fsl-easrc",
.pm = &fsl_easrc_pm_ops,
return ret;
}
-static int fsl_esai_remove(struct platform_device *pdev)
+static void fsl_esai_remove(struct platform_device *pdev)
{
struct fsl_esai *esai_priv = platform_get_drvdata(pdev);
fsl_esai_runtime_suspend(&pdev->dev);
cancel_work_sync(&esai_priv->work);
-
- return 0;
}
static const struct of_device_id fsl_esai_dt_ids[] = {
static struct platform_driver fsl_esai_driver = {
.probe = fsl_esai_probe,
- .remove = fsl_esai_remove,
+ .remove_new = fsl_esai_remove,
.driver = {
.name = "fsl-esai-dai",
.pm = &fsl_esai_pm_ops,
}
mqs_priv->regmap = syscon_node_to_regmap(gpr_np);
+ of_node_put(gpr_np);
if (IS_ERR(mqs_priv->regmap)) {
dev_err(&pdev->dev, "failed to get gpr regmap\n");
- ret = PTR_ERR(mqs_priv->regmap);
- goto err_free_gpr_np;
+ return PTR_ERR(mqs_priv->regmap);
}
} else {
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mqs_priv->mclk)) {
dev_err(&pdev->dev, "failed to get the clock: %ld\n",
PTR_ERR(mqs_priv->mclk));
- ret = PTR_ERR(mqs_priv->mclk);
- goto err_free_gpr_np;
+ return PTR_ERR(mqs_priv->mclk);
}
dev_set_drvdata(&pdev->dev, mqs_priv);
ret = devm_snd_soc_register_component(&pdev->dev, &soc_codec_fsl_mqs,
&fsl_mqs_dai, 1);
if (ret)
- goto err_free_gpr_np;
- return 0;
-
-err_free_gpr_np:
- of_node_put(gpr_np);
+ return ret;
- return ret;
+ return 0;
}
-static int fsl_mqs_remove(struct platform_device *pdev)
+static void fsl_mqs_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
#ifdef CONFIG_PM
static struct platform_driver fsl_mqs_driver = {
.probe = fsl_mqs_probe,
- .remove = fsl_mqs_remove,
+ .remove_new = fsl_mqs_remove,
.driver = {
.name = "fsl-mqs",
.of_match_table = fsl_mqs_dt_ids,
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ALSA SoC using the QUICC Multichannel Controller (QMC)
+ *
+ * Copyright 2022 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <soc/fsl/qe/qmc.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+struct qmc_dai {
+ char *name;
+ int id;
+ struct device *dev;
+ struct qmc_chan *qmc_chan;
+ unsigned int nb_tx_ts;
+ unsigned int nb_rx_ts;
+};
+
+struct qmc_audio {
+ struct device *dev;
+ unsigned int num_dais;
+ struct qmc_dai *dais;
+ struct snd_soc_dai_driver *dai_drivers;
+};
+
+struct qmc_dai_prtd {
+ struct qmc_dai *qmc_dai;
+ dma_addr_t dma_buffer_start;
+ dma_addr_t period_ptr_submitted;
+ dma_addr_t period_ptr_ended;
+ dma_addr_t dma_buffer_end;
+ size_t period_size;
+ struct snd_pcm_substream *substream;
+};
+
+static int qmc_audio_pcm_construct(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, card->dev,
+ 64*1024, 64*1024);
+ return 0;
+}
+
+static int qmc_audio_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct qmc_dai_prtd *prtd = substream->runtime->private_data;
+
+ prtd->dma_buffer_start = runtime->dma_addr;
+ prtd->dma_buffer_end = runtime->dma_addr + params_buffer_bytes(params);
+ prtd->period_size = params_period_bytes(params);
+ prtd->period_ptr_submitted = prtd->dma_buffer_start;
+ prtd->period_ptr_ended = prtd->dma_buffer_start;
+ prtd->substream = substream;
+
+ return 0;
+}
+
+static void qmc_audio_pcm_write_complete(void *context)
+{
+ struct qmc_dai_prtd *prtd = context;
+ int ret;
+
+ prtd->period_ptr_ended += prtd->period_size;
+ if (prtd->period_ptr_ended >= prtd->dma_buffer_end)
+ prtd->period_ptr_ended = prtd->dma_buffer_start;
+
+ prtd->period_ptr_submitted += prtd->period_size;
+ if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
+ prtd->period_ptr_submitted = prtd->dma_buffer_start;
+
+ ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan,
+ prtd->period_ptr_submitted, prtd->period_size,
+ qmc_audio_pcm_write_complete, prtd);
+ if (ret) {
+ dev_err(prtd->qmc_dai->dev, "write_submit failed %d\n",
+ ret);
+ }
+
+ snd_pcm_period_elapsed(prtd->substream);
+}
+
+static void qmc_audio_pcm_read_complete(void *context, size_t length)
+{
+ struct qmc_dai_prtd *prtd = context;
+ int ret;
+
+ if (length != prtd->period_size) {
+ dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n",
+ length, prtd->period_size);
+ }
+
+ prtd->period_ptr_ended += prtd->period_size;
+ if (prtd->period_ptr_ended >= prtd->dma_buffer_end)
+ prtd->period_ptr_ended = prtd->dma_buffer_start;
+
+ prtd->period_ptr_submitted += prtd->period_size;
+ if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
+ prtd->period_ptr_submitted = prtd->dma_buffer_start;
+
+ ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan,
+ prtd->period_ptr_submitted, prtd->period_size,
+ qmc_audio_pcm_read_complete, prtd);
+ if (ret) {
+ dev_err(prtd->qmc_dai->dev, "read_submit failed %d\n",
+ ret);
+ }
+
+ snd_pcm_period_elapsed(prtd->substream);
+}
+
+static int qmc_audio_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct qmc_dai_prtd *prtd = substream->runtime->private_data;
+ int ret;
+
+ if (!prtd->qmc_dai) {
+ dev_err(component->dev, "qmc_dai is not set\n");
+ return -EINVAL;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* Submit first chunk ... */
+ ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan,
+ prtd->period_ptr_submitted, prtd->period_size,
+ qmc_audio_pcm_write_complete, prtd);
+ if (ret) {
+ dev_err(component->dev, "write_submit failed %d\n",
+ ret);
+ return ret;
+ }
+
+ /* ... prepare next one ... */
+ prtd->period_ptr_submitted += prtd->period_size;
+ if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
+ prtd->period_ptr_submitted = prtd->dma_buffer_start;
+
+ /* ... and send it */
+ ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan,
+ prtd->period_ptr_submitted, prtd->period_size,
+ qmc_audio_pcm_write_complete, prtd);
+ if (ret) {
+ dev_err(component->dev, "write_submit failed %d\n",
+ ret);
+ return ret;
+ }
+ } else {
+ /* Submit first chunk ... */
+ ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan,
+ prtd->period_ptr_submitted, prtd->period_size,
+ qmc_audio_pcm_read_complete, prtd);
+ if (ret) {
+ dev_err(component->dev, "read_submit failed %d\n",
+ ret);
+ return ret;
+ }
+
+ /* ... prepare next one ... */
+ prtd->period_ptr_submitted += prtd->period_size;
+ if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
+ prtd->period_ptr_submitted = prtd->dma_buffer_start;
+
+ /* ... and send it */
+ ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan,
+ prtd->period_ptr_submitted, prtd->period_size,
+ qmc_audio_pcm_read_complete, prtd);
+ if (ret) {
+ dev_err(component->dev, "write_submit failed %d\n",
+ ret);
+ return ret;
+ }
+ }
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t qmc_audio_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct qmc_dai_prtd *prtd = substream->runtime->private_data;
+
+ return bytes_to_frames(substream->runtime,
+ prtd->period_ptr_ended - prtd->dma_buffer_start);
+}
+
+static int qmc_audio_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name)
+{
+ struct qmc_audio *qmc_audio = dev_get_drvdata(component->dev);
+ struct snd_soc_dai_driver *dai_driver;
+ int id = args->args[0];
+ int i;
+
+ for (i = 0; i < qmc_audio->num_dais; i++) {
+ dai_driver = qmc_audio->dai_drivers + i;
+ if (dai_driver->id == id) {
+ *dai_name = dai_driver->name;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct snd_pcm_hardware qmc_audio_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 64*1024,
+ .periods_min = 2,
+ .periods_max = 2*1024,
+ .buffer_bytes_max = 64*1024,
+};
+
+static int qmc_audio_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct qmc_dai_prtd *prtd;
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &qmc_audio_pcm_hardware);
+
+ /* ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+
+ return 0;
+}
+
+static int qmc_audio_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct qmc_dai_prtd *prtd = substream->runtime->private_data;
+
+ kfree(prtd);
+ return 0;
+}
+
+static const struct snd_soc_component_driver qmc_audio_soc_platform = {
+ .open = qmc_audio_pcm_open,
+ .close = qmc_audio_pcm_close,
+ .hw_params = qmc_audio_pcm_hw_params,
+ .trigger = qmc_audio_pcm_trigger,
+ .pointer = qmc_audio_pcm_pointer,
+ .pcm_construct = qmc_audio_pcm_construct,
+ .of_xlate_dai_name = qmc_audio_of_xlate_dai_name,
+};
+
+static unsigned int qmc_dai_get_index(struct snd_soc_dai *dai)
+{
+ struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai);
+
+ return dai->driver - qmc_audio->dai_drivers;
+}
+
+static struct qmc_dai *qmc_dai_get_data(struct snd_soc_dai *dai)
+{
+ struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai);
+ unsigned int index;
+
+ index = qmc_dai_get_index(dai);
+ if (index > qmc_audio->num_dais)
+ return NULL;
+
+ return qmc_audio->dais + index;
+}
+
+/*
+ * The constraints for format/channel is to match with the number of 8bit
+ * time-slots available.
+ */
+static int qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai,
+ struct snd_pcm_hw_params *params,
+ unsigned int nb_ts)
+{
+ struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ snd_pcm_format_t format = params_format(params);
+ struct snd_interval ch = {0};
+
+ switch (snd_pcm_format_physical_width(format)) {
+ case 8:
+ ch.max = nb_ts;
+ break;
+ case 16:
+ ch.max = nb_ts/2;
+ break;
+ case 32:
+ ch.max = nb_ts/4;
+ break;
+ case 64:
+ ch.max = nb_ts/8;
+ break;
+ default:
+ dev_err(qmc_dai->dev, "format physical width %u not supported\n",
+ snd_pcm_format_physical_width(format));
+ return -EINVAL;
+ }
+
+ ch.min = ch.max ? 1 : 0;
+
+ return snd_interval_refine(c, &ch);
+}
+
+static int qmc_dai_hw_rule_playback_channels_by_format(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct qmc_dai *qmc_dai = rule->private;
+
+ return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_tx_ts);
+}
+
+static int qmc_dai_hw_rule_capture_channels_by_format(
+ struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct qmc_dai *qmc_dai = rule->private;
+
+ return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_rx_ts);
+}
+
+static int qmc_dai_hw_rule_format_by_channels(struct qmc_dai *qmc_dai,
+ struct snd_pcm_hw_params *params,
+ unsigned int nb_ts)
+{
+ struct snd_mask *f_old = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ unsigned int channels = params_channels(params);
+ unsigned int slot_width;
+ struct snd_mask f_new;
+ unsigned int i;
+
+ if (!channels || channels > nb_ts) {
+ dev_err(qmc_dai->dev, "channels %u not supported\n",
+ nb_ts);
+ return -EINVAL;
+ }
+
+ slot_width = (nb_ts / channels) * 8;
+
+ snd_mask_none(&f_new);
+ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
+ if (snd_mask_test(f_old, i)) {
+ if (snd_pcm_format_physical_width(i) <= slot_width)
+ snd_mask_set(&f_new, i);
+ }
+ }
+
+ return snd_mask_refine(f_old, &f_new);
+}
+
+static int qmc_dai_hw_rule_playback_format_by_channels(
+ struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct qmc_dai *qmc_dai = rule->private;
+
+ return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_tx_ts);
+}
+
+static int qmc_dai_hw_rule_capture_format_by_channels(
+ struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ struct qmc_dai *qmc_dai = rule->private;
+
+ return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_rx_ts);
+}
+
+static int qmc_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct qmc_dai_prtd *prtd = substream->runtime->private_data;
+ snd_pcm_hw_rule_func_t hw_rule_channels_by_format;
+ snd_pcm_hw_rule_func_t hw_rule_format_by_channels;
+ struct qmc_dai *qmc_dai;
+ unsigned int frame_bits;
+ int ret;
+
+ qmc_dai = qmc_dai_get_data(dai);
+ if (!qmc_dai) {
+ dev_err(dai->dev, "Invalid dai\n");
+ return -EINVAL;
+ }
+
+ prtd->qmc_dai = qmc_dai;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ hw_rule_channels_by_format = qmc_dai_hw_rule_capture_channels_by_format;
+ hw_rule_format_by_channels = qmc_dai_hw_rule_capture_format_by_channels;
+ frame_bits = qmc_dai->nb_rx_ts * 8;
+ } else {
+ hw_rule_channels_by_format = qmc_dai_hw_rule_playback_channels_by_format;
+ hw_rule_format_by_channels = qmc_dai_hw_rule_playback_format_by_channels;
+ frame_bits = qmc_dai->nb_tx_ts * 8;
+ }
+
+ ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ hw_rule_channels_by_format, qmc_dai,
+ SNDRV_PCM_HW_PARAM_FORMAT, -1);
+ if (ret) {
+ dev_err(dai->dev, "Failed to add channels rule (%d)\n", ret);
+ return ret;
+ }
+
+ ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+ hw_rule_format_by_channels, qmc_dai,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (ret) {
+ dev_err(dai->dev, "Failed to add format rule (%d)\n", ret);
+ return ret;
+ }
+
+ ret = snd_pcm_hw_constraint_single(substream->runtime,
+ SNDRV_PCM_HW_PARAM_FRAME_BITS,
+ frame_bits);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to add frame_bits constraint (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int qmc_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct qmc_chan_param chan_param = {0};
+ struct qmc_dai *qmc_dai;
+ int ret;
+
+ qmc_dai = qmc_dai_get_data(dai);
+ if (!qmc_dai) {
+ dev_err(dai->dev, "Invalid dai\n");
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ chan_param.mode = QMC_TRANSPARENT;
+ chan_param.transp.max_rx_buf_size = params_period_bytes(params);
+ ret = qmc_chan_set_param(qmc_dai->qmc_chan, &chan_param);
+ if (ret) {
+ dev_err(dai->dev, "set param failed %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct qmc_dai *qmc_dai;
+ int direction;
+ int ret;
+
+ qmc_dai = qmc_dai_get_data(dai);
+ if (!qmc_dai) {
+ dev_err(dai->dev, "Invalid dai\n");
+ return -EINVAL;
+ }
+
+ direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+ QMC_CHAN_WRITE : QMC_CHAN_READ;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = qmc_chan_start(qmc_dai->qmc_chan, direction);
+ if (ret)
+ return ret;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = qmc_chan_stop(qmc_dai->qmc_chan, direction);
+ if (ret)
+ return ret;
+ ret = qmc_chan_reset(qmc_dai->qmc_chan, direction);
+ if (ret)
+ return ret;
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = qmc_chan_stop(qmc_dai->qmc_chan, direction);
+ if (ret)
+ return ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops qmc_dai_ops = {
+ .startup = qmc_dai_startup,
+ .trigger = qmc_dai_trigger,
+ .hw_params = qmc_dai_hw_params,
+};
+
+static u64 qmc_audio_formats(u8 nb_ts)
+{
+ u64 formats;
+ unsigned int chan_width;
+ unsigned int format_width;
+ int i;
+
+ if (!nb_ts)
+ return 0;
+
+ formats = 0;
+ chan_width = nb_ts * 8;
+ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
+ /*
+ * Support format other than little-endian (ie big-endian or
+ * without endianness such as 8bit formats)
+ */
+ if (snd_pcm_format_little_endian(i) == 1)
+ continue;
+
+ /* Support physical width multiple of 8bit */
+ format_width = snd_pcm_format_physical_width(i);
+ if (format_width == 0 || format_width % 8)
+ continue;
+
+ /*
+ * And support physical width that can fit N times in the
+ * channel
+ */
+ if (format_width > chan_width || chan_width % format_width)
+ continue;
+
+ formats |= (1ULL << i);
+ }
+ return formats;
+}
+
+static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *np,
+ struct qmc_dai *qmc_dai, struct snd_soc_dai_driver *qmc_soc_dai_driver)
+{
+ struct qmc_chan_info info;
+ u32 val;
+ int ret;
+
+ qmc_dai->dev = qmc_audio->dev;
+
+ ret = of_property_read_u32(np, "reg", &val);
+ if (ret) {
+ dev_err(qmc_audio->dev, "%pOF: failed to read reg\n", np);
+ return ret;
+ }
+ qmc_dai->id = val;
+
+ qmc_dai->name = devm_kasprintf(qmc_audio->dev, GFP_KERNEL, "%s.%d",
+ np->parent->name, qmc_dai->id);
+
+ qmc_dai->qmc_chan = devm_qmc_chan_get_byphandle(qmc_audio->dev, np,
+ "fsl,qmc-chan");
+ if (IS_ERR(qmc_dai->qmc_chan)) {
+ ret = PTR_ERR(qmc_dai->qmc_chan);
+ return dev_err_probe(qmc_audio->dev, ret,
+ "dai %d get QMC channel failed\n", qmc_dai->id);
+ }
+
+ qmc_soc_dai_driver->id = qmc_dai->id;
+ qmc_soc_dai_driver->name = qmc_dai->name;
+
+ ret = qmc_chan_get_info(qmc_dai->qmc_chan, &info);
+ if (ret) {
+ dev_err(qmc_audio->dev, "dai %d get QMC channel info failed %d\n",
+ qmc_dai->id, ret);
+ return ret;
+ }
+ dev_info(qmc_audio->dev, "dai %d QMC channel mode %d, nb_tx_ts %u, nb_rx_ts %u\n",
+ qmc_dai->id, info.mode, info.nb_tx_ts, info.nb_rx_ts);
+
+ if (info.mode != QMC_TRANSPARENT) {
+ dev_err(qmc_audio->dev, "dai %d QMC chan mode %d is not QMC_TRANSPARENT\n",
+ qmc_dai->id, info.mode);
+ return -EINVAL;
+ }
+ qmc_dai->nb_tx_ts = info.nb_tx_ts;
+ qmc_dai->nb_rx_ts = info.nb_rx_ts;
+
+ qmc_soc_dai_driver->playback.channels_min = 0;
+ qmc_soc_dai_driver->playback.channels_max = 0;
+ if (qmc_dai->nb_tx_ts) {
+ qmc_soc_dai_driver->playback.channels_min = 1;
+ qmc_soc_dai_driver->playback.channels_max = qmc_dai->nb_tx_ts;
+ }
+ qmc_soc_dai_driver->playback.formats = qmc_audio_formats(qmc_dai->nb_tx_ts);
+
+ qmc_soc_dai_driver->capture.channels_min = 0;
+ qmc_soc_dai_driver->capture.channels_max = 0;
+ if (qmc_dai->nb_rx_ts) {
+ qmc_soc_dai_driver->capture.channels_min = 1;
+ qmc_soc_dai_driver->capture.channels_max = qmc_dai->nb_rx_ts;
+ }
+ qmc_soc_dai_driver->capture.formats = qmc_audio_formats(qmc_dai->nb_rx_ts);
+
+ qmc_soc_dai_driver->playback.rates = snd_pcm_rate_to_rate_bit(info.tx_fs_rate);
+ qmc_soc_dai_driver->playback.rate_min = info.tx_fs_rate;
+ qmc_soc_dai_driver->playback.rate_max = info.tx_fs_rate;
+ qmc_soc_dai_driver->capture.rates = snd_pcm_rate_to_rate_bit(info.rx_fs_rate);
+ qmc_soc_dai_driver->capture.rate_min = info.rx_fs_rate;
+ qmc_soc_dai_driver->capture.rate_max = info.rx_fs_rate;
+
+ qmc_soc_dai_driver->ops = &qmc_dai_ops;
+
+ return 0;
+}
+
+static int qmc_audio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct qmc_audio *qmc_audio;
+ struct device_node *child;
+ unsigned int i;
+ int ret;
+
+ qmc_audio = devm_kzalloc(&pdev->dev, sizeof(*qmc_audio), GFP_KERNEL);
+ if (!qmc_audio)
+ return -ENOMEM;
+
+ qmc_audio->dev = &pdev->dev;
+
+ qmc_audio->num_dais = of_get_available_child_count(np);
+ if (qmc_audio->num_dais) {
+ qmc_audio->dais = devm_kcalloc(&pdev->dev, qmc_audio->num_dais,
+ sizeof(*qmc_audio->dais),
+ GFP_KERNEL);
+ if (!qmc_audio->dais)
+ return -ENOMEM;
+
+ qmc_audio->dai_drivers = devm_kcalloc(&pdev->dev, qmc_audio->num_dais,
+ sizeof(*qmc_audio->dai_drivers),
+ GFP_KERNEL);
+ if (!qmc_audio->dai_drivers)
+ return -ENOMEM;
+ }
+
+ i = 0;
+ for_each_available_child_of_node(np, child) {
+ ret = qmc_audio_dai_parse(qmc_audio, child,
+ qmc_audio->dais + i,
+ qmc_audio->dai_drivers + i);
+ if (ret) {
+ of_node_put(child);
+ return ret;
+ }
+ i++;
+ }
+
+
+ platform_set_drvdata(pdev, qmc_audio);
+
+ ret = devm_snd_soc_register_component(qmc_audio->dev,
+ &qmc_audio_soc_platform,
+ qmc_audio->dai_drivers,
+ qmc_audio->num_dais);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id qmc_audio_id_table[] = {
+ { .compatible = "fsl,qmc-audio" },
+ {} /* sentinel */
+};
+MODULE_DEVICE_TABLE(of, qmc_audio_id_table);
+
+static struct platform_driver qmc_audio_driver = {
+ .driver = {
+ .name = "fsl-qmc-audio",
+ .of_match_table = of_match_ptr(qmc_audio_id_table),
+ },
+ .probe = qmc_audio_probe,
+};
+module_platform_driver(qmc_audio_driver);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("CPM/QE QMC audio driver");
+MODULE_LICENSE("GPL");
return 0;
}
-static int fsl_rpmsg_remove(struct platform_device *pdev)
+static void fsl_rpmsg_remove(struct platform_device *pdev)
{
struct fsl_rpmsg *rpmsg = platform_get_drvdata(pdev);
if (rpmsg->card_pdev)
platform_device_unregister(rpmsg->card_pdev);
-
- return 0;
}
#ifdef CONFIG_PM
static struct platform_driver fsl_rpmsg_driver = {
.probe = fsl_rpmsg_probe,
- .remove = fsl_rpmsg_remove,
+ .remove_new = fsl_rpmsg_remove,
.driver = {
.name = "fsl_rpmsg",
.pm = &fsl_rpmsg_pm_ops,
sai->cpu_dai_drv.symmetric_channels = 1;
sai->cpu_dai_drv.symmetric_sample_bits = 1;
- if (of_find_property(np, "fsl,sai-synchronous-rx", NULL) &&
- of_find_property(np, "fsl,sai-asynchronous", NULL)) {
+ if (of_property_read_bool(np, "fsl,sai-synchronous-rx") &&
+ of_property_read_bool(np, "fsl,sai-asynchronous")) {
/* error out if both synchronous and asynchronous are present */
dev_err(dev, "invalid binding for synchronous mode\n");
return -EINVAL;
}
- if (of_find_property(np, "fsl,sai-synchronous-rx", NULL)) {
+ if (of_property_read_bool(np, "fsl,sai-synchronous-rx")) {
/* Sync Rx with Tx */
sai->synchronous[RX] = false;
sai->synchronous[TX] = true;
- } else if (of_find_property(np, "fsl,sai-asynchronous", NULL)) {
+ } else if (of_property_read_bool(np, "fsl,sai-asynchronous")) {
/* Discard all settings for asynchronous mode */
sai->synchronous[RX] = false;
sai->synchronous[TX] = false;
sai->cpu_dai_drv.symmetric_sample_bits = 0;
}
- if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) &&
+ if (of_property_read_bool(np, "fsl,sai-mclk-direction-output") &&
of_device_is_compatible(np, "fsl,imx6ul-sai")) {
gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr");
if (IS_ERR(gpr)) {
dev_warn(dev, "Error reading SAI version: %d\n", ret);
/* Select MCLK direction */
- if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) &&
+ if (of_property_read_bool(np, "fsl,sai-mclk-direction-output") &&
sai->soc_data->max_register >= FSL_SAI_MCTL) {
regmap_update_bits(sai->regmap, FSL_SAI_MCTL,
FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
return ret;
}
-static int fsl_sai_remove(struct platform_device *pdev)
+static void fsl_sai_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
fsl_sai_runtime_suspend(&pdev->dev);
-
- return 0;
}
static const struct fsl_sai_soc_data fsl_sai_vf610_data = {
static struct platform_driver fsl_sai_driver = {
.probe = fsl_sai_probe,
- .remove = fsl_sai_remove,
+ .remove_new = fsl_sai_remove,
.driver = {
.name = "fsl-sai",
.pm = &fsl_sai_pm_ops,
return ret;
}
-static int fsl_spdif_remove(struct platform_device *pdev)
+static void fsl_spdif_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM
.pm = &fsl_spdif_pm,
},
.probe = fsl_spdif_probe,
- .remove = fsl_spdif_remove,
+ .remove_new = fsl_spdif_remove,
};
module_platform_driver(fsl_spdif_driver);
return -EINVAL;
}
strcpy(ssi->card_name, "ac97-codec");
- } else if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) {
+ } else if (!of_property_read_bool(np, "fsl,ssi-asynchronous")) {
/*
* In synchronous mode, STCK and STFS ports are used by RX
* as well. So the software should limit the sample rates,
return ret;
}
-static int fsl_ssi_remove(struct platform_device *pdev)
+static void fsl_ssi_remove(struct platform_device *pdev)
{
struct fsl_ssi *ssi = dev_get_drvdata(&pdev->dev);
snd_soc_set_ac97_ops(NULL);
mutex_destroy(&ssi->ac97_reg_lock);
}
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &fsl_ssi_pm,
},
.probe = fsl_ssi_probe,
- .remove = fsl_ssi_remove,
+ .remove_new = fsl_ssi_remove,
};
module_platform_driver(fsl_ssi_driver);
return ret;
}
-static int fsl_xcvr_remove(struct platform_device *pdev)
+static void fsl_xcvr_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
static __maybe_unused int fsl_xcvr_runtime_suspend(struct device *dev)
.pm = &fsl_xcvr_pm_ops,
.of_match_table = fsl_xcvr_dt_ids,
},
- .remove = fsl_xcvr_remove,
+ .remove_new = fsl_xcvr_remove,
};
module_platform_driver(fsl_xcvr_driver);
for (i = 0; i < num_dai; i++) {
struct snd_soc_dai_link_component *dlc;
- /* for CPU/Codec/Platform x 2 */
- dlc = devm_kcalloc(&pdev->dev, 6, sizeof(*dlc), GFP_KERNEL);
+ /* for CPU/Codec x 2 */
+ dlc = devm_kcalloc(&pdev->dev, 4, sizeof(*dlc), GFP_KERNEL);
if (!dlc)
return -ENOMEM;
dai_name, "CPU-Capture");
}
- priv->dai[i].cpus = &dlc[0];
- priv->dai[i].codecs = &dlc[1];
- priv->dai[i].platforms = &dlc[2];
+ /*
+ * CPU == Platform
+ * platform is using soc-generic-dmaengine-pcm
+ */
+ priv->dai[i].cpus =
+ priv->dai[i].platforms = &dlc[0];
+ priv->dai[i].codecs = &dlc[1];
priv->dai[i].num_cpus = 1;
priv->dai[i].num_codecs = 1;
priv->dai[i].codecs->name = "snd-soc-dummy";
priv->dai[i].cpus->of_node = args.np;
priv->dai[i].cpus->dai_name = dev_name(&cpu_pdev->dev);
- priv->dai[i].platforms->of_node = args.np;
priv->dai[i].dynamic = 1;
priv->dai[i].dpcm_playback = 1;
priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0);
be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL,
"AUDMIX-Capture-%d", i);
- priv->dai[num_dai + i].cpus = &dlc[3];
- priv->dai[num_dai + i].codecs = &dlc[4];
- priv->dai[num_dai + i].platforms = &dlc[5];
+ priv->dai[num_dai + i].cpus = &dlc[2];
+ priv->dai[num_dai + i].codecs = &dlc[3];
priv->dai[num_dai + i].num_cpus = 1;
priv->dai[num_dai + i].num_codecs = 1;
- priv->dai[num_dai + i].num_platforms = 1;
priv->dai[num_dai + i].name = be_name;
priv->dai[num_dai + i].codecs->dai_name = "snd-soc-dummy-dai";
priv->dai[num_dai + i].codecs->name = "snd-soc-dummy";
priv->dai[num_dai + i].cpus->of_node = audmix_np;
priv->dai[num_dai + i].cpus->dai_name = be_name;
- priv->dai[num_dai + i].platforms->name = "snd-soc-dummy";
priv->dai[num_dai + i].no_pcm = 1;
priv->dai[num_dai + i].dpcm_playback = 1;
priv->dai[num_dai + i].dpcm_capture = 1;
return 0;
}
-static int imx_audmux_remove(struct platform_device *pdev)
+static void imx_audmux_remove(struct platform_device *pdev)
{
if (audmux_type == IMX31_AUDMUX)
audmux_debugfs_remove();
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
static struct platform_driver imx_audmux_driver = {
.probe = imx_audmux_probe,
- .remove = imx_audmux_remove,
+ .remove_new = imx_audmux_remove,
.driver = {
.name = DRIVER_NAME,
.pm = &imx_audmux_pm,
link_data->cpu_sysclk_id = FSL_SAI_CLK_MAST1;
/* sai may support mclk/bclk = 1 */
- if (of_find_property(np, "fsl,mclk-equal-bclk", NULL)) {
+ if (of_property_read_bool(np, "fsl,mclk-equal-bclk")) {
link_data->one2one_ratio = true;
} else {
int i;
goto put_device;
}
- comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL);
+ comp = devm_kzalloc(dev, 2 * sizeof(*comp), GFP_KERNEL);
if (!comp) {
ret = -ENOMEM;
goto put_device;
data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0);
- data->dai.cpus = &comp[0];
+ /*
+ * CPU == Platform
+ * platform is using soc-generic-dmaengine-pcm
+ */
+ data->dai.cpus =
+ data->dai.platforms = &comp[0];
data->dai.codecs = &comp[1];
- data->dai.platforms = &comp[2];
data->dai.num_cpus = 1;
data->dai.num_codecs = 1;
data->dai.codecs->dai_name = "es8328-hifi-analog";
data->dai.codecs->of_node = codec_np;
data->dai.cpus->of_node = ssi_np;
- data->dai.platforms->of_node = ssi_np;
data->dai.init = &imx_es8328_dai_init;
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP;
return ret;
}
-static int imx_rpmsg_pcm_remove(struct platform_device *pdev)
+static void imx_rpmsg_pcm_remove(struct platform_device *pdev)
{
struct rpmsg_info *info = platform_get_drvdata(pdev);
if (info->rpmsg_wq)
destroy_workqueue(info->rpmsg_wq);
-
- return 0;
}
#ifdef CONFIG_PM
static struct platform_driver imx_pcm_rpmsg_driver = {
.probe = imx_rpmsg_pcm_probe,
- .remove = imx_rpmsg_pcm_remove,
+ .remove_new = imx_rpmsg_pcm_remove,
.driver = {
.name = IMX_PCM_DRV_NAME,
.pm = &imx_rpmsg_pcm_pm_ops,
return ret;
}
-static int imx_sgtl5000_remove(struct platform_device *pdev)
+static void imx_sgtl5000_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card);
clk_put(data->codec_clk);
-
- return 0;
}
static const struct of_device_id imx_sgtl5000_dt_ids[] = {
.of_match_table = imx_sgtl5000_dt_ids,
},
.probe = imx_sgtl5000_probe,
- .remove = imx_sgtl5000_remove,
+ .remove_new = imx_sgtl5000_remove,
};
module_platform_driver(imx_sgtl5000_driver);
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
- comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL);
+ comp = devm_kzalloc(&pdev->dev, 2 * sizeof(*comp), GFP_KERNEL);
if (!data || !comp) {
ret = -ENOMEM;
goto end;
}
- data->dai.cpus = &comp[0];
+ /*
+ * CPU == Platform
+ * platform is using soc-generic-dmaengine-pcm
+ */
+ data->dai.cpus =
+ data->dai.platforms = &comp[0];
data->dai.codecs = &comp[1];
- data->dai.platforms = &comp[2];
data->dai.num_cpus = 1;
data->dai.num_codecs = 1;
data->dai.codecs->dai_name = "snd-soc-dummy-dai";
data->dai.codecs->name = "snd-soc-dummy";
data->dai.cpus->of_node = spdif_np;
- data->dai.platforms->of_node = spdif_np;
data->dai.playback_only = true;
data->dai.capture_only = true;
return 0;
}
-static int psc_ac97_of_remove(struct platform_device *op)
+static void psc_ac97_of_remove(struct platform_device *op)
{
mpc5200_audio_dma_destroy(op);
snd_soc_unregister_component(&op->dev);
snd_soc_set_ac97_ops(NULL);
- return 0;
}
/* Match table for of_platform binding */
static struct platform_driver psc_ac97_driver = {
.probe = psc_ac97_of_probe,
- .remove = psc_ac97_of_remove,
+ .remove_new = psc_ac97_of_remove,
.driver = {
.name = "mpc5200-psc-ac97",
.of_match_table = psc_ac97_match,
}
-static int psc_i2s_of_remove(struct platform_device *op)
+static void psc_i2s_of_remove(struct platform_device *op)
{
mpc5200_audio_dma_destroy(op);
snd_soc_unregister_component(&op->dev);
- return 0;
}
/* Match table for of_platform binding */
static struct platform_driver psc_i2s_driver = {
.probe = psc_i2s_of_probe,
- .remove = psc_i2s_of_remove,
+ .remove_new = psc_i2s_of_remove,
.driver = {
.name = "mpc5200-psc-i2s",
.of_match_table = psc_i2s_match,
*
* This function is called when the platform device is removed.
*/
-static int mpc8610_hpcd_remove(struct platform_device *pdev)
+static void mpc8610_hpcd_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct mpc8610_hpcd_data *machine_data =
snd_soc_unregister_card(card);
kfree(machine_data);
-
- return 0;
}
static struct platform_driver mpc8610_hpcd_driver = {
.probe = mpc8610_hpcd_probe,
- .remove = mpc8610_hpcd_remove,
+ .remove_new = mpc8610_hpcd_remove,
.driver = {
/* The name must match 'compatible' property in the device tree,
* in lowercase letters.
*
* This function is called when the platform device is removed.
*/
-static int p1022_ds_remove(struct platform_device *pdev)
+static void p1022_ds_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct machine_data *mdata =
snd_soc_unregister_card(card);
kfree(mdata);
-
- return 0;
}
static struct platform_driver p1022_ds_driver = {
.probe = p1022_ds_probe,
- .remove = p1022_ds_remove,
+ .remove_new = p1022_ds_remove,
.driver = {
/*
* The name must match 'compatible' property in the device tree,
*
* This function is called when the platform device is removed.
*/
-static int p1022_rdk_remove(struct platform_device *pdev)
+static void p1022_rdk_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct machine_data *mdata =
snd_soc_unregister_card(card);
kfree(mdata);
-
- return 0;
}
static struct platform_driver p1022_rdk_driver = {
.probe = p1022_rdk_probe,
- .remove = p1022_rdk_remove,
+ .remove_new = p1022_rdk_remove,
.driver = {
/*
* The name must match 'compatible' property in the device tree,
}
-static int pcm030_fabric_remove(struct platform_device *op)
+static void pcm030_fabric_remove(struct platform_device *op)
{
struct pcm030_audio_data *pdata = platform_get_drvdata(op);
snd_soc_unregister_card(pdata->card);
platform_device_unregister(pdata->codec_device);
-
- return 0;
}
static const struct of_device_id pcm030_audio_match[] = {
static struct platform_driver pcm030_fabric_driver = {
.probe = pcm030_fabric_probe,
- .remove = pcm030_fabric_remove,
+ .remove_new = pcm030_fabric_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = pcm030_audio_match,
* only of_graph_parse_endpoint().
* We need to check "reg" property
*/
- if (of_get_property(ep, "reg", NULL))
+ if (of_property_present(ep, "reg"))
return info.id;
node = of_get_parent(ep);
return -EINVAL;
}
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
li->num[li->link].cpus = 1;
- li->num[li->link].codecs = 1;
li->num[li->link].platforms = 1;
+ li->num[li->link].codecs = 1;
+
li->link += 1; /* 1xCPU-Codec */
dev_dbg(dev, "Count As Normal\n");
}
if (li->cpu) {
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
li->num[li->link].cpus = 1;
li->num[li->link].platforms = 1;
simple_priv = &custom_priv->simple_priv;
simple_priv->ops = &custom_ops; /* customize dai_link ops */
+ /* "audio-graph-card2-custom-sample" is too long */
+ simple_priv->snd_card.name = "card2-custom";
+
/* use audio-graph-card2 parsing with own custom hooks */
ret = audio_graph2_parse_of(simple_priv, dev, &custom_hooks);
if (ret < 0)
* only of_graph_parse_endpoint().
* We need to check "reg" property
*/
- if (of_get_property(ep, "reg", NULL))
+ if (of_property_present(ep, "reg"))
return info.id;
node = of_get_parent(ep);
c2c_conf->channels_min =
c2c_conf->channels_max = 2; /* update ME */
- dai_link->params = c2c_conf;
- dai_link->num_params = 1;
+ dai_link->c2c_params = c2c_conf;
+ dai_link->num_c2c_params = 1;
}
ep0 = port_to_endpoint(port0);
* => lnk: port { endpoint { .. }; };
* };
*/
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
li->num[li->link].cpus =
li->num[li->link].platforms = graph_counter(cpu_port);
+
li->num[li->link].codecs = graph_counter(codec_port);
of_node_put(cpu_ep);
*/
if (asoc_graph_is_ports0(lnk)) {
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
li->num[li->link].cpus = graph_counter(rport); /* FE */
li->num[li->link].platforms = graph_counter(rport);
} else {
* };
* };
*/
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
li->num[li->link].cpus =
li->num[li->link].platforms = graph_counter(codec0);
+
li->num[li->link].codecs = graph_counter(codec1);
of_node_put(ports);
{
struct snd_soc_dai_link *dai_link = rtd->dai_link;
struct snd_soc_component *component;
- struct snd_soc_pcm_stream *params;
+ struct snd_soc_pcm_stream *c2c_params;
struct snd_pcm_hardware hw;
int i, ret, stream;
/* Do nothing if it already has Codec2Codec settings */
- if (dai_link->params)
+ if (dai_link->c2c_params)
return 0;
/* Do nothing if it was DPCM :: BE */
return ret;
}
- params = devm_kzalloc(rtd->dev, sizeof(*params), GFP_KERNEL);
- if (!params)
+ c2c_params = devm_kzalloc(rtd->dev, sizeof(*c2c_params), GFP_KERNEL);
+ if (!c2c_params)
return -ENOMEM;
- params->formats = hw.formats;
- params->rates = hw.rates;
- params->rate_min = hw.rate_min;
- params->rate_max = hw.rate_max;
- params->channels_min = hw.channels_min;
- params->channels_max = hw.channels_max;
+ c2c_params->formats = hw.formats;
+ c2c_params->rates = hw.rates;
+ c2c_params->rate_min = hw.rate_min;
+ c2c_params->rate_max = hw.rate_max;
+ c2c_params->channels_min = hw.channels_min;
+ c2c_params->channels_max = hw.channels_max;
- dai_link->params = params;
- dai_link->num_params = 1;
+ dai_link->c2c_params = c2c_params;
+ dai_link->num_c2c_params = 1;
return 0;
}
void asoc_simple_canonicalize_platform(struct snd_soc_dai_link_component *platforms,
struct snd_soc_dai_link_component *cpus)
{
- /* Assumes platform == cpu */
+ /*
+ * Assumes Platform == CPU
+ *
+ * Some CPU might be using soc-generic-dmaengine-pcm. This means CPU and Platform
+ * are different Component, but are sharing same component->dev.
+ *
+ * Let's assume Platform is same as CPU if it doesn't identify Platform on DT.
+ * see
+ * simple-card.c :: simple_count_noml()
+ */
if (!platforms->of_node)
platforms->of_node = cpus->of_node;
}
}
of_node_put(codec);
+ of_node_put(plat);
node = of_get_next_child(top, node);
} while (!is_top && node);
return -EINVAL;
}
+ /*
+ * DON'T REMOVE platforms
+ *
+ * Some CPU might be using soc-generic-dmaengine-pcm. This means CPU and Platform
+ * are different Component, but are sharing same component->dev.
+ * Simple Card had been supported it without special Platform selection.
+ * We need platforms here.
+ *
+ * In case of no Platform, it will be Platform == CPU, but Platform will be
+ * ignored by snd_soc_rtd_add_component().
+ *
+ * see
+ * simple-card-utils.c :: asoc_simple_canonicalize_platform()
+ */
li->num[li->link].cpus = 1;
- li->num[li->link].codecs = 1;
li->num[li->link].platforms = 1;
+ li->num[li->link].codecs = 1;
+
li->link += 1;
return 0;
}
if (li->cpu) {
+ /*
+ * DON'T REMOVE platforms
+ * see
+ * simple_count_noml()
+ */
li->num[li->link].cpus = 1;
li->num[li->link].platforms = 1;
return 0;
}
-static int test_driver_remove(struct platform_device *pdev)
+static void test_driver_remove(struct platform_device *pdev)
{
mile_stone_x(&pdev->dev);
-
- return 0;
}
static struct platform_driver test_driver = {
.of_match_table = test_of_match,
},
.probe = test_driver_probe,
- .remove = test_driver_remove,
+ .remove_new = test_driver_remove,
};
module_platform_driver(test_driver);
return ret;
}
-static int img_i2s_in_dev_remove(struct platform_device *pdev)
+static void img_i2s_in_dev_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_i2s_in_runtime_suspend(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &img_i2s_in_pm_ops
},
.probe = img_i2s_in_probe,
- .remove = img_i2s_in_dev_remove
+ .remove_new = img_i2s_in_dev_remove
};
module_platform_driver(img_i2s_in_driver);
return ret;
}
-static int img_i2s_out_dev_remove(struct platform_device *pdev)
+static void img_i2s_out_dev_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_i2s_out_runtime_suspend(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &img_i2s_out_pm_ops
},
.probe = img_i2s_out_probe,
- .remove = img_i2s_out_dev_remove
+ .remove_new = img_i2s_out_dev_remove
};
module_platform_driver(img_i2s_out_driver);
return ret;
}
-static int img_prl_out_dev_remove(struct platform_device *pdev)
+static void img_prl_out_dev_remove(struct platform_device *pdev)
{
struct img_prl_out *prl = platform_get_drvdata(pdev);
img_prl_out_suspend(&pdev->dev);
clk_disable_unprepare(prl->clk_sys);
-
- return 0;
}
static const struct of_device_id img_prl_out_of_match[] = {
.pm = &img_prl_out_pm_ops
},
.probe = img_prl_out_probe,
- .remove = img_prl_out_dev_remove
+ .remove_new = img_prl_out_dev_remove
};
module_platform_driver(img_prl_out_driver);
return ret;
}
-static int img_spdif_in_dev_remove(struct platform_device *pdev)
+static void img_spdif_in_dev_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_spdif_in_runtime_suspend(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &img_spdif_in_pm_ops
},
.probe = img_spdif_in_probe,
- .remove = img_spdif_in_dev_remove
+ .remove_new = img_spdif_in_dev_remove
};
module_platform_driver(img_spdif_in_driver);
return ret;
}
-static int img_spdif_out_dev_remove(struct platform_device *pdev)
+static void img_spdif_out_dev_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
img_spdif_out_runtime_suspend(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &img_spdif_out_pm_ops
},
.probe = img_spdif_out_probe,
- .remove = img_spdif_out_dev_remove
+ .remove_new = img_spdif_out_dev_remove
};
module_platform_driver(img_spdif_out_driver);
return ret;
}
-static int pistachio_internal_dac_remove(struct platform_device *pdev)
+static void pistachio_internal_dac_remove(struct platform_device *pdev)
{
struct pistachio_internal_dac *dac = dev_get_drvdata(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pistachio_internal_dac_pwr_off(dac);
regulator_disable(dac->supply);
-
- return 0;
}
#ifdef CONFIG_PM
.pm = &pistachio_internal_dac_pm_ops
},
.probe = pistachio_internal_dac_probe,
- .remove = pistachio_internal_dac_remove
+ .remove_new = pistachio_internal_dac_remove
};
module_platform_driver(pistachio_internal_dac_plat_driver);
return ret;
}
-static int sst_platform_remove(struct platform_device *pdev)
+static void sst_platform_remove(struct platform_device *pdev)
{
dev_dbg(&pdev->dev, "sst_platform_remove success\n");
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &sst_platform_pm,
},
.probe = sst_platform_probe,
- .remove = sst_platform_remove,
+ .remove_new = sst_platform_remove,
};
module_platform_driver(sst_platform_driver);
* This function is called by OS when a device is unloaded
* This frees the interrupt etc
*/
-static int sst_acpi_remove(struct platform_device *pdev)
+static void sst_acpi_remove(struct platform_device *pdev)
{
struct intel_sst_drv *ctx;
ctx = platform_get_drvdata(pdev);
sst_context_cleanup(ctx);
platform_set_drvdata(pdev, NULL);
- return 0;
}
static const struct acpi_device_id sst_acpi_ids[] = {
.pm = &intel_sst_pm,
},
.probe = sst_acpi_probe,
- .remove = sst_acpi_remove,
+ .remove_new = sst_acpi_remove,
};
module_platform_driver(sst_acpi_driver);
return ret;
}
- for (n = 0; n < pcm_count; n++) {
- ret = snd_soc_add_pcm_runtime(card, &links[n]);
- if (ret < 0) {
- dev_err(card->dev, "add links failed: %d\n", ret);
- return ret;
- }
+ ret = snd_soc_add_pcm_runtimes(card, links, pcm_count);
+ if (ret < 0) {
+ dev_err(card->dev, "add links failed: %d\n", ret);
+ return ret;
}
ret = avs_create_dapm_routes(card->dev, codec, pcm_count, &routes, &n);
int ret, i;
num_pins = tcfg->generic.num_input_pins + tcfg->generic.num_output_pins;
- cfg_size = sizeof(*cfg) + sizeof(*cfg->pin_fmts) * num_pins;
+ cfg_size = struct_size(cfg, pin_fmts, num_pins);
cfg = kzalloc(cfg_size, GFP_KERNEL);
if (!cfg)
return ret;
}
-static int snd_byt_cht_es8316_mc_remove(struct platform_device *pdev)
+static void snd_byt_cht_es8316_mc_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card);
gpiod_put(priv->speaker_en_gpio);
device_remove_software_node(priv->codec_dev);
put_device(priv->codec_dev);
- return 0;
}
static struct platform_driver snd_byt_cht_es8316_mc_driver = {
.name = "bytcht_es8316",
},
.probe = snd_byt_cht_es8316_mc_probe,
- .remove = snd_byt_cht_es8316_mc_remove,
+ .remove_new = snd_byt_cht_es8316_mc_remove,
};
module_platform_driver(snd_byt_cht_es8316_mc_driver);
return ret_val;
}
-static int snd_byt_rt5640_mc_remove(struct platform_device *pdev)
+static void snd_byt_rt5640_mc_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card);
device_remove_software_node(priv->codec_dev);
put_device(priv->codec_dev);
- return 0;
}
static struct platform_driver snd_byt_rt5640_mc_driver = {
.name = "bytcr_rt5640",
},
.probe = snd_byt_rt5640_mc_probe,
- .remove = snd_byt_rt5640_mc_remove,
+ .remove_new = snd_byt_rt5640_mc_remove,
};
module_platform_driver(snd_byt_rt5640_mc_driver);
return ret_val;
}
-static int snd_byt_rt5651_mc_remove(struct platform_device *pdev)
+static void snd_byt_rt5651_mc_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card);
device_remove_software_node(priv->codec_dev);
put_device(priv->codec_dev);
- return 0;
}
static struct platform_driver snd_byt_rt5651_mc_driver = {
.name = "bytcr_rt5651",
},
.probe = snd_byt_rt5651_mc_probe,
- .remove = snd_byt_rt5651_mc_remove,
+ .remove_new = snd_byt_rt5651_mc_remove,
};
module_platform_driver(snd_byt_rt5651_mc_driver);
return ret;
}
-static int snd_byt_wm5102_mc_remove(struct platform_device *pdev)
+static void snd_byt_wm5102_mc_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct byt_wm5102_private *priv = snd_soc_card_get_drvdata(card);
gpiod_put(priv->spkvdd_en_gpio);
- return 0;
}
static struct platform_driver snd_byt_wm5102_mc_driver = {
.name = "bytcr_wm5102",
},
.probe = snd_byt_wm5102_mc_probe,
- .remove = snd_byt_wm5102_mc_remove,
+ .remove_new = snd_byt_wm5102_mc_remove,
};
module_platform_driver(snd_byt_wm5102_mc_driver);
return ret_val;
}
-static int snd_cht_mc_remove(struct platform_device *pdev)
+static void snd_cht_mc_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card);
if (ctx->quirks & QUIRK_PMC_PLT_CLK_0)
clk_disable_unprepare(ctx->mclk);
-
- return 0;
}
static struct platform_driver snd_cht_mc_driver = {
.name = "cht-bsw-max98090",
},
.probe = snd_cht_mc_probe,
- .remove = snd_cht_mc_remove,
+ .remove_new = snd_cht_mc_remove,
};
module_platform_driver(snd_cht_mc_driver)
continue;
}
physdev = get_device(acpi_get_first_physical_node(adev));
+ acpi_dev_put(adev);
+ if (!physdev) {
+ pr_devel("Cannot find physical node for HID %s UID %u (%s)\n", CS35L41_HID,
+ uid, cs35l41_name_prefixes[uid]);
+ return 0;
+ }
cs35l41_components[sz].name = dev_name(physdev);
cs35l41_components[sz].dai_name = CS35L41_CODEC_DAI;
cs35l41_codec_conf[sz].dlc.name = dev_name(physdev);
cs35l41_codec_conf[sz].name_prefix = cs35l41_name_prefixes[uid];
- acpi_dev_put(adev);
sz++;
}
return ret;
}
-static int sof_es8336_remove(struct platform_device *pdev)
+static void sof_es8336_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card);
gpiod_put(priv->gpio_speakers);
device_remove_software_node(priv->codec_dev);
put_device(priv->codec_dev);
-
- return 0;
}
static const struct platform_device_id board_ids[] = {
.pm = &snd_soc_pm_ops,
},
.probe = sof_es8336_probe,
- .remove = sof_es8336_remove,
+ .remove_new = sof_es8336_remove,
.id_table = board_ids,
};
module_platform_driver(sof_es8336_driver);
&sof_audio_card_pcm512x);
}
-static int sof_pcm512x_remove(struct platform_device *pdev)
+static void sof_pcm512x_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct snd_soc_component *component;
break;
}
}
-
- return 0;
}
static struct platform_driver sof_audio = {
.probe = sof_audio_probe,
- .remove = sof_pcm512x_remove,
+ .remove_new = sof_pcm512x_remove,
.driver = {
.name = "sof_pcm512x",
.pm = &snd_soc_pm_ops,
SOF_SPEAKER_AMP_PRESENT |
SOF_MAX98360A_SPEAKER_AMP_PRESENT |
SOF_RT5682_SSP_AMP(0) |
- SOF_RT5682_NUM_HDMIDEV(4)
+ SOF_RT5682_NUM_HDMIDEV(4) |
+ SOF_BT_OFFLOAD_SSP(1) |
+ SOF_SSP_BT_OFFLOAD_PRESENT
),
},
{
SOF_RT5682_SSP_CODEC(2) |
SOF_SPEAKER_AMP_PRESENT |
SOF_RT5682_SSP_AMP(0) |
- SOF_RT5682_NUM_HDMIDEV(4)
+ SOF_RT5682_NUM_HDMIDEV(4) |
+ SOF_BT_OFFLOAD_SSP(1) |
+ SOF_SSP_BT_OFFLOAD_PRESENT
),
},
{}
}
links[id].init = max_98390_spk_codec_init;
links[id].ops = &max_98390_ops;
- links[id].dpcm_capture = 1;
} else {
max_98357a_dai_link(&links[id]);
SOF_SPEAKER_AMP_PRESENT |
SOF_RT1019_SPEAKER_AMP_PRESENT |
SOF_RT5682_SSP_AMP(1) |
- SOF_RT5682_NUM_HDMIDEV(4)),
+ SOF_RT5682_NUM_HDMIDEV(4) |
+ SOF_BT_OFFLOAD_SSP(2) |
+ SOF_SSP_BT_OFFLOAD_PRESENT),
+ },
+ {
+ .name = "rpl_mx98360_rt5682",
+ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN |
+ SOF_RT5682_SSP_CODEC(0) |
+ SOF_SPEAKER_AMP_PRESENT |
+ SOF_MAX98360A_SPEAKER_AMP_PRESENT |
+ SOF_RT5682_SSP_AMP(1) |
+ SOF_RT5682_NUM_HDMIDEV(4) |
+ SOF_BT_OFFLOAD_SSP(2) |
+ SOF_SSP_BT_OFFLOAD_PRESENT),
},
{
.name = "mtl_mx98357_rt5682",
SOF_RT5682_SSP_CODEC(0) |
SOF_SPEAKER_AMP_PRESENT |
SOF_RT5682_SSP_AMP(1) |
- SOF_RT5682_NUM_HDMIDEV(4)),
+ SOF_RT5682_NUM_HDMIDEV(4) |
+ SOF_BT_OFFLOAD_SSP(2) |
+ SOF_SSP_BT_OFFLOAD_PRESENT),
},
{
.name = "mtl_mx98360_rt5682",
.direction = {true, true},
.dai_name = "max98373-aif1",
.init = sof_sdw_mx8373_init,
- .codec_card_late_probe = sof_sdw_mx8373_late_probe,
.codec_type = SOF_SDW_CODEC_TYPE_AMP,
},
{
int stream;
u64 adr;
- adr = link->adr_d->adr;
- codec_index = find_codec_info_part(adr);
- if (codec_index < 0)
- return codec_index;
+ for (i = 0; i < link->num_adr; i++) {
+ adr = link->adr_d[i].adr;
+ codec_index = find_codec_info_part(adr);
+ if (codec_index < 0)
+ return codec_index;
- if (codec_info_list[codec_index].codec_type < _codec_type)
- dev_warn(dev,
- "Unexpected address table ordering. Expected order: jack -> amp -> mic\n");
+ if (codec_info_list[codec_index].codec_type < _codec_type)
+ dev_warn(dev,
+ "Unexpected address table ordering. Expected order: jack -> amp -> mic\n");
- _codec_type = codec_info_list[codec_index].codec_type;
+ _codec_type = codec_info_list[codec_index].codec_type;
- endpoint = link->adr_d->endpoints;
+ endpoint = link->adr_d[i].endpoints;
- /* count DAI number for playback and capture */
- for_each_pcm_streams(stream) {
- if (!codec_info_list[codec_index].direction[stream])
- continue;
+ /* count DAI number for playback and capture */
+ for_each_pcm_streams(stream) {
+ if (!codec_info_list[codec_index].direction[stream])
+ continue;
- (*sdw_cpu_dai_num)++;
+ (*sdw_cpu_dai_num)++;
- /* count BE for each non-aggregated slave or group */
- if (!endpoint->aggregated || no_aggregation ||
- !group_visited[endpoint->group_id])
- (*sdw_be_num)++;
- }
+ /* count BE for each non-aggregated slave or group */
+ if (!endpoint->aggregated || no_aggregation ||
+ !group_visited[endpoint->group_id])
+ (*sdw_be_num)++;
+ }
- if (endpoint->aggregated)
- group_visited[endpoint->group_id] = true;
+ if (endpoint->aggregated)
+ group_visited[endpoint->group_id] = true;
+ }
}
return 0;
int offset,
struct snd_soc_codec_conf *codec_conf,
int codec_count,
- int *codec_conf_index)
+ int *codec_conf_index,
+ int adr_index)
{
+ int _codec_index = -1;
int i;
/* sanity check */
- if (*codec_conf_index + link->num_adr > codec_count) {
+ if (*codec_conf_index + link->num_adr - adr_index > codec_count) {
dev_err(dev, "codec_conf: out-of-bounds access requested\n");
return -EINVAL;
}
- for (i = 0; i < link->num_adr; i++) {
+ for (i = adr_index; i < link->num_adr; i++) {
unsigned int sdw_version, unique_id, mfg_id;
unsigned int link_id, part_id, class_id;
int codec_index, comp_index;
part_id = SDW_PART_ID(adr);
class_id = SDW_CLASS_ID(adr);
- comp_index = i + offset;
+ comp_index = i - adr_index + offset;
if (is_unique_device(link, sdw_version, mfg_id, part_id,
class_id, i)) {
codec_str = "sdw:%01x:%04x:%04x:%02x";
codec_index = find_codec_info_part(adr);
if (codec_index < 0)
return codec_index;
+ if (_codec_index != -1 && codec_index != _codec_index) {
+ dev_dbg(dev, "Different devices on the same sdw link\n");
+ break;
+ }
+ _codec_index = codec_index;
codec[comp_index].dai_name =
codec_info_list[codec_index].dai_name;
static int get_slave_info(const struct snd_soc_acpi_link_adr *adr_link,
struct device *dev, int *cpu_dai_id, int *cpu_dai_num,
int *codec_num, unsigned int *group_id,
- bool *group_generated)
+ bool *group_generated, int adr_index)
{
const struct snd_soc_acpi_adr_device *adr_d;
const struct snd_soc_acpi_link_adr *adr_next;
bool no_aggregation;
int index = 0;
+ int i;
no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION;
- *codec_num = adr_link->num_adr;
- adr_d = adr_link->adr_d;
+ adr_d = &adr_link->adr_d[adr_index];
/* make sure the link mask has a single bit set */
if (!is_power_of_2(adr_link->mask))
cpu_dai_id[index++] = ffs(adr_link->mask) - 1;
if (!adr_d->endpoints->aggregated || no_aggregation) {
*cpu_dai_num = 1;
+ *codec_num = 1;
*group_id = 0;
return 0;
}
*group_id = adr_d->endpoints->group_id;
+ /* Count endpoints with the same group_id in the adr_link */
+ *codec_num = 0;
+ for (i = 0; i < adr_link->num_adr; i++) {
+ if (adr_link->adr_d[i].endpoints->aggregated &&
+ adr_link->adr_d[i].endpoints->group_id == *group_id)
+ (*codec_num)++;
+ }
+
/* gather other link ID of slaves in the same group */
for (adr_next = adr_link + 1; adr_next && adr_next->num_adr;
adr_next++) {
}
cpu_dai_id[index++] = ffs(adr_next->mask) - 1;
- *codec_num += adr_next->num_adr;
+ for (i = 0; i < adr_next->num_adr; i++) {
+ if (adr_next->adr_d[i].endpoints->aggregated &&
+ adr_next->adr_d[i].endpoints->group_id == *group_id)
+ (*codec_num)++;
+ }
}
/*
return 0;
}
+static const char * const type_strings[] = {"SimpleJack", "SmartAmp", "SmartMic"};
+
static int create_sdw_dailink(struct snd_soc_card *card,
struct device *dev, int *link_index,
struct snd_soc_dai_link *dai_links,
struct snd_soc_codec_conf *codec_conf,
int codec_count, int *link_id,
int *codec_conf_index,
- bool *ignore_pch_dmic)
+ bool *ignore_pch_dmic,
+ bool append_codec_type,
+ int adr_index)
{
const struct snd_soc_acpi_link_adr *link_next;
struct snd_soc_dai_link_component *codecs;
int k;
ret = get_slave_info(link, dev, cpu_dai_id, &cpu_dai_num, &codec_num,
- &group_id, group_generated);
+ &group_id, group_generated, adr_index);
if (ret)
return ret;
continue;
ret = create_codec_dai_name(dev, link_next, codecs, codec_idx,
- codec_conf, codec_count, codec_conf_index);
+ codec_conf, codec_count, codec_conf_index, adr_index);
if (ret < 0)
return ret;
}
/* find codec info to create BE DAI */
- codec_index = find_codec_info_part(link->adr_d[0].adr);
+ codec_index = find_codec_info_part(link->adr_d[adr_index].adr);
if (codec_index < 0)
return codec_index;
static const char * const sdw_stream_name[] = {
"SDW%d-Playback",
"SDW%d-Capture",
+ "SDW%d-Playback-%s",
+ "SDW%d-Capture-%s",
};
if (!codec_info_list[codec_index].direction[stream])
continue;
/* create stream name according to first link id */
- name = devm_kasprintf(dev, GFP_KERNEL,
- sdw_stream_name[stream], cpu_dai_id[0]);
+ if (append_codec_type) {
+ name = devm_kasprintf(dev, GFP_KERNEL,
+ sdw_stream_name[stream + 2], cpu_dai_id[0],
+ type_strings[codec_info_list[codec_index].codec_type]);
+ } else {
+ name = devm_kasprintf(dev, GFP_KERNEL,
+ sdw_stream_name[stream], cpu_dai_id[0]);
+ }
if (!name)
return -ENOMEM;
const struct snd_soc_acpi_link_adr *adr_link;
struct snd_soc_dai_link_component *cpus;
struct snd_soc_codec_conf *codec_conf;
+ bool append_codec_type = false;
bool ignore_pch_dmic = false;
int codec_conf_count;
int codec_conf_index = 0;
for (i = 0; i < SDW_MAX_GROUPS; i++)
group_generated[i] = false;
- /* generate DAI links by each sdw link */
for (; adr_link->num_adr; adr_link++) {
- const struct snd_soc_acpi_endpoint *endpoint;
-
- endpoint = adr_link->adr_d->endpoints;
- if (endpoint->aggregated && !endpoint->group_id) {
- dev_err(dev, "invalid group id on link %x",
- adr_link->mask);
- continue;
+ /*
+ * If there are two or more different devices on the same sdw link, we have to
+ * append the codec type to the dai link name to prevent duplicated dai link name.
+ * The same type devices on the same sdw link will be in the same
+ * snd_soc_acpi_adr_device array. They won't be described in different adr_links.
+ */
+ for (i = 0; i < adr_link->num_adr; i++) {
+ for (j = 0; j < i; j++) {
+ if ((SDW_PART_ID(adr_link->adr_d[i].adr) !=
+ SDW_PART_ID(adr_link->adr_d[j].adr)) ||
+ (SDW_MFG_ID(adr_link->adr_d[i].adr) !=
+ SDW_MFG_ID(adr_link->adr_d[i].adr))) {
+ append_codec_type = true;
+ goto out;
+ }
+ }
}
+ }
+out:
- /* this group has been generated */
- if (endpoint->aggregated &&
- group_generated[endpoint->group_id])
- continue;
+ /* generate DAI links by each sdw link */
+ for (adr_link = mach_params->links ; adr_link->num_adr; adr_link++) {
+ for (i = 0; i < adr_link->num_adr; i++) {
+ const struct snd_soc_acpi_endpoint *endpoint;
- ret = create_sdw_dailink(card, dev, &link_index, links, sdw_be_num,
- sdw_cpu_dai_num, cpus, adr_link,
- &cpu_id, group_generated,
- codec_conf, codec_conf_count,
- &be_id, &codec_conf_index,
- &ignore_pch_dmic);
- if (ret < 0) {
- dev_err(dev, "failed to create dai link %d", link_index);
- return ret;
+ endpoint = adr_link->adr_d[i].endpoints;
+ if (endpoint->aggregated && !endpoint->group_id) {
+ dev_err(dev, "invalid group id on link %x",
+ adr_link->mask);
+ continue;
+ }
+
+ /* this group has been generated */
+ if (endpoint->aggregated &&
+ group_generated[endpoint->group_id])
+ continue;
+
+ ret = create_sdw_dailink(card, dev, &link_index, links, sdw_be_num,
+ sdw_cpu_dai_num, cpus, adr_link,
+ &cpu_id, group_generated,
+ codec_conf, codec_conf_count,
+ &be_id, &codec_conf_index,
+ &ignore_pch_dmic, append_codec_type, i);
+ if (ret < 0) {
+ dev_err(dev, "failed to create dai link %d", link_index);
+ return ret;
+ }
}
}
int i;
for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) {
- if (!codec_info_list[i].late_probe)
- continue;
+ if (codec_info_list[i].codec_card_late_probe) {
+ ret = codec_info_list[i].codec_card_late_probe(card);
- ret = codec_info_list[i].codec_card_late_probe(card);
- if (ret < 0)
- return ret;
+ if (ret < 0)
+ return ret;
+ }
}
if (ctx->idisp_codec)
return ret;
}
-static int mc_remove(struct platform_device *pdev)
+static void mc_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
mc_dailink_exit_loop(card);
-
- return 0;
}
static struct platform_driver sof_sdw_driver = {
.pm = &snd_soc_pm_ops,
},
.probe = mc_probe,
- .remove = mc_remove,
+ .remove_new = mc_remove,
};
module_platform_driver(sof_sdw_driver);
bool playback);
int (*exit)(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link);
- bool late_probe;
int (*codec_card_late_probe)(struct snd_soc_card *card);
};
struct sof_sdw_codec_info *info,
bool playback);
-int sof_sdw_mx8373_late_probe(struct snd_soc_card *card);
-
/* RT5682 support */
int sof_sdw_rt5682_init(struct snd_soc_card *card,
const struct snd_soc_acpi_link_adr *link,
.shutdown = sdw_shutdown,
};
+static int mx8373_sdw_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_dapm_context *dapm = &card->dapm;
+
+ /* Disable Left and Right Spk pin after boot */
+ snd_soc_dapm_disable_pin(dapm, "Left Spk");
+ snd_soc_dapm_disable_pin(dapm, "Right Spk");
+ return snd_soc_dapm_sync(dapm);
+}
+
int sof_sdw_mx8373_init(struct snd_soc_card *card,
const struct snd_soc_acpi_link_adr *link,
struct snd_soc_dai_link *dai_links,
if (info->amp_num == 2)
dai_links->init = spk_init;
- info->late_probe = true;
+ info->codec_card_late_probe = mx8373_sdw_late_probe;
dai_links->ops = &max_98373_sdw_ops;
return 0;
}
-
-int sof_sdw_mx8373_late_probe(struct snd_soc_card *card)
-{
- struct snd_soc_dapm_context *dapm = &card->dapm;
-
- /* Disable Left and Right Spk pin after boot */
- snd_soc_dapm_disable_pin(dapm, "Left Spk");
- snd_soc_dapm_disable_pin(dapm, "Right Spk");
- return snd_soc_dapm_sync(dapm);
-}
return devm_snd_soc_register_card(&pdev->dev, card);
}
-static int sof_wm8804_remove(struct platform_device *pdev)
+static void sof_wm8804_remove(struct platform_device *pdev)
{
if (sof_wm8804_quirk & SOF_WM8804_UP2_QUIRK)
gpiod_remove_lookup_table(&up2_gpios_table);
- return 0;
}
static struct platform_driver sof_wm8804_driver = {
.pm = &snd_soc_pm_ops,
},
.probe = sof_wm8804_probe,
- .remove = sof_wm8804_remove,
+ .remove_new = sof_wm8804_remove,
};
module_platform_driver(sof_wm8804_driver);
return catpt_probe_components(cdev);
}
-static int catpt_acpi_remove(struct platform_device *pdev)
+static void catpt_acpi_remove(struct platform_device *pdev)
{
struct catpt_dev *cdev = platform_get_drvdata(pdev);
catpt_sram_free(&cdev->iram);
catpt_sram_free(&cdev->dram);
-
- return 0;
}
static struct snd_soc_acpi_mach lpt_machines[] = {
static struct platform_driver catpt_acpi_driver = {
.probe = catpt_acpi_probe,
- .remove = catpt_acpi_remove,
+ .remove_new = catpt_acpi_remove,
.driver = {
.name = "intel_catpt",
.acpi_match_table = catpt_ids,
{}
};
+static const struct snd_soc_acpi_link_adr rplp_crb[] = {
+ {
+ .mask = BIT(2),
+ .num_adr = ARRAY_SIZE(rt711_sdca_2_adr),
+ .adr_d = rt711_sdca_2_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_codecs rpl_rt5682_hp = {
+ .num_codecs = 2,
+ .codecs = {"10EC5682", "RTL5682"},
+};
+
+static const struct snd_soc_acpi_codecs rpl_max98360a_amp = {
+ .num_codecs = 1,
+ .codecs = {"MX98360A"},
+};
+
struct snd_soc_acpi_mach snd_soc_acpi_intel_rpl_machines[] = {
+ {
+ .comp_ids = &rpl_rt5682_hp,
+ .drv_name = "rpl_mx98360_rt5682",
+ .machine_quirk = snd_soc_acpi_codec_list,
+ .quirk_data = &rpl_max98360a_amp,
+ .sof_tplg_filename = "sof-rpl-max98360a-rt5682.tplg",
+ },
{},
};
EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_rpl_machines);
.link_mask = 0x1, /* link0 required */
.links = rpl_rvp,
.drv_name = "sof_sdw",
- .sof_tplg_filename = "sof-rpl-rt711.tplg",
+ .sof_tplg_filename = "sof-rpl-rt711-l0.tplg",
+ },
+ {
+ .link_mask = 0x4, /* link2 required */
+ .links = rplp_crb,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-rpl-rt711-l2.tplg",
},
{},
};
return ret;
}
-static int skl_clk_dev_remove(struct platform_device *pdev)
+static void skl_clk_dev_remove(struct platform_device *pdev)
{
struct skl_clk_data *data;
data = platform_get_drvdata(pdev);
unregister_src_clk(data);
unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC);
-
- return 0;
}
static struct platform_driver skl_clk_driver = {
.name = "skl-ssp-clk",
},
.probe = skl_clk_dev_probe,
- .remove = skl_clk_dev_remove,
+ .remove_new = skl_clk_dev_remove,
};
module_platform_driver(skl_clk_driver);
return err;
}
-static int kirkwood_i2s_dev_remove(struct platform_device *pdev)
+static void kirkwood_i2s_dev_remove(struct platform_device *pdev)
{
struct kirkwood_dma_data *priv = dev_get_drvdata(&pdev->dev);
if (!IS_ERR(priv->extclk))
clk_disable_unprepare(priv->extclk);
clk_disable_unprepare(priv->clk);
-
- return 0;
}
#ifdef CONFIG_OF
static struct platform_driver kirkwood_i2s_driver = {
.probe = kirkwood_i2s_dev_probe,
- .remove = kirkwood_i2s_dev_remove,
+ .remove_new = kirkwood_i2s_dev_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(mvebu_audio_of_match),
return ret;
}
-static int mtk_btcvsd_snd_remove(struct platform_device *pdev)
+static void mtk_btcvsd_snd_remove(struct platform_device *pdev)
{
struct mtk_btcvsd_snd *btcvsd = dev_get_drvdata(&pdev->dev);
iounmap(btcvsd->bt_pkv_base);
iounmap(btcvsd->bt_sram_bank2_base);
- return 0;
}
static const struct of_device_id mtk_btcvsd_snd_dt_match[] = {
.of_match_table = mtk_btcvsd_snd_dt_match,
},
.probe = mtk_btcvsd_snd_probe,
- .remove = mtk_btcvsd_snd_remove,
+ .remove_new = mtk_btcvsd_snd_remove,
};
module_platform_driver(mtk_btcvsd_snd_driver);
/* Loop over all the dai link sub nodes */
for_each_available_child_of_node(dev->of_node, sub_node) {
if (of_property_read_string(sub_node, "link-name",
- &dai_link_name))
+ &dai_link_name)) {
+ of_node_put(sub_node);
return -EINVAL;
+ }
for_each_card_prelinks(card, i, dai_link) {
if (!strcmp(dai_link_name, dai_link->name))
break;
}
- if (i >= card->num_links)
+ if (i >= card->num_links) {
+ of_node_put(sub_node);
return -EINVAL;
+ }
ret = set_card_codec_info(card, sub_node, dai_link);
- if (ret < 0)
+ if (ret < 0) {
+ of_node_put(sub_node);
return ret;
+ }
}
return 0;
return ret;
}
-static int mt2701_afe_pcm_dev_remove(struct platform_device *pdev)
+static void mt2701_afe_pcm_dev_remove(struct platform_device *pdev)
{
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
mt2701_afe_runtime_suspend(&pdev->dev);
-
- return 0;
}
static const struct mt2701_soc_variants mt2701_soc_v1 = {
.pm = &mt2701_afe_pm_ops,
},
.probe = mt2701_afe_pcm_dev_probe,
- .remove = mt2701_afe_pcm_dev_remove,
+ .remove_new = mt2701_afe_pcm_dev_remove,
};
module_platform_driver(mt2701_afe_pcm_driver);
return ret;
}
-static int mt6797_afe_pcm_dev_remove(struct platform_device *pdev)
+static void mt6797_afe_pcm_dev_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
mt6797_afe_runtime_suspend(&pdev->dev);
pm_runtime_put_sync(&pdev->dev);
-
- return 0;
}
static const struct of_device_id mt6797_afe_pcm_dt_match[] = {
.pm = &mt6797_afe_pm_ops,
},
.probe = mt6797_afe_pcm_dev_probe,
- .remove = mt6797_afe_pcm_dev_remove,
+ .remove_new = mt6797_afe_pcm_dev_remove,
};
module_platform_driver(mt6797_afe_pcm_driver);
return ret;
}
-static int mt8173_afe_pcm_dev_remove(struct platform_device *pdev)
+static void mt8173_afe_pcm_dev_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
mt8173_afe_runtime_suspend(&pdev->dev);
- return 0;
}
static const struct of_device_id mt8173_afe_pcm_dt_match[] = {
.pm = &mt8173_afe_pm_ops,
},
.probe = mt8173_afe_pcm_dev_probe,
- .remove = mt8173_afe_pcm_dev_remove,
+ .remove_new = mt8173_afe_pcm_dev_remove,
};
module_platform_driver(mt8173_afe_pcm_driver);
return ret;
}
-static int mt8183_afe_pcm_dev_remove(struct platform_device *pdev)
+static void mt8183_afe_pcm_dev_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
mt8183_afe_runtime_suspend(&pdev->dev);
-
- return 0;
}
static const struct of_device_id mt8183_afe_pcm_dt_match[] = {
.pm = &mt8183_afe_pm_ops,
},
.probe = mt8183_afe_pcm_dev_probe,
- .remove = mt8183_afe_pcm_dev_remove,
+ .remove_new = mt8183_afe_pcm_dev_remove,
};
module_platform_driver(mt8183_afe_pcm_driver);
#include "mt8186-afe-common.h"
#include "mt8186-afe-gpio.h"
-struct pinctrl *aud_pinctrl;
+static struct pinctrl *aud_pinctrl;
enum mt8186_afe_gpio {
MT8186_AFE_GPIO_CLK_MOSI_OFF,
aud_gpios[i].name);
if (IS_ERR(aud_gpios[i].gpioctrl)) {
ret = PTR_ERR(aud_gpios[i].gpioctrl);
- dev_info(dev, "%s(), pinctrl_lookup_state %s fail, ret %d\n",
+ dev_dbg(dev, "%s(), pinctrl_lookup_state %s fail, ret %d\n",
__func__, aud_gpios[i].name, ret);
} else {
aud_gpios[i].gpio_prepare = true;
int ret = 0;
if (type < 0 || type >= MT8186_AFE_GPIO_GPIO_NUM) {
- dev_err(dev, "%s(), error, invalid gpio type %d\n",
+ dev_dbg(dev, "%s(), error, invalid gpio type %d\n",
__func__, type);
return -EINVAL;
}
if (!aud_gpios[type].gpio_prepare) {
- dev_err(dev, "%s(), error, gpio type %d not prepared\n",
+ dev_dbg(dev, "%s(), error, gpio type %d not prepared\n",
__func__, type);
return -EIO;
}
ret = pinctrl_select_state(aud_pinctrl,
aud_gpios[type].gpioctrl);
if (ret) {
- dev_err(dev, "%s(), error, can not set gpio type %d\n",
+ dev_dbg(dev, "%s(), error, can not set gpio type %d\n",
__func__, type);
return ret;
}
if (enable) {
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MOSI_ON);
if (ret) {
- dev_err(dev, "%s(), MOSI CLK ON select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MOSI CLK ON select fail!\n", __func__);
return ret;
}
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MOSI_ON);
if (ret) {
- dev_err(dev, "%s(), MOSI DAT ON select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MOSI DAT ON select fail!\n", __func__);
return ret;
}
} else {
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MOSI_OFF);
if (ret) {
- dev_err(dev, "%s(), MOSI DAT OFF select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MOSI DAT OFF select fail!\n", __func__);
return ret;
}
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MOSI_OFF);
if (ret) {
- dev_err(dev, "%s(), MOSI CLK ON select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MOSI CLK ON select fail!\n", __func__);
return ret;
}
}
if (enable) {
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MISO_ON);
if (ret) {
- dev_err(dev, "%s(), MISO CLK ON select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MISO CLK ON select fail!\n", __func__);
return ret;
}
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MISO_ON);
if (ret) {
- dev_err(dev, "%s(), MISO DAT ON select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MISO DAT ON select fail!\n", __func__);
return ret;
}
} else {
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MISO_OFF);
if (ret) {
- dev_err(dev, "%s(), MISO DAT OFF select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MISO DAT OFF select fail!\n", __func__);
return ret;
}
ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MISO_OFF);
if (ret) {
- dev_err(dev, "%s(), MISO CLK OFF select fail!\n", __func__);
+ dev_dbg(dev, "%s(), MISO CLK OFF select fail!\n", __func__);
return ret;
}
}
sel = enable ? MT8186_AFE_GPIO_PCM_ON : MT8186_AFE_GPIO_PCM_OFF;
break;
default:
- dev_err(dev, "%s(), invalid dai %d\n", __func__, dai);
+ dev_dbg(dev, "%s(), invalid dai %d\n", __func__, dai);
goto unlock;
}
case 192000:
return MTK_AFE_ADDA_DL_RATE_192K;
default:
- dev_info(afe->dev, "%s(), rate %d invalid, use 48kHz!!!\n",
+ dev_dbg(afe->dev, "%s(), rate %d invalid, use 48kHz!!!\n",
__func__, rate);
}
case 192000:
return MTK_AFE_ADDA_UL_RATE_192K;
default:
- dev_info(afe->dev, "%s(), rate %d invalid, use 48kHz!!!\n",
+ dev_dbg(afe->dev, "%s(), rate %d invalid, use 48kHz!!!\n",
__func__, rate);
}
card->name = card->topology_shortname;
sof_on = 1;
} else {
- dev_info(&pdev->dev, "Probe without adsp\n");
+ dev_dbg(&pdev->dev, "Probe without adsp\n");
}
if (of_property_read_bool(pdev->dev.of_node, "mediatek,dai-link")) {
priv->dmic_switch = ucontrol->value.integer.value[0];
if (priv->dmic_sel) {
gpiod_set_value(priv->dmic_sel, priv->dmic_switch);
- dev_info(dapm->card->dev, "dmic_set_value %d\n",
+ dev_dbg(dapm->card->dev, "dmic_set_value %d\n",
priv->dmic_switch);
}
return 0;
}
if (!priv->dmic_sel) {
- dev_info(card->dev, "dmic_sel is null\n");
+ dev_dbg(card->dev, "dmic_sel is null\n");
return 0;
}
card->name = card->topology_shortname;
sof_on = 1;
} else {
- dev_info(&pdev->dev, "Probe without adsp\n");
+ dev_dbg(&pdev->dev, "Probe without adsp\n");
}
if (of_property_read_bool(pdev->dev.of_node, "mediatek,dai-link")) {
return ret;
}
-static int mt8188_afe_pcm_dev_remove(struct platform_device *pdev)
+static void mt8188_afe_pcm_dev_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
-
- return 0;
}
static const struct of_device_id mt8188_afe_pcm_dt_match[] = {
.pm = &mt8188_afe_pm_ops,
},
.probe = mt8188_afe_pcm_dev_probe,
- .remove = mt8188_afe_pcm_dev_remove,
+ .remove_new = mt8188_afe_pcm_dev_remove,
};
module_platform_driver(mt8188_afe_pcm_driver);
struct mt8192_afe_private *afe_priv = afe->platform_priv;
int ret;
- dev_info(afe->dev, "%s()\n", __func__);
-
ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_SYS_AUDIO]);
if (ret) {
dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n",
{
struct mt8192_afe_private *afe_priv = afe->platform_priv;
- dev_info(afe->dev, "%s()\n", __func__);
-
clk_disable_unprepare(afe_priv->clk[CLK_AFE]);
mt8192_set_audio_int_bus_parent(afe, CLK_CLK26M);
clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIOINTBUS]);
unsigned int reg_shift;
unsigned int reg_mask_shift;
- dev_info(afe->dev, "%s(), event 0x%x\n", __func__, event);
+ dev_dbg(afe->dev, "%s(), event 0x%x\n", __func__, event);
if (strstr(w->name, "UL1")) {
reg_shift = VUL1_USE_TINY_SFT;
unsigned int value;
int ret;
- dev_info(afe->dev, "%s()\n", __func__);
-
if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl)
goto skip_regmap;
struct mt8192_afe_private *afe_priv = afe->platform_priv;
int ret;
- dev_info(afe->dev, "%s()\n", __func__);
-
ret = mt8192_afe_enable_clock(afe);
if (ret)
return ret;
return ret;
}
-static int mt8192_afe_pcm_dev_remove(struct platform_device *pdev)
+static void mt8192_afe_pcm_dev_remove(struct platform_device *pdev)
{
struct mtk_base_afe *afe = platform_get_drvdata(pdev);
/* disable afe clock */
mt8192_afe_disable_clock(afe);
- return 0;
}
static const struct of_device_id mt8192_afe_pcm_dt_match[] = {
.pm = &mt8192_afe_pm_ops,
},
.probe = mt8192_afe_pcm_dev_probe,
- .remove = mt8192_afe_pcm_dev_remove,
+ .remove_new = mt8192_afe_pcm_dev_remove,
};
module_platform_driver(mt8192_afe_pcm_driver);
return -EINVAL;
}
- dev_info(cmpnt->dev, "%s(), name %s, event 0x%x\n",
- __func__, w->name, event);
+ dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n",
+ __func__, w->name, event);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
return -EINVAL;
}
- dev_info(cmpnt->dev, "%s(), name %s, event 0x%x, dai_id %d\n",
- __func__, w->name, event, dai_id);
+ dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x, dai_id %d\n",
+ __func__, w->name, event, dai_id);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
return -EINVAL;
}
- dev_info(cmpnt->dev, "%s(), name %s, event 0x%x, dai_id %d\n",
- __func__, w->name, event, dai_id);
+ dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x, dai_id %d\n",
+ __func__, w->name, event, dai_id);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
if (tdm_priv->mclk_rate % tdm_priv->bck_rate != 0)
dev_warn(afe->dev, "%s(), bck cannot generate", __func__);
- dev_info(afe->dev, "%s(), id %d, rate %d, channels %d, format %d, mclk_rate %d, bck_rate %d\n",
- __func__,
- tdm_id, rate, channels, format,
- tdm_priv->mclk_rate, tdm_priv->bck_rate);
+ dev_dbg(afe->dev, "%s(), id %d, rate %d, channels %d, format %d, mclk_rate %d, bck_rate %d\n",
+ __func__,
+ tdm_id, rate, channels, format,
+ tdm_priv->mclk_rate, tdm_priv->bck_rate);
- dev_info(afe->dev, "%s(), out_channels_per_sdata = %d\n",
- __func__, out_channels_per_sdata);
+ dev_dbg(afe->dev, "%s(), out_channels_per_sdata = %d\n",
+ __func__, out_channels_per_sdata);
/* set tdm */
if (tdm_priv->bck_invert)
return -EINVAL;
}
- dev_info(afe->dev, "%s(), freq %d\n", __func__, freq);
+ dev_dbg(afe->dev, "%s(), freq %d\n", __func__, freq);
return mtk_dai_tdm_cal_mclk(afe, tdm_priv, freq);
}
struct mtk_afe_tdm_priv *tdm_priv;
struct mtk_base_afe_dai *dai;
- dev_info(afe->dev, "%s()\n", __func__);
-
dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL);
if (!dai)
return -ENOMEM;
int counter;
int mtkaif_calib_ok;
- dev_info(afe->dev, "%s(), start\n", __func__);
-
pm_runtime_get_sync(afe->dev);
mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1);
mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0);
mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 0);
pm_runtime_put(afe->dev);
- dev_info(afe->dev, "%s(), mtkaif_chosen_phase[0/1/2]:%d/%d/%d\n",
- __func__,
- afe_priv->mtkaif_chosen_phase[0],
- afe_priv->mtkaif_chosen_phase[1],
- afe_priv->mtkaif_chosen_phase[2]);
+ dev_dbg(afe->dev, "%s(), mtkaif_chosen_phase[0/1/2]:%d/%d/%d\n",
+ __func__,
+ afe_priv->mtkaif_chosen_phase[0],
+ afe_priv->mtkaif_chosen_phase[1],
+ afe_priv->mtkaif_chosen_phase[2]);
return 0;
}
return ret;
}
-static int mt8195_afe_pcm_dev_remove(struct platform_device *pdev)
+static void mt8195_afe_pcm_dev_remove(struct platform_device *pdev)
{
struct mtk_base_afe *afe = platform_get_drvdata(pdev);
mt8195_afe_runtime_suspend(&pdev->dev);
mt8195_afe_deinit_clock(afe);
- return 0;
}
static const struct of_device_id mt8195_afe_pcm_dt_match[] = {
.pm = &mt8195_afe_pm_ops,
},
.probe = mt8195_afe_pcm_dev_probe,
- .remove = mt8195_afe_pcm_dev_remove,
+ .remove_new = mt8195_afe_pcm_dev_remove,
};
module_platform_driver(mt8195_afe_pcm_driver);
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_adda_priv *adda_priv = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_adda_priv *adda_priv;
unsigned int rate = params_rate(params);
- int id = dai->id;
- int ret = 0;
+ int ret;
+
+ if (dai->id != MT8195_AFE_IO_DL_SRC &&
+ dai->id != MT8195_AFE_IO_UL_SRC1 &&
+ dai->id != MT8195_AFE_IO_UL_SRC2)
+ return -EINVAL;
+ adda_priv = afe_priv->dai_priv[dai->id];
dev_dbg(afe->dev, "%s(), id %d, stream %d, rate %d\n",
- __func__, id, substream->stream, rate);
+ __func__, dai->id, substream->stream, rate);
if (rate > ADDA_HIRES_THRES)
adda_priv->hires_required = 1;
adda_priv->hires_required = 0;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- ret = mtk_dai_da_configure(afe, rate, id);
+ ret = mtk_dai_da_configure(afe, rate, dai->id);
else
- ret = mtk_dai_ad_configure(afe, rate, id);
+ ret = mtk_dai_ad_configure(afe, rate, dai->id);
return ret;
}
{ .rate = 352800, .reg_value = 21, },
};
+static bool mt8195_afe_etdm_is_valid(int id)
+{
+ switch (id) {
+ case MT8195_AFE_IO_ETDM1_IN:
+ fallthrough;
+ case MT8195_AFE_IO_ETDM2_IN:
+ fallthrough;
+ case MT8195_AFE_IO_ETDM1_OUT:
+ fallthrough;
+ case MT8195_AFE_IO_ETDM2_OUT:
+ fallthrough;
+ case MT8195_AFE_IO_DPTX:
+ fallthrough;
+ case MT8195_AFE_IO_ETDM3_OUT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mt8195_afe_hdmitx_dptx_is_valid(int id)
+{
+ switch (id) {
+ case MT8195_AFE_IO_DPTX:
+ fallthrough;
+ case MT8195_AFE_IO_ETDM3_OUT:
+ return true;
+ default:
+ return false;
+ }
+}
+
static int get_etdm_fs_timing(unsigned int rate)
{
int i;
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_etdm_priv *etdm_data;
+ if (!mt8195_afe_etdm_is_valid(dai->id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai->id];
return (etdm_data->cowork_slv_count > 0 ||
etdm_data->cowork_source_id != COWORK_ETDM_NONE);
}
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
- int dai_id = etdm_data->cowork_source_id;
+ struct mtk_dai_etdm_priv *etdm_data;
+ int dai_id;
+
+ if (!mt8195_afe_etdm_is_valid(dai->id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai->id];
+ dai_id = etdm_data->cowork_source_id;
if (dai_id == COWORK_ETDM_NONE)
dai_id = dai->id;
int ret = 0;
struct etdm_con_reg etdm_reg;
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
unsigned long flags;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
spin_lock_irqsave(&afe_priv->afe_ctrl_lock, flags);
etdm_data->en_ref_cnt++;
if (etdm_data->en_ref_cnt == 1) {
int ret = 0;
struct etdm_con_reg etdm_reg;
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
unsigned long flags;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
spin_lock_irqsave(&afe_priv->afe_ctrl_lock, flags);
if (etdm_data->en_ref_cnt > 0) {
etdm_data->en_ref_cnt--;
static int mt8195_etdm_sync_mode_configure(struct mtk_base_afe *afe, int dai_id)
{
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
unsigned int reg = 0;
unsigned int mask;
unsigned int val;
int cowork_source_sel;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
if (etdm_data->cowork_source_id == COWORK_ETDM_NONE)
return 0;
if (is_cowork_mode(dai)) {
mst_dai_id = get_etdm_cowork_master_id(dai);
- mtk_dai_etdm_enable_mclk(afe, mst_dai_id);
+ if (!mt8195_afe_etdm_is_valid(mst_dai_id))
+ return -EINVAL;
+ mtk_dai_etdm_enable_mclk(afe, mst_dai_id);
cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id);
if (cg_id >= 0)
mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]);
if (is_cowork_mode(dai)) {
mst_dai_id = get_etdm_cowork_master_id(dai);
+ if (!mt8195_afe_etdm_is_valid(mst_dai_id))
+ return;
+
cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id);
if (cg_id >= 0)
mt8195_afe_disable_clk(afe, afe_priv->clk[cg_id]);
int dai_id)
{
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
struct etdm_con_reg etdm_reg;
- bool slave_mode = etdm_data->slave_mode;
- unsigned int data_mode = etdm_data->data_mode;
- unsigned int lrck_width = etdm_data->lrck_width;
+ bool slave_mode;
+ unsigned int data_mode;
+ unsigned int lrck_width;
unsigned int val = 0;
unsigned int mask = 0;
int i;
int ret;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
+ slave_mode = etdm_data->slave_mode;
+ data_mode = etdm_data->data_mode;
+ lrck_width = etdm_data->lrck_width;
+
dev_dbg(afe->dev, "%s rate %u channels %u, id %d\n",
__func__, rate, channels, dai_id);
int dai_id)
{
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
struct etdm_con_reg etdm_reg;
- bool slave_mode = etdm_data->slave_mode;
- unsigned int lrck_width = etdm_data->lrck_width;
+ bool slave_mode;
+ unsigned int lrck_width;
unsigned int val = 0;
unsigned int mask = 0;
int ret;
int fs = 0;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
+ slave_mode = etdm_data->slave_mode;
+ lrck_width = etdm_data->lrck_width;
+
dev_dbg(afe->dev, "%s rate %u channels %u, id %d\n",
__func__, rate, channels, dai_id);
static int mtk_dai_etdm_mclk_configure(struct mtk_base_afe *afe, int dai_id)
{
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
int clk_id = mtk_dai_etdm_get_clk_id_by_dai_id(dai_id);
int clkdiv_id = mtk_dai_etdm_get_clkdiv_id_by_dai_id(dai_id);
int apll;
if (clk_id < 0 || clkdiv_id < 0)
return 0;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
ret = get_etdm_reg(dai_id, &etdm_reg);
if (ret < 0)
return ret;
int dai_id)
{
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
struct etdm_con_reg etdm_reg;
- bool slave_mode = etdm_data->slave_mode;
+ bool slave_mode;
unsigned int etdm_channels;
unsigned int val = 0;
unsigned int mask = 0;
unsigned int wlen = get_etdm_wlen(bit_width);
int ret;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
+ slave_mode = etdm_data->slave_mode;
ret = get_etdm_reg(dai_id, &etdm_reg);
if (ret < 0)
return ret;
if (is_cowork_mode(dai)) {
mst_dai_id = get_etdm_cowork_master_id(dai);
+ if (!mt8195_afe_etdm_is_valid(mst_dai_id))
+ return -EINVAL;
ret = mtk_dai_etdm_mclk_configure(afe, mst_dai_id);
if (ret)
case SNDRV_PCM_TRIGGER_RESUME:
if (is_cowork_mode(dai)) {
mst_dai_id = get_etdm_cowork_master_id(dai);
+ if (!mt8195_afe_etdm_is_valid(mst_dai_id))
+ return -EINVAL;
+
mst_etdm_data = afe_priv->dai_priv[mst_dai_id];
//open master first
case SNDRV_PCM_TRIGGER_SUSPEND:
if (is_cowork_mode(dai)) {
mst_dai_id = get_etdm_cowork_master_id(dai);
+ if (!mt8195_afe_etdm_is_valid(mst_dai_id))
+ return -EINVAL;
+
mst_etdm_data = afe_priv->dai_priv[mst_dai_id];
for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) {
static int mtk_dai_etdm_cal_mclk(struct mtk_base_afe *afe, int freq, int dai_id)
{
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai_id];
+ struct mtk_dai_etdm_priv *etdm_data;
int apll;
int apll_rate;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai_id];
if (freq == 0) {
etdm_data->mclk_freq = freq;
return 0;
else
dai_id = dai->id;
+ if (!mt8195_afe_etdm_is_valid(dai_id))
+ return -EINVAL;
+
etdm_data = afe_priv->dai_priv[dai_id];
etdm_data->mclk_dir = dir;
return mtk_dai_etdm_cal_mclk(afe, freq, dai_id);
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_etdm_priv *etdm_data;
+
+ if (!mt8195_afe_etdm_is_valid(dai->id))
+ return -EINVAL;
+ etdm_data = afe_priv->dai_priv[dai->id];
dev_dbg(dai->dev, "%s id %d slot_width %d\n",
__func__, dai->id, slot_width);
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_etdm_priv *etdm_data;
+
+ if (!mt8195_afe_etdm_is_valid(dai->id))
+ return -EINVAL;
+ etdm_data = afe_priv->dai_priv[dai->id];
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
etdm_data->format = MTK_DAI_ETDM_FORMAT_I2S;
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_etdm_priv *etdm_data;
unsigned int rate = params_rate(params);
unsigned int channels = params_channels(params);
snd_pcm_format_t format = params_format(params);
int width = snd_pcm_format_physical_width(format);
int ret = 0;
+ if (!mt8195_afe_hdmitx_dptx_is_valid(dai->id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai->id];
+
/* dptx configure */
if (dai->id == MT8195_AFE_IO_DPTX) {
regmap_update_bits(afe->regmap, AFE_DPTX_CON,
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_etdm_priv *etdm_data;
+
+ if (!mt8195_afe_hdmitx_dptx_is_valid(dai->id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai->id];
dev_dbg(dai->dev, "%s id %d freq %u, dir %d\n",
__func__, dai->id, freq, dir);
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_etdm_priv *etdm_data;
dev_dbg(dai->dev, "%s id %d\n", __func__, dai->id);
+ if (!mt8195_afe_etdm_is_valid(dai->id))
+ return -EINVAL;
+
+ etdm_data = afe_priv->dai_priv[dai->id];
if (etdm_data->mclk_freq) {
dev_dbg(afe->dev, "MCLK always on, rate %d\n",
etdm_data->mclk_freq);
etdm_data = afe_priv->dai_priv[i];
if (etdm_data->cowork_source_id != COWORK_ETDM_NONE) {
mst_dai_id = etdm_data->cowork_source_id;
+ if (!mt8195_afe_etdm_is_valid(mst_dai_id)) {
+ dev_err(afe->dev, "%s invalid dai id %d\n",
+ __func__, mst_dai_id);
+ return;
+ }
mst_data = afe_priv->dai_priv[mst_dai_id];
if (mst_data->cowork_source_id != COWORK_ETDM_NONE)
dev_info(afe->dev, "%s [%d] wrong sync source\n"
for (i = 0; i < MT8195_AFE_IO_ETDM_NUM; i++) {
dai_id = ETDM_TO_DAI_ID(i);
+ if (!mt8195_afe_etdm_is_valid(dai_id)) {
+ dev_err(afe->dev, "%s invalid dai id %d\n",
+ __func__, dai_id);
+ return;
+ }
+
etdm_data = afe_priv->dai_priv[dai_id];
ret = snprintf(prop, sizeof(prop),
struct snd_pcm_runtime * const runtime = substream->runtime;
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_pcmif_priv *pcmif_priv = afe_priv->dai_priv[dai->id];
- unsigned int slave_mode = pcmif_priv->slave_mode;
- unsigned int lrck_inv = pcmif_priv->lrck_inv;
- unsigned int bck_inv = pcmif_priv->bck_inv;
- unsigned int fmt = pcmif_priv->format;
+ struct mtk_dai_pcmif_priv *pcmif_priv;
+ unsigned int slave_mode;
+ unsigned int lrck_inv;
+ unsigned int bck_inv;
+ unsigned int fmt;
unsigned int bit_width = dai->sample_bits;
unsigned int val = 0;
unsigned int mask = 0;
int fs = 0;
int mode = 0;
+ if (dai->id != MT8195_AFE_IO_PCM)
+ return -EINVAL;
+
+ pcmif_priv = afe_priv->dai_priv[dai->id];
+ slave_mode = pcmif_priv->slave_mode;
+ lrck_inv = pcmif_priv->lrck_inv;
+ bck_inv = pcmif_priv->bck_inv;
+ fmt = pcmif_priv->format;
+
/* sync freq mode */
fs = mt8195_afe_fs_timing(runtime->rate);
if (fs < 0)
{
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
struct mt8195_afe_private *afe_priv = afe->platform_priv;
- struct mtk_dai_pcmif_priv *pcmif_priv = afe_priv->dai_priv[dai->id];
+ struct mtk_dai_pcmif_priv *pcmif_priv;
dev_dbg(dai->dev, "%s fmt 0x%x\n", __func__, fmt);
+ if (dai->id != MT8195_AFE_IO_PCM)
+ return -EINVAL;
+
+ pcmif_priv = afe_priv->dai_priv[dai->id];
+
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
pcmif_priv->format = MTK_DAI_PCM_FMT_I2S;
int mtkaif_phase_cycle[MT8195_MTKAIF_MISO_NUM];
int mtkaif_calibration_num_phase;
bool mtkaif_calibration_ok;
- unsigned int monitor;
+ unsigned int monitor = 0;
int counter;
int phase;
int i;
return ret;
}
-static int aiu_remove(struct platform_device *pdev)
+static void aiu_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
-
- return 0;
}
static const struct aiu_platform_data aiu_gxbb_pdata = {
static struct platform_driver aiu_pdrv = {
.probe = aiu_probe,
- .remove = aiu_remove,
+ .remove_new = aiu_remove,
.driver = {
.name = "meson-aiu",
.of_match_table = aiu_of_match,
return ret;
if (axg_card_cpu_is_codec(dai_link->cpus->of_node)) {
- dai_link->params = &codec_params;
+ dai_link->c2c_params = &codec_params;
+ dai_link->num_c2c_params = 1;
} else {
dai_link->no_pcm = 1;
snd_soc_dai_link_set_capabilities(dai_link);
struct device *dev = &pdev->dev;
struct snd_soc_dai_driver *dai_drv;
struct axg_tdm_iface *iface;
- int ret, i;
+ int i;
iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL);
if (!iface)
* At this point, ignore the error if mclk is missing. We'll
* throw an error if the cpu dai is master and mclk is missing
*/
- iface->mclk = devm_clk_get(dev, "mclk");
- if (IS_ERR(iface->mclk)) {
- ret = PTR_ERR(iface->mclk);
- if (ret == -ENOENT)
- iface->mclk = NULL;
- else
- return dev_err_probe(dev, ret, "failed to get mclk\n");
- }
+ iface->mclk = devm_clk_get_optional(dev, "mclk");
+ if (IS_ERR(iface->mclk))
+ return dev_err_probe(dev, PTR_ERR(iface->mclk), "failed to get mclk\n");
return devm_snd_soc_register_component(dev,
&axg_tdm_iface_component_drv, dai_drv,
/* Or apply codec to codec params if necessary */
if (gx_card_cpu_identify(dai_link->cpus, "CODEC CTRL")) {
- dai_link->params = &codec_params;
+ dai_link->c2c_params = &codec_params;
+ dai_link->num_c2c_params = 1;
} else {
dai_link->no_pcm = 1;
snd_soc_dai_link_set_capabilities(dai_link);
if (!in_data)
return -ENODEV;
- if (WARN_ON(!rtd->dai_link->params)) {
+ if (WARN_ON(!rtd->dai_link->c2c_params)) {
dev_warn(dai->dev, "codec2codec link expected\n");
return -EINVAL;
}
/* Replace link params with the input params */
- rtd->dai_link->params = &in_data->params;
+ rtd->dai_link->c2c_params = &in_data->params;
+ rtd->dai_link->num_c2c_params = 1;
return snd_soc_runtime_set_dai_fmt(rtd, in_data->fmt);
}
card->dev = &pdev->dev;
- if (of_find_property(np, "audio-routing", NULL)) {
+ if (of_property_present(np, "audio-routing")) {
card->dapm_widgets = mxs_sgtl5000_dapm_widgets;
card->num_dapm_widgets = ARRAY_SIZE(mxs_sgtl5000_dapm_widgets);
return 0;
}
-static int mxs_sgtl5000_remove(struct platform_device *pdev)
+static void mxs_sgtl5000_remove(struct platform_device *pdev)
{
mxs_saif_put_mclk(0);
-
- return 0;
}
static const struct of_device_id mxs_sgtl5000_dt_ids[] = {
.of_match_table = mxs_sgtl5000_dt_ids,
},
.probe = mxs_sgtl5000_probe,
- .remove = mxs_sgtl5000_remove,
+ .remove_new = mxs_sgtl5000_remove,
};
module_platform_driver(mxs_sgtl5000_audio_driver);
return 0;
}
-static int asoc_mmp_sspa_remove(struct platform_device *pdev)
+static void asoc_mmp_sspa_remove(struct platform_device *pdev)
{
struct sspa_priv *sspa = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
if (pdev->dev.of_node)
- return 0;
+ return;
clk_put(sspa->audio_clk);
clk_put(sspa->sysclk);
- return 0;
}
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(mmp_sspa_of_match),
},
.probe = asoc_mmp_sspa_probe,
- .remove = asoc_mmp_sspa_remove,
+ .remove_new = asoc_mmp_sspa_remove,
};
module_platform_driver(asoc_mmp_sspa_driver);
pxa_ac97_dai_driver, ARRAY_SIZE(pxa_ac97_dai_driver));
}
-static int pxa2xx_ac97_dev_remove(struct platform_device *pdev)
+static void pxa2xx_ac97_dev_remove(struct platform_device *pdev)
{
struct ac97_controller *ctrl = platform_get_drvdata(pdev);
snd_ac97_controller_unregister(ctrl);
pxa2xx_ac97_hw_remove(pdev);
- return 0;
}
#ifdef CONFIG_PM_SLEEP
static struct platform_driver pxa2xx_ac97_driver = {
.probe = pxa2xx_ac97_dev_probe,
- .remove = pxa2xx_ac97_dev_remove,
+ .remove_new = pxa2xx_ac97_dev_remove,
.driver = {
.name = "pxa2xx-ac97",
#ifdef CONFIG_PM_SLEEP
}
EXPORT_SYMBOL_GPL(qcom_snd_parse_of);
+static struct snd_soc_jack_pin qcom_headset_jack_pins[] = {
+ /* Headset */
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd,
struct snd_soc_jack *jack, bool *jack_setup)
{
int rval, i;
if (!*jack_setup) {
- rval = snd_soc_card_jack_new(card, "Headset Jack",
+ rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
SND_JACK_HEADSET | SND_JACK_LINEOUT |
SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 | SND_JACK_BTN_5,
- jack);
+ jack, qcom_headset_jack_pins,
+ ARRAY_SIZE(qcom_headset_jack_pins));
if (rval < 0) {
dev_err(card->dev, "Unable to add Headphone Jack\n");
if (dai_data->is_port_started[dai->id]) {
q6apm_graph_stop(dai_data->graph[dai->id]);
dai_data->is_port_started[dai->id] = false;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ q6apm_graph_close(dai_data->graph[dai->id]);
}
/**
ret = devm_snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0);
if (ret < 0) {
- dev_err(dev, "failed to get register q6apm: %d\n", ret);
+ dev_err(dev, "failed to register q6apm: %d\n", ret);
return ret;
}
NULL, 0);
}
-static int q6pcm_routing_remove(struct platform_device *pdev)
+static void q6pcm_routing_remove(struct platform_device *pdev)
{
kfree(routing_data);
routing_data = NULL;
-
- return 0;
}
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(q6pcm_routing_device_id),
},
.probe = q6pcm_routing_probe,
- .remove = q6pcm_routing_remove,
+ .remove_new = q6pcm_routing_remove,
};
module_platform_driver(q6pcm_routing_platform_driver);
return 0;
}
- if (*stream_prepared) {
- sdw_disable_stream(sruntime);
- sdw_deprepare_stream(sruntime);
- *stream_prepared = false;
- }
+ if (*stream_prepared)
+ return 0;
ret = sdw_prepare_stream(sruntime);
if (ret)
{ .compatible = "rockchip,rk3366-i2s", },
{ .compatible = "rockchip,rk3368-i2s", },
{ .compatible = "rockchip,rk3399-i2s", .data = &rk3399_i2s_pins },
+ { .compatible = "rockchip,rk3588-i2s", },
{ .compatible = "rockchip,rv1126-i2s", },
{},
};
return ret;
}
-static int rockchip_i2s_remove(struct platform_device *pdev)
+static void rockchip_i2s_remove(struct platform_device *pdev)
{
struct rk_i2s_dev *i2s = dev_get_drvdata(&pdev->dev);
i2s_runtime_suspend(&pdev->dev);
clk_disable_unprepare(i2s->hclk);
-
- return 0;
}
static const struct dev_pm_ops rockchip_i2s_pm_ops = {
static struct platform_driver rockchip_i2s_driver = {
.probe = rockchip_i2s_probe,
- .remove = rockchip_i2s_remove,
+ .remove_new = rockchip_i2s_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(rockchip_i2s_match),
return ret;
}
-static int rockchip_pdm_remove(struct platform_device *pdev)
+static void rockchip_pdm_remove(struct platform_device *pdev)
{
struct rk_pdm_dev *pdm = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(pdm->clk);
clk_disable_unprepare(pdm->hclk);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
static struct platform_driver rockchip_pdm_driver = {
.probe = rockchip_pdm_probe,
- .remove = rockchip_pdm_remove,
+ .remove_new = rockchip_pdm_remove,
.driver = {
.name = "rockchip-pdm",
.of_match_table = of_match_ptr(rockchip_pdm_match),
return ret;
}
-static int snd_rk_mc_remove(struct platform_device *pdev)
+static void snd_rk_mc_remove(struct platform_device *pdev)
{
of_node_put(rk_dailink.cpus->of_node);
rk_dailink.cpus->of_node = NULL;
of_node_put(rk_dailink.codecs->of_node);
rk_dailink.codecs->of_node = NULL;
-
- return 0;
}
static const struct of_device_id rockchip_rt5645_of_match[] = {
static struct platform_driver snd_rk_mc_driver = {
.probe = snd_rk_mc_probe,
- .remove = snd_rk_mc_remove,
+ .remove_new = snd_rk_mc_remove,
.driver = {
.name = DRV_NAME,
.pm = &snd_soc_pm_ops,
return ret;
}
-static int rk_spdif_remove(struct platform_device *pdev)
+static void rk_spdif_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
rk_spdif_runtime_suspend(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops rk_spdif_pm_ops = {
static struct platform_driver rk_spdif_driver = {
.probe = rk_spdif_probe,
- .remove = rk_spdif_remove,
+ .remove_new = rk_spdif_remove,
.driver = {
.name = "rockchip-spdif",
.of_match_table = of_match_ptr(rk_spdif_match),
.name = "WM8994 AIF2",
.stream_name = "Baseband",
.init = &aries_baseband_init,
- .params = &baseband_params,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(baseband),
},
{
.name = "WM8994 AIF3",
.stream_name = "Bluetooth",
- .params = &bluetooth_params,
+ .c2c_params = &bluetooth_params,
+ .num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(bluetooth),
},
return ret;
}
-static int arndale_audio_remove(struct platform_device *pdev)
+static void arndale_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
arndale_put_of_nodes(card);
- return 0;
}
static const struct of_device_id arndale_audio_of_match[] = {
.of_match_table = arndale_audio_of_match,
},
.probe = arndale_audio_probe,
- .remove = arndale_audio_remove,
+ .remove_new = arndale_audio_remove,
};
module_platform_driver(arndale_audio_driver);
.stream_name = "DSP-CODEC",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
- .params = &sub_params,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm2200_dsp_codec),
},
.stream_name = "DSP-CODEC",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
- .params = &sub_params,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm5102_dsp_codec),
},
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
- .params = &baseband_params,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5102_baseband),
},
{
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1,
- .params = &sub_params,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5102_sub),
},
};
.stream_name = "DSP-CODEC",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
- .params = &sub_params,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(wm5110_dsp_codec),
},
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
- .params = &baseband_params,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5110_baseband),
},
{
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1,
- .params = &sub_params,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
SND_SOC_DAILINK_REG(wm5110_sub),
},
};
int ret, i;
/* Register the clock provider only if it's expected in the DTB */
- if (!of_find_property(dev->of_node, "#clock-cells", NULL))
+ if (!of_property_present(dev->of_node, "#clock-cells"))
return 0;
/* Get the RCLKSRC mux clock parent clock names */
return ret;
}
-static int samsung_i2s_remove(struct platform_device *pdev)
+static void samsung_i2s_remove(struct platform_device *pdev)
{
struct samsung_i2s_priv *priv = dev_get_drvdata(&pdev->dev);
/* The secondary device has no driver data assigned */
if (!priv)
- return 0;
+ return;
pm_runtime_get_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(priv->clk);
pm_runtime_put_noidle(&pdev->dev);
-
- return 0;
}
static void fsd_i2s_fixup_early(struct snd_pcm_substream *substream,
static struct platform_driver samsung_i2s_driver = {
.probe = samsung_i2s_probe,
- .remove = samsung_i2s_remove,
+ .remove_new = samsung_i2s_remove,
.id_table = samsung_i2s_driver_ids,
.driver = {
.name = "samsung-i2s",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
- .params = &baseband_params,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
SND_SOC_DAILINK_REG(baseband),
},
};
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
- .params = &sub_params,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
.init = lowland_wm9081_init,
SND_SOC_DAILINK_REG(speaker),
},
return ret;
}
-static int odroid_audio_remove(struct platform_device *pdev)
+static void odroid_audio_remove(struct platform_device *pdev)
{
struct odroid_priv *priv = platform_get_drvdata(pdev);
snd_soc_of_put_dai_link_codecs(&priv->card.dai_link[1]);
clk_put(priv->sclk_i2s);
clk_put(priv->clk_i2s_bus);
-
- return 0;
}
static const struct of_device_id odroid_audio_of_match[] = {
.pm = &snd_soc_pm_ops,
},
.probe = odroid_audio_probe,
- .remove = odroid_audio_remove,
+ .remove_new = odroid_audio_remove,
};
module_platform_driver(odroid_audio_driver);
return ret;
}
-static int s3c_pcm_dev_remove(struct platform_device *pdev)
+static void s3c_pcm_dev_remove(struct platform_device *pdev)
{
struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(pcm->cclk);
clk_disable_unprepare(pcm->pclk);
-
- return 0;
}
static struct platform_driver s3c_pcm_driver = {
.probe = s3c_pcm_dev_probe,
- .remove = s3c_pcm_dev_remove,
+ .remove_new = s3c_pcm_dev_remove,
.driver = {
.name = "samsung-pcm",
},
return 0;
}
-static int snow_remove(struct platform_device *pdev)
+static void snow_remove(struct platform_device *pdev)
{
struct snow_priv *priv = platform_get_drvdata(pdev);
struct snd_soc_dai_link *link = &priv->dai_link;
snd_soc_of_put_dai_link_codecs(link);
clk_put(priv->clk_i2s_bus);
-
- return 0;
}
static const struct of_device_id snow_of_match[] = {
.of_match_table = snow_of_match,
},
.probe = snow_probe,
- .remove = snow_remove,
+ .remove_new = snow_remove,
};
module_platform_driver(snow_driver);
return ret;
}
-static int spdif_remove(struct platform_device *pdev)
+static void spdif_remove(struct platform_device *pdev)
{
struct samsung_spdif_info *spdif = &spdif_info;
struct resource *mem_res;
clk_disable_unprepare(spdif->sclk);
clk_disable_unprepare(spdif->pclk);
-
- return 0;
}
static struct platform_driver samsung_spdif_driver = {
.probe = spdif_probe,
- .remove = spdif_remove,
+ .remove_new = spdif_remove,
.driver = {
.name = "samsung-spdif",
},
.init = speyside_wm8996_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
- .params = &dsp_codec_params,
+ .c2c_params = &dsp_codec_params,
+ .num_c2c_params = 1,
.ignore_suspend = 1,
SND_SOC_DAILINK_REG(dsp_codec),
},
for (i = 0; i < ARRAY_SIZE(of_parse_property); i++) {
sprintf(prop, "%s,%s", name, of_parse_property[i].name);
- if (of_get_property(np, prop, NULL))
+ if (of_property_present(np, prop))
flags |= of_parse_property[i].val;
}
info->flags = flags;
return ret;
}
-static int fsi_remove(struct platform_device *pdev)
+static void fsi_remove(struct platform_device *pdev)
{
struct fsi_master *master;
fsi_stream_remove(&master->fsia);
fsi_stream_remove(&master->fsib);
-
- return 0;
}
static void __fsi_suspend(struct fsi_priv *fsi,
.of_match_table = fsi_of_match,
},
.probe = fsi_probe,
- .remove = fsi_remove,
+ .remove_new = fsi_remove,
.id_table = fsi_id_table,
};
sh4_hac_dai, ARRAY_SIZE(sh4_hac_dai));
}
-static int hac_soc_platform_remove(struct platform_device *pdev)
+static void hac_soc_platform_remove(struct platform_device *pdev)
{
snd_soc_set_ac97_ops(NULL);
- return 0;
}
static struct platform_driver hac_pcm_driver = {
},
.probe = hac_soc_platform_probe,
- .remove = hac_soc_platform_remove,
+ .remove_new = hac_soc_platform_remove,
};
module_platform_driver(hac_pcm_driver);
return ret;
}
-static int rsnd_remove(struct platform_device *pdev)
+static void rsnd_remove(struct platform_device *pdev)
{
struct rsnd_priv *priv = dev_get_drvdata(&pdev->dev);
struct rsnd_dai *rdai;
for (i = 0; i < ARRAY_SIZE(remove_func); i++)
remove_func[i](priv);
-
- return 0;
}
static int __maybe_unused rsnd_suspend(struct device *dev)
.of_match_table = rsnd_of_match,
},
.probe = rsnd_probe,
- .remove = rsnd_remove,
+ .remove_new = rsnd_remove,
};
module_platform_driver(rsnd_driver);
goto rsnd_ssi_probe_done;
}
- if (of_get_property(np, "shared-pin", NULL))
+ if (of_property_read_bool(np, "shared-pin"))
rsnd_flags_set(ssi, RSND_SSI_CLK_PIN_SHARE);
- if (of_get_property(np, "no-busif", NULL))
+ if (of_property_read_bool(np, "no-busif"))
rsnd_flags_set(ssi, RSND_SSI_NO_BUSIF);
ssi->irq = irq_of_parse_and_map(np, 0);
int irq_int;
int irq_tx;
int irq_rx;
+ int irq_rt;
spinlock_t lock;
rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
}
+ if (irq == ssi->irq_rt) {
+ struct snd_pcm_substream *substream = strm->substream;
+
+ if (rz_ssi_stream_is_play(ssi, substream)) {
+ strm->transfer(ssi, &ssi->playback);
+ } else {
+ strm->transfer(ssi, &ssi->capture);
+ rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0);
+ }
+ }
+
return IRQ_HANDLED;
}
if (!rz_ssi_is_dma_enabled(ssi)) {
/* Tx and Rx interrupts (pio only) */
ssi->irq_tx = platform_get_irq_byname(pdev, "dma_tx");
- if (ssi->irq_tx < 0)
- return ssi->irq_tx;
-
- ret = devm_request_irq(&pdev->dev, ssi->irq_tx,
- &rz_ssi_interrupt, 0,
- dev_name(&pdev->dev), ssi);
- if (ret < 0)
- return dev_err_probe(&pdev->dev, ret,
- "irq request error (dma_tx)\n");
-
ssi->irq_rx = platform_get_irq_byname(pdev, "dma_rx");
- if (ssi->irq_rx < 0)
- return ssi->irq_rx;
-
- ret = devm_request_irq(&pdev->dev, ssi->irq_rx,
- &rz_ssi_interrupt, 0,
- dev_name(&pdev->dev), ssi);
- if (ret < 0)
- return dev_err_probe(&pdev->dev, ret,
- "irq request error (dma_rx)\n");
+ if (ssi->irq_tx == -ENXIO && ssi->irq_rx == -ENXIO) {
+ ssi->irq_rt = platform_get_irq_byname(pdev, "dma_rt");
+ if (ssi->irq_rt < 0)
+ return ssi->irq_rt;
+
+ ret = devm_request_irq(&pdev->dev, ssi->irq_rt,
+ &rz_ssi_interrupt, 0,
+ dev_name(&pdev->dev), ssi);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "irq request error (dma_tx)\n");
+ } else {
+ if (ssi->irq_tx < 0)
+ return ssi->irq_tx;
+
+ if (ssi->irq_rx < 0)
+ return ssi->irq_rx;
+
+ ret = devm_request_irq(&pdev->dev, ssi->irq_tx,
+ &rz_ssi_interrupt, 0,
+ dev_name(&pdev->dev), ssi);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "irq request error (dma_tx)\n");
+
+ ret = devm_request_irq(&pdev->dev, ssi->irq_rx,
+ &rz_ssi_interrupt, 0,
+ dev_name(&pdev->dev), ssi);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "irq request error (dma_rx)\n");
+ }
}
ssi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
return ret;
}
-static int rz_ssi_remove(struct platform_device *pdev)
+static void rz_ssi_remove(struct platform_device *pdev)
{
struct rz_ssi_priv *ssi = dev_get_drvdata(&pdev->dev);
pm_runtime_put(ssi->dev);
pm_runtime_disable(ssi->dev);
reset_control_assert(ssi->rstc);
-
- return 0;
}
static const struct of_device_id rz_ssi_of_match[] = {
.of_match_table = rz_ssi_of_match,
},
.probe = rz_ssi_probe,
- .remove = rz_ssi_remove,
+ .remove_new = rz_ssi_remove,
};
module_platform_driver(rz_ssi_driver);
return 0;
}
-static int siu_remove(struct platform_device *pdev)
+static void siu_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
- return 0;
}
static struct platform_driver siu_driver = {
.name = "siu-pcm-audio",
},
.probe = siu_probe,
- .remove = siu_remove,
+ .remove_new = siu_remove,
};
module_platform_driver(siu_driver);
struct snd_soc_component *component;
int i, ret = 0;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
for_each_rtd_components(rtd, i, component) {
if (component->driver->compress_ops &&
}
}
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return soc_component_ret(component, ret);
}
struct snd_soc_component *component;
int i, ret = 0;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
for_each_rtd_components(rtd, i, component) {
if (component->driver->compress_ops &&
}
}
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return soc_component_ret(component, ret);
}
struct snd_soc_component *component;
int i, ret = 0;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
for_each_rtd_components(rtd, i, component) {
if (component->driver->compress_ops &&
}
}
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return soc_component_ret(component, ret);
}
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
if (!rollback)
snd_soc_runtime_deactivate(rtd, stream);
if (!rollback)
snd_soc_dapm_stream_stop(rtd, stream);
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
snd_soc_pcm_component_pm_runtime_put(rtd, cstream, rollback);
if (ret < 0)
goto err_no_lock;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
ret = snd_soc_dai_compr_startup(cpu_dai, cstream);
if (ret < 0)
snd_soc_runtime_activate(rtd, stream);
err:
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
err_no_lock:
if (ret < 0)
soc_compr_clean(cstream, 1);
static int soc_compr_open_fe(struct snd_compr_stream *cstream)
{
struct snd_soc_pcm_runtime *fe = cstream->private_data;
- struct snd_pcm_substream *fe_substream =
- fe->pcm->streams[cstream->direction].substream;
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(fe, 0);
struct snd_soc_dpcm *dpcm;
struct snd_soc_dapm_widget_list *list;
int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */
int ret;
- mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
- fe->dpcm[stream].runtime = fe_substream->runtime;
+ snd_soc_card_mutex_lock(fe->card);
ret = dpcm_path_get(fe, stream, &list);
if (ret < 0)
goto be_err;
- mutex_lock_nested(&fe->card->pcm_mutex, fe->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(fe);
/* calculate valid and active FE <-> BE dpcms */
dpcm_process_paths(fe, stream, &list, 1);
- fe->dpcm[stream].runtime = fe_substream->runtime;
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE;
dpcm_be_disconnect(fe, stream);
- fe->dpcm[stream].runtime = NULL;
goto out;
}
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
snd_soc_runtime_activate(fe, stream);
- mutex_unlock(&fe->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(fe);
- mutex_unlock(&fe->card->mutex);
+ snd_soc_card_mutex_unlock(fe->card);
return 0;
dpcm_path_put(&list);
be_err:
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
- mutex_unlock(&fe->card->mutex);
+ snd_soc_card_mutex_unlock(fe->card);
return ret;
}
struct snd_soc_dpcm *dpcm;
int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */
- mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
+ snd_soc_card_mutex_lock(fe->card);
- mutex_lock_nested(&fe->card->pcm_mutex, fe->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(fe);
snd_soc_runtime_deactivate(fe, stream);
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
dpcm_be_disconnect(fe, stream);
- mutex_unlock(&fe->card->pcm_mutex);
-
- fe->dpcm[stream].runtime = NULL;
+ snd_soc_dpcm_mutex_unlock(fe);
snd_soc_link_compr_shutdown(cstream, 0);
snd_soc_dai_compr_shutdown(cpu_dai, cstream, 0);
- mutex_unlock(&fe->card->mutex);
+ snd_soc_card_mutex_unlock(fe->card);
return 0;
}
int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */
int ret;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
ret = snd_soc_component_compr_trigger(cstream, cmd);
if (ret < 0)
}
out:
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
cmd == SND_COMPR_TRIGGER_DRAIN)
return snd_soc_component_compr_trigger(cstream, cmd);
- mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
+ snd_soc_card_mutex_lock(fe->card);
ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd);
if (ret < 0)
out:
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
- mutex_unlock(&fe->card->mutex);
+ snd_soc_card_mutex_unlock(fe->card);
return ret;
}
int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */
int ret;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
/*
* First we call set_params for the CPU DAI, then the component
/* cancel any delayed stream shutdown that is pending */
rtd->pop_wait = 0;
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
cancel_delayed_work_sync(&rtd->delayed_work);
return 0;
err:
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */
int ret;
- mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
+ snd_soc_card_mutex_lock(fe->card);
/*
* Create an empty hw_params for the BE as the machine driver must
ret = snd_soc_link_compr_set_params(cstream);
if (ret < 0)
goto out;
- mutex_lock_nested(&fe->card->pcm_mutex, fe->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(fe);
dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START);
- mutex_unlock(&fe->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(fe);
fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
out:
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
- mutex_unlock(&fe->card->mutex);
+ snd_soc_card_mutex_unlock(fe->card);
return ret;
}
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
int ret = 0;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
ret = snd_soc_dai_compr_get_params(cpu_dai, cstream, params);
if (ret < 0)
ret = snd_soc_component_compr_get_params(cstream, params);
err:
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
int ret;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
ret = snd_soc_dai_compr_ack(cpu_dai, cstream, bytes);
if (ret < 0)
ret = snd_soc_component_compr_ack(cstream, bytes);
err:
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
int ret;
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
ret = snd_soc_dai_compr_pointer(cpu_dai, cstream, tstamp);
if (ret < 0)
ret = snd_soc_component_compr_pointer(cstream, tstamp);
out:
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
return ret;
}
return ret;
}
+ /* inherit atomicity from DAI link */
+ be_pcm->nonatomic = rtd->dai_link->nonatomic;
+
rtd->pcm = be_pcm;
rtd->fe_compr = 1;
if (rtd->dai_link->dpcm_playback)
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
int playback = SNDRV_PCM_STREAM_PLAYBACK;
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(rtd);
dev_dbg(rtd->dev,
"ASoC: pop wq checking: %s status: %s waiting: %s\n",
SND_SOC_DAPM_STREAM_STOP);
}
- mutex_unlock(&rtd->card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(rtd);
}
EXPORT_SYMBOL_GPL(snd_soc_close_delayed_work);
* topology component. And machine drivers can still define static
* DAI links in dai_link array.
*/
-int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
- struct snd_soc_dai_link *dai_link)
+static int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai_link_component *codec, *platform, *cpu;
snd_soc_remove_pcm_runtime(card, rtd);
return -EPROBE_DEFER;
}
-EXPORT_SYMBOL_GPL(snd_soc_add_pcm_runtime);
+
+int snd_soc_add_pcm_runtimes(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link,
+ int num_dai_link)
+{
+ for (int i = 0; i < num_dai_link; i++) {
+ int ret = snd_soc_add_pcm_runtime(card, dai_link + i);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_add_pcm_runtimes);
static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai_link *dai_link = rtd->dai_link;
struct snd_soc_dai *dai, *not_used;
- struct device *dev = rtd->dev;
u64 pos, possible_fmt;
unsigned int mask = 0, dai_fmt = 0;
int i, j, priority, pri, until;
*/
until = snd_soc_dai_get_fmt_max_priority(rtd);
for (priority = 1; priority <= until; priority++) {
-
- dev_dbg(dev, "priority = %d\n", priority);
for_each_rtd_dais(rtd, j, not_used) {
possible_fmt = ULLONG_MAX;
pri = (j >= i) ? priority : priority - 1;
fmt = snd_soc_dai_get_fmt(dai, pri);
- dev_dbg(dev, "%s: (pri, fmt) = (%d, %016llX)\n", dai->name, pri, fmt);
possible_fmt &= fmt;
}
if (possible_fmt)
/* Not Found */
return;
found:
- dev_dbg(dev, "found auto selected format: %016llX\n", possible_fmt);
-
/*
* convert POSSIBLE_DAIFMT to DAIFMT
*
for_each_comp_order(order) {
for_each_card_rtds(card, rtd) {
-
- dev_dbg(card->dev,
- "ASoC: probe %s dai link %d late %d\n",
- card->name, rtd->num, order);
-
/* probe all rtd connected DAIs in good order */
ret = snd_soc_pcm_dai_probe(rtd, order);
if (ret)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_component *component;
- struct snd_soc_dai_link *dai_link;
- int ret, i;
+ int ret;
mutex_lock(&client_mutex);
- mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);
+ snd_soc_card_mutex_lock_root(card);
snd_soc_dapm_init(&card->dapm, card, NULL);
/* add predefined DAI links to the list */
card->num_rtd = 0;
- for_each_card_prelinks(card, i, dai_link) {
- ret = snd_soc_add_pcm_runtime(card, dai_link);
- if (ret < 0)
- goto probe_end;
- }
+ ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links);
+ if (ret < 0)
+ goto probe_end;
/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
if (ret < 0)
soc_cleanup_card_resources(card);
- mutex_unlock(&card->mutex);
+ snd_soc_card_mutex_unlock(card);
mutex_unlock(&client_mutex);
return ret;
struct device *dev = component->dev;
struct snd_soc_dai *dai;
- dev_dbg(dev, "ASoC: dynamically register DAI %s\n", dev_name(dev));
-
lockdep_assert_held(&client_mutex);
dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL);
if (dai->driver->probe_order != order)
continue;
+ if (dai->probed)
+ continue;
+
if (dai->driver->probe) {
int ret = dai->driver->probe(dai);
[snd_soc_dapm_adc] = 11,
[snd_soc_dapm_out_drv] = 12,
[snd_soc_dapm_hp] = 12,
- [snd_soc_dapm_spk] = 12,
[snd_soc_dapm_line] = 12,
[snd_soc_dapm_sink] = 12,
- [snd_soc_dapm_kcontrol] = 13,
- [snd_soc_dapm_post] = 14,
+ [snd_soc_dapm_spk] = 13,
+ [snd_soc_dapm_kcontrol] = 14,
+ [snd_soc_dapm_post] = 15,
};
static int dapm_down_seq[] = {
[snd_soc_dapm_pre] = 1,
[snd_soc_dapm_kcontrol] = 2,
[snd_soc_dapm_adc] = 3,
- [snd_soc_dapm_hp] = 4,
[snd_soc_dapm_spk] = 4,
- [snd_soc_dapm_line] = 4,
- [snd_soc_dapm_out_drv] = 4,
- [snd_soc_dapm_sink] = 4,
- [snd_soc_dapm_pga] = 5,
- [snd_soc_dapm_buffer] = 5,
- [snd_soc_dapm_scheduler] = 5,
- [snd_soc_dapm_effect] = 5,
- [snd_soc_dapm_src] = 5,
- [snd_soc_dapm_asrc] = 5,
- [snd_soc_dapm_encoder] = 5,
- [snd_soc_dapm_decoder] = 5,
- [snd_soc_dapm_switch] = 6,
- [snd_soc_dapm_mixer_named_ctl] = 6,
- [snd_soc_dapm_mixer] = 6,
- [snd_soc_dapm_dac] = 7,
- [snd_soc_dapm_mic] = 8,
- [snd_soc_dapm_siggen] = 8,
- [snd_soc_dapm_input] = 8,
- [snd_soc_dapm_output] = 8,
- [snd_soc_dapm_micbias] = 9,
- [snd_soc_dapm_vmid] = 9,
- [snd_soc_dapm_mux] = 10,
- [snd_soc_dapm_demux] = 10,
- [snd_soc_dapm_aif_in] = 11,
- [snd_soc_dapm_aif_out] = 11,
- [snd_soc_dapm_dai_in] = 11,
- [snd_soc_dapm_dai_out] = 11,
- [snd_soc_dapm_dai_link] = 12,
- [snd_soc_dapm_supply] = 13,
- [snd_soc_dapm_clock_supply] = 14,
- [snd_soc_dapm_pinctrl] = 14,
- [snd_soc_dapm_regulator_supply] = 14,
- [snd_soc_dapm_post] = 15,
+ [snd_soc_dapm_hp] = 5,
+ [snd_soc_dapm_line] = 5,
+ [snd_soc_dapm_out_drv] = 5,
+ [snd_soc_dapm_sink] = 6,
+ [snd_soc_dapm_pga] = 6,
+ [snd_soc_dapm_buffer] = 6,
+ [snd_soc_dapm_scheduler] = 6,
+ [snd_soc_dapm_effect] = 6,
+ [snd_soc_dapm_src] = 6,
+ [snd_soc_dapm_asrc] = 6,
+ [snd_soc_dapm_encoder] = 6,
+ [snd_soc_dapm_decoder] = 6,
+ [snd_soc_dapm_switch] = 7,
+ [snd_soc_dapm_mixer_named_ctl] = 7,
+ [snd_soc_dapm_mixer] = 7,
+ [snd_soc_dapm_dac] = 8,
+ [snd_soc_dapm_mic] = 9,
+ [snd_soc_dapm_siggen] = 9,
+ [snd_soc_dapm_input] = 9,
+ [snd_soc_dapm_output] = 9,
+ [snd_soc_dapm_micbias] = 10,
+ [snd_soc_dapm_vmid] = 10,
+ [snd_soc_dapm_mux] = 11,
+ [snd_soc_dapm_demux] = 11,
+ [snd_soc_dapm_aif_in] = 12,
+ [snd_soc_dapm_aif_out] = 12,
+ [snd_soc_dapm_dai_in] = 12,
+ [snd_soc_dapm_dai_out] = 12,
+ [snd_soc_dapm_dai_link] = 13,
+ [snd_soc_dapm_supply] = 14,
+ [snd_soc_dapm_clock_supply] = 15,
+ [snd_soc_dapm_pinctrl] = 15,
+ [snd_soc_dapm_regulator_supply] = 15,
+ [snd_soc_dapm_post] = 16,
};
static void dapm_assert_locked(struct snd_soc_dapm_context *dapm)
{
if (snd_soc_card_is_instantiated(dapm->card))
- lockdep_assert_held(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_assert_held(dapm);
}
static void pop_wait(u32 pop_time)
{
struct snd_soc_dapm_widget *w;
- mutex_lock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_lock_root(card);
for_each_card_widgets(card, w) {
if (w->is_ep) {
}
}
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
}
EXPORT_SYMBOL_GPL(dapm_mark_endpoints_dirty);
{
struct snd_soc_dapm_widget *w;
- lockdep_assert_held(&card->dapm_mutex);
+ snd_soc_dapm_mutex_assert_held(card);
memset(&card->dapm_stats, 0, sizeof(card->dapm_stats));
struct snd_soc_pcm_runtime *rtd = w->priv;
/* create control for links with > 1 config */
- if (rtd->dai_link->num_params <= 1)
+ if (rtd->dai_link->num_c2c_params <= 1)
return 0;
/* add kcontrol */
int paths;
int ret;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
invalidate_paths_ep(w, SND_SOC_DAPM_DIR_OUT);
paths = ret;
trace_snd_soc_dapm_connected(paths, stream);
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
return paths;
}
enum snd_soc_bias_level bias;
int ret;
- lockdep_assert_held(&card->dapm_mutex);
+ snd_soc_dapm_mutex_assert_held(card);
trace_snd_soc_dapm_start(card);
size_t count, loff_t *ppos)
{
struct snd_soc_dapm_widget *w = file->private_data;
- struct snd_soc_card *card = w->dapm->card;
enum snd_soc_dapm_direction dir, rdir;
char *buf;
int in, out;
if (!buf)
return -ENOMEM;
- mutex_lock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_lock_root(w->dapm);
/* Supply widgets are not handled by is_connected_{input,output}_ep() */
if (w->is_supply) {
}
}
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(w->dapm);
ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
int found = 0;
bool connect;
- lockdep_assert_held(&card->dapm_mutex);
+ snd_soc_dapm_mutex_assert_held(card);
/* find dapm widget path assoc with kcontrol */
dapm_kcontrol_for_each_path(path, kcontrol) {
struct snd_soc_card *card = dapm->card;
int ret;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
card->update = update;
ret = soc_dapm_mux_update_power(card, kcontrol, mux, e);
card->update = NULL;
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
if (ret > 0)
snd_soc_dpcm_runtime_update(card);
return ret;
struct snd_soc_dapm_path *path;
int found = 0;
- lockdep_assert_held(&card->dapm_mutex);
+ snd_soc_dapm_mutex_assert_held(card);
/* find dapm widget path assoc with kcontrol */
dapm_kcontrol_for_each_path(path, kcontrol) {
struct snd_soc_card *card = dapm->card;
int ret;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
card->update = update;
ret = soc_dapm_mixer_update_power(card, kcontrol, connect, -1);
card->update = NULL;
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
if (ret > 0)
snd_soc_dpcm_runtime_update(card);
return ret;
struct snd_soc_dai *codec_dai;
int i, count = 0;
- mutex_lock(&rtd->card->dapm_mutex);
+ snd_soc_dapm_mutex_lock_root(rtd->card);
for_each_rtd_codec_dais(rtd, i, codec_dai) {
struct snd_soc_component *cmpnt = codec_dai->component;
count = dapm_widget_show_component(cmpnt, buf, count);
}
- mutex_unlock(&rtd->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(rtd->card);
return count;
}
{
int ret;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
ret = snd_soc_dapm_sync_unlocked(dapm);
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_sync);
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
int ret;
- mutex_lock_nested(&rtd->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(rtd->card);
ret = dapm_update_dai_unlocked(substream, params, dai);
- mutex_unlock(&rtd->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(rtd->card);
return ret;
}
{
int i, ret = 0;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
for (i = 0; i < num; i++) {
int r = snd_soc_dapm_add_route(dapm, route);
if (r < 0)
ret = r;
route++;
}
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
{
int i;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
for (i = 0; i < num; i++) {
snd_soc_dapm_del_route(dapm, route);
route++;
}
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return 0;
}
int i;
int ret = 0;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
+ snd_soc_dapm_mutex_lock_root(dapm);
for (i = 0; i < num; i++) {
int err = snd_soc_dapm_weak_route(dapm, route);
if (err)
ret = err;
route++;
}
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
struct snd_soc_dapm_widget *w;
unsigned int val;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
+ snd_soc_dapm_mutex_lock_root(card);
for_each_card_widgets(card, w)
{
sizeof(struct snd_kcontrol *),
GFP_KERNEL);
if (!w->kcontrols) {
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
return -ENOMEM;
}
}
}
dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets);
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
- struct snd_soc_card *card = dapm->card;
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int reg = mc->reg;
unsigned int invert = mc->invert;
unsigned int reg_val, val, rval = 0;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
if (dapm_kcontrol_is_powered(kcontrol) && reg != SND_SOC_NOPM) {
reg_val = soc_dapm_read(dapm, reg);
val = (reg_val >> shift) & mask;
if (snd_soc_volsw_is_stereo(mc))
rval = (reg_val >> width) & mask;
}
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
if (invert)
ucontrol->value.integer.value[0] = max - val;
rval = max - rval;
}
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
/* This assumes field width < (bits in unsigned int / 2) */
if (width > sizeof(unsigned int) * 8 / 2)
card->update = NULL;
}
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
if (ret > 0)
snd_soc_dpcm_runtime_update(card);
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
- struct snd_soc_card *card = dapm->card;
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int reg_val, val;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
if (e->reg != SND_SOC_NOPM && dapm_kcontrol_is_powered(kcontrol)) {
reg_val = soc_dapm_read(dapm, e->reg);
} else {
reg_val = dapm_kcontrol_get_value(kcontrol);
}
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
val = (reg_val >> e->shift_l) & e->mask;
ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(e, val);
mask |= e->mask << e->shift_r;
}
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
change = dapm_kcontrol_set_value(kcontrol, val);
card->update = NULL;
}
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
if (ret > 0)
snd_soc_dpcm_runtime_update(card);
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
const char *pin = (const char *)kcontrol->private_value;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
ucontrol->value.integer.value[0] =
snd_soc_dapm_get_pin_status(&card->dapm, pin);
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
return 0;
}
const char *pin = (const char *)kcontrol->private_value;
int ret;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
ret = __snd_soc_dapm_set_pin(&card->dapm, pin,
!!ucontrol->value.integer.value[0]);
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
snd_soc_dapm_sync(&card->dapm);
return ret;
{
struct snd_soc_dapm_widget *w;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
w = snd_soc_dapm_new_control_unlocked(dapm, widget);
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return w;
}
int i;
int ret = 0;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
+ snd_soc_dapm_mutex_lock_root(dapm);
for (i = 0; i < num; i++) {
struct snd_soc_dapm_widget *w = snd_soc_dapm_new_control_unlocked(dapm, widget);
if (IS_ERR(w)) {
}
widget++;
}
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
* either party on the link to alter the configuration if
* necessary
*/
- config = rtd->dai_link->params + rtd->params_select;
+ config = rtd->dai_link->c2c_params + rtd->c2c_params_select;
if (!config) {
dev_err(w->dapm->dev, "ASoC: link config missing\n");
ret = -EINVAL;
struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol);
struct snd_soc_pcm_runtime *rtd = w->priv;
- ucontrol->value.enumerated.item[0] = rtd->params_select;
+ ucontrol->value.enumerated.item[0] = rtd->c2c_params_select;
return 0;
}
if (w->power)
return -EBUSY;
- if (ucontrol->value.enumerated.item[0] == rtd->params_select)
+ if (ucontrol->value.enumerated.item[0] == rtd->c2c_params_select)
return 0;
- if (ucontrol->value.enumerated.item[0] >= rtd->dai_link->num_params)
+ if (ucontrol->value.enumerated.item[0] >= rtd->dai_link->num_c2c_params)
return -EINVAL;
- rtd->params_select = ucontrol->value.enumerated.item[0];
+ rtd->c2c_params_select = ucontrol->value.enumerated.item[0];
return 1;
}
static void
snd_soc_dapm_free_kcontrol(struct snd_soc_card *card,
unsigned long *private_value,
- int num_params,
+ int num_c2c_params,
const char **w_param_text)
{
int count;
if (!w_param_text)
return;
- for (count = 0 ; count < num_params; count++)
+ for (count = 0 ; count < num_c2c_params; count++)
devm_kfree(card->dev, (void *)w_param_text[count]);
devm_kfree(card->dev, w_param_text);
}
static struct snd_kcontrol_new *
snd_soc_dapm_alloc_kcontrol(struct snd_soc_card *card,
char *link_name,
- const struct snd_soc_pcm_stream *params,
- int num_params, const char **w_param_text,
+ const struct snd_soc_pcm_stream *c2c_params,
+ int num_c2c_params, const char **w_param_text,
unsigned long *private_value)
{
struct soc_enum w_param_enum[] = {
snd_soc_dapm_dai_link_put),
};
struct snd_kcontrol_new *kcontrol_news;
- const struct snd_soc_pcm_stream *config = params;
+ const struct snd_soc_pcm_stream *config = c2c_params;
int count;
- for (count = 0 ; count < num_params; count++) {
+ for (count = 0 ; count < num_c2c_params; count++) {
if (!config->stream_name) {
dev_warn(card->dapm.dev,
"ASoC: anonymous config %d for dai link %s\n",
config++;
}
- w_param_enum[0].items = num_params;
+ w_param_enum[0].items = num_c2c_params;
w_param_enum[0].texts = w_param_text;
*private_value =
return kcontrol_news;
outfree_w_param:
- snd_soc_dapm_free_kcontrol(card, private_value, num_params, w_param_text);
+ snd_soc_dapm_free_kcontrol(card, private_value, num_c2c_params, w_param_text);
return NULL;
}
w_param_text = NULL;
kcontrol_news = NULL;
num_kcontrols = 0;
- if (rtd->dai_link->num_params > 1) {
+ if (rtd->dai_link->num_c2c_params > 1) {
w_param_text = devm_kcalloc(card->dev,
- rtd->dai_link->num_params,
+ rtd->dai_link->num_c2c_params,
sizeof(char *), GFP_KERNEL);
if (!w_param_text)
goto param_fail;
num_kcontrols = 1;
kcontrol_news = snd_soc_dapm_alloc_kcontrol(card, link_name,
- rtd->dai_link->params,
- rtd->dai_link->num_params,
+ rtd->dai_link->c2c_params,
+ rtd->dai_link->num_c2c_params,
w_param_text, &private_value);
if (!kcontrol_news)
goto param_fail;
outfree_kcontrol_news:
devm_kfree(card->dev, (void *)template.kcontrol_news);
snd_soc_dapm_free_kcontrol(card, &private_value,
- rtd->dai_link->num_params, w_param_text);
+ rtd->dai_link->num_c2c_params, w_param_text);
param_fail:
devm_kfree(card->dev, link_name);
name_fail:
snd_soc_dapm_add_path(dapm, src, sink, NULL, NULL);
}
+static int get_stream_cpu(struct snd_soc_dai_link *dai_link, int stream)
+{
+ /*
+ * [Normal]
+ *
+ * Playback
+ * CPU : SNDRV_PCM_STREAM_PLAYBACK
+ * Codec: SNDRV_PCM_STREAM_PLAYBACK
+ *
+ * Playback
+ * CPU : SNDRV_PCM_STREAM_CAPTURE
+ * Codec: SNDRV_PCM_STREAM_CAPTURE
+ */
+ if (!dai_link->c2c_params)
+ return stream;
+
+ /*
+ * [Codec2Codec]
+ *
+ * Playback
+ * CPU : SNDRV_PCM_STREAM_CAPTURE
+ * Codec: SNDRV_PCM_STREAM_PLAYBACK
+ *
+ * Capture
+ * CPU : SNDRV_PCM_STREAM_PLAYBACK
+ * Codec: SNDRV_PCM_STREAM_CAPTURE
+ */
+ if (stream == SNDRV_PCM_STREAM_CAPTURE)
+ return SNDRV_PCM_STREAM_PLAYBACK;
+
+ return SNDRV_PCM_STREAM_CAPTURE;
+}
+
static void dapm_connect_dai_pair(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *codec_dai,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_dai_link *dai_link = rtd->dai_link;
- struct snd_soc_dapm_widget *dai, *codec, *playback_cpu, *capture_cpu;
- struct snd_pcm_substream *substream;
- struct snd_pcm_str *streams = rtd->pcm->streams;
+ struct snd_soc_dapm_widget *codec, *cpu;
+ struct snd_soc_dai *src_dai[] = { cpu_dai, codec_dai };
+ struct snd_soc_dai *sink_dai[] = { codec_dai, cpu_dai };
+ struct snd_soc_dapm_widget **src[] = { &cpu, &codec };
+ struct snd_soc_dapm_widget **sink[] = { &codec, &cpu };
+ char *widget_name[] = { "playback", "capture" };
int stream;
- if (dai_link->params) {
- playback_cpu = snd_soc_dai_get_widget_capture(cpu_dai);
- capture_cpu = snd_soc_dai_get_widget_playback(cpu_dai);
- } else {
- playback_cpu = snd_soc_dai_get_widget_playback(cpu_dai);
- capture_cpu = snd_soc_dai_get_widget_capture(cpu_dai);
- }
+ for_each_pcm_streams(stream) {
+ int stream_cpu, stream_codec;
- /* connect BE DAI playback if widgets are valid */
- stream = SNDRV_PCM_STREAM_PLAYBACK;
- codec = snd_soc_dai_get_widget(codec_dai, stream);
+ stream_cpu = get_stream_cpu(dai_link, stream);
+ stream_codec = stream;
- if (playback_cpu && codec) {
- if (dai_link->params && !rtd->c2c_widget[stream]) {
- substream = streams[stream].substream;
- dai = snd_soc_dapm_new_dai(card, substream, "playback");
- if (IS_ERR(dai))
- goto capture;
- rtd->c2c_widget[stream] = dai;
- }
+ /* connect BE DAI playback if widgets are valid */
+ cpu = snd_soc_dai_get_widget(cpu_dai, stream_cpu);
+ codec = snd_soc_dai_get_widget(codec_dai, stream_codec);
- dapm_connect_dai_routes(&card->dapm, cpu_dai, playback_cpu,
- rtd->c2c_widget[stream],
- codec_dai, codec);
- }
+ if (!cpu || !codec)
+ continue;
-capture:
- /* connect BE DAI capture if widgets are valid */
- stream = SNDRV_PCM_STREAM_CAPTURE;
- codec = snd_soc_dai_get_widget(codec_dai, stream);
+ /* special handling for [Codec2Codec] */
+ if (dai_link->c2c_params && !rtd->c2c_widget[stream]) {
+ struct snd_pcm_substream *substream = rtd->pcm->streams[stream].substream;
+ struct snd_soc_dapm_widget *dai = snd_soc_dapm_new_dai(card, substream,
+ widget_name[stream]);
- if (codec && capture_cpu) {
- if (dai_link->params && !rtd->c2c_widget[stream]) {
- substream = streams[stream].substream;
- dai = snd_soc_dapm_new_dai(card, substream, "capture");
if (IS_ERR(dai))
- return;
+ continue;
+
rtd->c2c_widget[stream] = dai;
}
- dapm_connect_dai_routes(&card->dapm, codec_dai, codec,
+ dapm_connect_dai_routes(&card->dapm, src_dai[stream], *src[stream],
rtd->c2c_widget[stream],
- cpu_dai, capture_cpu);
+ sink_dai[stream], *sink[stream]);
}
}
{
struct snd_soc_card *card = rtd->card;
- mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(card);
soc_dapm_stream_event(rtd, stream, event);
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
}
void snd_soc_dapm_stream_stop(struct snd_soc_pcm_runtime *rtd, int stream)
{
int ret;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
ret = snd_soc_dapm_set_pin(dapm, pin, 1);
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
{
int ret;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, pin);
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
{
int ret;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
ret = snd_soc_dapm_set_pin(dapm, pin, 0);
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
{
int ret;
- mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
+ snd_soc_dapm_mutex_lock(dapm);
ret = snd_soc_dapm_set_pin(dapm, pin, 0);
- mutex_unlock(&dapm->card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
LIST_HEAD(down_list);
int powerdown = 0;
- mutex_lock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_lock_root(card);
for_each_card_widgets(dapm->card, w) {
if (w->dapm != dapm)
SND_SOC_BIAS_STANDBY);
}
- mutex_unlock(&card->dapm_mutex);
+ snd_soc_dapm_mutex_unlock(card);
}
/*
ret = request_any_context_irq(gpiod_to_irq(gpios[i].desc),
gpio_handler,
+ IRQF_SHARED |
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING,
gpios[i].name,
return ret;
}
-static inline void snd_soc_dpcm_mutex_lock(struct snd_soc_pcm_runtime *rtd)
-{
- mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
-}
-
-static inline void snd_soc_dpcm_mutex_unlock(struct snd_soc_pcm_runtime *rtd)
-{
- mutex_unlock(&rtd->card->pcm_mutex);
-}
-
-#define snd_soc_dpcm_mutex_assert_held(rtd) \
- lockdep_assert_held(&(rtd)->card->pcm_mutex)
-
static inline void snd_soc_dpcm_stream_lock_irq(struct snd_soc_pcm_runtime *rtd,
int stream)
{
dpcm->be = be;
dpcm->fe = fe;
- be->dpcm[stream].runtime = fe->dpcm[stream].runtime;
dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW;
snd_soc_dpcm_stream_lock_irq(fe, stream);
list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients);
struct snd_soc_dapm_widget_list *list = *list_;
struct snd_soc_pcm_runtime *be;
struct snd_soc_dapm_widget *widget;
+ struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream);
int i, new = 0, err;
/* don't connect if FE is not running */
- if (!fe->dpcm[stream].runtime && !fe->fe_compr)
+ if (!fe_substream->runtime && !fe->fe_compr)
return new;
/* Create any new FE <--> BE connections */
int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream)
{
+ struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream);
struct snd_soc_pcm_runtime *be;
struct snd_soc_dpcm *dpcm;
int err, count = 0;
dev_dbg(be->dev, "ASoC: open %s BE %s\n",
stream ? "capture" : "playback", be->dai_link->name);
- be_substream->runtime = be->dpcm[stream].runtime;
+ be_substream->runtime = fe_substream->runtime;
err = __soc_pcm_open(be, be_substream);
if (err < 0) {
be->dpcm[stream].users--;
struct snd_soc_pcm_runtime *fe;
int ret = 0;
- mutex_lock_nested(&card->pcm_mutex, card->pcm_subclass);
+ snd_soc_dpcm_mutex_lock(card);
/* shutdown all old paths first */
for_each_card_rtds(card, fe) {
ret = soc_dpcm_fe_runtime_update(fe, 0);
}
out:
- mutex_unlock(&card->pcm_mutex);
+ snd_soc_dpcm_mutex_unlock(card);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_dpcm_runtime_update);
dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE;
dpcm_be_disconnect(fe, stream);
-
- fe->dpcm[stream].runtime = NULL;
}
static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream)
int stream = fe_substream->stream;
snd_soc_dpcm_mutex_lock(fe);
- fe->dpcm[stream].runtime = fe_substream->runtime;
ret = dpcm_path_get(fe, stream, &list);
if (ret < 0)
struct snd_soc_dai *codec_dai;
/* Adapt stream for codec2codec links */
- int cpu_capture = rtd->dai_link->params ?
+ int cpu_capture = rtd->dai_link->c2c_params ?
SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE;
- int cpu_playback = rtd->dai_link->params ?
+ int cpu_playback = rtd->dai_link->c2c_params ?
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
for_each_rtd_codec_dais(rtd, i, codec_dai) {
int ret;
/* create the PCM */
- if (rtd->dai_link->params) {
+ if (rtd->dai_link->c2c_params) {
snprintf(new_name, sizeof(new_name), "codec2codec(%s)",
rtd->dai_link->stream_name);
* don't interface with the outside world or application layer
* we don't have to do any special handling on close.
*/
- if (!rtd->dai_link->params)
+ if (!rtd->dai_link->c2c_params)
rtd->close_delayed_work_func = snd_soc_close_delayed_work;
rtd->pcm = pcm;
pcm->private_data = rtd;
pcm->no_device_suspend = true;
- if (rtd->dai_link->no_pcm || rtd->dai_link->params) {
+ if (rtd->dai_link->no_pcm || rtd->dai_link->c2c_params) {
if (playback)
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
if (capture)
link->cpus = &dlc[0];
link->codecs = &dlc[1];
- link->platforms = &dlc[2];
link->num_cpus = 1;
link->num_codecs = 1;
- link->num_platforms = 1;
link->dobj.index = tplg->index;
link->dobj.type = SND_SOC_DOBJ_DAI_LINK;
link->codecs->name = "snd-soc-dummy";
link->codecs->dai_name = "snd-soc-dummy-dai";
+ /*
+ * Many topology is assuming link has Platform.
+ * This might be overwritten at soc_tplg_dai_link_load().
+ */
+ link->platforms = &dlc[2];
link->platforms->name = "snd-soc-dummy";
+ link->num_platforms = 1;
/* enable DPCM */
link->dynamic = 1;
goto err;
}
- ret = snd_soc_add_pcm_runtime(tplg->comp->card, link);
+ ret = snd_soc_add_pcm_runtimes(tplg->comp->card, link, 1);
if (ret < 0) {
dev_err(tplg->dev, "ASoC: adding FE link failed\n");
goto err;
#include "acp-dsp-offset.h"
#include <sound/sof/xtensa.h>
-int acp_dai_probe(struct snd_soc_dai *dai)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component);
- const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
- unsigned int val;
-
- val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->i2s_pin_config_offset);
- if (val != desc->i2s_mode) {
- dev_err(sdev->dev, "I2S Mode is not supported (I2S_PIN_CONFIG: %#x)\n", val);
- return -EINVAL;
- }
-
- return 0;
-}
-EXPORT_SYMBOL_NS(acp_dai_probe, SND_SOC_SOF_AMD_COMMON);
-
/**
* amd_sof_ipc_dump() - This function is called when IPC tx times out.
* @sdev: SOF device.
.pcm_open = acp_pcm_open,
.pcm_close = acp_pcm_close,
.pcm_hw_params = acp_pcm_hw_params,
+ .pcm_pointer = acp_pcm_pointer,
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
platform_params->use_phy_address = true;
platform_params->phy_addr = stream->reg_offset;
platform_params->stream_tag = stream->stream_tag;
+ platform_params->cont_update_posn = 1;
/* write buffer size of stream in scratch memory */
return acp_dsp_stream_put(sdev, stream);
}
EXPORT_SYMBOL_NS(acp_pcm_close, SND_SOC_SOF_AMD_COMMON);
+
+snd_pcm_uframes_t acp_pcm_pointer(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *scomp = sdev->component;
+ struct snd_sof_pcm_stream *stream;
+ struct sof_ipc_stream_posn posn;
+ struct snd_sof_pcm *spcm;
+ snd_pcm_uframes_t pos;
+ int ret;
+
+ spcm = snd_sof_find_spcm_dai(scomp, rtd);
+ if (!spcm) {
+ dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n",
+ rtd->dai_link->id);
+ return 0;
+ }
+
+ stream = &spcm->stream[substream->stream];
+ ret = snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn));
+ if (ret < 0) {
+ dev_warn(sdev->dev, "failed to read stream position: %d\n", ret);
+ return 0;
+ }
+
+ memcpy(&stream->posn, &posn, sizeof(posn));
+ pos = spcm->stream[substream->stream].posn.host_posn;
+ pos = bytes_to_frames(substream->runtime, pos);
+
+ return pos;
+}
+EXPORT_SYMBOL_NS(acp_pcm_pointer, SND_SOC_SOF_AMD_COMMON);
unsigned int addr;
int ret;
+ chip = get_chip_info(sdev->pdata);
+ if (!chip) {
+ dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device);
+ return -EIO;
+ }
adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data),
GFP_KERNEL);
if (!adata)
return -ENOMEM;
adata->dev = sdev;
+ adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec",
+ PLATFORM_DEVID_NONE, NULL, 0);
+ if (IS_ERR(adata->dmic_dev)) {
+ dev_err(sdev->dev, "failed to register platform for dmic codec\n");
+ return PTR_ERR(adata->dmic_dev);
+ }
addr = pci_resource_start(pci, ACP_DSP_BAR);
sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR));
if (!sdev->bar[ACP_DSP_BAR]) {
dev_err(sdev->dev, "ioremap error\n");
- return -ENXIO;
+ ret = -ENXIO;
+ goto unregister_dev;
}
pci_set_master(pci);
sdev->pdata->hw_pdata = adata;
-
- chip = get_chip_info(sdev->pdata);
- if (!chip) {
- dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device);
- return -EIO;
- }
-
adata->smn_dev = pci_get_device(PCI_VENDOR_ID_AMD, chip->host_bridge_id, NULL);
if (!adata->smn_dev) {
dev_err(sdev->dev, "Failed to get host bridge device\n");
- return -ENODEV;
+ ret = -ENODEV;
+ goto unregister_dev;
}
sdev->ipc_irq = pci->irq;
if (ret < 0) {
dev_err(sdev->dev, "failed to register IRQ %d\n",
sdev->ipc_irq);
- pci_dev_put(adata->smn_dev);
- return ret;
+ goto free_smn_dev;
}
ret = acp_init(sdev);
- if (ret < 0) {
- free_irq(sdev->ipc_irq, sdev);
- pci_dev_put(adata->smn_dev);
- return ret;
- }
+ if (ret < 0)
+ goto free_ipc_irq;
sdev->dsp_box.offset = 0;
sdev->dsp_box.size = BOX_SIZE_512;
acp_dsp_stream_init(sdev);
return 0;
+
+free_ipc_irq:
+ free_irq(sdev->ipc_irq, sdev);
+free_smn_dev:
+ pci_dev_put(adata->smn_dev);
+unregister_dev:
+ platform_device_unregister(adata->dmic_dev);
+ return ret;
}
EXPORT_SYMBOL_NS(amd_sof_acp_probe, SND_SOC_SOF_AMD_COMMON);
if (sdev->ipc_irq)
free_irq(sdev->ipc_irq, sdev);
+ if (adata->dmic_dev)
+ platform_device_unregister(adata->dmic_dev);
+
return acp_reset(sdev);
}
EXPORT_SYMBOL_NS(amd_sof_acp_remove, SND_SOC_SOF_AMD_COMMON);
struct sof_amd_acp_desc {
unsigned int rev;
unsigned int host_bridge_id;
- unsigned int i2s_mode;
u32 pgfsm_base;
u32 ext_intr_stat;
u32 dsp_intr_base;
u32 sram_pte_offset;
- u32 i2s_pin_config_offset;
u32 hw_semaphore_offset;
u32 acp_clkmux_sel;
u32 fusion_dsp_offset;
/* Common device data struct for ACP devices */
struct acp_dev_data {
struct snd_sof_dev *dev;
+ /* DMIC device */
+ struct platform_device *dmic_dev;
unsigned int fw_bin_size;
unsigned int fw_data_bin_size;
u32 fw_bin_page_count;
int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_sof_platform_stream_params *platform_params);
+snd_pcm_uframes_t acp_pcm_pointer(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream);
extern struct snd_sof_dsp_ops sof_acp_common_ops;
extern struct snd_sof_dsp_ops sof_rembrandt_ops;
int sof_rembrandt_ops_init(struct snd_sof_dev *sdev);
-int acp_dai_probe(struct snd_soc_dai *dai);
struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev);
/* Machine configuration */
int snd_amd_acp_find_config(struct pci_dev *pci);
#define ACP6x_REG_START 0x1240000
#define ACP6x_REG_END 0x125C000
-static struct platform_device *dmic_dev;
-static struct platform_device *pdev;
-
-static const struct resource rembrandt_res[] = {
- {
- .start = 0,
- .end = ACP6x_REG_END - ACP6x_REG_START,
- .name = "acp_mem",
- .flags = IORESOURCE_MEM,
- },
- {
- .start = 0,
- .end = 0,
- .name = "acp_dai_irq",
- .flags = IORESOURCE_IRQ,
- },
-};
-
static const struct sof_amd_acp_desc rembrandt_chip_info = {
.rev = 6,
.host_bridge_id = HOST_BRIDGE_RMB,
- .i2s_mode = 0x0a,
.pgfsm_base = ACP6X_PGFSM_BASE,
.ext_intr_stat = ACP6X_EXT_INTR_STAT,
.dsp_intr_base = ACP6X_DSP_SW_INTR_BASE,
.sram_pte_offset = ACP6X_SRAM_PTE_OFFSET,
- .i2s_pin_config_offset = ACP6X_I2S_PIN_CONFIG,
.hw_semaphore_offset = ACP6X_AXI2DAGB_SEM_0,
.acp_clkmux_sel = ACP6X_CLKMUX_SEL,
.fusion_dsp_offset = ACP6X_DSP_FUSION_RUNSTALL,
static int acp_pci_rmb_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
- struct platform_device_info pdevinfo;
- struct device *dev = &pci->dev;
- const struct resource *res_i2s;
- struct resource *res;
- unsigned int flag, i, addr;
- int ret;
+ unsigned int flag;
flag = snd_amd_acp_find_config(pci);
if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC)
return -ENODEV;
- ret = sof_pci_probe(pci, pci_id);
- if (ret != 0)
- return ret;
-
- dmic_dev = platform_device_register_data(dev, "dmic-codec", PLATFORM_DEVID_NONE, NULL, 0);
- if (IS_ERR(dmic_dev)) {
- dev_err(dev, "failed to create DMIC device\n");
- sof_pci_remove(pci);
- return PTR_ERR(dmic_dev);
- }
-
- /* Register platform device only if flag set to FLAG_AMD_SOF_ONLY_DMIC */
- if (flag != FLAG_AMD_SOF_ONLY_DMIC)
- return 0;
-
- addr = pci_resource_start(pci, 0);
- res = devm_kzalloc(&pci->dev, sizeof(struct resource) * ARRAY_SIZE(rembrandt_res),
- GFP_KERNEL);
- if (!res) {
- platform_device_unregister(dmic_dev);
- sof_pci_remove(pci);
- return -ENOMEM;
- }
-
- res_i2s = rembrandt_res;
- for (i = 0; i < ARRAY_SIZE(rembrandt_res); i++, res_i2s++) {
- res[i].name = res_i2s->name;
- res[i].flags = res_i2s->flags;
- res[i].start = addr + res_i2s->start;
- res[i].end = addr + res_i2s->end;
- if (res_i2s->flags == IORESOURCE_IRQ) {
- res[i].start = pci->irq;
- res[i].end = res[i].start;
- }
- }
-
- memset(&pdevinfo, 0, sizeof(pdevinfo));
-
- /*
- * We have common PCI driver probe for ACP device but we have to support I2S without SOF
- * for some distributions. Register platform device that will be used to support non dsp
- * ACP's audio ends points on some machines.
- */
- pdevinfo.name = "acp_asoc_rembrandt";
- pdevinfo.id = 0;
- pdevinfo.parent = &pci->dev;
- pdevinfo.num_res = ARRAY_SIZE(rembrandt_res);
- pdevinfo.res = &res[0];
-
- pdev = platform_device_register_full(&pdevinfo);
- if (IS_ERR(pdev)) {
- dev_err(&pci->dev, "cannot register %s device\n", pdevinfo.name);
- platform_device_unregister(dmic_dev);
- sof_pci_remove(pci);
- ret = PTR_ERR(pdev);
- }
-
- return ret;
+ return sof_pci_probe(pci, pci_id);
};
static void acp_pci_rmb_remove(struct pci_dev *pci)
{
- if (dmic_dev)
- platform_device_unregister(dmic_dev);
- if (pdev)
- platform_device_unregister(pdev);
-
sof_pci_remove(pci);
}
#define ACP3x_REG_START 0x1240000
#define ACP3x_REG_END 0x125C000
-static struct platform_device *dmic_dev;
-static struct platform_device *pdev;
-
-static const struct resource renoir_res[] = {
- {
- .start = 0,
- .end = ACP3x_REG_END - ACP3x_REG_START,
- .name = "acp_mem",
- .flags = IORESOURCE_MEM,
- },
- {
- .start = 0,
- .end = 0,
- .name = "acp_dai_irq",
- .flags = IORESOURCE_IRQ,
- },
-};
-
static const struct sof_amd_acp_desc renoir_chip_info = {
.rev = 3,
.host_bridge_id = HOST_BRIDGE_CZN,
- .i2s_mode = 0x04,
.pgfsm_base = ACP3X_PGFSM_BASE,
.ext_intr_stat = ACP3X_EXT_INTR_STAT,
.dsp_intr_base = ACP3X_DSP_SW_INTR_BASE,
.sram_pte_offset = ACP3X_SRAM_PTE_OFFSET,
- .i2s_pin_config_offset = ACP3X_I2S_PIN_CONFIG,
.hw_semaphore_offset = ACP3X_AXI2DAGB_SEM_0,
.acp_clkmux_sel = ACP3X_CLKMUX_SEL,
};
static int acp_pci_rn_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
- struct platform_device_info pdevinfo;
- struct device *dev = &pci->dev;
- const struct resource *res_i2s;
- struct resource *res;
- unsigned int flag, i, addr;
- int ret;
+ unsigned int flag;
flag = snd_amd_acp_find_config(pci);
if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC)
return -ENODEV;
- ret = sof_pci_probe(pci, pci_id);
- if (ret != 0)
- return ret;
-
- dmic_dev = platform_device_register_data(dev, "dmic-codec", PLATFORM_DEVID_NONE, NULL, 0);
- if (IS_ERR(dmic_dev)) {
- dev_err(dev, "failed to create DMIC device\n");
- sof_pci_remove(pci);
- return PTR_ERR(dmic_dev);
- }
-
- /* Register platform device only if flag set to FLAG_AMD_SOF_ONLY_DMIC */
- if (flag != FLAG_AMD_SOF_ONLY_DMIC)
- return 0;
-
- addr = pci_resource_start(pci, 0);
- res = devm_kzalloc(&pci->dev, sizeof(struct resource) * ARRAY_SIZE(renoir_res), GFP_KERNEL);
- if (!res) {
- sof_pci_remove(pci);
- platform_device_unregister(dmic_dev);
- return -ENOMEM;
- }
-
- res_i2s = renoir_res;
- for (i = 0; i < ARRAY_SIZE(renoir_res); i++, res_i2s++) {
- res[i].name = res_i2s->name;
- res[i].flags = res_i2s->flags;
- res[i].start = addr + res_i2s->start;
- res[i].end = addr + res_i2s->end;
- if (res_i2s->flags == IORESOURCE_IRQ) {
- res[i].start = pci->irq;
- res[i].end = res[i].start;
- }
- }
-
- memset(&pdevinfo, 0, sizeof(pdevinfo));
-
- /*
- * We have common PCI driver probe for ACP device but we have to support I2S without SOF
- * for some distributions. Register platform device that will be used to support non dsp
- * ACP's audio ends points on some machines.
- */
-
- pdevinfo.name = "acp_asoc_renoir";
- pdevinfo.id = 0;
- pdevinfo.parent = &pci->dev;
- pdevinfo.num_res = ARRAY_SIZE(renoir_res);
- pdevinfo.res = &res[0];
-
- pdev = platform_device_register_full(&pdevinfo);
- if (IS_ERR(pdev)) {
- dev_err(&pci->dev, "cannot register %s device\n", pdevinfo.name);
- sof_pci_remove(pci);
- platform_device_unregister(dmic_dev);
- ret = PTR_ERR(pdev);
- }
-
- return ret;
+ return sof_pci_probe(pci, pci_id);
};
static void acp_pci_rn_remove(struct pci_dev *pci)
{
- if (dmic_dev)
- platform_device_unregister(dmic_dev);
- if (pdev)
- platform_device_unregister(pdev);
-
return sof_pci_remove(pci);
}
.rate_min = 8000,
.rate_max = 48000,
},
- .probe = &acp_dai_probe,
},
[I2S_BT_INSTANCE] = {
.rate_min = 8000,
.rate_max = 48000,
},
- .probe = &acp_dai_probe,
},
[I2S_SP_INSTANCE] = {
.rate_min = 8000,
.rate_max = 48000,
},
- .probe = &acp_dai_probe,
},
[PDM_DMIC_INSTANCE] = {
.rate_min = 8000,
.rate_max = 96000,
},
- .probe = &acp_dai_probe,
},
};
.rate_min = 8000,
.rate_max = 48000,
},
- .probe = &acp_dai_probe,
},
[I2S_SP_INSTANCE] = {
.rate_min = 8000,
.rate_max = 48000,
},
- .probe = &acp_dai_probe,
},
[PDM_DMIC_INSTANCE] = {
.rate_min = 8000,
.rate_max = 96000,
},
- .probe = &acp_dai_probe,
},
};
struct sof_compr_stream *sstream = cstream->runtime->private_data;
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
struct snd_sof_pcm *spcm;
int ret = 0;
stream.comp_id = spcm->stream[cstream->direction].comp_id;
if (spcm->prepared[cstream->direction]) {
- ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream),
- &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
if (!ret)
spcm->prepared[cstream->direction] = false;
}
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_soc_pcm_runtime *rtd = cstream->private_data;
struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
struct snd_sof_pcm *spcm;
spcm = snd_sof_find_spcm_dai(component, rtd);
break;
}
- return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream),
- &reply, sizeof(reply));
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
}
static int sof_compr_copy_playback(struct snd_compr_runtime *rtd,
/* set up platform component driver */
snd_sof_new_platform_drv(sdev);
+ if (sdev->dspless_mode_selected) {
+ sof_set_fw_state(sdev, SOF_DSPLESS_MODE);
+ goto skip_dsp_init;
+ }
+
/* register any debug/trace capabilities */
ret = snd_sof_dbg_init(sdev);
if (ret < 0) {
dev_dbg(sdev->dev, "SOF firmware trace disabled\n");
}
+skip_dsp_init:
/* hereafter all FW boot flows are for PM reasons */
sdev->first_boot = false;
if (sof_core_debug)
dev_info(dev, "sof_debug value: %#x\n", sof_core_debug);
+ if (sof_debug_check_flag(SOF_DBG_DSPLESS_MODE)) {
+ if (plat_data->desc->dspless_mode_supported) {
+ dev_info(dev, "Switching to DSPless mode\n");
+ sdev->dspless_mode_selected = true;
+ } else {
+ dev_info(dev, "DSPless mode is not supported by the platform\n");
+ }
+ }
+
/* check IPC support */
if (!(BIT(plat_data->ipc_type) & plat_data->desc->ipc_supported_mask)) {
dev_err(dev, "ipc_type %d is not supported on this platform, mask is %#x\n",
return ret;
/* check all mandatory ops */
- if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run ||
- !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write ||
- !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware ||
- !sof_ops(sdev)->ipc_msg_data) {
+ if (!sof_ops(sdev) || !sof_ops(sdev)->probe) {
+ sof_ops_free(sdev);
+ dev_err(dev, "missing mandatory ops\n");
+ return -EINVAL;
+ }
+
+ if (!sdev->dspless_mode_selected &&
+ (!sof_ops(sdev)->run || !sof_ops(sdev)->block_read ||
+ !sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg ||
+ !sof_ops(sdev)->load_firmware || !sof_ops(sdev)->ipc_msg_data)) {
sof_ops_free(sdev);
- dev_err(dev, "error: missing mandatory ops\n");
+ dev_err(dev, "missing mandatory DSP ops\n");
return -EINVAL;
}
const char *name;
} fw_state_dbg[] = {
{SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"},
+ {SOF_DSPLESS_MODE, "SOF_DSPLESS_MODE"},
{SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"},
{SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"},
{SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"},
select SND_INTEL_DSP_CONFIG
select SND_SOC_SOF_HDA_LINK_BASELINE
select SND_SOC_SOF_HDA_PROBES
+ select SND_SOC_SOF_HDA_MLINK if SND_SOC_SOF_HDA_LINK
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level.
+
+config SND_SOC_SOF_HDA_MLINK
+ tristate
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \
hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \
- hda-dai.o hda-bus.o hda-mlink.o \
+ hda-dai.o hda-dai-ops.o hda-bus.o \
skl.o hda-loader-skl.o \
apl.o cnl.o tgl.o icl.o mtl.o hda-common-ops.o
+snd-sof-intel-hda-mlink-objs := hda-mlink.o
+
snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o
snd-sof-intel-hda-objs := hda-codec.o
obj-$(CONFIG_SND_SOC_SOF_BAYTRAIL) += snd-sof-acpi-intel-byt.o
obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-acpi-intel-bdw.o
obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o
+obj-$(CONFIG_SND_SOC_SOF_HDA_MLINK) += snd-sof-intel-hda-mlink.o
obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o
snd-sof-pci-intel-tng-objs := pci-tng.o
/* debug */
sof_apl_ops.ipc_dump = hda_ipc_dump;
+
+ sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) {
/* debug */
sof_apl_ops.ipc_dump = hda_ipc4_dump;
+
+ sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* set DAI driver ops */
/* debug */
sof_cnl_ops.ipc_dump = cnl_ipc_dump;
+
+ sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) {
/* debug */
sof_cnl_ops.ipc_dump = cnl_ipc4_dump;
+
+ sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* set DAI driver ops */
.runtime_resume = hda_dsp_runtime_resume,
.runtime_idle = hda_dsp_runtime_idle,
.set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume,
- .set_power_state = hda_dsp_set_power_state,
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda_component.h>
+#include <sound/hda-mlink.h>
#include "../ops.h"
#include "hda.h"
*/
int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
u32 val;
/* enable/disable audio dsp clock gating */
val = enable ? PCI_CGCTL_ADSPDCGE : 0;
snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val);
- /* enable/disable DMI Link L1 support */
+ /* disable the DMI link when requested. But enable only if it wasn't disabled previously */
val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0;
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
- HDA_VS_INTEL_EM2_L1SEN, val);
+ if (!enable || !hda->l1_disabled)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
+ HDA_VS_INTEL_EM2_L1SEN, val);
/* enable/disable audio dsp power gating */
val = enable ? 0 : PCI_PGCTL_ADSPPGD;
--- /dev/null
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+
+#include <sound/pcm_params.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/sof/ipc4/header.h>
+#include <uapi/sound/sof/header.h>
+#include "../ipc4-priv.h"
+#include "../ipc4-topology.h"
+#include "../sof-priv.h"
+#include "../sof-audio.h"
+#include "hda.h"
+
+/* These ops are only applicable for the HDA DAI's in their current form */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+/*
+ * This function checks if the host dma channel corresponding
+ * to the link DMA stream_tag argument is assigned to one
+ * of the FEs connected to the BE DAI.
+ */
+static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
+ int dir, int stream_tag)
+{
+ struct snd_pcm_substream *fe_substream;
+ struct hdac_stream *fe_hstream;
+ struct snd_soc_dpcm *dpcm;
+
+ for_each_dpcm_fe(rtd, dir, dpcm) {
+ fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
+ fe_hstream = fe_substream->runtime->private_data;
+ if (fe_hstream->stream_tag == stream_tag)
+ return true;
+ }
+
+ return false;
+}
+
+static struct hdac_ext_stream *
+hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct sof_intel_hda_stream *hda_stream;
+ const struct sof_intel_dsp_desc *chip;
+ struct snd_sof_dev *sdev;
+ struct hdac_ext_stream *res = NULL;
+ struct hdac_stream *hstream = NULL;
+
+ int stream_dir = substream->stream;
+
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "stream type not supported\n");
+ return NULL;
+ }
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(hstream, &bus->stream_list, list) {
+ struct hdac_ext_stream *hext_stream =
+ stream_to_hdac_ext_stream(hstream);
+ if (hstream->direction != substream->stream)
+ continue;
+
+ hda_stream = hstream_to_sof_hda_stream(hext_stream);
+ sdev = hda_stream->sdev;
+ chip = get_chip_info(sdev->pdata);
+
+ /* check if link is available */
+ if (!hext_stream->link_locked) {
+ /*
+ * choose the first available link for platforms that do not have the
+ * PROCEN_FMT_QUIRK set.
+ */
+ if (!(chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) {
+ res = hext_stream;
+ break;
+ }
+
+ if (hstream->opened) {
+ /*
+ * check if the stream tag matches the stream
+ * tag of one of the connected FEs
+ */
+ if (hda_check_fes(rtd, stream_dir,
+ hstream->stream_tag)) {
+ res = hext_stream;
+ break;
+ }
+ } else {
+ res = hext_stream;
+
+ /*
+ * This must be a hostless stream.
+ * So reserve the host DMA channel.
+ */
+ hda_stream->host_reserved = 1;
+ break;
+ }
+ }
+ }
+
+ if (res) {
+ /* Make sure that host and link DMA is decoupled. */
+ snd_hdac_ext_stream_decouple_locked(bus, res, true);
+
+ res->link_locked = 1;
+ res->link_substream = substream;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+
+ return res;
+}
+
+static struct hdac_ext_stream *hda_get_hext_stream(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ return snd_soc_dai_get_dma_data(cpu_dai, substream);
+}
+
+static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_ext_stream *hext_stream;
+
+ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream);
+ if (!hext_stream)
+ return NULL;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream);
+
+ return hext_stream;
+}
+
+static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream);
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
+ snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK);
+}
+
+static void hda_setup_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,
+ unsigned int format_val)
+{
+ snd_hdac_ext_stream_setup(hext_stream, format_val);
+}
+
+static void hda_reset_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream)
+{
+ snd_hdac_ext_stream_reset(hext_stream);
+}
+
+static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+ int ret;
+
+ w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ swidget = w->dobj.private;
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ return ret;
+
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+ break;
+ default:
+ dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hda_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_hdac_ext_stream_start(hext_stream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ snd_hdac_ext_stream_clear(hext_stream);
+ break;
+ default:
+ dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+ int ret;
+
+ w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ swidget = w->dobj.private;
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (pipeline->state != SOF_IPC4_PIPE_PAUSED) {
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ return ret;
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+ }
+
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_RUNNING);
+ if (ret < 0)
+ return ret;
+ pipeline->state = SOF_IPC4_PIPE_RUNNING;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ {
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_RESET);
+ if (ret < 0)
+ return ret;
+
+ pipeline->state = SOF_IPC4_PIPE_RESET;
+ break;
+ }
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = {
+ .get_hext_stream = hda_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .pre_trigger = hda_ipc4_pre_trigger,
+ .trigger = hda_trigger,
+ .post_trigger = hda_ipc4_post_trigger
+};
+
+static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = {
+ .get_hext_stream = hda_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .trigger = hda_trigger,
+};
+
+static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ {
+ struct snd_sof_dai_config_data data = { 0 };
+
+ data.dai_data = DMA_CHAN_INVALID;
+ return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_FREE, &data);
+ }
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hda_dai_widget_dma_ops hda_ipc3_dma_ops = {
+ .get_hext_stream = hda_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .trigger = hda_trigger,
+ .post_trigger = hda_ipc3_post_trigger,
+};
+
+static struct hdac_ext_stream *
+hda_dspless_get_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_stream *hstream = substream->runtime->private_data;
+
+ return stream_to_hdac_ext_stream(hstream);
+}
+
+static void hda_dspless_setup_hext_stream(struct snd_sof_dev *sdev,
+ struct hdac_ext_stream *hext_stream,
+ unsigned int format_val)
+{
+ /*
+ * Save the format_val which was adjusted by the maxbps of the codec.
+ * This information is not available on the FE side since there we are
+ * using dummy_codec.
+ */
+ hext_stream->hstream.format_val = format_val;
+}
+
+static const struct hda_dai_widget_dma_ops hda_dspless_dma_ops = {
+ .get_hext_stream = hda_dspless_get_hext_stream,
+ .setup_hext_stream = hda_dspless_setup_hext_stream,
+};
+
+#endif
+
+const struct hda_dai_widget_dma_ops *
+hda_select_dai_widget_ops(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+ struct snd_sof_dai *sdai;
+
+ if (sdev->dspless_mode_selected)
+ return &hda_dspless_dma_ops;
+
+ sdai = swidget->private;
+
+ switch (sdev->pdata->ipc_type) {
+ case SOF_IPC:
+ {
+ struct sof_dai_private_data *private = sdai->private;
+
+ if (private->dai_config->type == SOF_DAI_INTEL_HDA)
+ return &hda_ipc3_dma_ops;
+ break;
+ }
+ case SOF_INTEL_IPC4:
+ {
+ struct sof_ipc4_copier *ipc4_copier = sdai->private;
+
+ if (ipc4_copier->dai_type == SOF_DAI_INTEL_HDA) {
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (pipeline->use_chain_dma)
+ return &hda_ipc4_chain_dma_ops;
+
+ return &hda_ipc4_dma_ops;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+#endif
+ return NULL;
+}
module_param_named(sof_use_tplg_nhlt, hda_use_tplg_nhlt, bool, 0444);
MODULE_PARM_DESC(sof_use_tplg_nhlt, "SOF topology nhlt override");
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags,
+ struct snd_sof_dai_config_data *data)
+{
+ struct snd_sof_widget *swidget = w->dobj.private;
+ const struct sof_ipc_tplg_ops *tplg_ops;
+ struct snd_soc_component *component;
+ struct snd_sof_dev *sdev;
+ int ret;
-struct hda_pipe_params {
- u32 ch;
- u32 s_freq;
- snd_pcm_format_t format;
- int link_index;
- unsigned int link_bps;
-};
+ if (!swidget)
+ return 0;
-/*
- * This function checks if the host dma channel corresponding
- * to the link DMA stream_tag argument is assigned to one
- * of the FEs connected to the BE DAI.
- */
-static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
- int dir, int stream_tag)
-{
- struct snd_pcm_substream *fe_substream;
- struct hdac_stream *fe_hstream;
- struct snd_soc_dpcm *dpcm;
-
- for_each_dpcm_fe(rtd, dir, dpcm) {
- fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
- fe_hstream = fe_substream->runtime->private_data;
- if (fe_hstream->stream_tag == stream_tag)
- return true;
+ component = swidget->scomp;
+ sdev = snd_soc_component_get_drvdata(component);
+ tplg_ops = sof_ipc_get_ops(sdev, tplg);
+
+ if (tplg_ops && tplg_ops->dai_config) {
+ ret = tplg_ops->dai_config(sdev, swidget, flags, data);
+ if (ret < 0) {
+ dev_err(sdev->dev, "DAI config with flags %x failed for widget %s\n",
+ flags, w->name);
+ return ret;
+ }
}
- return false;
+ return 0;
}
-static struct hdac_ext_stream *
-hda_link_stream_assign(struct hdac_bus *bus,
- struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct sof_intel_hda_stream *hda_stream;
- const struct sof_intel_dsp_desc *chip;
- struct snd_sof_dev *sdev;
- struct hdac_ext_stream *res = NULL;
- struct hdac_stream *hstream = NULL;
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
- int stream_dir = substream->stream;
+static const struct hda_dai_widget_dma_ops *
+hda_dai_get_ops(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component);
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_sof_dai *sdai;
- if (!bus->ppcap) {
- dev_err(bus->dev, "stream type not supported\n");
- return NULL;
- }
+ /*
+ * The swidget parameter of hda_select_dai_widget_ops() is ignored in
+ * case of DSPless mode
+ */
+ if (sdev->dspless_mode_selected)
+ return hda_select_dai_widget_ops(sdev, NULL);
- spin_lock_irq(&bus->reg_lock);
- list_for_each_entry(hstream, &bus->stream_list, list) {
- struct hdac_ext_stream *hext_stream =
- stream_to_hdac_ext_stream(hstream);
- if (hstream->direction != substream->stream)
- continue;
-
- hda_stream = hstream_to_sof_hda_stream(hext_stream);
- sdev = hda_stream->sdev;
- chip = get_chip_info(sdev->pdata);
-
- /* check if link is available */
- if (!hext_stream->link_locked) {
- /*
- * choose the first available link for platforms that do not have the
- * PROCEN_FMT_QUIRK set.
- */
- if (!(chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) {
- res = hext_stream;
- break;
- }
+ sdai = swidget->private;
- if (hstream->opened) {
- /*
- * check if the stream tag matches the stream
- * tag of one of the connected FEs
- */
- if (hda_check_fes(rtd, stream_dir,
- hstream->stream_tag)) {
- res = hext_stream;
- break;
- }
- } else {
- res = hext_stream;
-
- /*
- * This must be a hostless stream.
- * So reserve the host DMA channel.
- */
- hda_stream->host_reserved = 1;
- break;
- }
- }
- }
+ /* select and set the DAI widget ops if not set already */
+ if (!sdai->platform_private) {
+ const struct hda_dai_widget_dma_ops *ops =
+ hda_select_dai_widget_ops(sdev, swidget);
+ if (!ops)
+ return NULL;
- if (res) {
- /* Make sure that host and link DMA is decoupled. */
- snd_hdac_ext_stream_decouple_locked(bus, res, true);
+ /* check if mandatory ops are set */
+ if (!ops || !ops->get_hext_stream)
+ return NULL;
- res->link_locked = 1;
- res->link_substream = substream;
+ sdai->platform_private = ops;
}
- spin_unlock_irq(&bus->reg_lock);
- return res;
+ return sdai->platform_private;
}
static int hda_link_dma_cleanup(struct snd_pcm_substream *substream,
struct hdac_ext_stream *hext_stream,
struct snd_soc_dai *cpu_dai,
- struct snd_soc_dai *codec_dai,
- bool trigger_suspend_stop)
+ struct snd_soc_dai *codec_dai)
{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component);
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
struct hdac_stream *hstream = &hext_stream->hstream;
struct hdac_bus *bus = hstream->bus;
struct sof_intel_hda_stream *hda_stream;
if (!hlink)
return -EINVAL;
- if (trigger_suspend_stop)
- snd_hdac_ext_stream_clear(hext_stream);
-
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
stream_tag = hdac_stream(hext_stream)->stream_tag;
snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag);
}
- snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
- snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK);
+
+ if (ops->release_hext_stream)
+ ops->release_hext_stream(sdev, cpu_dai, substream);
+
hext_stream->link_prepared = 0;
/* free the host DMA channel reserved by hostless streams */
return 0;
}
-static int hda_link_dma_params(struct hdac_ext_stream *hext_stream,
- struct hda_pipe_params *params)
-{
- struct hdac_stream *hstream = &hext_stream->hstream;
- unsigned char stream_tag = hstream->stream_tag;
- struct hdac_bus *bus = hstream->bus;
- struct hdac_ext_link *hlink;
- unsigned int format_val;
-
- snd_hdac_ext_stream_reset(hext_stream);
-
- format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch,
- params->format,
- params->link_bps, 0);
-
- dev_dbg(bus->dev, "format_val=%d, rate=%d, ch=%d, format=%d\n",
- format_val, params->s_freq, params->ch, params->format);
-
- snd_hdac_ext_stream_setup(hext_stream, format_val);
-
- if (hext_stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) {
- list_for_each_entry(hlink, &bus->hlink_list, list) {
- if (hlink->index == params->link_index)
- snd_hdac_ext_bus_link_set_stream_id(hlink,
- stream_tag);
- }
- }
-
- hext_stream->link_prepared = 1;
-
- return 0;
-}
-
static int hda_link_dma_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *cpu_dai)
{
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
- struct hda_pipe_params p_params = {0};
struct hdac_ext_stream *hext_stream;
+ struct hdac_stream *hstream;
struct hdac_ext_link *hlink;
struct snd_sof_dev *sdev;
struct hdac_bus *bus;
+ unsigned int format_val;
+ unsigned int link_bps;
+ int stream_tag;
sdev = snd_soc_component_get_drvdata(cpu_dai->component);
bus = sof_to_bus(sdev);
if (!hlink)
return -EINVAL;
- hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
- if (!hext_stream) {
- hext_stream = hda_link_stream_assign(bus, substream);
- if (!hext_stream)
- return -EBUSY;
+ hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
- snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream);
+ if (!hext_stream) {
+ if (ops->assign_hext_stream)
+ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream);
}
+ if (!hext_stream)
+ return -EBUSY;
+
+ hstream = &hext_stream->hstream;
+ stream_tag = hstream->stream_tag;
+
+ if (hext_stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_hdac_ext_bus_link_set_stream_id(hlink, stream_tag);
+
/* set the hdac_stream in the codec dai */
snd_soc_dai_set_stream(codec_dai, hdac_stream(hext_stream), substream->stream);
- p_params.ch = params_channels(params);
- p_params.s_freq = params_rate(params);
- p_params.link_index = hlink->index;
- p_params.format = params_format(params);
-
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- p_params.link_bps = codec_dai->driver->playback.sig_bits;
+ link_bps = codec_dai->driver->playback.sig_bits;
else
- p_params.link_bps = codec_dai->driver->capture.sig_bits;
+ link_bps = codec_dai->driver->capture.sig_bits;
- return hda_link_dma_params(hext_stream, &p_params);
-}
+ if (ops->reset_hext_stream)
+ ops->reset_hext_stream(sdev, hext_stream);
-static int hda_link_dma_prepare(struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- int stream = substream->stream;
+ format_val = snd_hdac_calc_stream_format(params_rate(params), params_channels(params),
+ params_format(params), link_bps, 0);
- return hda_link_dma_hw_params(substream, &rtd->dpcm[stream].hw_params);
-}
+ dev_dbg(bus->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val,
+ params_rate(params), params_channels(params), params_format(params));
-static int hda_link_dma_trigger(struct snd_pcm_substream *substream, int cmd)
-{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
- struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
- int ret;
+ if (ops->setup_hext_stream)
+ ops->setup_hext_stream(sdev, hext_stream, format_val);
- if (!hext_stream)
- return 0;
-
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- snd_hdac_ext_stream_start(hext_stream);
- break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_STOP:
- ret = hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai, true);
- if (ret < 0)
- return ret;
-
- break;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- snd_hdac_ext_stream_clear(hext_stream);
+ hext_stream->link_prepared = 1;
- break;
- default:
- return -EINVAL;
- }
return 0;
}
-static int hda_link_dma_hw_free(struct snd_pcm_substream *substream)
+static int hda_link_dma_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
- struct hdac_ext_stream *hext_stream;
-
- hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
- if (!hext_stream)
- return 0;
-
- return hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai, false);
-}
-
-static int hda_dai_widget_update(struct snd_soc_dapm_widget *w,
- int channel, bool widget_setup)
-{
- struct snd_sof_dai_config_data data;
-
- data.dai_data = channel;
-
- /* set up/free DAI widget and send DAI_CONFIG IPC */
- if (widget_setup)
- return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP, &data);
+ int stream = substream->stream;
- return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
+ return hda_link_dma_hw_params(substream, &rtd->dpcm[stream].hw_params, cpu_dai);
}
-static int hda_dai_hw_params_update(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
+static int hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component);
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct hdac_ext_stream *hext_stream;
- struct snd_soc_dapm_widget *w;
- int stream_tag;
- hext_stream = snd_soc_dai_get_dma_data(dai, substream);
- if (!hext_stream)
+ if (!ops) {
+ dev_err(sdev->dev, "DAI widget ops not set\n");
return -EINVAL;
+ }
- stream_tag = hdac_stream(hext_stream)->stream_tag;
-
- w = snd_soc_dai_get_widget(dai, substream->stream);
+ hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
+ if (!hext_stream)
+ return 0;
- /* set up the DAI widget and send the DAI_CONFIG with the new tag */
- return hda_dai_widget_update(w, stream_tag - 1, true);
+ return hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai);
}
static int hda_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
- struct hdac_ext_stream *hext_stream =
- snd_soc_dai_get_dma_data(dai, substream);
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component);
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
+ struct hdac_ext_stream *hext_stream;
+ struct snd_sof_dai_config_data data = { 0 };
+ unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
int ret;
+ if (!ops) {
+ dev_err(sdev->dev, "DAI widget ops not set\n");
+ return -EINVAL;
+ }
+
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
if (hext_stream && hext_stream->link_prepared)
return 0;
- ret = hda_link_dma_hw_params(substream, params);
+ ret = hda_link_dma_hw_params(substream, params, dai);
if (ret < 0)
return ret;
- return hda_dai_hw_params_update(substream, params, dai);
-}
-
-
-static int hda_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w)
-{
- struct snd_sof_widget *swidget = w->dobj.private;
- struct snd_soc_component *component = swidget->scomp;
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
- const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
- int ret = 0;
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
- if (tplg_ops->dai_config) {
- ret = tplg_ops->dai_config(sdev, swidget, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL);
- if (ret < 0)
- dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
- w->name);
- }
+ flags |= SOF_DAI_CONFIG_FLAGS_2_STEP_STOP << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
+ data.dai_data = hdac_stream(hext_stream)->stream_tag - 1;
- return ret;
+ return hda_dai_config(w, flags, &data);
}
static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
- struct hdac_ext_stream *hext_stream =
- snd_soc_dai_get_dma_data(dai, substream);
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component);
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- int stream = substream->stream;
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
+ struct hdac_ext_stream *hext_stream;
+ struct snd_sof_dai_config_data data = { 0 };
+ unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
int ret;
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
if (hext_stream && hext_stream->link_prepared)
return 0;
dev_dbg(sdev->dev, "prepare stream dir %d\n", substream->stream);
- ret = hda_link_dma_prepare(substream);
- if (ret < 0)
- return ret;
-
- return hda_dai_hw_params_update(substream, &rtd->dpcm[stream].hw_params, dai);
-}
-
-static int hda_dai_hw_free_ipc(int stream, /* direction */
- struct snd_soc_dai *dai)
-{
- struct snd_soc_dapm_widget *w;
-
- w = snd_soc_dai_get_widget(dai, stream);
-
- /* free the link DMA channel in the FW and the DAI widget */
- return hda_dai_widget_update(w, DMA_CHAN_INVALID, false);
-}
-
-static int ipc3_hda_dai_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
-{
- struct snd_soc_dapm_widget *w;
- int ret;
-
- dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd,
- dai->name, substream->stream);
-
- ret = hda_link_dma_trigger(substream, cmd);
+ ret = hda_link_dma_prepare(substream, dai);
if (ret < 0)
return ret;
- w = snd_soc_dai_get_widget(dai, substream->stream);
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_STOP:
- /*
- * free DAI widget during stop/suspend to keep widget use_count's balanced.
- */
- ret = hda_dai_hw_free_ipc(substream->stream, dai);
- if (ret < 0)
- return ret;
-
- break;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- ret = hda_dai_config_pause_push_ipc(w);
- if (ret < 0)
- return ret;
- break;
+ flags |= SOF_DAI_CONFIG_FLAGS_2_STEP_STOP << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
+ data.dai_data = hdac_stream(hext_stream)->stream_tag - 1;
- default:
- break;
- }
- return 0;
+ return hda_dai_config(w, flags, &data);
}
/*
* In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes
* (over IPC channel) and DMA state change (direct host register changes).
*/
-static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
+static int hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
- struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(dai, substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component);
- struct snd_sof_widget *pipe_widget;
- struct sof_ipc4_pipeline *pipeline;
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
+ struct hdac_ext_stream *hext_stream;
struct snd_soc_pcm_runtime *rtd;
- struct snd_sof_widget *swidget;
- struct snd_soc_dapm_widget *w;
struct snd_soc_dai *codec_dai;
- struct snd_soc_dai *cpu_dai;
int ret;
dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd,
dai->name, substream->stream);
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
+ if (!hext_stream)
+ return -EINVAL;
+
rtd = asoc_substream_to_rtd(substream);
- cpu_dai = asoc_rtd_to_cpu(rtd, 0);
codec_dai = asoc_rtd_to_codec(rtd, 0);
- w = snd_soc_dai_get_widget(dai, substream->stream);
- swidget = w->dobj.private;
- pipe_widget = swidget->spipe->pipe_widget;
- pipeline = pipe_widget->private;
-
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- snd_hdac_ext_stream_start(hext_stream);
- if (pipeline->state != SOF_IPC4_PIPE_PAUSED) {
- ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
- SOF_IPC4_PIPE_PAUSED);
- if (ret < 0)
- return ret;
- pipeline->state = SOF_IPC4_PIPE_PAUSED;
- }
-
- ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
- SOF_IPC4_PIPE_RUNNING);
+ if (ops->pre_trigger) {
+ ret = ops->pre_trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
- pipeline->state = SOF_IPC4_PIPE_RUNNING;
- break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_STOP:
- {
- ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
- SOF_IPC4_PIPE_PAUSED);
+ }
+
+ if (ops->trigger) {
+ ret = ops->trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
+ }
- pipeline->state = SOF_IPC4_PIPE_PAUSED;
-
- snd_hdac_ext_stream_clear(hext_stream);
-
- ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
- SOF_IPC4_PIPE_RESET);
+ if (ops->post_trigger) {
+ ret = ops->post_trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
+ }
- pipeline->state = SOF_IPC4_PIPE_RESET;
-
- ret = hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai, false);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = hda_link_dma_cleanup(substream, hext_stream, dai, codec_dai);
if (ret < 0) {
dev_err(sdev->dev, "%s: failed to clean up link DMA\n", __func__);
return ret;
}
break;
- }
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- {
- ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
- SOF_IPC4_PIPE_PAUSED);
- if (ret < 0)
- return ret;
-
- pipeline->state = SOF_IPC4_PIPE_PAUSED;
-
- snd_hdac_ext_stream_clear(hext_stream);
- break;
- }
default:
- dev_err(sdev->dev, "%s: unknown trigger command %d\n", __func__, cmd);
- return -EINVAL;
+ break;
}
return 0;
}
-static int hda_dai_hw_free(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- int ret;
-
- ret = hda_link_dma_hw_free(substream);
- if (ret < 0)
- return ret;
-
- return hda_dai_hw_free_ipc(substream->stream, dai);
-}
-
-static const struct snd_soc_dai_ops ipc3_hda_dai_ops = {
+static const struct snd_soc_dai_ops hda_dai_ops = {
.hw_params = hda_dai_hw_params,
.hw_free = hda_dai_hw_free,
- .trigger = ipc3_hda_dai_trigger,
+ .trigger = hda_dai_trigger,
.prepare = hda_dai_prepare,
};
* explicitly during suspend.
*/
if (hext_stream->link_substream) {
- struct snd_soc_dai *cpu_dai;
+ const struct hda_dai_widget_dma_ops *ops;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
struct snd_soc_dai *codec_dai;
+ struct snd_soc_dai *cpu_dai;
+ struct snd_sof_dev *sdev;
+ struct snd_sof_dai *sdai;
rtd = asoc_substream_to_rtd(hext_stream->link_substream);
cpu_dai = asoc_rtd_to_cpu(rtd, 0);
codec_dai = asoc_rtd_to_codec(rtd, 0);
+ w = snd_soc_dai_get_widget(cpu_dai, hdac_stream(hext_stream)->direction);
+ swidget = w->dobj.private;
+ sdev = snd_soc_component_get_drvdata(swidget->scomp);
+ sdai = swidget->private;
+ ops = sdai->platform_private;
ret = hda_link_dma_cleanup(hext_stream->link_substream,
hext_stream,
- cpu_dai, codec_dai, false);
+ cpu_dai, codec_dai);
if (ret < 0)
return ret;
- /* for consistency with TRIGGER_SUSPEND we free DAI resources */
- ret = hda_dai_hw_free_ipc(hdac_stream(hext_stream)->direction, cpu_dai);
- if (ret < 0)
- return ret;
+ /* for consistency with TRIGGER_SUSPEND */
+ if (ops->post_trigger) {
+ ret = ops->post_trigger(sdev, cpu_dai,
+ hext_stream->link_substream,
+ SNDRV_PCM_TRIGGER_SUSPEND);
+ if (ret < 0)
+ return ret;
+ }
}
}
return 0;
}
-static const struct snd_soc_dai_ops ipc4_hda_dai_ops = {
- .hw_params = hda_dai_hw_params,
- .hw_free = hda_dai_hw_free,
- .trigger = ipc4_hda_dai_trigger,
- .prepare = hda_dai_prepare,
-};
-
#endif
-/* only one flag used so far to harden hw_params/hw_free/trigger/prepare */
-struct ssp_dai_dma_data {
- bool setup;
-};
-
-static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai,
- bool setup)
-{
- struct snd_soc_dapm_widget *w;
-
- w = snd_soc_dai_get_widget(dai, substream->stream);
-
- if (setup)
- return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL);
-
- return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL);
-}
-
-static int ssp_dai_startup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- struct ssp_dai_dma_data *dma_data;
-
- dma_data = kzalloc(sizeof(*dma_data), GFP_KERNEL);
- if (!dma_data)
- return -ENOMEM;
-
- snd_soc_dai_set_dma_data(dai, substream, dma_data);
-
- return 0;
-}
-
-static int ssp_dai_setup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai,
- bool setup)
-{
- struct ssp_dai_dma_data *dma_data;
- int ret = 0;
-
- dma_data = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma_data) {
- dev_err(dai->dev, "%s: failed to get dma_data\n", __func__);
- return -EIO;
- }
-
- if (dma_data->setup != setup) {
- ret = ssp_dai_setup_or_free(substream, dai, setup);
- if (!ret)
- dma_data->setup = setup;
- }
- return ret;
-}
-
-static int ssp_dai_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
-{
- /* params are ignored for now */
- return ssp_dai_setup(substream, dai, true);
-}
-
-static int ssp_dai_prepare(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- /*
- * the SSP will only be reconfigured during resume operations and
- * not in case of xruns
- */
- return ssp_dai_setup(substream, dai, true);
-}
-
-static int ipc3_ssp_dai_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
-{
- if (cmd != SNDRV_PCM_TRIGGER_SUSPEND)
- return 0;
-
- return ssp_dai_setup(substream, dai, false);
-}
-
-static int ssp_dai_hw_free(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- return ssp_dai_setup(substream, dai, false);
-}
-
-static void ssp_dai_shutdown(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- struct ssp_dai_dma_data *dma_data;
-
- dma_data = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma_data) {
- dev_err(dai->dev, "%s: failed to get dma_data\n", __func__);
- return;
- }
- snd_soc_dai_set_dma_data(dai, substream, NULL);
- kfree(dma_data);
-}
-
-static const struct snd_soc_dai_ops ipc3_ssp_dai_ops = {
- .startup = ssp_dai_startup,
- .hw_params = ssp_dai_hw_params,
- .prepare = ssp_dai_prepare,
- .trigger = ipc3_ssp_dai_trigger,
- .hw_free = ssp_dai_hw_free,
- .shutdown = ssp_dai_shutdown,
-};
-
void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
{
int i;
- switch (sdev->pdata->ipc_type) {
- case SOF_IPC:
- for (i = 0; i < ops->num_drv; i++) {
- if (strstr(ops->drv[i].name, "SSP")) {
- ops->drv[i].ops = &ipc3_ssp_dai_ops;
- continue;
- }
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
- if (strstr(ops->drv[i].name, "iDisp") ||
- strstr(ops->drv[i].name, "Analog") ||
- strstr(ops->drv[i].name, "Digital"))
- ops->drv[i].ops = &ipc3_hda_dai_ops;
-#endif
- }
- break;
- case SOF_INTEL_IPC4:
- {
- struct sof_ipc4_fw_data *ipc4_data = sdev->private;
-
- for (i = 0; i < ops->num_drv; i++) {
+ for (i = 0; i < ops->num_drv; i++) {
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
- if (strstr(ops->drv[i].name, "iDisp") ||
- strstr(ops->drv[i].name, "Analog") ||
- strstr(ops->drv[i].name, "Digital"))
- ops->drv[i].ops = &ipc4_hda_dai_ops;
+ if (strstr(ops->drv[i].name, "iDisp") ||
+ strstr(ops->drv[i].name, "Analog") ||
+ strstr(ops->drv[i].name, "Digital"))
+ ops->drv[i].ops = &hda_dai_ops;
#endif
- }
+ }
- if (!hda_use_tplg_nhlt)
- ipc4_data->nhlt = intel_nhlt_init(sdev->dev);
+ if (sdev->pdata->ipc_type == SOF_INTEL_IPC4 && !hda_use_tplg_nhlt) {
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
- break;
- }
- default:
- break;
+ ipc4_data->nhlt = intel_nhlt_init(sdev->dev);
}
}
#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
+#include <sound/hda-mlink.h>
#include <trace/events/sof_intel.h>
#include "../sof-audio.h"
#include "../ops.h"
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
+ if (sdev->dspless_mode_selected)
+ return;
+
/* enable IPC DONE and BUSY interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY,
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
+ if (sdev->dspless_mode_selected)
+ return;
+
/* disable IPC interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
HDA_DSP_ADSPIC_IPC, 0);
* is called again either because of a new IPC sent to the DSP or
* during system suspend/resume.
*/
-int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
- const struct sof_dsp_power_state *target_state)
+static int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
{
int ret = 0;
- /*
- * When the DSP is already in D0I3 and the target state is D0I3,
- * it could be the case that the DSP is in D0I3 during S0
- * and the system is suspending to S0Ix. Therefore,
- * hda_dsp_set_D0_state() must be called to disable trace DMA
- * by sending the PM_GATE IPC to the FW.
- */
- if (target_state->substate == SOF_HDA_DSP_PM_D0I3 &&
- sdev->system_suspend_target == SOF_SUSPEND_S0IX)
- goto set_state;
-
- /*
- * For all other cases, return without doing anything if
- * the DSP is already in the target state.
- */
- if (target_state->state == sdev->dsp_power_state.state &&
- target_state->substate == sdev->dsp_power_state.substate)
- return 0;
-
-set_state:
switch (target_state->state) {
case SOF_DSP_PM_D0:
ret = hda_dsp_set_D0_state(sdev, target_state);
return ret;
}
+int hda_dsp_set_power_state_ipc3(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ /*
+ * When the DSP is already in D0I3 and the target state is D0I3,
+ * it could be the case that the DSP is in D0I3 during S0
+ * and the system is suspending to S0Ix. Therefore,
+ * hda_dsp_set_D0_state() must be called to disable trace DMA
+ * by sending the PM_GATE IPC to the FW.
+ */
+ if (target_state->substate == SOF_HDA_DSP_PM_D0I3 &&
+ sdev->system_suspend_target == SOF_SUSPEND_S0IX)
+ return hda_dsp_set_power_state(sdev, target_state);
+
+ /*
+ * For all other cases, return without doing anything if
+ * the DSP is already in the target state.
+ */
+ if (target_state->state == sdev->dsp_power_state.state &&
+ target_state->substate == sdev->dsp_power_state.substate)
+ return 0;
+
+ return hda_dsp_set_power_state(sdev, target_state);
+}
+
+int hda_dsp_set_power_state_ipc4(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ /* Return without doing anything if the DSP is already in the target state */
+ if (target_state->state == sdev->dsp_power_state.state &&
+ target_state->substate == sdev->dsp_power_state.substate)
+ return 0;
+
+ return hda_dsp_set_power_state(sdev, target_state);
+}
+
/*
* Audio DSP states may transform as below:-
*
/* power down all hda links */
hda_bus_ml_suspend(bus);
+ if (sdev->dspless_mode_selected)
+ goto skip_dsp;
+
ret = chip->power_down_dsp(sdev);
if (ret < 0) {
dev_err(sdev->dev, "failed to power down DSP during suspend\n");
/* disable ppcap interrupt */
hda_dsp_ctrl_ppcap_enable(sdev, false);
hda_dsp_ctrl_ppcap_int_enable(sdev, false);
+skip_dsp:
/* disable hda bus irq and streams */
hda_dsp_ctrl_stop_chip(sdev);
hda_codec_jack_check(sdev);
}
- /* enable ppcap interrupt */
- hda_dsp_ctrl_ppcap_enable(sdev, true);
- hda_dsp_ctrl_ppcap_int_enable(sdev, true);
+ if (!sdev->dspless_mode_selected) {
+ /* enable ppcap interrupt */
+ hda_dsp_ctrl_ppcap_enable(sdev, true);
+ hda_dsp_ctrl_ppcap_int_enable(sdev, true);
+ }
cleanup:
/* display codec can powered off after controller init */
}
/* restore L1SEN bit */
- if (hda->l1_support_changed)
+ if (hda->l1_disabled)
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
HDA_VS_INTEL_EM2,
HDA_VS_INTEL_EM2_L1SEN, 0);
};
int ret;
- /* cancel any attempt for DSP D0I3 */
- cancel_delayed_work_sync(&hda->d0i3_work);
+ if (!sdev->dspless_mode_selected) {
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
+ }
/* stop hda controller and power dsp off */
ret = hda_suspend(sdev, true);
};
int ret;
- /* cancel any attempt for DSP D0I3 */
- cancel_delayed_work_sync(&hda->d0i3_work);
+ if (!sdev->dspless_mode_selected) {
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
+ }
if (target_state == SOF_DSP_PM_D0) {
/* Set DSP power state */
}
/* enable L1SEN to make sure the system can enter S0Ix */
- hda->l1_support_changed =
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
- HDA_VS_INTEL_EM2,
- HDA_VS_INTEL_EM2_L1SEN,
- HDA_VS_INTEL_EM2_L1SEN);
+ if (hda->l1_disabled)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
+ HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN);
/* stop the CORB/RIRB DMA if it is On */
hda_codec_suspend_cmd_io(sdev);
bool ret = false;
u32 irq_status;
+ if (sdev->dspless_mode_selected)
+ return false;
+
/* store status */
irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS);
trace_sof_intel_hda_irq_ipc_check(sdev, irq_status);
int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev)
{
struct hdac_ext_stream *iccmax_stream;
- struct hdac_bus *bus = sof_to_bus(sdev);
struct snd_dma_buffer dmab_bdl;
int ret, ret1;
u8 original_gb;
/* save the original LTRP guardband value */
- original_gb = snd_hdac_chip_readb(bus, VS_LTRP) & HDA_VS_INTEL_LTRP_GB_MASK;
+ original_gb = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP) &
+ HDA_VS_INTEL_LTRP_GB_MASK;
/*
* Prepare capture stream for ICCMAX. We do not need to store
}
/* restore the original guardband value after FW boot */
- snd_hdac_chip_updateb(bus, VS_LTRP, HDA_VS_INTEL_LTRP_GB_MASK, original_gb);
+ snd_sof_dsp_update8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP,
+ HDA_VS_INTEL_LTRP_GB_MASK, original_gb);
return ret;
}
goto cleanup;
}
- ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
ret1 = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_STOP);
if (ret1 < 0) {
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
+#include <sound/hda-mlink.h>
-#include <linux/acpi.h>
+#include <linux/bitfield.h>
#include <linux/module.h>
-#include <linux/soundwire/sdw.h>
-#include <linux/soundwire/sdw_intel.h>
-#include <sound/intel-dsp-config.h>
-#include <sound/intel-nhlt.h>
-#include <sound/sof.h>
-#include <sound/sof/xtensa.h>
-#include "../sof-audio.h"
-#include "../sof-pci-dev.h"
-#include "../ops.h"
-#include "hda.h"
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
-void hda_bus_ml_get_capabilities(struct hdac_bus *bus)
+/**
+ * struct hdac_ext2_link - HDAudio extended+alternate link
+ *
+ * @hext_link: hdac_ext_link
+ * @alt: flag set for alternate extended links
+ * @intc: boolean for interrupt capable
+ * @ofls: boolean for offload support
+ * @lss: boolean for link synchronization capabilities
+ * @slcount: sublink count
+ * @elid: extended link ID (AZX_REG_ML_LEPTR_ID_ defines)
+ * @elver: extended link version
+ * @leptr: extended link pointer
+ * @eml_lock: mutual exclusion to access shared registers e.g. CPA/SPA bits
+ * in LCTL register
+ * @base_ptr: pointer to shim/ip/shim_vs space
+ * @instance_offset: offset between each of @slcount instances managed by link
+ * @shim_offset: offset to SHIM register base
+ * @ip_offset: offset to IP register base
+ * @shim_vs_offset: offset to vendor-specific (VS) SHIM base
+ */
+struct hdac_ext2_link {
+ struct hdac_ext_link hext_link;
+
+ /* read directly from LCAP register */
+ bool alt;
+ bool intc;
+ bool ofls;
+ bool lss;
+ int slcount;
+ int elid;
+ int elver;
+ u32 leptr;
+
+ struct mutex eml_lock; /* prevent concurrent access to e.g. CPA/SPA */
+
+ /* internal values computed from LCAP contents */
+ void __iomem *base_ptr;
+ u32 instance_offset;
+ u32 shim_offset;
+ u32 ip_offset;
+ u32 shim_vs_offset;
+};
+
+#define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link)
+
+#define AZX_REG_SDW_INSTANCE_OFFSET 0x8000
+#define AZX_REG_SDW_SHIM_OFFSET 0x0
+#define AZX_REG_SDW_IP_OFFSET 0x100
+#define AZX_REG_SDW_VS_SHIM_OFFSET 0x6000
+
+/* only one instance supported */
+#define AZX_REG_INTEL_DMIC_SHIM_OFFSET 0x0
+#define AZX_REG_INTEL_DMIC_IP_OFFSET 0x100
+#define AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET 0x6000
+
+#define AZX_REG_INTEL_SSP_INSTANCE_OFFSET 0x1000
+#define AZX_REG_INTEL_SSP_SHIM_OFFSET 0x0
+#define AZX_REG_INTEL_SSP_IP_OFFSET 0x100
+#define AZX_REG_INTEL_SSP_VS_SHIM_OFFSET 0xC00
+
+/* only one instance supported */
+#define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x0
+#define AZX_REG_INTEL_UAOL_IP_OFFSET 0x100
+#define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC00
+
+/* HDAML section - this part follows sequences in the hardware specification,
+ * including naming conventions and the use of the hdaml_ prefix.
+ * The code is intentionally minimal with limited dependencies on frameworks or
+ * helpers. Locking and scanning lists is handled at a higher level
+ */
+
+static int hdaml_lnk_enum(struct device *dev, struct hdac_ext2_link *h2link,
+ void __iomem *ml_addr, int link_idx)
+{
+ struct hdac_ext_link *hlink = &h2link->hext_link;
+ u32 base_offset;
+
+ hlink->lcaps = readl(ml_addr + AZX_REG_ML_LCAP);
+
+ h2link->alt = FIELD_GET(AZX_ML_HDA_LCAP_ALT, hlink->lcaps);
+
+ /* handle alternate extensions */
+ if (!h2link->alt) {
+ h2link->slcount = 1;
+
+ /*
+ * LSDIID is initialized by hardware for HDaudio link,
+ * it needs to be setup by software for alternate links
+ */
+ hlink->lsdiid = readw(ml_addr + AZX_REG_ML_LSDIID);
+
+ dev_dbg(dev, "Link %d: HDAudio - lsdiid=%d\n",
+ link_idx, hlink->lsdiid);
+
+ return 0;
+ }
+
+ h2link->intc = FIELD_GET(AZX_ML_HDA_LCAP_INTC, hlink->lcaps);
+ h2link->ofls = FIELD_GET(AZX_ML_HDA_LCAP_OFLS, hlink->lcaps);
+ h2link->lss = FIELD_GET(AZX_ML_HDA_LCAP_LSS, hlink->lcaps);
+
+ /* read slcount (increment due to zero-based hardware representation */
+ h2link->slcount = FIELD_GET(AZX_ML_HDA_LCAP_SLCOUNT, hlink->lcaps) + 1;
+ dev_dbg(dev, "Link %d: HDAudio extended - sublink count %d\n",
+ link_idx, h2link->slcount);
+
+ /* find IP ID and offsets */
+ h2link->leptr = readl(hlink->ml_addr + AZX_REG_ML_LEPTR);
+
+ h2link->elid = FIELD_GET(AZX_REG_ML_LEPTR_ID, h2link->leptr);
+
+ base_offset = FIELD_GET(AZX_REG_ML_LEPTR_PTR, h2link->leptr);
+ h2link->base_ptr = hlink->ml_addr + base_offset;
+
+ switch (h2link->elid) {
+ case AZX_REG_ML_LEPTR_ID_SDW:
+ h2link->shim_offset = AZX_REG_SDW_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_SDW_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_SDW_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - SoundWire alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ case AZX_REG_ML_LEPTR_ID_INTEL_DMIC:
+ h2link->shim_offset = AZX_REG_INTEL_DMIC_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_INTEL_DMIC_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - INTEL DMIC alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ case AZX_REG_ML_LEPTR_ID_INTEL_SSP:
+ h2link->shim_offset = AZX_REG_INTEL_SSP_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_INTEL_SSP_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_INTEL_SSP_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - INTEL SSP alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
+ h2link->shim_offset = AZX_REG_INTEL_UAOL_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_INTEL_UAOL_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - INTEL UAOL alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ default:
+ dev_err(dev, "Link %d: HDAudio extended - Unsupported alternate link, leptr.id=%#02x value\n",
+ link_idx, h2link->elid);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Hardware recommendations are to wait ~10us before checking any hardware transition
+ * reported by bits changing status.
+ * This value does not need to be super-precise, a slack of 5us is perfectly acceptable.
+ * The worst-case is about 1ms before reporting an issue
+ */
+#define HDAML_POLL_DELAY_MIN_US 10
+#define HDAML_POLL_DELAY_SLACK_US 5
+#define HDAML_POLL_DELAY_RETRY 100
+
+static int check_sublink_power(u32 __iomem *lctl, int sublink, bool enabled)
{
- if (bus->mlcap)
- snd_hdac_ext_bus_get_ml_capabilities(bus);
+ int mask = BIT(sublink) << AZX_ML_LCTL_CPA_SHIFT;
+ int retry = HDAML_POLL_DELAY_RETRY;
+ u32 val;
+
+ usleep_range(HDAML_POLL_DELAY_MIN_US,
+ HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
+ do {
+ val = readl(lctl);
+ if (enabled) {
+ if (val & mask)
+ return 0;
+ } else {
+ if (!(val & mask))
+ return 0;
+ }
+ usleep_range(HDAML_POLL_DELAY_MIN_US,
+ HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
+
+ } while (--retry);
+
+ return -EIO;
}
-void hda_bus_ml_free(struct hdac_bus *bus)
+static int hdaml_link_init(u32 __iomem *lctl, int sublink)
+{
+ u32 val;
+ u32 mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
+
+ val = readl(lctl);
+ val |= mask;
+
+ writel(val, lctl);
+
+ return check_sublink_power(lctl, sublink, true);
+}
+
+static int hdaml_link_shutdown(u32 __iomem *lctl, int sublink)
+{
+ u32 val;
+ u32 mask;
+
+ val = readl(lctl);
+ mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
+ val &= ~mask;
+
+ writel(val, lctl);
+
+ return check_sublink_power(lctl, sublink, false);
+}
+
+static void hdaml_link_enable_interrupt(u32 __iomem *lctl, bool enable)
+{
+ u32 val;
+
+ val = readl(lctl);
+ if (enable)
+ val |= AZX_ML_LCTL_INTEN;
+ else
+ val &= ~AZX_ML_LCTL_INTEN;
+
+ writel(val, lctl);
+}
+
+static bool hdaml_link_check_interrupt(u32 __iomem *lctl)
+{
+ u32 val;
+
+ val = readl(lctl);
+
+ return val & AZX_ML_LCTL_INTSTS;
+}
+
+static int hdaml_wait_bit(void __iomem *base, int offset, u32 mask, u32 target)
+{
+ int timeout = HDAML_POLL_DELAY_RETRY;
+ u32 reg_read;
+
+ do {
+ reg_read = readl(base + offset);
+ if ((reg_read & mask) == target)
+ return 0;
+
+ timeout--;
+ usleep_range(HDAML_POLL_DELAY_MIN_US,
+ HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
+ } while (timeout != 0);
+
+ return -EAGAIN;
+}
+
+static void hdaml_link_set_syncprd(u32 __iomem *lsync, u32 syncprd)
+{
+ u32 val;
+
+ val = readl(lsync);
+ val &= ~AZX_REG_ML_LSYNC_SYNCPRD;
+ val |= (syncprd & AZX_REG_ML_LSYNC_SYNCPRD);
+
+ /*
+ * set SYNCPU but do not wait. The bit is cleared by hardware when
+ * the link becomes active.
+ */
+ val |= AZX_REG_ML_LSYNC_SYNCPU;
+
+ writel(val, lsync);
+}
+
+static int hdaml_link_wait_syncpu(u32 __iomem *lsync)
+{
+ return hdaml_wait_bit(lsync, 0, AZX_REG_ML_LSYNC_SYNCPU, 0);
+}
+
+static void hdaml_link_sync_arm(u32 __iomem *lsync, int sublink)
+{
+ u32 val;
+
+ val = readl(lsync);
+ val |= (AZX_REG_ML_LSYNC_CMDSYNC << sublink);
+
+ writel(val, lsync);
+}
+
+static void hdaml_link_sync_go(u32 __iomem *lsync)
{
+ u32 val;
+
+ val = readl(lsync);
+ val |= AZX_REG_ML_LSYNC_SYNCGO;
+
+ writel(val, lsync);
+}
+
+static bool hdaml_link_check_cmdsync(u32 __iomem *lsync, u32 cmdsync_mask)
+{
+ u32 val;
+
+ val = readl(lsync);
+
+ return !!(val & cmdsync_mask);
+}
+
+static void hdaml_link_set_lsdiid(u32 __iomem *lsdiid, int dev_num)
+{
+ u32 val;
+
+ val = readl(lsdiid);
+ val |= BIT(dev_num);
+
+ writel(val, lsdiid);
+}
+
+static void hdaml_lctl_offload_enable(u32 __iomem *lctl, bool enable)
+{
+ u32 val = readl(lctl);
+
+ if (enable)
+ val |= AZX_ML_LCTL_OFLEN;
+ else
+ val &= ~AZX_ML_LCTL_OFLEN;
+
+ writel(val, lctl);
+}
+
+/* END HDAML section */
+
+static int hda_ml_alloc_h2link(struct hdac_bus *bus, int index)
+{
+ struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
+ int ret;
+
+ h2link = kzalloc(sizeof(*h2link), GFP_KERNEL);
+ if (!h2link)
+ return -ENOMEM;
+
+ /* basic initialization */
+ hlink = &h2link->hext_link;
+
+ hlink->index = index;
+ hlink->bus = bus;
+ hlink->ml_addr = bus->mlcap + AZX_ML_BASE + (AZX_ML_INTERVAL * index);
+
+ ret = hdaml_lnk_enum(bus->dev, h2link, hlink->ml_addr, index);
+ if (ret < 0) {
+ kfree(h2link);
+ return ret;
+ }
+
+ mutex_init(&h2link->eml_lock);
+
+ list_add_tail(&hlink->list, &bus->hlink_list);
+
+ /*
+ * HDaudio regular links are powered-on by default, the
+ * refcount needs to be initialized.
+ */
+ if (!h2link->alt)
+ hlink->ref_count = 1;
+
+ return 0;
+}
+
+int hda_bus_ml_init(struct hdac_bus *bus)
+{
+ u32 link_count;
+ int ret;
+ int i;
+
+ if (!bus->mlcap)
+ return 0;
+
+ link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
+
+ dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count);
+
+ for (i = 0; i < link_count; i++) {
+ ret = hda_ml_alloc_h2link(bus, i);
+ if (ret < 0) {
+ hda_bus_ml_free(bus);
+ return ret;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_init, SND_SOC_SOF_HDA_MLINK);
+
+void hda_bus_ml_free(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink, *_h;
+ struct hdac_ext2_link *h2link;
if (!bus->mlcap)
return;
- while (!list_empty(&bus->hlink_list)) {
- hlink = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list);
+ list_for_each_entry_safe(hlink, _h, &bus->hlink_list, list) {
list_del(&hlink->list);
- kfree(hlink);
+ h2link = hdac_ext_link_to_ext2(hlink);
+
+ mutex_destroy(&h2link->eml_lock);
+ kfree(h2link);
+ }
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_free, SND_SOC_SOF_HDA_MLINK);
+
+static struct hdac_ext2_link *
+find_ext2_link(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext_link *hlink;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (h2link->alt == alt && h2link->elid == elid)
+ return h2link;
}
+
+ return NULL;
+}
+
+int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ return h2link->slcount;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, SND_SOC_SOF_HDA_MLINK);
+
+void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return;
+
+ if (!h2link->intc)
+ return;
+
+ hlink = &h2link->hext_link;
+
+ mutex_lock(&h2link->eml_lock);
+
+ hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
+
+ mutex_unlock(&h2link->eml_lock);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, SND_SOC_SOF_HDA_MLINK);
+
+bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return false;
+
+ if (!h2link->intc)
+ return false;
+
+ hlink = &h2link->hext_link;
+
+ return hdaml_link_check_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ hdaml_link_set_syncprd(hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
+{
+ return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ return hdaml_link_wait_syncpu(hlink->ml_addr + AZX_REG_ML_LSYNC);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus)
+{
+ return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return;
+
+ if (!h2link->lss)
+ return;
+
+ hlink = &h2link->hext_link;
+
+ hdaml_link_sync_arm(hlink->ml_addr + AZX_REG_ML_LSYNC, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink)
+{
+ hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ hdaml_link_sync_go(hlink->ml_addr + AZX_REG_ML_LSYNC);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus)
+{
+ return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+ u32 cmdsync_mask;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1,
+ AZX_REG_ML_LSYNC_CMDSYNC_SHIFT);
+
+ return hdaml_link_check_cmdsync(hlink->ml_addr + AZX_REG_ML_LSYNC,
+ cmdsync_mask);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus)
+{
+ return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
+ bool eml_lock)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+ int ret = 0;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return -ENODEV;
+
+ if (sublink >= h2link->slcount)
+ return -EINVAL;
+
+ hlink = &h2link->hext_link;
+
+ if (eml_lock)
+ mutex_lock(&h2link->eml_lock);
+
+ if (++hlink->ref_count > 1)
+ goto skip_init;
+
+ ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
+
+skip_init:
+ if (eml_lock)
+ mutex_unlock(&h2link->eml_lock);
+
+ return ret;
+}
+
+int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, true);
}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, false);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
+ bool eml_lock)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+ int ret = 0;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return -ENODEV;
+
+ if (sublink >= h2link->slcount)
+ return -EINVAL;
+
+ hlink = &h2link->hext_link;
+
+ if (eml_lock)
+ mutex_lock(&h2link->eml_lock);
+
+ if (--hlink->ref_count > 0)
+ goto skip_shutdown;
+
+ ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
+
+skip_shutdown:
+ if (eml_lock)
+ mutex_unlock(&h2link->eml_lock);
+
+ return ret;
+}
+
+int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, true);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, false);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink)
+{
+ return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink)
+{
+ return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+ if (!h2link)
+ return -ENODEV;
+
+ hlink = &h2link->hext_link;
+
+ mutex_lock(&h2link->eml_lock);
+
+ hdaml_link_set_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num);
+
+ mutex_unlock(&h2link->eml_lock);
+
+ return 0;
+} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, SND_SOC_SOF_HDA_MLINK);
void hda_bus_ml_put_all(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink;
- list_for_each_entry(hlink, &bus->hlink_list, list)
- snd_hdac_ext_bus_link_put(bus, hlink);
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (!h2link->alt)
+ snd_hdac_ext_bus_link_put(bus, hlink);
+ }
}
+EXPORT_SYMBOL_NS(hda_bus_ml_put_all, SND_SOC_SOF_HDA_MLINK);
void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
{
list_for_each_entry(hlink, &bus->hlink_list, list)
writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
}
+EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, SND_SOC_SOF_HDA_MLINK);
int hda_bus_ml_resume(struct hdac_bus *bus)
{
/* power up links that were active before suspend */
list_for_each_entry(hlink, &bus->hlink_list, list) {
- if (hlink->ref_count) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (!h2link->alt && hlink->ref_count) {
ret = snd_hdac_ext_bus_link_power_up(hlink);
if (ret < 0)
return ret;
}
return 0;
}
+EXPORT_SYMBOL_NS(hda_bus_ml_resume, SND_SOC_SOF_HDA_MLINK);
int hda_bus_ml_suspend(struct hdac_bus *bus)
{
- return snd_hdac_ext_bus_link_power_down_all(bus);
+ struct hdac_ext_link *hlink;
+ int ret;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (!h2link->alt) {
+ ret = snd_hdac_ext_bus_link_power_down(hlink);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_suspend, SND_SOC_SOF_HDA_MLINK);
+
+struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return NULL;
+
+ return &h2link->eml_lock;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, SND_SOC_SOF_HDA_MLINK);
+
+struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
+ if (!h2link)
+ return NULL;
+
+ return &h2link->hext_link;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, SND_SOC_SOF_HDA_MLINK);
+
+struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
+ if (!h2link)
+ return NULL;
+
+ return &h2link->hext_link;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return -ENODEV;
+
+ if (!h2link->ofls)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ mutex_lock(&h2link->eml_lock);
+
+ hdaml_lctl_offload_enable(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
+
+ mutex_unlock(&h2link->eml_lock);
+
+ return 0;
}
+EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, SND_SOC_SOF_HDA_MLINK);
#endif
+
+MODULE_LICENSE("Dual BSD/GPL");
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct snd_dma_buffer *dmab;
int ret;
- u32 size, rate, bits;
-
- size = params_buffer_bytes(params);
- rate = hda_dsp_get_mult_div(sdev, params_rate(params));
- bits = hda_dsp_get_bits(sdev, params_width(params));
hstream->substream = substream;
dmab = substream->runtime->dma_buffer_p;
- hstream->format_val = rate | bits | (params_channels(params) - 1);
- hstream->bufsize = size;
+ /*
+ * Use the codec required format val (which is link_bps adjusted) when
+ * the DSP is not in use
+ */
+ if (!sdev->dspless_mode_selected) {
+ u32 rate = hda_dsp_get_mult_div(sdev, params_rate(params));
+ u32 bits = hda_dsp_get_bits(sdev, params_width(params));
+
+ hstream->format_val = rate | bits | (params_channels(params) - 1);
+ }
+
+ hstream->bufsize = params_buffer_bytes(params);
hstream->period_bytes = params_period_bytes(params);
hstream->no_period_wakeup =
(params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
+ /* Only S16 and S32 supported by HDA hardware when used without DSP */
+ if (sdev->dspless_mode_selected)
+ snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_FORMAT,
+ SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32);
+
/* binding pcm substream to hda stream */
substream->runtime->private_data = &dsp_stream->hstream;
return 0;
struct hdac_ext_stream *
hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags)
{
+ const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata);
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct hdac_bus *bus = sof_to_bus(sdev);
struct sof_intel_hda_stream *hda_stream;
struct hdac_ext_stream *hext_stream = NULL;
/*
* Prevent DMI Link L1 entry for streams that don't support it.
* Workaround to address a known issue with host DMA that results
- * in xruns during pause/release in capture scenarios.
+ * in xruns during pause/release in capture scenarios. This is not needed for the ACE IP.
*/
- if (!(flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE))
+ if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 &&
+ !(flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) {
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
HDA_VS_INTEL_EM2,
HDA_VS_INTEL_EM2_L1SEN, 0);
+ hda->l1_disabled = true;
+ }
return hext_stream;
}
/* free a stream */
int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag)
{
+ const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata);
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct hdac_bus *bus = sof_to_bus(sdev);
struct sof_intel_hda_stream *hda_stream;
struct hdac_ext_stream *hext_stream;
spin_unlock_irq(&bus->reg_lock);
/* Enable DMI L1 if permitted */
- if (dmi_l1_enable)
+ if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 && dmi_l1_enable) {
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN);
+ hda->l1_disabled = false;
+ }
if (!found) {
dev_err(sdev->dev, "%s: stream_tag %d not opened!\n",
/* cmd must be for audio stream */
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!sdev->dspless_mode_selected)
+ break;
+ fallthrough;
case SNDRV_PCM_TRIGGER_START:
+ if (hstream->running)
+ break;
+
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
1 << hstream->index,
1 << hstream->index);
hstream->running = true;
break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (!sdev->dspless_mode_selected)
+ break;
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset,
{
const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata);
struct hdac_bus *bus = sof_to_bus(sdev);
- struct hdac_stream *hstream = &hext_stream->hstream;
- int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
- int ret;
+ struct hdac_stream *hstream;
+ int sd_offset, ret;
u32 dma_start = SOF_HDA_SD_CTL_DMA_START;
u32 mask;
u32 run;
return -ENODEV;
}
- /* decouple host and link DMA */
- mask = 0x1 << hstream->index;
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- mask, mask);
+ hstream = &hext_stream->hstream;
+ sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+ mask = BIT(hstream->index);
+
+ /* decouple host and link DMA if the DSP is used */
+ if (!sdev->dspless_mode_selected)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+ mask, mask);
/* clear stream status */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
* enable decoupled mode
*/
- if (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK) {
+ if (!sdev->dspless_mode_selected && (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK))
/* couple host and link DMA, disable DSP features */
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
mask, 0);
- }
/* program stream format */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
SOF_HDA_ADSP_REG_SD_FORMAT,
0xffff, hstream->format_val);
- if (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK) {
+ if (!sdev->dspless_mode_selected && (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK))
/* decouple host and link DMA, enable DSP features */
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
mask, mask);
- }
/* program last valid index */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
struct hdac_ext_stream *hext_stream = container_of(hstream,
struct hdac_ext_stream,
hstream);
- struct hdac_bus *bus = sof_to_bus(sdev);
- u32 mask = 0x1 << hstream->index;
int ret;
ret = hda_dsp_stream_reset(sdev, hstream);
if (ret < 0)
return ret;
- spin_lock_irq(&bus->reg_lock);
- /* couple host and link DMA if link DMA channel is idle */
- if (!hext_stream->link_locked)
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR,
- SOF_HDA_REG_PP_PPCTL, mask, 0);
- spin_unlock_irq(&bus->reg_lock);
+ if (!sdev->dspless_mode_selected) {
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ u32 mask = BIT(hstream->index);
+
+ spin_lock_irq(&bus->reg_lock);
+ /* couple host and link DMA if link DMA channel is idle */
+ if (!hext_stream->link_locked)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR,
+ SOF_HDA_REG_PP_PPCTL, mask, 0);
+ spin_unlock_irq(&bus->reg_lock);
+ }
hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0);
hext_stream = &hda_stream->hext_stream;
- hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
+ if (sdev->bar[HDA_DSP_PP_BAR]) {
+ hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+ SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
- hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
- SOF_HDA_PPLC_INTERVAL * i;
+ hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+ SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
+ SOF_HDA_PPLC_INTERVAL * i;
+ }
hstream = &hext_stream->hstream;
hext_stream = &hda_stream->hext_stream;
- /* we always have DSP support */
- hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
+ if (sdev->bar[HDA_DSP_PP_BAR]) {
+ hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+ SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
- hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
- SOF_HDA_PPLC_INTERVAL * i;
+ hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+ SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
+ SOF_HDA_PPLC_INTERVAL * i;
+ }
hstream = &hext_stream->hstream;
#include <sound/intel-nhlt.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
+#include <sound/hda-mlink.h>
#include "../sof-audio.h"
#include "../sof-pci-dev.h"
#include "../ops.h"
#define EXCEPT_MAX_HDR_SIZE 0x400
#define HDA_EXT_ROM_STATUS_SIZE 8
-int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
- struct snd_sof_dai_config_data *data)
+static u32 hda_get_interface_mask(struct snd_sof_dev *sdev)
{
- struct snd_sof_widget *swidget = w->dobj.private;
- struct snd_soc_component *component = swidget->scomp;
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
- const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
- struct snd_sof_dai *sof_dai = swidget->private;
- int ret;
-
- if (!sof_dai) {
- dev_err(sdev->dev, "%s: No DAI for DAI widget %s\n", __func__, w->name);
- return -EINVAL;
- }
-
- if (tplg_ops->dai_config) {
- unsigned int flags;
-
- /* set HW_PARAMS flag along with quirks */
- flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS |
- quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
-
- ret = tplg_ops->dai_config(sdev, swidget, flags, data);
- if (ret < 0) {
- dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
- w->name);
- return ret;
- }
- }
-
- return 0;
-}
-
-int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
- struct snd_sof_dai_config_data *data)
-{
- struct snd_sof_widget *swidget = w->dobj.private;
- struct snd_soc_component *component = swidget->scomp;
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
- const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
- struct snd_sof_dai *sof_dai = swidget->private;
-
- if (!sof_dai) {
- dev_err(sdev->dev, "%s: No DAI for BE DAI widget %s\n", __func__, w->name);
- return -EINVAL;
- }
-
- if (tplg_ops->dai_config) {
- unsigned int flags;
- int ret;
-
- /* set HW_FREE flag along with any quirks */
- flags = SOF_DAI_CONFIG_FLAGS_HW_FREE |
- quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
+ const struct sof_intel_dsp_desc *chip;
+ u32 interface_mask[2] = { 0 };
- ret = tplg_ops->dai_config(sdev, swidget, flags, data);
- if (ret < 0)
- dev_err(sdev->dev, "%s: DAI config failed for widget '%s'\n", __func__,
- w->name);
+ chip = get_chip_info(sdev->pdata);
+ switch (chip->hw_ip_version) {
+ case SOF_INTEL_TANGIER:
+ case SOF_INTEL_BAYTRAIL:
+ case SOF_INTEL_BROADWELL:
+ interface_mask[0] = BIT(SOF_DAI_INTEL_SSP);
+ break;
+ case SOF_INTEL_CAVS_1_5:
+ case SOF_INTEL_CAVS_1_5_PLUS:
+ interface_mask[0] = BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC) |
+ BIT(SOF_DAI_INTEL_HDA);
+ interface_mask[1] = BIT(SOF_DAI_INTEL_HDA);
+ break;
+ case SOF_INTEL_CAVS_1_8:
+ case SOF_INTEL_CAVS_2_0:
+ case SOF_INTEL_CAVS_2_5:
+ case SOF_INTEL_ACE_1_0:
+ interface_mask[0] = BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC) |
+ BIT(SOF_DAI_INTEL_HDA) | BIT(SOF_DAI_INTEL_ALH);
+ interface_mask[1] = BIT(SOF_DAI_INTEL_HDA);
+ break;
+ default:
+ break;
}
- return 0;
+ return interface_mask[sdev->dspless_mode_selected];
}
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
struct sdw_intel_stream_params_data *params_data)
{
struct snd_soc_dai *d = params_data->dai;
- struct snd_sof_dai_config_data data;
- struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(d, params_data->stream);
+ struct snd_sof_dai_config_data data = { 0 };
- w = snd_soc_dai_get_widget(d, params_data->stream);
data.dai_index = (params_data->link_id << 8) | d->id;
data.dai_data = params_data->alh_stream_id;
- return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
-}
-
-static int sdw_free_stream(struct device *dev,
- struct sdw_intel_stream_free_data *free_data)
-{
- struct snd_soc_dai *d = free_data->dai;
- struct snd_sof_dai_config_data data;
- struct snd_soc_dapm_widget *w;
-
- w = snd_soc_dai_get_widget(d, free_data->stream);
- data.dai_index = (free_data->link_id << 8) | d->id;
-
- /* send invalid stream_id */
- data.dai_data = 0xFFFF;
-
- return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
+ return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_PARAMS, &data);
}
struct sdw_intel_ops sdw_callback = {
.params_stream = sdw_params_stream,
- .free_stream = sdw_free_stream,
};
void hda_common_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable)
void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
const struct sof_intel_dsp_desc *chip;
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return;
+
chip = get_chip_info(sdev->pdata);
if (chip && chip->enable_sdw_irq)
chip->enable_sdw_irq(sdev, enable);
static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct sof_intel_hda_dev *hdev;
acpi_handle handle;
int ret;
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return -EINVAL;
+
handle = ACPI_HANDLE(sdev->dev);
/* save ACPI info for the probe step */
/* Check HW supported vs property value */
if (caps < ctx->count) {
dev_err(sdev->dev,
- "BIOS master count %d is larger than hardware capabilities %d\n",
- ctx->count, caps);
+ "%s: BIOS master count %d is larger than hardware capabilities %d\n",
+ __func__, ctx->count, caps);
return -EINVAL;
}
static bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
const struct sof_intel_dsp_desc *chip;
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return false;
+
chip = get_chip_info(sdev->pdata);
if (chip && chip->check_sdw_irq)
return chip->check_sdw_irq(sdev);
static bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct sof_intel_hda_dev *hdev;
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return false;
+
hdev = sdev->pdata->hw_pdata;
if (hdev->sdw &&
snd_sof_dsp_read(sdev, HDA_DSP_BAR,
void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct sof_intel_hda_dev *hdev;
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return;
+
hdev = sdev->pdata->hw_pdata;
if (!hdev->sdw)
return;
static int hda_init_caps(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct hdac_bus *bus = sof_to_bus(sdev);
struct snd_sof_pdata *pdata = sdev->pdata;
struct sof_intel_hda_dev *hdev = pdata->hw_pdata;
return ret;
}
- hda_bus_ml_get_capabilities(bus);
+ hda_bus_ml_init(bus);
+
+ /* Skip SoundWire if it is not supported */
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ goto skip_soundwire;
/* scan SoundWire capabilities exposed by DSDT */
ret = hda_sdw_acpi_scan(sdev);
const struct sof_intel_dsp_desc *chip;
int ret = 0;
- /*
- * detect DSP by checking class/subclass/prog-id information
- * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required
- * class=04 subclass 01 prog-if 00: DSP is present
- * (and may be required e.g. for DMIC or SSP support)
- * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works
- */
- if (pci->class == 0x040300) {
- dev_err(sdev->dev, "error: the DSP is not enabled on this platform, aborting probe\n");
- return -ENODEV;
- } else if (pci->class != 0x040100 && pci->class != 0x040380) {
- dev_err(sdev->dev, "error: unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n", pci->class);
- return -ENODEV;
+ if (!sdev->dspless_mode_selected) {
+ /*
+ * detect DSP by checking class/subclass/prog-id information
+ * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required
+ * class=04 subclass 01 prog-if 00: DSP is present
+ * (and may be required e.g. for DMIC or SSP support)
+ * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works
+ */
+ if (pci->class == 0x040300) {
+ dev_err(sdev->dev, "the DSP is not enabled on this platform, aborting probe\n");
+ return -ENODEV;
+ } else if (pci->class != 0x040100 && pci->class != 0x040380) {
+ dev_err(sdev->dev, "unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n",
+ pci->class);
+ return -ENODEV;
+ }
+ dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n",
+ pci->class);
}
- dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n", pci->class);
chip = get_chip_info(sdev->pdata);
if (!chip) {
hdev->no_ipc_position = sof_ops(sdev)->pcm_pointer ? 1 : 0;
#endif
+ if (sdev->dspless_mode_selected)
+ hdev->no_ipc_position = 1;
+
/* set up HDA base */
bus = sof_to_bus(sdev);
ret = hda_init(sdev);
if (ret < 0)
goto hdac_bus_unmap;
+ if (sdev->dspless_mode_selected)
+ goto skip_dsp_setup;
+
/* DSP base */
sdev->bar[HDA_DSP_BAR] = pci_ioremap_bar(pci, HDA_DSP_BAR);
if (!sdev->bar[HDA_DSP_BAR]) {
sdev->mmio_bar = HDA_DSP_BAR;
sdev->mailbox_bar = HDA_DSP_BAR;
+skip_dsp_setup:
/* allow 64bit DMA address if supported by H/W */
if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(64))) {
if (ret < 0)
goto free_ipc_irq;
- /* enable ppcap interrupt */
- hda_dsp_ctrl_ppcap_enable(sdev, true);
- hda_dsp_ctrl_ppcap_int_enable(sdev, true);
+ if (!sdev->dspless_mode_selected) {
+ /* enable ppcap interrupt */
+ hda_dsp_ctrl_ppcap_enable(sdev, true);
+ hda_dsp_ctrl_ppcap_int_enable(sdev, true);
- /* set default mailbox offset for FW ready message */
- sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET;
+ /* set default mailbox offset for FW ready message */
+ sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET;
- INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work);
+ INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work);
+ }
init_waitqueue_head(&hdev->waitq);
free_streams:
hda_dsp_stream_free(sdev);
/* dsp_unmap: not currently used */
- iounmap(sdev->bar[HDA_DSP_BAR]);
+ if (!sdev->dspless_mode_selected)
+ iounmap(sdev->bar[HDA_DSP_BAR]);
hdac_bus_unmap:
platform_device_unregister(hdev->dmic_dev);
iounmap(bus->remap_addr);
if (nhlt)
intel_nhlt_free(nhlt);
- /* cancel any attempt for DSP D0I3 */
- cancel_delayed_work_sync(&hda->d0i3_work);
+ if (!sdev->dspless_mode_selected)
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
hda_codec_device_remove(sdev);
if (!IS_ERR_OR_NULL(hda->dmic_dev))
platform_device_unregister(hda->dmic_dev);
- /* disable DSP IRQ */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- SOF_HDA_PPCTL_PIE, 0);
+ if (!sdev->dspless_mode_selected) {
+ /* disable DSP IRQ */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+ SOF_HDA_PPCTL_PIE, 0);
+ }
/* disable CIE and GIE interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, 0);
+ if (sdev->dspless_mode_selected)
+ goto skip_disable_dsp;
+
/* no need to check for error as the DSP will be disabled anyway */
if (chip && chip->power_down_dsp)
chip->power_down_dsp(sdev);
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
SOF_HDA_PPCTL_GPROCEN, 0);
+skip_disable_dsp:
free_irq(sdev->ipc_irq, sdev);
if (sdev->msi_enabled)
pci_free_irq_vectors(pci);
hda_bus_ml_free(sof_to_bus(sdev));
- iounmap(sdev->bar[HDA_DSP_BAR]);
+ if (!sdev->dspless_mode_selected)
+ iounmap(sdev->bar[HDA_DSP_BAR]);
+
iounmap(bus->remap_addr);
sof_hda_bus_exit(sdev);
struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct snd_sof_pdata *sof_pdata = sdev->pdata;
const struct sof_dev_desc *desc = sof_pdata->desc;
- struct snd_soc_acpi_mach *mach;
+ struct snd_soc_acpi_mach *mach = NULL;
const char *tplg_filename;
- mach = snd_soc_acpi_find_machine(desc->machines);
+ /* Try I2S or DMIC if it is supported */
+ if (interface_mask & (BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC)))
+ mach = snd_soc_acpi_find_machine(desc->machines);
+
if (mach) {
bool add_extension = false;
bool tplg_fixup = false;
}
}
- /*
- * If I2S fails, try SoundWire
- */
- if (!mach)
+ /* If I2S fails, try SoundWire if it is supported */
+ if (!mach && (interface_mask & BIT(SOF_DAI_INTEL_ALH)))
mach = hda_sdw_machine_select(sdev);
/*
MODULE_IMPORT_NS(SND_INTEL_SOUNDWIRE_ACPI);
MODULE_IMPORT_NS(SOUNDWIRE_INTEL_INIT);
MODULE_IMPORT_NS(SOUNDWIRE_INTEL);
+MODULE_IMPORT_NS(SND_SOC_SOF_HDA_MLINK);
u32 stream_max;
/* PM related */
- bool l1_support_changed;/* during suspend, is L1SEN changed or not */
+ bool l1_disabled;/* is DMI link L1 disabled? */
/* DMIC device */
struct platform_device *dmic_dev;
void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev);
bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask);
-int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
- const struct sof_dsp_power_state *target_state);
+int hda_dsp_set_power_state_ipc3(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state);
+int hda_dsp_set_power_state_ipc4(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state);
int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state);
int hda_dsp_resume(struct snd_sof_dev *sdev);
#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-
-void hda_bus_ml_get_capabilities(struct hdac_bus *bus);
-void hda_bus_ml_free(struct hdac_bus *bus);
-void hda_bus_ml_put_all(struct hdac_bus *bus);
-void hda_bus_ml_reset_losidv(struct hdac_bus *bus);
-int hda_bus_ml_resume(struct hdac_bus *bus);
-int hda_bus_ml_suspend(struct hdac_bus *bus);
-
-#else
-
-static inline void hda_bus_ml_get_capabilities(struct hdac_bus *bus) { }
-static inline void hda_bus_ml_free(struct hdac_bus *bus) { }
-static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { }
-static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { }
-static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; }
-static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; }
-
-#endif /* CONFIG_SND_SOC_SOF_HDA */
-
/*
* Trace Control.
*/
struct snd_sof_dai;
struct sof_ipc_dai_config;
-int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
- struct snd_sof_dai_config_data *data);
-int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
- struct snd_sof_dai_config_data *data);
#define SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY (0) /* previous implementation */
#define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */
struct sof_ipc4_fw_library;
int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev,
struct sof_ipc4_fw_library *fw_lib, bool reload);
+
+/**
+ * struct hda_dai_widget_dma_ops - DAI DMA ops optional by default unless specified otherwise
+ * @get_hext_stream: Mandatory function pointer to get the saved pointer to struct hdac_ext_stream
+ * @assign_hext_stream: Function pointer to assign a hdac_ext_stream
+ * @release_hext_stream: Function pointer to release the hdac_ext_stream
+ * @setup_hext_stream: Function pointer for hdac_ext_stream setup
+ * @reset_hext_stream: Function pointer for hdac_ext_stream reset
+ * @pre_trigger: Function pointer for DAI DMA pre-trigger actions
+ * @trigger: Function pointer for DAI DMA trigger actions
+ * @post_trigger: Function pointer for DAI DMA post-trigger actions
+ */
+struct hda_dai_widget_dma_ops {
+ struct hdac_ext_stream *(*get_hext_stream)(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream);
+ struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream);
+ void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream);
+ void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,
+ unsigned int format_val);
+ void (*reset_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_sream);
+ int (*pre_trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd);
+ int (*trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd);
+ int (*post_trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd);
+};
+
+const struct hda_dai_widget_dma_ops *
+hda_select_dai_widget_ops(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags,
+ struct snd_sof_dai_config_data *data);
+
#endif
/* debug */
sof_icl_ops.ipc_dump = cnl_ipc_dump;
+
+ sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) {
/* debug */
sof_icl_ops.ipc_dump = cnl_ipc4_dump;
+
+ sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* debug */
u32 irq_status;
u32 hfintipptr;
+ if (sdev->dspless_mode_selected)
+ return false;
+
/* read Interrupt IP Pointer */
hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS);
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
+ if (sdev->dspless_mode_selected)
+ return;
+
/* enable IPC DONE and BUSY interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE,
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
+ if (sdev->dspless_mode_selected)
+ return;
+
/* disable IPC DONE and BUSY interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, 0);
u32 val;
int ret;
+ if (sdev->dspless_mode_selected)
+ return;
+
/* Enable/Disable SoundWire interrupt */
mask = MTL_DSP_REG_HfSNDWIE_IE_MASK;
if (enable)
u32 val;
int ret;
+ if (sdev->dspless_mode_selected)
+ return 0;
+
/* read Interrupt IP Pointer */
hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
/* pre fw run operations */
static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev)
{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
u32 dsphfpwrsts;
u32 dsphfdsscs;
u32 cpa;
if (ret < 0)
dev_err(sdev->dev, "failed to power up gated DSP domain\n");
- /* make sure SoundWire is not power-gated */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, MTL_HFPWRCTL,
- MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1));
+ /* if SoundWire is used, make sure it is not power-gated */
+ if (hdev->info.handle && hdev->info.link_mask > 0)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL,
+ MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1));
+
return ret;
}
/* set DAI ops */
hda_set_dai_drv_ops(sdev, &sof_mtl_ops);
+ sof_mtl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
+
return 0;
};
EXPORT_SYMBOL_NS(sof_mtl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
.chip_info = &apl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/apl",
.chip_info = &apl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/glk",
.chip_info = &cnl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/cnl",
.chip_info = &cnl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/cnl",
.chip_info = &cnl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/cnl",
.chip_info = &icl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/icl",
.chip_info = &jsl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/jsl",
.chip_info = &mtl_chip_info,
.ipc_supported_mask = BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_INTEL_IPC4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_INTEL_IPC4] = "intel/sof-ipc4/mtl",
},
.irqindex_host_ipc = -1,
.ipc_supported_mask = BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_INTEL_IPC4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_INTEL_IPC4] = "intel/avs/skl",
},
.irqindex_host_ipc = -1,
.ipc_supported_mask = BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_INTEL_IPC4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_INTEL_IPC4] = "intel/avs/kbl",
},
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/tgl",
.chip_info = &tglh_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/tgl-h",
.chip_info = &ehl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/ehl",
.chip_info = &adls_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/adl-s",
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/adl",
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/adl-n",
.chip_info = &adls_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/rpl-s",
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4),
.ipc_default = SOF_IPC,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC] = "intel/sof",
[SOF_INTEL_IPC4] = "intel/avs/rpl",
/* debug */
sof_tgl_ops.ipc_dump = cnl_ipc_dump;
+
+ sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) {
/* debug */
sof_tgl_ops.ipc_dump = cnl_ipc4_dump;
+
+ sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* set DAI driver ops */
return ret;
}
-static void snd_sof_refresh_control(struct snd_sof_control *scontrol)
+static void sof_ipc3_refresh_control(struct snd_sof_control *scontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
unsigned int channels = scontrol->num_channels;
unsigned int i;
- snd_sof_refresh_control(scontrol);
+ sof_ipc3_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
unsigned int channels = scontrol->num_channels;
unsigned int i;
- snd_sof_refresh_control(scontrol);
+ sof_ipc3_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
unsigned int channels = scontrol->num_channels;
unsigned int i;
- snd_sof_refresh_control(scontrol);
+ sof_ipc3_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
struct sof_abi_hdr *data = cdata->data;
size_t size;
- snd_sof_refresh_control(scontrol);
+ sof_ipc3_refresh_control(scontrol);
if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
return 0;
}
-static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol,
- const unsigned int __user *binary_data, unsigned int size)
-{
- struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
- struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
- struct snd_soc_component *scomp = scontrol->scomp;
- struct snd_ctl_tlv header;
- size_t data_size;
-
- snd_sof_refresh_control(scontrol);
-
- /*
- * Decrement the limit by ext bytes header size to
- * ensure the user space buffer is not exceeded.
- */
- if (size < sizeof(struct snd_ctl_tlv))
- return -ENOSPC;
-
- size -= sizeof(struct snd_ctl_tlv);
-
- /* set the ABI header values */
- cdata->data->magic = SOF_ABI_MAGIC;
- cdata->data->abi = SOF_ABI_VERSION;
-
- /* check data size doesn't exceed max coming from topology */
- if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
- dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n",
- cdata->data->size,
- scontrol->max_size - sizeof(struct sof_abi_hdr));
- return -EINVAL;
- }
-
- data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
-
- /* make sure we don't exceed size provided by user space for data */
- if (data_size > size)
- return -ENOSPC;
-
- header.numid = cdata->cmd;
- header.length = data_size;
- if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
- return -EFAULT;
-
- if (copy_to_user(tlvd->tlv, cdata->data, data_size))
- return -EFAULT;
-
- return 0;
-}
-
static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data,
unsigned int size)
return 0;
}
-static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
- const unsigned int __user *binary_data,
- unsigned int size)
+static int _sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size, bool from_dsp)
{
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
struct snd_ctl_tlv header;
size_t data_size;
- int ret;
/*
* Decrement the limit by ext bytes header size to
cdata->data->abi = SOF_ABI_VERSION;
/* get all the component data from DSP */
- ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true);
- if (ret < 0)
- return ret;
+ if (from_dsp) {
+ int ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true);
+
+ if (ret < 0)
+ return ret;
+ }
/* check data size doesn't exceed max coming from topology */
if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
return -EFAULT;
- return ret;
+ return 0;
+}
+
+static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data, unsigned int size)
+{
+ return _sof_ipc3_bytes_ext_get(scontrol, binary_data, size, false);
+}
+
+static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return _sof_ipc3_bytes_ext_get(scontrol, binary_data, size, true);
}
static void snd_sof_update_control(struct snd_sof_control *scontrol,
struct sof_ipc_trace_filter_elem *elems)
{
struct sof_ipc_trace_filter *msg;
- struct sof_ipc_reply reply;
size_t size;
int ret;
dev_err(sdev->dev, "enabling device failed: %d\n", ret);
goto error;
}
- ret = sof_ipc_tx_message(sdev->ipc, msg, msg->hdr.size, &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, msg->hdr.size);
pm_runtime_mark_last_busy(sdev->dev);
pm_runtime_put_autosuspend(sdev->dev);
error:
kfree(msg);
- return ret ? ret : reply.error;
+ return ret;
}
static ssize_t dfsentry_trace_filter_write(struct file *file, const char __user *from,
struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
struct sof_ipc_fw_version *v = &ready->version;
struct sof_ipc_dma_trace_params_ext params;
- struct sof_ipc_reply ipc_reply;
int ret;
if (!sdev->fw_trace_is_supported)
/* send IPC to the DSP */
priv->dtrace_state = SOF_DTRACE_INITIALIZING;
- ret = sof_ipc_tx_message(sdev->ipc, ¶ms, sizeof(params), &ipc_reply, sizeof(ipc_reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, ¶ms, sizeof(params));
if (ret < 0) {
dev_err(sdev->dev, "can't set params for DMA for trace %d\n", ret);
goto trace_release;
struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
struct sof_ipc_fw_version *v = &ready->version;
struct sof_ipc_cmd_hdr hdr;
- struct sof_ipc_reply ipc_reply;
int ret;
if (!sdev->fw_trace_is_supported || priv->dtrace_state == SOF_DTRACE_DISABLED)
hdr.size = sizeof(hdr);
hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE;
- ret = sof_ipc_tx_message(sdev->ipc, &hdr, hdr.size,
- &ipc_reply, sizeof(ipc_reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &hdr, hdr.size);
if (ret < 0)
dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret);
}
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
struct snd_sof_pcm *spcm;
spcm = snd_sof_find_spcm_dai(component, rtd);
stream.comp_id = spcm->stream[substream->stream].comp_id;
/* send IPC to the DSP */
- return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply));
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
}
static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
struct snd_sof_pcm *spcm;
spcm = snd_sof_find_spcm_dai(component, rtd);
}
/* send IPC to the DSP */
- return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply));
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
}
static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
.hw_free = sof_ipc3_pcm_hw_free,
.trigger = sof_ipc3_pcm_trigger,
.dai_link_fixup = sof_ipc3_pcm_dai_link_fixup,
+ .reset_hw_params_during_stop = true,
};
static int sof_ipc3_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
{
struct sof_ipc_pipe_comp_connect connect;
- struct sof_ipc_reply reply;
int ret;
connect.hdr.size = sizeof(connect);
sroute->sink_widget->widget->name);
/* send ipc */
- ret = sof_ipc_tx_message(sdev->ipc, &connect, sizeof(connect), &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &connect, sizeof(connect));
if (ret < 0)
dev_err(sdev->dev, "%s: route %s -> %s failed\n", __func__,
sroute->src_widget->widget->name, sroute->sink_widget->widget->name);
fcomp.id = scontrol->comp_id;
/* send IPC to the DSP */
- return sof_ipc_tx_message(sdev->ipc, &fcomp, sizeof(fcomp), NULL, 0);
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &fcomp, sizeof(fcomp));
}
/* send pcm params ipc */
{
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_pcm_params_reply ipc_params_reply;
struct snd_pcm_hw_params *params;
struct sof_ipc_pcm_params pcm;
struct snd_sof_pcm *spcm;
}
/* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm),
- &ipc_params_reply, sizeof(ipc_params_reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &pcm, sizeof(pcm));
if (ret < 0)
dev_err(scomp->dev, "%s: PCM params failed for %s\n", __func__,
swidget->widget->name);
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
int ret;
/* set IPC stream params */
stream.comp_id = swidget->comp_id;
/* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
if (ret < 0)
dev_err(scomp->dev, "%s: Failed to trigger %s\n", __func__, swidget->widget->name);
static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_pipe_ready ready;
- struct sof_ipc_reply reply;
int ret;
dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n",
ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE;
ready.comp_id = swidget->comp_id;
- ret = sof_ipc_tx_message(sdev->ipc, &ready, sizeof(ready), &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &ready, sizeof(ready));
if (ret < 0)
return ret;
},
.id = swidget->comp_id,
};
- struct sof_ipc_reply reply;
int ret;
if (!swidget->private)
break;
}
- ret = sof_ipc_tx_message(sdev->ipc, &ipc_free, sizeof(ipc_free),
- &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &ipc_free, sizeof(ipc_free));
if (ret < 0)
dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name);
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
- struct sof_ipc_reply reply;
int ret = 0;
if (!dai || !dai->private) {
/* only send the IPC if the widget is set up in the DSP */
if (swidget->use_count > 0) {
- ret = sof_ipc_tx_message(sdev->ipc, config, config->hdr.size,
- &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, config, config->hdr.size);
if (ret < 0)
dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name);
static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
- struct sof_ipc_comp_reply reply;
int ret;
if (!swidget->private)
struct sof_dai_private_data *dai_data = dai->private;
struct sof_ipc_comp *comp = &dai_data->comp_dai->comp;
- ret = sof_ipc_tx_message(sdev->ipc, dai_data->comp_dai,
- comp->hdr.size, &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, dai_data->comp_dai, comp->hdr.size);
break;
}
case snd_soc_dapm_scheduler:
struct sof_ipc_pipe_new *pipeline;
pipeline = swidget->private;
- ret = sof_ipc_tx_message(sdev->ipc, pipeline, sizeof(*pipeline),
- &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, pipeline, sizeof(*pipeline));
break;
}
default:
struct sof_ipc_cmd_hdr *hdr;
hdr = swidget->private;
- ret = sof_ipc_tx_message(sdev->ipc, swidget->private, hdr->size,
- &reply, sizeof(reply));
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, swidget->private, hdr->size);
break;
}
}
.hdr.size = sizeof(core_cfg),
.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE,
};
- struct sof_ipc_reply reply;
if (on)
core_cfg.enable_mask = sdev->enabled_cores_mask | BIT(core_idx);
else
core_cfg.enable_mask = sdev->enabled_cores_mask & ~BIT(core_idx);
- return sof_ipc3_tx_msg(sdev, &core_cfg, sizeof(core_cfg),
- &reply, sizeof(reply), false);
+ return sof_ipc3_tx_msg(sdev, &core_cfg, sizeof(core_cfg), NULL, 0, false);
}
static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
.hdr.size = sizeof(pm_ctx),
.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd,
};
- struct sof_ipc_reply reply;
/* send ctx save ipc to dsp */
- return sof_ipc3_tx_msg(sdev, &pm_ctx, sizeof(pm_ctx),
- &reply, sizeof(reply), false);
+ return sof_ipc3_tx_msg(sdev, &pm_ctx, sizeof(pm_ctx), NULL, 0, false);
}
static int sof_ipc3_ctx_save(struct snd_sof_dev *sdev)
static int sof_ipc3_set_pm_gate(struct snd_sof_dev *sdev, u32 flags)
{
struct sof_ipc_pm_gate pm_gate;
- struct sof_ipc_reply reply;
memset(&pm_gate, 0, sizeof(pm_gate));
pm_gate.flags = flags;
/* send pm_gate ipc to dsp */
- return sof_ipc_tx_message_no_pm(sdev->ipc, &pm_gate, sizeof(pm_gate),
- &reply, sizeof(reply));
+ return sof_ipc_tx_message_no_pm_no_reply(sdev->ipc, &pm_gate, sizeof(pm_gate));
}
static const struct sof_ipc_pm_ops ipc3_pm_ops = {
return 0;
}
+static int sof_ipc4_set_get_bytes_data(struct snd_sof_dev *sdev,
+ struct snd_sof_control *scontrol,
+ bool set, bool lock)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_abi_hdr *data = cdata->data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ int ret = 0;
+
+ /* Send the new data to the firmware only if it is powered up */
+ if (set && !pm_runtime_active(sdev->dev))
+ return 0;
+
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(data->type);
+
+ msg->data_ptr = data->data;
+ msg->data_size = data->size;
+
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, set, lock);
+ if (ret < 0)
+ dev_err(sdev->dev, "Failed to %s for %s\n",
+ set ? "set bytes update" : "get bytes",
+ scontrol->name);
+
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+
+ return ret;
+}
+
+static int sof_ipc4_bytes_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_abi_hdr *data = cdata->data;
+ size_t size;
+
+ if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
+ dev_err_ratelimited(scomp->dev,
+ "data max %zu exceeds ucontrol data array size\n",
+ scontrol->max_size);
+ return -EINVAL;
+ }
+
+ /* scontrol->max_size has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "data size too big %u bytes max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ size = data->size + sizeof(*data);
+
+ /* copy from kcontrol */
+ memcpy(data, ucontrol->value.bytes.data, size);
+
+ sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true);
+
+ return 0;
+}
+
+static int sof_ipc4_bytes_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_abi_hdr *data = cdata->data;
+ size_t size;
+
+ if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
+ dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
+ scontrol->max_size);
+ return -EINVAL;
+ }
+
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "%u bytes of control data is invalid, max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ size = data->size + sizeof(*data);
+
+ /* copy back to kcontrol */
+ memcpy(ucontrol->value.bytes.data, data, size);
+
+ return 0;
+}
+
+static int sof_ipc4_bytes_ext_put(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_abi_hdr *data = cdata->data;
+ struct sof_abi_hdr abi_hdr;
+ struct snd_ctl_tlv header;
+
+ /*
+ * The beginning of bytes data contains a header from where
+ * the length (as bytes) is needed to know the correct copy
+ * length of data from tlvd->tlv.
+ */
+ if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv)))
+ return -EFAULT;
+
+ /* make sure TLV info is consistent */
+ if (header.length + sizeof(struct snd_ctl_tlv) > size) {
+ dev_err_ratelimited(scomp->dev,
+ "Inconsistent TLV, data %d + header %zu > %d\n",
+ header.length, sizeof(struct snd_ctl_tlv), size);
+ return -EINVAL;
+ }
+
+ /* be->max is coming from topology */
+ if (header.length > scontrol->max_size) {
+ dev_err_ratelimited(scomp->dev,
+ "Bytes data size %d exceeds max %zu\n",
+ header.length, scontrol->max_size);
+ return -EINVAL;
+ }
+
+ /* Verify the ABI header first */
+ if (copy_from_user(&abi_hdr, tlvd->tlv, sizeof(abi_hdr)))
+ return -EFAULT;
+
+ if (abi_hdr.magic != SOF_IPC4_ABI_MAGIC) {
+ dev_err_ratelimited(scomp->dev, "Wrong ABI magic 0x%08x\n",
+ abi_hdr.magic);
+ return -EINVAL;
+ }
+
+ if (abi_hdr.size > scontrol->max_size - sizeof(abi_hdr)) {
+ dev_err_ratelimited(scomp->dev,
+ "%u bytes of control data is invalid, max is %zu\n",
+ abi_hdr.size, scontrol->max_size - sizeof(abi_hdr));
+ return -EINVAL;
+ }
+
+ /* Copy the whole binary data which includes the ABI header and the payload */
+ if (copy_from_user(data, tlvd->tlv, header.length))
+ return -EFAULT;
+
+ sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true);
+
+ return 0;
+}
+
+static int _sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size, bool from_dsp)
+{
+ struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_abi_hdr *data = cdata->data;
+ struct snd_ctl_tlv header;
+ size_t data_size;
+
+ /*
+ * Decrement the limit by ext bytes header size to ensure the user space
+ * buffer is not exceeded.
+ */
+ if (size < sizeof(struct snd_ctl_tlv))
+ return -ENOSPC;
+
+ size -= sizeof(struct snd_ctl_tlv);
+
+ /* get all the component data from DSP */
+ if (from_dsp) {
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ int ret = sof_ipc4_set_get_bytes_data(sdev, scontrol, false, true);
+
+ if (ret < 0)
+ return ret;
+
+ /* Set the ABI magic (if the control is not initialized) */
+ data->magic = SOF_IPC4_ABI_MAGIC;
+ }
+
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "%u bytes of control data is invalid, max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ data_size = data->size + sizeof(struct sof_abi_hdr);
+
+ /* make sure we don't exceed size provided by user space for data */
+ if (data_size > size)
+ return -ENOSPC;
+
+ header.numid = scontrol->comp_id;
+ header.length = data_size;
+
+ if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
+ return -EFAULT;
+
+ if (copy_to_user(tlvd->tlv, data, data_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, false);
+}
+
+static int sof_ipc4_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, true);
+}
+
/* set up all controls for the widget */
static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct snd_sof_control *scontrol;
- int ret;
+ int ret = 0;
- list_for_each_entry(scontrol, &sdev->kcontrol_list, list)
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
if (scontrol->comp_id == swidget->comp_id) {
- ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol, false);
+ switch (scontrol->info_type) {
+ case SND_SOC_TPLG_CTL_VOLSW:
+ case SND_SOC_TPLG_CTL_VOLSW_SX:
+ case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
+ ret = sof_ipc4_set_volume_data(sdev, swidget,
+ scontrol, false);
+ break;
+ case SND_SOC_TPLG_CTL_BYTES:
+ ret = sof_ipc4_set_get_bytes_data(sdev, scontrol,
+ true, false);
+ break;
+ default:
+ break;
+ }
+
if (ret < 0) {
- dev_err(sdev->dev, "%s: kcontrol %d set up failed for widget %s\n",
- __func__, scontrol->comp_id, swidget->widget->name);
+ dev_err(sdev->dev,
+ "kcontrol %d set up failed for widget %s\n",
+ scontrol->comp_id, swidget->widget->name);
return ret;
}
}
+ }
return 0;
}
const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = {
.volume_put = sof_ipc4_volume_put,
.volume_get = sof_ipc4_volume_get,
+ .bytes_put = sof_ipc4_bytes_put,
+ .bytes_get = sof_ipc4_bytes_get,
+ .bytes_ext_put = sof_ipc4_bytes_ext_put,
+ .bytes_ext_get = sof_ipc4_bytes_ext_get,
+ .bytes_ext_volatile_get = sof_ipc4_bytes_ext_volatile_get,
.widget_kcontrol_setup = sof_ipc4_widget_kcontrol_setup,
.set_up_volume_table = sof_ipc4_set_up_volume_table,
};
ipc4_mtrace_disable(sdev);
}
+static int sof_ipc4_mtrace_update_pos_all_cores(struct snd_sof_dev *sdev)
+{
+ int i;
+
+ for (i = 0; i < sdev->num_cores; i++)
+ sof_ipc4_mtrace_update_pos(sdev, i);
+
+ return 0;
+}
+
int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core)
{
struct sof_mtrace_priv *priv = sdev->fw_trace_data;
return 0;
}
+static void ipc4_mtrace_fw_crashed(struct snd_sof_dev *sdev)
+{
+ /*
+ * The DSP might not be able to send SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS
+ * messages anymore, so check the log buffer status on all
+ * cores and process any pending messages.
+ */
+ sof_ipc4_mtrace_update_pos_all_cores(sdev);
+}
+
static int ipc4_mtrace_resume(struct snd_sof_dev *sdev)
{
return ipc4_mtrace_enable(sdev);
const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops = {
.init = ipc4_mtrace_init,
.free = ipc4_mtrace_free,
+ .fw_crashed = ipc4_mtrace_fw_crashed,
.suspend = ipc4_mtrace_suspend,
.resume = ipc4_mtrace_resume,
};
msg.data_size = ipc_size;
msg.data_ptr = trigger_list;
- return sof_ipc_tx_message(sdev->ipc, &msg, ipc_size, NULL, 0);
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, ipc_size);
}
int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state)
msg.primary = primary;
- return sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
}
EXPORT_SYMBOL(sof_ipc4_set_pipeline_state);
* prepare ioctl before the START trigger.
*/
+/*
+ * Chained DMA is a special case where there is no processing on
+ * DSP. The samples are just moved over by host side DMA to a single
+ * buffer on DSP and directly from there to link DMA. However, the
+ * model on SOF driver has two notional pipelines, one at host DAI,
+ * and another at link DAI. They both shall have the use_chain_dma
+ * attribute.
+ */
+
+static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream_pipeline_list *pipeline_list,
+ int state, int cmd)
+{
+ bool allocate, enable, set_fifo_size;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ int i;
+
+ switch (state) {
+ case SOF_IPC4_PIPE_RUNNING: /* Allocate and start chained dma */
+ allocate = true;
+ enable = true;
+ /*
+ * SOF assumes creation of a new stream from the presence of fifo_size
+ * in the message, so we must leave it out in pause release case.
+ */
+ if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
+ set_fifo_size = false;
+ else
+ set_fifo_size = true;
+ break;
+ case SOF_IPC4_PIPE_PAUSED: /* Disable chained DMA. */
+ allocate = true;
+ enable = false;
+ set_fifo_size = false;
+ break;
+ case SOF_IPC4_PIPE_RESET: /* Disable and free chained DMA. */
+ allocate = false;
+ enable = false;
+ set_fifo_size = false;
+ break;
+ default:
+ dev_err(sdev->dev, "Unexpected state %d", state);
+ return -EINVAL;
+ }
+
+ msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ /*
+ * To set-up the DMA chain, the host DMA ID and SCS setting
+ * are retrieved from the host pipeline configuration. Likewise
+ * the link DMA ID and fifo_size are retrieved from the link
+ * pipeline configuration.
+ */
+ for (i = 0; i < pipeline_list->count; i++) {
+ struct snd_sof_pipeline *spipe = pipeline_list->pipelines[i];
+ struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (!pipeline->use_chain_dma) {
+ dev_err(sdev->dev,
+ "All pipelines in chained DMA stream should have use_chain_dma attribute set.");
+ return -EINVAL;
+ }
+
+ msg.primary |= pipeline->msg.primary;
+
+ /* Add fifo_size (actually DMA buffer size) field to the message */
+ if (set_fifo_size)
+ msg.extension |= pipeline->msg.extension;
+ }
+
+ if (allocate)
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK;
+
+ if (enable)
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;
+
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+}
+
static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int state, int cmd)
{
struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
struct ipc4_pipeline_set_state_data *trigger_list;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
struct snd_sof_pipeline *spipe;
struct snd_sof_pcm *spcm;
int ret;
if (!pipeline_list->pipelines || !pipeline_list->count)
return 0;
+ spipe = pipeline_list->pipelines[0];
+ pipe_widget = spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ /*
+ * If use_chain_dma attribute is set we proceed to chained DMA
+ * trigger function that handles the rest for the substream.
+ */
+ if (pipeline->use_chain_dma)
+ return sof_ipc4_chain_dma_trigger(sdev, pipeline_list, state, cmd);
+
/* allocate memory for the pipeline data */
trigger_list = kzalloc(struct_size(trigger_list, pipeline_ids, pipeline_list->count),
GFP_KERNEL);
}
}
+/*
+ * Fixup DAI link parameters for sampling rate based on
+ * DAI copier configuration.
+ */
+static int sof_ipc4_pcm_dai_link_fixup_rate(struct snd_sof_dev *sdev,
+ struct snd_pcm_hw_params *params,
+ struct sof_ipc4_copier *ipc4_copier)
+{
+ struct sof_ipc4_pin_format *pin_fmts = ipc4_copier->available_fmt.input_pin_fmts;
+ struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ int num_input_formats = ipc4_copier->available_fmt.num_input_formats;
+ unsigned int fe_rate = params_rate(params);
+ bool fe_be_rate_match = false;
+ bool single_be_rate = true;
+ unsigned int be_rate;
+ int i;
+
+ /*
+ * Copier does not change sampling rate, so we
+ * need to only consider the input pin information.
+ */
+ for (i = 0; i < num_input_formats; i++) {
+ unsigned int val = pin_fmts[i].audio_fmt.sampling_frequency;
+
+ if (i == 0)
+ be_rate = val;
+ else if (val != be_rate)
+ single_be_rate = false;
+
+ if (val == fe_rate) {
+ fe_be_rate_match = true;
+ break;
+ }
+ }
+
+ /*
+ * If rate is different than FE rate, topology must
+ * contain an SRC. But we do require topology to
+ * define a single rate in the DAI copier config in
+ * this case (FE rate may be variable).
+ */
+ if (!fe_be_rate_match) {
+ if (!single_be_rate) {
+ dev_err(sdev->dev, "Unable to select sampling rate for DAI link\n");
+ return -EINVAL;
+ }
+
+ rate->min = be_rate;
+ rate->max = rate->min;
+ }
+
+ return 0;
+}
+
static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_sof_dai *dai = snd_sof_find_dai(component, rtd->dai_link->name);
- struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
- struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
struct sof_ipc4_copier *ipc4_copier;
+ bool use_chain_dma = false;
+ int dir;
if (!dai) {
dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
return -EINVAL;
}
- /* always set BE format to 32-bits for both playback and capture */
- snd_mask_none(fmt);
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ for_each_pcm_streams(dir) {
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, dir);
+
+ if (w) {
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (pipeline->use_chain_dma)
+ use_chain_dma = true;
+ }
+ }
+
+ /* Chain DMA does not use copiers, so no fixup needed */
+ if (!use_chain_dma) {
+ int ret = sof_ipc4_pcm_dai_link_fixup_rate(sdev, params, ipc4_copier);
- rate->min = ipc4_copier->available_fmt.base_config->audio_fmt.sampling_frequency;
- rate->max = rate->min;
+ if (ret)
+ return ret;
+ }
switch (ipc4_copier->dai_type) {
case SOF_DAI_INTEL_SSP:
.dai_link_fixup = sof_ipc4_pcm_dai_link_fixup,
.pcm_setup = sof_ipc4_pcm_setup,
.pcm_free = sof_ipc4_pcm_free,
- .delay = sof_ipc4_pcm_delay
+ .delay = sof_ipc4_pcm_delay,
+ .ipc_first_on_start = true,
+ .platform_stop_during_hw_free = true,
};
// Copyright(c) 2022 Intel Corporation. All rights reserved.
//
//
+#include <linux/bitfield.h>
#include <uapi/sound/sof/tokens.h>
#include <sound/pcm_params.h>
#include <sound/sof/ext_manifest4.h>
#define SOF_IPC4_GAIN_PARAM_ID 0
#define SOF_IPC4_TPLG_ABI_SIZE 6
+#define SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS 2
static DEFINE_IDA(alh_group_ida);
static DEFINE_IDA(pipeline_ida);
static const struct sof_topology_token ipc4_sched_tokens[] = {
{SOF_TKN_SCHED_LP_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_pipeline, lp_mode)}
+ offsetof(struct sof_ipc4_pipeline, lp_mode)},
+ {SOF_TKN_SCHED_USE_CHAIN_DMA, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct sof_ipc4_pipeline, use_chain_dma)},
+ {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pipeline, core_id)},
};
static const struct sof_topology_token pipeline_tokens[] = {
offsetof(struct sof_ipc4_base_module_cfg, is_pages)},
};
-static const struct sof_topology_token ipc4_audio_format_buffer_size_tokens[] = {
- {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_base_module_cfg, ibs)},
- {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_base_module_cfg, obs)},
-};
-
static const struct sof_topology_token ipc4_in_audio_format_tokens[] = {
{SOF_TKN_CAVS_AUDIO_FORMAT_IN_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, sampling_frequency)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.sampling_frequency)},
{SOF_TKN_CAVS_AUDIO_FORMAT_IN_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, bit_depth)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.bit_depth)},
{SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, ch_map)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_map)},
{SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, ch_cfg)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_cfg)},
{SOF_TKN_CAVS_AUDIO_FORMAT_IN_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)},
+ get_token_u32, offsetof(struct sof_ipc4_pin_format,
+ audio_fmt.interleaving_style)},
{SOF_TKN_CAVS_AUDIO_FORMAT_IN_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, fmt_cfg)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.fmt_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_PIN_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, pin_index)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, buffer_size)},
};
static const struct sof_topology_token ipc4_out_audio_format_tokens[] = {
{SOF_TKN_CAVS_AUDIO_FORMAT_OUT_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, sampling_frequency)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.sampling_frequency)},
{SOF_TKN_CAVS_AUDIO_FORMAT_OUT_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, bit_depth)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.bit_depth)},
{SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, ch_map)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_map)},
{SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, ch_cfg)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_cfg)},
{SOF_TKN_CAVS_AUDIO_FORMAT_OUT_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)},
+ get_token_u32, offsetof(struct sof_ipc4_pin_format,
+ audio_fmt.interleaving_style)},
{SOF_TKN_CAVS_AUDIO_FORMAT_OUT_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc4_audio_format, fmt_cfg)},
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.fmt_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_PIN_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, pin_index)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, buffer_size)},
};
-static const struct sof_topology_token ipc4_copier_gateway_cfg_tokens[] = {
- {SOF_TKN_CAVS_AUDIO_FORMAT_DMA_BUFFER_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0},
+static const struct sof_topology_token ipc4_copier_deep_buffer_tokens[] = {
+ {SOF_TKN_INTEL_COPIER_DEEP_BUFFER_DMA_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0},
};
static const struct sof_topology_token ipc4_copier_tokens[] = {
};
static const struct sof_topology_token ipc4_audio_fmt_num_tokens[] = {
- {SOF_TKN_COMP_NUM_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- 0},
+ {SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_available_audio_format, num_input_formats)},
+ {SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_available_audio_format, num_output_formats)},
};
static const struct sof_topology_token dai_tokens[] = {
static const struct sof_topology_token comp_ext_tokens[] = {
{SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid,
offsetof(struct snd_sof_widget, uuid)},
+ {SOF_TKN_COMP_CORE_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_widget, core)},
};
static const struct sof_topology_token gain_tokens[] = {
ipc4_in_audio_format_tokens, ARRAY_SIZE(ipc4_in_audio_format_tokens)},
[SOF_OUT_AUDIO_FORMAT_TOKENS] = {"IPC4 Output Audio format tokens",
ipc4_out_audio_format_tokens, ARRAY_SIZE(ipc4_out_audio_format_tokens)},
- [SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS] = {"IPC4 Audio format buffer size tokens",
- ipc4_audio_format_buffer_size_tokens,
- ARRAY_SIZE(ipc4_audio_format_buffer_size_tokens)},
- [SOF_COPIER_GATEWAY_CFG_TOKENS] = {"IPC4 Copier gateway config tokens",
- ipc4_copier_gateway_cfg_tokens, ARRAY_SIZE(ipc4_copier_gateway_cfg_tokens)},
+ [SOF_COPIER_DEEP_BUFFER_TOKENS] = {"IPC4 Copier deep buffer tokens",
+ ipc4_copier_deep_buffer_tokens, ARRAY_SIZE(ipc4_copier_deep_buffer_tokens)},
[SOF_COPIER_TOKENS] = {"IPC4 Copier tokens", ipc4_copier_tokens,
ARRAY_SIZE(ipc4_copier_tokens)},
[SOF_AUDIO_FMT_NUM_TOKENS] = {"IPC4 Audio format number tokens",
[SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)},
};
-static void sof_ipc4_dbg_audio_format(struct device *dev,
- struct sof_ipc4_audio_format *format,
- size_t object_size, int num_format)
+static void sof_ipc4_dbg_audio_format(struct device *dev, struct sof_ipc4_pin_format *pin_fmt,
+ int num_formats)
{
- struct sof_ipc4_audio_format *fmt;
- void *ptr = format;
int i;
- for (i = 0; i < num_format; i++, ptr = (u8 *)ptr + object_size) {
- fmt = ptr;
+ for (i = 0; i < num_formats; i++) {
+ struct sof_ipc4_audio_format *fmt = &pin_fmt[i].audio_fmt;
dev_dbg(dev,
- " #%d: %uHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x)\n",
- i, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map,
- fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg);
+ "Pin index #%d: %uHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x) buffer size %d\n",
+ pin_fmt[i].pin_index, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map,
+ fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg,
+ pin_fmt[i].buffer_size);
}
}
+static const struct sof_ipc4_audio_format *
+sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index)
+{
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext;
+ struct sof_ipc4_process *process;
+ int i;
+
+ if (swidget->id != snd_soc_dapm_effect) {
+ struct sof_ipc4_base_module_cfg *base = swidget->private;
+
+ /* For non-process modules, base module config format is used for all input pins */
+ return &base->audio_fmt;
+ }
+
+ process = swidget->private;
+ base_cfg_ext = process->base_config_ext;
+
+ /*
+ * If there are multiple input formats available for a pin, the first available format
+ * is chosen.
+ */
+ for (i = 0; i < base_cfg_ext->num_input_pin_fmts; i++) {
+ struct sof_ipc4_pin_format *pin_format = &base_cfg_ext->pin_formats[i];
+
+ if (pin_format->pin_index == pin_index)
+ return &pin_format->audio_fmt;
+ }
+
+ return NULL;
+}
+
/**
* sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples
* @scomp: pointer to pointer to SOC component
* @swidget: pointer to struct snd_sof_widget containing tuples
* @available_fmt: pointer to struct sof_ipc4_available_audio_format being filling in
- * @has_out_format: true if available_fmt contains output format
+ * @module_base_cfg: Pointer to the base_config in the module init IPC payload
*
* Return: 0 if successful
*/
static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp,
struct snd_sof_widget *swidget,
struct sof_ipc4_available_audio_format *available_fmt,
- bool has_out_format)
+ struct sof_ipc4_base_module_cfg *module_base_cfg)
{
- struct sof_ipc4_base_module_cfg *base_config;
- struct sof_ipc4_audio_format *out_format;
- int audio_fmt_num = 0;
- int ret, i;
+ struct sof_ipc4_pin_format *in_format = NULL;
+ struct sof_ipc4_pin_format *out_format;
+ int ret;
- ret = sof_update_ipc_object(scomp, &audio_fmt_num,
+ ret = sof_update_ipc_object(scomp, available_fmt,
SOF_AUDIO_FMT_NUM_TOKENS, swidget->tuples,
- swidget->num_tuples, sizeof(audio_fmt_num), 1);
- if (ret || audio_fmt_num <= 0) {
- dev_err(scomp->dev, "Invalid number of audio formats: %d\n", audio_fmt_num);
- return -EINVAL;
- }
- available_fmt->audio_fmt_num = audio_fmt_num;
-
- dev_dbg(scomp->dev, "Number of audio formats: %d\n", available_fmt->audio_fmt_num);
-
- base_config = kcalloc(available_fmt->audio_fmt_num, sizeof(*base_config), GFP_KERNEL);
- if (!base_config)
- return -ENOMEM;
-
- /* set cpc and is_pages for all base_cfg */
- for (i = 0; i < available_fmt->audio_fmt_num; i++) {
- ret = sof_update_ipc_object(scomp, &base_config[i],
- SOF_COMP_TOKENS, swidget->tuples,
- swidget->num_tuples, sizeof(*base_config), 1);
- if (ret) {
- dev_err(scomp->dev, "parse comp tokens failed %d\n", ret);
- goto err_in;
- }
+ swidget->num_tuples, sizeof(available_fmt), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Failed to parse audio format token count\n");
+ return ret;
}
- /* copy the ibs/obs for each base_cfg */
- ret = sof_update_ipc_object(scomp, base_config,
- SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, swidget->tuples,
- swidget->num_tuples, sizeof(*base_config),
- available_fmt->audio_fmt_num);
- if (ret) {
- dev_err(scomp->dev, "parse buffer size tokens failed %d\n", ret);
- goto err_in;
+ if (!available_fmt->num_input_formats && !available_fmt->num_output_formats) {
+ dev_err(scomp->dev, "No input/output pin formats set in topology\n");
+ return -EINVAL;
}
- for (i = 0; i < available_fmt->audio_fmt_num; i++)
- dev_dbg(scomp->dev, "%d: ibs: %d obs: %d cpc: %d is_pages: %d\n", i,
- base_config[i].ibs, base_config[i].obs,
- base_config[i].cpc, base_config[i].is_pages);
+ dev_dbg(scomp->dev,
+ "Number of input audio formats: %d. Number of output audio formats: %d\n",
+ available_fmt->num_input_formats, available_fmt->num_output_formats);
- ret = sof_update_ipc_object(scomp, &base_config->audio_fmt,
- SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples,
- swidget->num_tuples, sizeof(*base_config),
- available_fmt->audio_fmt_num);
+ /* set cpc and is_pages in the module's base_config */
+ ret = sof_update_ipc_object(scomp, module_base_cfg, SOF_COMP_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*module_base_cfg), 1);
if (ret) {
- dev_err(scomp->dev, "parse base_config audio_fmt tokens failed %d\n", ret);
- goto err_in;
+ dev_err(scomp->dev, "parse comp tokens for %s failed, error: %d\n",
+ swidget->widget->name, ret);
+ return ret;
}
- dev_dbg(scomp->dev, "Get input audio formats for %s\n", swidget->widget->name);
- sof_ipc4_dbg_audio_format(scomp->dev, &base_config->audio_fmt,
- sizeof(*base_config),
- available_fmt->audio_fmt_num);
+ dev_dbg(scomp->dev, "widget %s cpc: %d is_pages: %d\n",
+ swidget->widget->name, module_base_cfg->cpc, module_base_cfg->is_pages);
- available_fmt->base_config = base_config;
+ if (available_fmt->num_input_formats) {
+ in_format = kcalloc(available_fmt->num_input_formats,
+ sizeof(*in_format), GFP_KERNEL);
+ if (!in_format)
+ return -ENOMEM;
+ available_fmt->input_pin_fmts = in_format;
- if (!has_out_format)
- return 0;
+ ret = sof_update_ipc_object(scomp, in_format,
+ SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*in_format),
+ available_fmt->num_input_formats);
+ if (ret) {
+ dev_err(scomp->dev, "parse input audio fmt tokens failed %d\n", ret);
+ goto err_in;
+ }
- out_format = kcalloc(available_fmt->audio_fmt_num, sizeof(*out_format), GFP_KERNEL);
- if (!out_format) {
- ret = -ENOMEM;
- goto err_in;
+ dev_dbg(scomp->dev, "Input audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(scomp->dev, in_format,
+ available_fmt->num_input_formats);
}
- ret = sof_update_ipc_object(scomp, out_format,
- SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples,
- swidget->num_tuples, sizeof(*out_format),
- available_fmt->audio_fmt_num);
+ if (available_fmt->num_output_formats) {
+ out_format = kcalloc(available_fmt->num_output_formats, sizeof(*out_format),
+ GFP_KERNEL);
+ if (!out_format) {
+ ret = -ENOMEM;
+ goto err_in;
+ }
- if (ret) {
- dev_err(scomp->dev, "parse output audio_fmt tokens failed\n");
- goto err_out;
- }
+ ret = sof_update_ipc_object(scomp, out_format,
+ SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*out_format),
+ available_fmt->num_output_formats);
+ if (ret) {
+ dev_err(scomp->dev, "parse output audio fmt tokens failed\n");
+ goto err_out;
+ }
- available_fmt->out_audio_fmt = out_format;
- dev_dbg(scomp->dev, "Get output audio formats for %s\n", swidget->widget->name);
- sof_ipc4_dbg_audio_format(scomp->dev, out_format, sizeof(*out_format),
- available_fmt->audio_fmt_num);
+ available_fmt->output_pin_fmts = out_format;
+ dev_dbg(scomp->dev, "Output audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(scomp->dev, out_format,
+ available_fmt->num_output_formats);
+ }
return 0;
err_out:
kfree(out_format);
err_in:
- kfree(base_config);
-
+ kfree(in_format);
+ available_fmt->input_pin_fmts = NULL;
return ret;
}
static void sof_ipc4_free_audio_fmt(struct sof_ipc4_available_audio_format *available_fmt)
{
- kfree(available_fmt->base_config);
- available_fmt->base_config = NULL;
- kfree(available_fmt->out_audio_fmt);
- available_fmt->out_audio_fmt = NULL;
+ kfree(available_fmt->output_pin_fmts);
+ available_fmt->output_pin_fmts = NULL;
+ kfree(available_fmt->input_pin_fmts);
+ available_fmt->input_pin_fmts = NULL;
}
static void sof_ipc4_widget_free_comp_pipeline(struct snd_sof_widget *swidget)
return 0;
}
+static void sof_ipc4_widget_update_kcontrol_module_id(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ struct snd_sof_control *scontrol;
+
+ /* update module ID for all kcontrols for this widget */
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
+ if (scontrol->comp_id == swidget->comp_id) {
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+
+ msg->primary |= fw_module->man4_module_entry.id;
+ }
+ }
+}
+
static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget)
{
struct sof_ipc4_available_audio_format *available_fmt;
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc4_copier *ipc4_copier;
int node_type = 0;
- int ret, i;
+ int ret;
ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL);
if (!ipc4_copier)
dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
- ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true);
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt,
+ &ipc4_copier->data.base_config);
if (ret)
goto free_copier;
- available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32),
- GFP_KERNEL);
- if (!available_fmt->dma_buffer_size) {
- ret = -ENOMEM;
- goto free_available_fmt;
- }
-
/*
* This callback is used by host copier and module-to-module copier,
* and only host copier needs to set gtw_cfg.
if (!WIDGET_IS_AIF(swidget->id))
goto skip_gtw_cfg;
- ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size,
- SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples,
- swidget->num_tuples, sizeof(u32),
- available_fmt->audio_fmt_num);
- if (ret) {
- dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n",
- swidget->widget->name);
- goto err;
- }
-
- dev_dbg(scomp->dev, "dma buffer size:\n");
- for (i = 0; i < available_fmt->audio_fmt_num; i++)
- dev_dbg(scomp->dev, "%d: %u\n", i,
- available_fmt->dma_buffer_size[i]);
-
ret = sof_update_ipc_object(scomp, &node_type,
SOF_COPIER_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(node_type), 1);
if (ret) {
dev_err(scomp->dev, "parse host copier node type token failed %d\n",
ret);
- goto err;
+ goto free_available_fmt;
}
dev_dbg(scomp->dev, "host copier '%s' node_type %u\n", swidget->widget->name, node_type);
ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL);
if (!ipc4_copier->gtw_attr) {
ret = -ENOMEM;
- goto err;
+ goto free_available_fmt;
}
ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr;
free_gtw_attr:
kfree(ipc4_copier->gtw_attr);
-err:
- kfree(available_fmt->dma_buffer_size);
free_available_fmt:
sof_ipc4_free_audio_fmt(available_fmt);
free_copier:
return;
available_fmt = &ipc4_copier->available_fmt;
- kfree(available_fmt->dma_buffer_size);
- kfree(available_fmt->base_config);
- kfree(available_fmt->out_audio_fmt);
+ kfree(available_fmt->output_pin_fmts);
kfree(ipc4_copier->gtw_attr);
kfree(ipc4_copier);
swidget->private = NULL;
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dai *dai = swidget->private;
struct sof_ipc4_copier *ipc4_copier;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
int node_type = 0;
- int ret, i;
+ int ret;
ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL);
if (!ipc4_copier)
dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
- ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true);
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt,
+ &ipc4_copier->data.base_config);
if (ret)
goto free_copier;
- available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32),
- GFP_KERNEL);
- if (!available_fmt->dma_buffer_size) {
- ret = -ENOMEM;
- goto free_available_fmt;
- }
-
- ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size,
- SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples,
- swidget->num_tuples, sizeof(u32),
- available_fmt->audio_fmt_num);
- if (ret) {
- dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n",
- swidget->widget->name);
- goto err;
- }
-
- for (i = 0; i < available_fmt->audio_fmt_num; i++)
- dev_dbg(scomp->dev, "%d: dma buffer size: %u\n", i,
- available_fmt->dma_buffer_size[i]);
-
ret = sof_update_ipc_object(scomp, &node_type,
SOF_COPIER_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(node_type), 1);
if (ret) {
dev_err(scomp->dev, "parse dai node type failed %d\n", ret);
- goto err;
+ goto free_available_fmt;
}
ret = sof_update_ipc_object(scomp, ipc4_copier,
swidget->num_tuples, sizeof(u32), 1);
if (ret) {
dev_err(scomp->dev, "parse dai copier node token failed %d\n", ret);
- goto err;
+ goto free_available_fmt;
}
dev_dbg(scomp->dev, "dai %s node_type %u dai_type %u dai_index %d\n", swidget->widget->name,
ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type);
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+ if (pipeline->use_chain_dma && ipc4_copier->dai_type != SOF_DAI_INTEL_HDA) {
+ dev_err(scomp->dev,
+ "Bad DAI type '%d', Chained DMA is only supported by HDA DAIs (%d).\n",
+ ipc4_copier->dai_type, SOF_DAI_INTEL_HDA);
+ ret = -ENODEV;
+ goto free_available_fmt;
+ }
+
switch (ipc4_copier->dai_type) {
case SOF_DAI_INTEL_ALH:
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc4_alh_configuration_blob *blob;
+ struct snd_soc_dapm_path *p;
struct snd_sof_widget *w;
+ int src_num = 0;
+
+ snd_soc_dapm_widget_for_each_source_path(swidget->widget, p)
+ src_num++;
+
+ if (swidget->id == snd_soc_dapm_dai_in && src_num == 0) {
+ /*
+ * The blob will not be used if the ALH copier is playback direction
+ * and doesn't connect to any source.
+ * It is fine to call kfree(ipc4_copier->copier_config) since
+ * ipc4_copier->copier_config is null.
+ */
+ ret = 0;
+ break;
+ }
blob = kzalloc(sizeof(*blob), GFP_KERNEL);
if (!blob) {
ret = -ENOMEM;
- goto err;
+ goto free_available_fmt;
}
list_for_each_entry(w, &sdev->widget_list, list) {
ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL);
if (!ipc4_copier->gtw_attr) {
ret = -ENOMEM;
- goto err;
+ goto free_available_fmt;
}
ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr;
free_copier_config:
kfree(ipc4_copier->copier_config);
-err:
- kfree(available_fmt->dma_buffer_size);
free_available_fmt:
sof_ipc4_free_audio_fmt(available_fmt);
free_copier:
ipc4_copier = dai->private;
available_fmt = &ipc4_copier->available_fmt;
- kfree(available_fmt->dma_buffer_size);
- kfree(available_fmt->base_config);
- kfree(available_fmt->out_audio_fmt);
+ kfree(available_fmt->output_pin_fmts);
if (ipc4_copier->dai_type != SOF_DAI_INTEL_SSP &&
ipc4_copier->dai_type != SOF_DAI_INTEL_DMIC)
kfree(ipc4_copier->copier_config);
goto err;
}
+ swidget->core = pipeline->core_id;
+
+ if (pipeline->use_chain_dma) {
+ dev_dbg(scomp->dev, "Set up chain DMA for %s\n", swidget->widget->name);
+ swidget->private = pipeline;
+ return 0;
+ }
+
/* parse one set of pipeline tokens */
ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*swidget), 1);
/* TODO: Get priority from topology */
pipeline->priority = 0;
- dev_dbg(scomp->dev, "pipeline '%s': id %d pri %d lp mode %d\n",
+ dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d\n",
swidget->widget->name, swidget->pipeline_id,
- pipeline->priority, pipeline->lp_mode);
+ pipeline->priority, pipeline->core_id, pipeline->lp_mode);
swidget->private = pipeline;
pipeline->msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
pipeline->msg.extension = pipeline->lp_mode;
+ pipeline->msg.extension |= SOF_IPC4_GLB_PIPE_EXT_CORE_ID(pipeline->core_id);
pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED;
return 0;
static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc4_fw_module *fw_module;
- struct snd_sof_control *scontrol;
struct sof_ipc4_gain *gain;
int ret;
gain->data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK;
gain->data.init_val = SOF_IPC4_VOL_ZERO_DB;
- /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */
- ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, false);
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, &gain->base_config);
if (ret)
goto err;
if (ret)
goto err;
- fw_module = swidget->module_info;
-
- /* update module ID for all kcontrols for this widget */
- list_for_each_entry(scontrol, &sdev->kcontrol_list, list)
- if (scontrol->comp_id == swidget->comp_id) {
- struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
- struct sof_ipc4_msg *msg = &cdata->msg;
-
- msg->primary |= fw_module->man4_module_entry.id;
- }
+ sof_ipc4_widget_update_kcontrol_module_id(swidget);
return 0;
err:
swidget->private = mixer;
- /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */
- ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt, false);
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt,
+ &mixer->base_config);
if (ret)
goto err;
swidget->private = src;
- /* The out_audio_fmt in topology is ignored as it is not required by SRC */
- ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt, false);
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt, &src->base_config);
if (ret)
goto err;
swidget->private = NULL;
}
+/*
+ * Add the process modules support. The process modules are defined as snd_soc_dapm_effect modules.
+ */
+static int sof_ipc4_widget_setup_comp_process(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_fw_module *fw_module;
+ struct sof_ipc4_process *process;
+ void *cfg;
+ int ret;
+
+ process = kzalloc(sizeof(*process), GFP_KERNEL);
+ if (!process)
+ return -ENOMEM;
+
+ swidget->private = process;
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &process->available_fmt,
+ &process->base_config);
+ if (ret)
+ goto err;
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &process->msg);
+ if (ret)
+ goto err;
+
+ /* parse process init module payload config type from module info */
+ fw_module = swidget->module_info;
+ process->init_config = FIELD_GET(SOF_IPC4_MODULE_INIT_CONFIG_MASK,
+ fw_module->man4_module_entry.type);
+
+ process->ipc_config_size = sizeof(struct sof_ipc4_base_module_cfg);
+
+ /* allocate memory for base config extension if needed */
+ if (process->init_config == SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT) {
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext;
+ u32 ext_size = struct_size(base_cfg_ext, pin_formats,
+ swidget->num_input_pins + swidget->num_output_pins);
+
+ base_cfg_ext = kzalloc(ext_size, GFP_KERNEL);
+ if (!base_cfg_ext) {
+ ret = -ENOMEM;
+ goto free_available_fmt;
+ }
+
+ base_cfg_ext->num_input_pin_fmts = swidget->num_input_pins;
+ base_cfg_ext->num_output_pin_fmts = swidget->num_output_pins;
+ process->base_config_ext = base_cfg_ext;
+ process->base_config_ext_size = ext_size;
+ process->ipc_config_size += ext_size;
+ }
+
+ cfg = kzalloc(process->ipc_config_size, GFP_KERNEL);
+ if (!cfg) {
+ ret = -ENOMEM;
+ goto free_base_cfg_ext;
+ }
+
+ process->ipc_config_data = cfg;
+
+ sof_ipc4_widget_update_kcontrol_module_id(swidget);
+
+ return 0;
+free_base_cfg_ext:
+ kfree(process->base_config_ext);
+ process->base_config_ext = NULL;
+free_available_fmt:
+ sof_ipc4_free_audio_fmt(&process->available_fmt);
+err:
+ kfree(process);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_process(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_process *process = swidget->private;
+
+ if (!process)
+ return;
+
+ kfree(process->ipc_config_data);
+ kfree(process->base_config_ext);
+ sof_ipc4_free_audio_fmt(&process->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
static void
sof_ipc4_update_pipeline_mem_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
struct sof_ipc4_base_module_cfg *base_config)
return 0;
}
+/* update hw_params based on the audio stream format */
+static int sof_ipc4_update_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_hw_params *params,
+ struct sof_ipc4_audio_format *fmt)
+{
+ snd_pcm_format_t snd_fmt;
+ struct snd_interval *i;
+ struct snd_mask *m;
+ int valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+ unsigned int channels, rate;
+
+ switch (valid_bits) {
+ case 16:
+ snd_fmt = SNDRV_PCM_FORMAT_S16_LE;
+ break;
+ case 24:
+ snd_fmt = SNDRV_PCM_FORMAT_S24_LE;
+ break;
+ case 32:
+ snd_fmt = SNDRV_PCM_FORMAT_S32_LE;
+ break;
+ default:
+ dev_err(sdev->dev, "invalid PCM valid_bits %d\n", valid_bits);
+ return -EINVAL;
+ }
+
+ m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(m);
+ snd_mask_set_format(m, snd_fmt);
+
+ rate = fmt->sampling_frequency;
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ i->min = rate;
+ i->max = rate;
+
+ channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg);
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ i->min = channels;
+ i->max = channels;
+
+ return 0;
+}
+
static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev,
struct snd_sof_widget *swidget,
struct sof_ipc4_base_module_cfg *base_config,
- struct sof_ipc4_audio_format *out_format,
struct snd_pcm_hw_params *params,
struct sof_ipc4_available_audio_format *available_fmt,
- size_t object_offset)
+ struct sof_ipc4_pin_format *pin_fmts, u32 pin_fmts_size)
{
- void *ptr = available_fmt->ref_audio_fmt;
u32 valid_bits;
u32 channels;
u32 rate;
int sample_valid_bits;
int i;
- if (!ptr) {
+ if (!pin_fmts) {
dev_err(sdev->dev, "no reference formats for %s\n", swidget->widget->name);
return -EINVAL;
}
return -EINVAL;
}
- if (!available_fmt->audio_fmt_num) {
+ if (!pin_fmts_size) {
dev_err(sdev->dev, "no formats available for %s\n", swidget->widget->name);
return -EINVAL;
}
/*
- * Search supported audio formats to match rate, channels ,and
+ * Search supported audio formats with pin index 0 to match rate, channels ,and
* sample_valid_bytes from runtime params
*/
- for (i = 0; i < available_fmt->audio_fmt_num; i++, ptr = (u8 *)ptr + object_offset) {
- struct sof_ipc4_audio_format *fmt = ptr;
+ for (i = 0; i < pin_fmts_size; i++) {
+ struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt;
+
+ if (pin_fmts[i].pin_index)
+ continue;
rate = fmt->sampling_frequency;
channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg);
valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
if (params_rate(params) == rate && params_channels(params) == channels &&
sample_valid_bits == valid_bits) {
- dev_dbg(sdev->dev, "matching audio format index for %uHz, %ubit, %u channels: %d\n",
+ dev_dbg(sdev->dev, "matched audio format index for %uHz, %ubit, %u channels: %d\n",
rate, valid_bits, channels, i);
-
- /* copy ibs/obs and input format */
- memcpy(base_config, &available_fmt->base_config[i],
- sizeof(struct sof_ipc4_base_module_cfg));
-
- /* copy output format */
- if (out_format)
- memcpy(out_format, &available_fmt->out_audio_fmt[i],
- sizeof(struct sof_ipc4_audio_format));
break;
}
}
- if (i == available_fmt->audio_fmt_num) {
+ if (i == pin_fmts_size) {
dev_err(sdev->dev, "%s: Unsupported audio format: %uHz, %ubit, %u channels\n",
__func__, params_rate(params), sample_valid_bits, params_channels(params));
return -EINVAL;
}
- dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name);
- sof_ipc4_dbg_audio_format(sdev->dev, &base_config->audio_fmt,
- sizeof(*base_config), 1);
- if (out_format) {
- dev_dbg(sdev->dev, "Init output audio formats for %s\n", swidget->widget->name);
- sof_ipc4_dbg_audio_format(sdev->dev, out_format,
- sizeof(*out_format), 1);
+ /* copy input format */
+ if (available_fmt->num_input_formats && i < available_fmt->num_input_formats) {
+ memcpy(&base_config->audio_fmt, &available_fmt->input_pin_fmts[i].audio_fmt,
+ sizeof(struct sof_ipc4_audio_format));
+
+ /* set base_cfg ibs/obs */
+ base_config->ibs = available_fmt->input_pin_fmts[i].buffer_size;
+
+ dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->input_pin_fmts[i], 1);
}
+ if (available_fmt->num_output_formats && i < available_fmt->num_output_formats)
+ base_config->obs = available_fmt->output_pin_fmts[i].buffer_size;
+
/* Return the index of the matched format */
return i;
}
pipeline->mem_usage = 0;
if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) {
+ if (pipeline->use_chain_dma) {
+ pipeline->msg.primary = 0;
+ pipeline->msg.extension = 0;
+ }
ipc4_copier = swidget->private;
} else if (WIDGET_IS_DAI(swidget->id)) {
struct snd_sof_dai *dai = swidget->private;
ipc4_copier = dai->private;
+
+ if (pipeline->use_chain_dma) {
+ pipeline->msg.primary = 0;
+ pipeline->msg.extension = 0;
+ }
+
if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) {
struct sof_ipc4_copier_data *copier_data = &ipc4_copier->data;
struct sof_ipc4_alh_configuration_blob *blob;
}
#endif
+static int ipc4_set_fmt_mask(struct snd_mask *fmt, unsigned int bit_depth)
+{
+ switch (bit_depth) {
+ case 16:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+ break;
+ case 24:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ break;
+ case 32:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ipc4_copier_set_capture_fmt(struct snd_sof_dev *sdev,
+ struct snd_pcm_hw_params *pipeline_params,
+ struct snd_pcm_hw_params *fe_params,
+ struct sof_ipc4_available_audio_format *available_fmt)
+{
+ struct sof_ipc4_audio_format *audio_fmt;
+ unsigned int sample_valid_bits;
+ bool multiple_formats = false;
+ bool fe_format_match = false;
+ struct snd_mask *fmt;
+ int i;
+
+ for (i = 0; i < available_fmt->num_output_formats; i++) {
+ unsigned int val;
+
+ audio_fmt = &available_fmt->output_pin_fmts[i].audio_fmt;
+ val = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(audio_fmt->fmt_cfg);
+
+ if (i == 0)
+ sample_valid_bits = val;
+ else if (sample_valid_bits != val)
+ multiple_formats = true;
+
+ if (snd_pcm_format_width(params_format(fe_params)) == val)
+ fe_format_match = true;
+ }
+
+ fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(fmt);
+
+ if (multiple_formats) {
+ if (fe_format_match) {
+ /* multiple formats defined and one matches FE */
+ snd_mask_set_format(fmt, params_format(fe_params));
+ return 0;
+ }
+
+ dev_err(sdev->dev, "Multiple audio formats for single dai_out not supported\n");
+ return -EINVAL;
+ }
+
+ return ipc4_set_fmt_mask(fmt, sample_valid_bits);
+}
+
static int
sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget,
struct snd_pcm_hw_params *fe_params,
struct sof_ipc4_available_audio_format *available_fmt;
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_pin_format *format_list_to_search;
struct sof_ipc4_copier_data *copier_data;
struct snd_pcm_hw_params *ref_params;
struct sof_ipc4_copier *ipc4_copier;
struct snd_sof_dai *dai;
struct snd_mask *fmt;
int out_sample_valid_bits;
- size_t ref_audio_fmt_size;
void **ipc_config_data;
int *ipc_config_size;
u32 **data;
int ipc_size, ret;
+ u32 deep_buffer_dma_ms = 0;
+ u32 format_list_count;
dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id);
struct snd_sof_widget *pipe_widget;
struct sof_ipc4_pipeline *pipeline;
- pipe_widget = swidget->spipe->pipe_widget;
- pipeline = pipe_widget->private;
+ /* parse the deep buffer dma size */
+ ret = sof_update_ipc_object(scomp, &deep_buffer_dma_ms,
+ SOF_COPIER_DEEP_BUFFER_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(u32), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Failed to parse deep buffer dma size for %s\n",
+ swidget->widget->name);
+ return ret;
+ }
+
ipc4_copier = (struct sof_ipc4_copier *)swidget->private;
gtw_attr = ipc4_copier->gtw_attr;
copier_data = &ipc4_copier->data;
available_fmt = &ipc4_copier->available_fmt;
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ if (pipeline->use_chain_dma) {
+ u32 host_dma_id;
+ u32 fifo_size;
+
+ host_dma_id = platform_params->stream_tag - 1;
+ pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(host_dma_id);
+
+ /* Set SCS bit for S16_LE format only */
+ if (params_format(fe_params) == SNDRV_PCM_FORMAT_S16_LE)
+ pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_SCS_MASK;
+
+ /*
+ * Despite its name the bitfield 'fifo_size' is used to define DMA buffer
+ * size. The expression calculates 2ms buffer size.
+ */
+ fifo_size = DIV_ROUND_UP((SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS *
+ params_rate(fe_params) *
+ params_channels(fe_params) *
+ params_physical_width(fe_params)), 8000);
+ pipeline->msg.extension |= SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(fifo_size);
+
+ /*
+ * Chain DMA does not support stream timestamping, set node_id to invalid
+ * to skip the code in sof_ipc4_get_stream_start_offset().
+ */
+ copier_data->gtw_cfg.node_id = SOF_IPC4_INVALID_NODE_ID;
+
+ return 0;
+ }
+
/*
- * base_config->audio_fmt and out_audio_fmt represent the input and output audio
- * formats. Use the input format as the reference to match pcm params for playback
- * and the output format as reference for capture.
+ * Use the input_pin_fmts to match pcm params for playback and the output_pin_fmts
+ * for capture.
*/
if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
- available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt;
- ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg);
+ format_list_to_search = available_fmt->input_pin_fmts;
+ format_list_count = available_fmt->num_input_formats;
} else {
- available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt;
- ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format);
+ format_list_to_search = available_fmt->output_pin_fmts;
+ format_list_count = available_fmt->num_output_formats;
}
+
copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
copier_data->gtw_cfg.node_id |=
SOF_IPC4_NODE_INDEX(platform_params->stream_tag - 1);
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
{
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (pipeline->use_chain_dma)
+ return 0;
+
dai = swidget->private;
ipc4_copier = (struct sof_ipc4_copier *)dai->private;
copier_data = &ipc4_copier->data;
available_fmt = &ipc4_copier->available_fmt;
if (dir == SNDRV_PCM_STREAM_CAPTURE) {
- available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt;
- ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format);
+ format_list_to_search = available_fmt->output_pin_fmts;
+ format_list_count = available_fmt->num_output_formats;
- /*
- * modify the input params for the dai copier as it only supports
- * 32-bit always
- */
- fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT);
- snd_mask_none(fmt);
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ ret = ipc4_copier_set_capture_fmt(sdev, pipeline_params, fe_params,
+ available_fmt);
+ if (ret < 0)
+ return ret;
} else {
- available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt;
- ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg);
+ format_list_to_search = available_fmt->input_pin_fmts;
+ format_list_count = available_fmt->num_input_formats;
}
ref_params = pipeline_params;
copier_data = &ipc4_copier->data;
available_fmt = &ipc4_copier->available_fmt;
- /*
- * base_config->audio_fmt represent the input audio formats. Use
- * the input format as the reference to match pcm params
- */
- available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt;
- ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg);
+ /* Use the input formats to match pcm params */
+ format_list_to_search = available_fmt->input_pin_fmts;
+ format_list_count = available_fmt->num_input_formats;
ref_params = pipeline_params;
break;
}
/* set input and output audio formats */
- ret = sof_ipc4_init_audio_fmt(sdev, swidget, &copier_data->base_config,
- &copier_data->out_format, ref_params,
- available_fmt, ref_audio_fmt_size);
+ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &copier_data->base_config, ref_params,
+ available_fmt, format_list_to_search, format_list_count);
if (ret < 0)
return ret;
+ /*
+ * Set the output format. Current topology defines pin 0 input and output formats in pairs.
+ * This assumes that the pin 0 formats are defined before all other pins.
+ * So pick the output audio format with the same index as the chosen
+ * input format. This logic will need to be updated when the format definitions
+ * in topology change.
+ */
+ memcpy(&copier_data->out_format, &available_fmt->output_pin_fmts[ret].audio_fmt,
+ sizeof(struct sof_ipc4_audio_format));
+ dev_dbg(sdev->dev, "Output audio format for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->output_pin_fmts[ret], 1);
+
switch (swidget->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
out_sample_valid_bits =
SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(copier_data->out_format.fmt_cfg);
snd_mask_none(fmt);
- switch (out_sample_valid_bits) {
- case 16:
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+ ret = ipc4_set_fmt_mask(fmt, out_sample_valid_bits);
+ if (ret)
+ return ret;
+
+ /*
+ * Set the gateway dma_buffer_size to 2ms buffer size to meet the FW expectation. In the
+ * deep buffer case, set the dma_buffer_size depending on the deep_buffer_dma_ms set
+ * in topology.
+ */
+ switch (swidget->id) {
+ case snd_soc_dapm_dai_in:
+ copier_data->gtw_cfg.dma_buffer_size =
+ SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.ibs;
break;
- case 24:
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ case snd_soc_dapm_aif_in:
+ copier_data->gtw_cfg.dma_buffer_size =
+ max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms) *
+ copier_data->base_config.ibs;
break;
- case 32:
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ case snd_soc_dapm_dai_out:
+ case snd_soc_dapm_aif_out:
+ copier_data->gtw_cfg.dma_buffer_size =
+ SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.obs;
break;
default:
- dev_err(sdev->dev, "invalid sample frame format %d\n",
- params_format(pipeline_params));
- return -EINVAL;
+ break;
}
- /* set the gateway dma_buffer_size using the matched ID returned above */
- copier_data->gtw_cfg.dma_buffer_size = available_fmt->dma_buffer_size[ret];
-
data = &ipc4_copier->copier_config;
ipc_config_size = &ipc4_copier->ipc_config_size;
ipc_config_data = &ipc4_copier->ipc_config_data;
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc4_gain *gain = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &gain->available_fmt;
int ret;
- gain->available_fmt.ref_audio_fmt = &gain->available_fmt.base_config->audio_fmt;
-
- /* output format is not required to be sent to the FW for gain */
ret = sof_ipc4_init_audio_fmt(sdev, swidget, &gain->base_config,
- NULL, pipeline_params, &gain->available_fmt,
- sizeof(gain->base_config));
+ pipeline_params, available_fmt,
+ available_fmt->input_pin_fmts,
+ available_fmt->num_input_formats);
if (ret < 0)
return ret;
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc4_mixer *mixer = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &mixer->available_fmt;
int ret;
- /* only 32bit is supported by mixer */
- mixer->available_fmt.ref_audio_fmt = &mixer->available_fmt.base_config->audio_fmt;
-
- /* output format is not required to be sent to the FW for mixer */
ret = sof_ipc4_init_audio_fmt(sdev, swidget, &mixer->base_config,
- NULL, pipeline_params, &mixer->available_fmt,
- sizeof(mixer->base_config));
+ pipeline_params, available_fmt,
+ available_fmt->input_pin_fmts,
+ available_fmt->num_input_formats);
if (ret < 0)
return ret;
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc4_src *src = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &src->available_fmt;
struct snd_interval *rate;
int ret;
- src->available_fmt.ref_audio_fmt = &src->available_fmt.base_config->audio_fmt;
-
- /* output format is not required to be sent to the FW for SRC */
ret = sof_ipc4_init_audio_fmt(sdev, swidget, &src->base_config,
- NULL, pipeline_params, &src->available_fmt,
- sizeof(src->base_config));
+ pipeline_params, available_fmt,
+ available_fmt->input_pin_fmts,
+ available_fmt->num_input_formats);
if (ret < 0)
return ret;
return 0;
}
+static int
+sof_ipc4_process_set_pin_formats(struct snd_sof_widget *swidget, int pin_type)
+{
+ struct sof_ipc4_process *process = swidget->private;
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext = process->base_config_ext;
+ struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt;
+ struct sof_ipc4_pin_format *pin_format, *format_list_to_search;
+ struct snd_soc_component *scomp = swidget->scomp;
+ int num_pins, format_list_count;
+ int pin_format_offset = 0;
+ int i, j;
+
+ /* set number of pins, offset of pin format and format list to search based on pin type */
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ num_pins = swidget->num_input_pins;
+ format_list_to_search = available_fmt->input_pin_fmts;
+ format_list_count = available_fmt->num_input_formats;
+ } else {
+ num_pins = swidget->num_output_pins;
+ pin_format_offset = swidget->num_input_pins;
+ format_list_to_search = available_fmt->output_pin_fmts;
+ format_list_count = available_fmt->num_output_formats;
+ }
+
+ for (i = pin_format_offset; i < num_pins + pin_format_offset; i++) {
+ pin_format = &base_cfg_ext->pin_formats[i];
+
+ /* Pin 0 audio formats are derived from the base config input/output format */
+ if (i == pin_format_offset) {
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ pin_format->buffer_size = process->base_config.ibs;
+ pin_format->audio_fmt = process->base_config.audio_fmt;
+ } else {
+ pin_format->buffer_size = process->base_config.obs;
+ pin_format->audio_fmt = process->output_format;
+ }
+ continue;
+ }
+
+ /*
+ * For all other pins, find the pin formats from those set in topology. If there
+ * is more than one format specified for a pin, this will pick the first available
+ * one.
+ */
+ for (j = 0; j < format_list_count; j++) {
+ struct sof_ipc4_pin_format *pin_format_item = &format_list_to_search[j];
+
+ if (pin_format_item->pin_index == i - pin_format_offset) {
+ *pin_format = *pin_format_item;
+ break;
+ }
+ }
+
+ if (j == format_list_count) {
+ dev_err(scomp->dev, "%s pin %d format not found for %s\n",
+ (pin_type == SOF_PIN_TYPE_INPUT) ? "input" : "output",
+ i - pin_format_offset, swidget->widget->name);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_process_add_base_cfg_extn(struct snd_sof_widget *swidget)
+{
+ int ret, i;
+
+ /* copy input and output pin formats */
+ for (i = 0; i <= SOF_PIN_TYPE_OUTPUT; i++) {
+ ret = sof_ipc4_process_set_pin_formats(swidget, i);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_process *process = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt;
+ void *cfg = process->ipc_config_data;
+ int ret;
+
+ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &process->base_config,
+ pipeline_params, available_fmt,
+ available_fmt->input_pin_fmts,
+ available_fmt->num_input_formats);
+ if (ret < 0)
+ return ret;
+
+ /* copy Pin 0 output format */
+ if (available_fmt->num_output_formats && ret < available_fmt->num_output_formats &&
+ !available_fmt->output_pin_fmts[ret].pin_index) {
+ memcpy(&process->output_format, &available_fmt->output_pin_fmts[ret].audio_fmt,
+ sizeof(struct sof_ipc4_audio_format));
+
+ /* modify the pipeline params with the pin 0 output format */
+ ret = sof_ipc4_update_hw_params(sdev, pipeline_params, &process->output_format);
+ if (ret)
+ return ret;
+ }
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &process->base_config);
+
+ /* ipc_config_data is composed of the base_config followed by an optional extension */
+ memcpy(cfg, &process->base_config, sizeof(struct sof_ipc4_base_module_cfg));
+ cfg += sizeof(struct sof_ipc4_base_module_cfg);
+
+ if (process->init_config == SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT) {
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext = process->base_config_ext;
+
+ ret = sof_ipc4_process_add_base_cfg_extn(swidget);
+ if (ret < 0)
+ return ret;
+
+ memcpy(cfg, base_cfg_ext, process->base_config_ext_size);
+ }
+
+ return 0;
+}
+
static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
struct sof_ipc4_control_data *control_data;
return 0;
}
+static int sof_ipc4_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc4_control_data *control_data;
+ struct sof_ipc4_msg *msg;
+ int ret;
+
+ if (scontrol->max_size < (sizeof(*control_data) + sizeof(struct sof_abi_hdr))) {
+ dev_err(sdev->dev, "insufficient size for a bytes control %s: %zu.\n",
+ scontrol->name, scontrol->max_size);
+ return -EINVAL;
+ }
+
+ if (scontrol->priv_size > scontrol->max_size - sizeof(*control_data)) {
+ dev_err(sdev->dev, "scontrol %s bytes data size %zu exceeds max %zu.\n",
+ scontrol->name, scontrol->priv_size,
+ scontrol->max_size - sizeof(*control_data));
+ return -EINVAL;
+ }
+
+ scontrol->size = sizeof(struct sof_ipc4_control_data) + scontrol->priv_size;
+
+ scontrol->ipc_control_data = kzalloc(scontrol->max_size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ control_data = scontrol->ipc_control_data;
+ control_data->index = scontrol->index;
+ if (scontrol->priv_size > 0) {
+ memcpy(control_data->data, scontrol->priv, scontrol->priv_size);
+ kfree(scontrol->priv);
+ scontrol->priv = NULL;
+
+ if (control_data->data->magic != SOF_IPC4_ABI_MAGIC) {
+ dev_err(sdev->dev, "Wrong ABI magic (%#x) for control: %s\n",
+ control_data->data->magic, scontrol->name);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* TODO: check the ABI version */
+
+ if (control_data->data->size + sizeof(struct sof_abi_hdr) !=
+ scontrol->priv_size) {
+ dev_err(sdev->dev, "Control %s conflict in bytes %zu vs. priv size %zu.\n",
+ scontrol->name,
+ control_data->data->size + sizeof(struct sof_abi_hdr),
+ scontrol->priv_size);
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ msg = &control_data->msg;
+ msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
+ msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ return 0;
+
+err:
+ kfree(scontrol->ipc_control_data);
+ scontrol->ipc_control_data = NULL;
+ return ret;
+}
+
static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
switch (scontrol->info_type) {
case SND_SOC_TPLG_CTL_VOLSW_SX:
case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
return sof_ipc4_control_load_volume(sdev, scontrol);
+ case SND_SOC_TPLG_CTL_BYTES:
+ return sof_ipc4_control_load_bytes(sdev, scontrol);
default:
break;
}
case snd_soc_dapm_scheduler:
pipeline = swidget->private;
+ if (pipeline->use_chain_dma) {
+ dev_warn(sdev->dev, "use_chain_dma set for scheduler %s",
+ swidget->widget->name);
+ return 0;
+ }
+
dev_dbg(sdev->dev, "pipeline: %d memory pages: %d\n", swidget->pipeline_id,
pipeline->mem_usage);
{
struct sof_ipc4_copier *ipc4_copier = swidget->private;
+ pipeline = pipe_widget->private;
+ if (pipeline->use_chain_dma)
+ return 0;
+
ipc_size = ipc4_copier->ipc_config_size;
ipc_data = ipc4_copier->ipc_config_data;
struct snd_sof_dai *dai = swidget->private;
struct sof_ipc4_copier *ipc4_copier = dai->private;
+ pipeline = pipe_widget->private;
+ if (pipeline->use_chain_dma)
+ return 0;
+
ipc_size = ipc4_copier->ipc_config_size;
ipc_data = ipc4_copier->ipc_config_data;
msg = &src->msg;
break;
}
+ case snd_soc_dapm_effect:
+ {
+ struct sof_ipc4_process *process = swidget->private;
+
+ if (!process->ipc_config_size) {
+ dev_err(sdev->dev, "module %s has no config data!\n",
+ swidget->widget->name);
+ return -EINVAL;
+ }
+
+ ipc_size = process->ipc_config_size;
+ ipc_data = process->ipc_config_data;
+
+ msg = &process->msg;
+ break;
+ }
default:
dev_err(sdev->dev, "widget type %d not supported", swidget->id);
return -EINVAL;
msg->data_size = ipc_size;
msg->data_ptr = ipc_data;
- ret = sof_ipc_tx_message(sdev->ipc, msg, ipc_size, NULL, 0);
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, ipc_size);
if (ret < 0) {
dev_err(sdev->dev, "failed to create module %s\n", swidget->widget->name);
struct sof_ipc4_msg msg = {{ 0 }};
u32 header;
+ if (pipeline->use_chain_dma) {
+ dev_warn(sdev->dev, "use_chain_dma set for scheduler %s",
+ swidget->widget->name);
+ mutex_unlock(&ipc4_data->pipeline_state_mutex);
+ return 0;
+ }
+
header = SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->instance_id);
header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_DELETE_PIPELINE);
header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
msg.primary = header;
- ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
if (ret < 0)
dev_err(sdev->dev, "failed to free pipeline widget %s\n",
swidget->widget->name);
pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED;
ida_free(&pipeline_ida, swidget->instance_id);
} else {
- ida_free(&fw_module->m_ida, swidget->instance_id);
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (!pipeline->use_chain_dma)
+ ida_free(&fw_module->m_ida, swidget->instance_id);
}
mutex_unlock(&ipc4_data->pipeline_state_mutex);
u32 num_pins;
int i;
- if (pin_type == SOF_PIN_TYPE_SOURCE) {
+ if (pin_type == SOF_PIN_TYPE_OUTPUT) {
current_swidget = src_widget;
- pin_binding = src_widget->src_pin_binding;
- queue_ida = &src_widget->src_queue_ida;
- num_pins = src_widget->num_source_pins;
+ pin_binding = src_widget->output_pin_binding;
+ queue_ida = &src_widget->output_queue_ida;
+ num_pins = src_widget->num_output_pins;
buddy_name = sink_widget->widget->name;
} else {
current_swidget = sink_widget;
- pin_binding = sink_widget->sink_pin_binding;
- queue_ida = &sink_widget->sink_queue_ida;
- num_pins = sink_widget->num_sink_pins;
+ pin_binding = sink_widget->input_pin_binding;
+ queue_ida = &sink_widget->input_queue_ida;
+ num_pins = sink_widget->num_input_pins;
buddy_name = src_widget->widget->name;
}
if (num_pins < 1) {
dev_err(scomp->dev, "invalid %s num_pins: %d for queue allocation for %s\n",
- (pin_type == SOF_PIN_TYPE_SOURCE ? "source" : "sink"),
+ (pin_type == SOF_PIN_TYPE_OUTPUT ? "output" : "input"),
num_pins, current_swidget->widget->name);
return -EINVAL;
}
- /* If there is only one sink/source pin, queue id must be 0 */
+ /* If there is only one input/output pin, queue id must be 0 */
if (num_pins == 1)
return 0;
* mixed use pin binding array and ida for queue ID allocation.
*/
dev_err(scomp->dev, "no %s queue id found from pin binding array for %s\n",
- (pin_type == SOF_PIN_TYPE_SOURCE ? "source" : "sink"),
+ (pin_type == SOF_PIN_TYPE_OUTPUT ? "output" : "input"),
current_swidget->widget->name);
return -EINVAL;
}
char **pin_binding;
int num_pins;
- if (pin_type == SOF_PIN_TYPE_SOURCE) {
- pin_binding = swidget->src_pin_binding;
- queue_ida = &swidget->src_queue_ida;
- num_pins = swidget->num_source_pins;
+ if (pin_type == SOF_PIN_TYPE_OUTPUT) {
+ pin_binding = swidget->output_pin_binding;
+ queue_ida = &swidget->output_queue_ida;
+ num_pins = swidget->num_output_pins;
} else {
- pin_binding = swidget->sink_pin_binding;
- queue_ida = &swidget->sink_queue_ida;
- num_pins = swidget->num_sink_pins;
+ pin_binding = swidget->input_pin_binding;
+ queue_ida = &swidget->input_queue_ida;
+ num_pins = swidget->num_input_pins;
}
/* Nothing to free if queue ID is not allocated with ida. */
struct snd_sof_widget *sink_widget,
int sink_id)
{
- struct sof_ipc4_base_module_cfg *sink_config = sink_widget->private;
- struct sof_ipc4_base_module_cfg *src_config;
struct sof_ipc4_copier_config_set_sink_format format;
+ struct sof_ipc4_base_module_cfg *src_config;
+ const struct sof_ipc4_audio_format *pin_fmt;
struct sof_ipc4_fw_module *fw_module;
struct sof_ipc4_msg msg = {{ 0 }};
u32 header, extension;
format.sink_id = sink_id;
memcpy(&format.source_fmt, &src_config->audio_fmt, sizeof(format.source_fmt));
- memcpy(&format.sink_fmt, &sink_config->audio_fmt, sizeof(format.sink_fmt));
+
+ pin_fmt = sof_ipc4_get_input_pin_audio_fmt(sink_widget, sink_id);
+ if (!pin_fmt) {
+ dev_err(sdev->dev, "Unable to get pin %d format for %s",
+ sink_id, sink_widget->widget->name);
+ return -EINVAL;
+ }
+
+ memcpy(&format.sink_fmt, pin_fmt, sizeof(format.sink_fmt));
+
msg.data_size = sizeof(format);
msg.data_ptr = &format;
msg.primary = header;
msg.extension = extension;
- return sof_ipc_tx_message(sdev->ipc, &msg, msg.data_size, NULL, 0);
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, msg.data_size);
}
static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
{
struct snd_sof_widget *src_widget = sroute->src_widget;
struct snd_sof_widget *sink_widget = sroute->sink_widget;
+ struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget;
+ struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget;
struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
+ struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private;
+ struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private;
struct sof_ipc4_msg msg = {{ 0 }};
u32 header, extension;
int ret;
+ /* no route set up if chain DMA is used */
+ if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma) {
+ if (!src_pipeline->use_chain_dma || !sink_pipeline->use_chain_dma) {
+ dev_err(sdev->dev,
+ "use_chain_dma must be set for both src %s and sink %s pipelines\n",
+ src_widget->widget->name, sink_widget->widget->name);
+ return -EINVAL;
+ }
+ return 0;
+ }
+
if (!src_fw_module || !sink_fw_module) {
dev_err(sdev->dev,
"cannot bind %s -> %s, no firmware module for: %s%s\n",
}
sroute->src_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget,
- SOF_PIN_TYPE_SOURCE);
+ SOF_PIN_TYPE_OUTPUT);
if (sroute->src_queue_id < 0) {
dev_err(sdev->dev, "failed to get queue ID for source widget: %s\n",
src_widget->widget->name);
}
sroute->dst_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget,
- SOF_PIN_TYPE_SINK);
+ SOF_PIN_TYPE_INPUT);
if (sroute->dst_queue_id < 0) {
dev_err(sdev->dev, "failed to get queue ID for sink widget: %s\n",
sink_widget->widget->name);
sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id,
- SOF_PIN_TYPE_SOURCE);
+ SOF_PIN_TYPE_OUTPUT);
return sroute->dst_queue_id;
}
msg.primary = header;
msg.extension = extension;
- ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
if (ret < 0) {
dev_err(sdev->dev, "failed to bind modules %s:%d -> %s:%d\n",
src_widget->widget->name, sroute->src_queue_id,
return ret;
out:
- sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_SOURCE);
- sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_SINK);
+ sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_OUTPUT);
+ sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_INPUT);
return ret;
}
struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
struct sof_ipc4_msg msg = {{ 0 }};
+ struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget;
+ struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private;
+ struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private;
u32 header, extension;
int ret = 0;
+ /* no route is set up if chain DMA is used */
+ if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma)
+ return 0;
+
dev_dbg(sdev->dev, "unbind modules %s:%d -> %s:%d\n",
src_widget->widget->name, sroute->src_queue_id,
sink_widget->widget->name, sroute->dst_queue_id);
msg.primary = header;
msg.extension = extension;
- ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
if (ret < 0)
dev_err(sdev->dev, "failed to unbind modules %s:%d -> %s:%d\n",
src_widget->widget->name, sroute->src_queue_id,
sink_widget->widget->name, sroute->dst_queue_id);
out:
- sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_SINK);
- sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_SOURCE);
+ sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_INPUT);
+ sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_OUTPUT);
return ret;
}
switch (ipc4_copier->dai_type) {
case SOF_DAI_INTEL_HDA:
+ if (pipeline->use_chain_dma) {
+ pipeline->msg.primary &= ~SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK;
+ pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(data->dai_data);
+ break;
+ }
gtw_attr = ipc4_copier->gtw_attr;
gtw_attr->lp_buffer_alloc = pipeline->lp_mode;
pipeline->skip_during_fe_trigger = true;
static enum sof_tokens common_copier_token_list[] = {
SOF_COMP_TOKENS,
SOF_AUDIO_FMT_NUM_TOKENS,
- SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
SOF_IN_AUDIO_FORMAT_TOKENS,
SOF_OUT_AUDIO_FORMAT_TOKENS,
- SOF_COPIER_GATEWAY_CFG_TOKENS,
+ SOF_COPIER_DEEP_BUFFER_TOKENS,
SOF_COPIER_TOKENS,
SOF_COMP_EXT_TOKENS,
};
static enum sof_tokens dai_token_list[] = {
SOF_COMP_TOKENS,
SOF_AUDIO_FMT_NUM_TOKENS,
- SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
SOF_IN_AUDIO_FORMAT_TOKENS,
SOF_OUT_AUDIO_FORMAT_TOKENS,
- SOF_COPIER_GATEWAY_CFG_TOKENS,
SOF_COPIER_TOKENS,
SOF_DAI_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_COMP_TOKENS,
SOF_GAIN_TOKENS,
SOF_AUDIO_FMT_NUM_TOKENS,
- SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
SOF_COMP_EXT_TOKENS,
};
SOF_COMP_TOKENS,
SOF_AUDIO_FMT_NUM_TOKENS,
SOF_IN_AUDIO_FORMAT_TOKENS,
- SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
SOF_COMP_EXT_TOKENS,
};
SOF_SRC_TOKENS,
SOF_AUDIO_FMT_NUM_TOKENS,
SOF_IN_AUDIO_FORMAT_TOKENS,
- SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens process_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
SOF_COMP_EXT_TOKENS,
};
src_token_list, ARRAY_SIZE(src_token_list),
NULL, sof_ipc4_prepare_src_module,
NULL},
+ [snd_soc_dapm_effect] = {sof_ipc4_widget_setup_comp_process,
+ sof_ipc4_widget_free_comp_process,
+ process_token_list, ARRAY_SIZE(process_token_list),
+ NULL, sof_ipc4_prepare_process_module,
+ NULL},
};
const struct sof_ipc_tplg_ops ipc4_tplg_ops = {
#define SOF_IPC4_MODULE_LL BIT(5)
#define SOF_IPC4_MODULE_DP BIT(6)
#define SOF_IPC4_MODULE_LIB_CODE BIT(7)
+#define SOF_IPC4_MODULE_INIT_CONFIG_MASK GENMASK(11, 8)
+
+#define SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG 0
+#define SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT 1
#define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12
#define SOF_IPC4_PIPELINE_OBJECT_SIZE 448
#define SOF_IPC4_INVALID_NODE_ID 0xffffffff
+/* FW requires minimum 2ms DMA buffer size */
+#define SOF_IPC4_MIN_DMA_BUFFER_SIZE 2
+
/*
* The base of multi-gateways. Multi-gateways addressing starts from
* ALH_MULTI_GTW_BASE and there are ALH_MULTI_GTW_COUNT multi-sources
* @priority: Priority of this pipeline
* @lp_mode: Low power mode
* @mem_usage: Memory usage
+ * @core_id: Target core for the pipeline
* @state: Pipeline state
+ * @use_chain_dma: flag to indicate if the firmware shall use chained DMA
* @msg: message structure for pipeline
* @skip_during_fe_trigger: skip triggering this pipeline during the FE DAI trigger
*/
uint32_t priority;
uint32_t lp_mode;
uint32_t mem_usage;
+ uint32_t core_id;
int state;
+ bool use_chain_dma;
struct sof_ipc4_msg msg;
bool skip_during_fe_trigger;
};
DECLARE_FLEX_ARRAY(u32, pipeline_ids);
} __packed;
+/**
+ * struct sof_ipc4_pin_format - Module pin format
+ * @pin_index: pin index
+ * @buffer_size: buffer size in bytes
+ * @audio_fmt: audio format for the pin
+ *
+ * This structure can be used for both output or input pins and the pin_index is relative to the
+ * pin type i.e output/input pin
+ */
+struct sof_ipc4_pin_format {
+ u32 pin_index;
+ u32 buffer_size;
+ struct sof_ipc4_audio_format audio_fmt;
+};
+
/**
* struct sof_ipc4_available_audio_format - Available audio formats
- * @base_config: Available base config
- * @out_audio_fmt: Available output audio format
- * @ref_audio_fmt: Reference audio format to match runtime audio format
- * @dma_buffer_size: Available Gateway DMA buffer size (in bytes)
- * @audio_fmt_num: Number of available audio formats
+ * @output_pin_fmts: Available output pin formats
+ * @input_pin_fmts: Available input pin formats
+ * @num_input_formats: Number of input pin formats
+ * @num_output_formats: Number of output pin formats
*/
struct sof_ipc4_available_audio_format {
- struct sof_ipc4_base_module_cfg *base_config;
- struct sof_ipc4_audio_format *out_audio_fmt;
- struct sof_ipc4_audio_format *ref_audio_fmt;
- u32 *dma_buffer_size;
- int audio_fmt_num;
+ struct sof_ipc4_pin_format *output_pin_fmts;
+ struct sof_ipc4_pin_format *input_pin_fmts;
+ u32 num_input_formats;
+ u32 num_output_formats;
};
/**
int index;
union {
- struct sof_ipc4_ctrl_value_chan chanv[0];
- struct sof_abi_hdr data[0];
+ DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv);
+ DECLARE_FLEX_ARRAY(struct sof_abi_hdr, data);
};
};
struct sof_ipc4_msg msg;
};
+/**
+ * struct sof_ipc4_base_module_cfg_ext - base module config extension containing the pin format
+ * information for the module. Both @num_input_pin_fmts and @num_output_pin_fmts cannot be 0 for a
+ * module.
+ * @num_input_pin_fmts: number of input pin formats in the @pin_formats array
+ * @num_output_pin_fmts: number of output pin formats in the @pin_formats array
+ * @reserved: reserved for future use
+ * @pin_formats: flexible array consisting of @num_input_pin_fmts input pin format items followed
+ * by @num_output_pin_fmts output pin format items
+ */
+struct sof_ipc4_base_module_cfg_ext {
+ u16 num_input_pin_fmts;
+ u16 num_output_pin_fmts;
+ u8 reserved[12];
+ DECLARE_FLEX_ARRAY(struct sof_ipc4_pin_format, pin_formats);
+} __packed;
+
+/**
+ * struct sof_ipc4_process - process config data
+ * @base_config: IPC base config data
+ * @base_config_ext: Base config extension data for module init
+ * @output_format: Output audio format
+ * @available_fmt: Available audio format
+ * @ipc_config_data: Process module config data
+ * @ipc_config_size: Size of process module config data
+ * @msg: IPC4 message struct containing header and data info
+ * @base_config_ext_size: Size of the base config extension data in bytes
+ * @init_config: Module init config type (SOF_IPC4_MODULE_INIT_CONFIG_TYPE_*)
+ */
+struct sof_ipc4_process {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_base_module_cfg_ext *base_config_ext;
+ struct sof_ipc4_audio_format output_format;
+ struct sof_ipc4_available_audio_format available_fmt;
+ void *ipc_config_data;
+ uint32_t ipc_config_size;
+ struct sof_ipc4_msg msg;
+ u32 base_config_ext_size;
+ u32 init_config;
+};
+
#endif
"fw_version", 0444);
/* errors are only due to memory allocation, not debugfs */
if (ret < 0) {
- dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
+ dev_err(sdev->dev, "snd_sof_debugfs_buf_item failed\n");
return ret;
}
}
/* perform pre fw run operations */
ret = snd_sof_dsp_pre_fw_run(sdev);
if (ret < 0) {
- dev_err(sdev->dev, "error: failed pre fw run op\n");
+ dev_err(sdev->dev, "failed pre fw run op\n");
return ret;
}
dev_dbg(component->dev, "pcm: free stream %d dir %d\n",
spcm->pcm.pcm_id, substream->stream);
- /* free PCM in the DSP */
- if (pcm_ops && pcm_ops->hw_free && spcm->prepared[substream->stream]) {
- ret = pcm_ops->hw_free(component, substream);
- if (ret < 0)
- err = ret;
+ if (spcm->prepared[substream->stream]) {
+ /* stop DMA first if needed */
+ if (pcm_ops && pcm_ops->platform_stop_during_hw_free)
+ snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP);
+
+ /* free PCM in the DSP */
+ if (pcm_ops && pcm_ops->hw_free) {
+ ret = pcm_ops->hw_free(component, substream);
+ if (ret < 0)
+ err = ret;
+ }
spcm->prepared[substream->stream] = false;
}
- /* stop DMA */
+ /* reset DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
if (ret < 0) {
dev_err(component->dev, "error: platform hw free failed\n");
ipc_first = true;
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (pcm_ops && pcm_ops->ipc_first_on_start)
+ ipc_first = true;
break;
case SNDRV_PCM_TRIGGER_START:
if (spcm->stream[substream->stream].suspend_ignored) {
spcm->stream[substream->stream].suspend_ignored = false;
return 0;
}
+
+ if (pcm_ops && pcm_ops->ipc_first_on_start)
+ ipc_first = true;
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
if (sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
spcm->stream[substream->stream].suspend_ignored = true;
return 0;
}
+
+ /* On suspend the DMA must be stopped in DSPless mode */
+ if (sdev->dspless_mode_selected)
+ reset_hw_params = true;
+
fallthrough;
case SNDRV_PCM_TRIGGER_STOP:
ipc_first = true;
- reset_hw_params = true;
+ if (pcm_ops && pcm_ops->reset_hw_params_during_stop)
+ reset_hw_params = true;
break;
default:
dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
return -EINVAL;
}
- /*
- * DMA and IPC sequence is different for start and stop. Need to send
- * STOP IPC before stop DMA
- */
if (!ipc_first)
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
if (pcm_ops && pcm_ops->trigger)
ret = pcm_ops->trigger(component, substream, cmd);
- /* need to STOP DMA even if trigger IPC failed */
- if (ipc_first)
- snd_sof_pcm_platform_trigger(sdev, substream, cmd);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_START:
+ /* invoke platform trigger to start DMA only if pcm_ops is successful */
+ if (ipc_first && !ret)
+ snd_sof_pcm_platform_trigger(sdev, substream, cmd);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* invoke platform trigger to stop DMA even if pcm_ops isn't set or if it failed */
+ if (!pcm_ops || (pcm_ops && !pcm_ops->platform_stop_during_hw_free))
+ snd_sof_pcm_platform_trigger(sdev, substream, cmd);
+ break;
+ default:
+ break;
+ }
/* free PCM if reset_hw_params is set and the STOP IPC is successful */
if (!ret && reset_hw_params)
pd->pcm_construct = sof_pcm_new;
pd->ignore_machine = drv_name;
- pd->be_hw_params_fixup = sof_pcm_dai_link_fixup;
pd->be_pcm_base = SOF_BE_PCM_BASE;
pd->use_dai_pcm_id = true;
pd->topology_name_prefix = "sof";
pd->module_get_upon_open = 1;
pd->legacy_dai_naming = 1;
+
+ /*
+ * The fixup is only needed when the DSP is in use as with the DSPless
+ * mode we are directly using the audio interface
+ */
+ if (!sdev->dspless_mode_selected)
+ pd->be_hw_params_fixup = sof_pcm_dai_link_fixup;
}
return ret;
}
+ if (sdev->dspless_mode_selected) {
+ sof_set_fw_state(sdev, SOF_DSPLESS_MODE);
+ return 0;
+ }
+
/*
* Nothing further to be done for platforms that support the low power
* D0 substate. Resume trace and return when resuming from
static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
struct snd_soc_dapm_widget_list *list, int dir)
{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_soc_dapm_widget *widget;
+ struct snd_sof_route *sroute;
struct snd_soc_dapm_path *p;
- int ret;
+ int ret = 0;
int i;
/*
}
}
+ /*
+ * The above loop handles connections between widgets that belong to the DAPM widget list.
+ * This is not sufficient to handle loopback cases between pipelines configured with
+ * different directions, e.g. a sidetone or an amplifier feedback connected to a speaker
+ * protection module.
+ */
+ list_for_each_entry(sroute, &sdev->route_list, list) {
+ bool src_widget_in_dapm_list, sink_widget_in_dapm_list;
+ struct snd_sof_widget *swidget;
+
+ if (sroute->setup)
+ continue;
+
+ src_widget_in_dapm_list = widget_in_list(list, sroute->src_widget->widget);
+ sink_widget_in_dapm_list = widget_in_list(list, sroute->sink_widget->widget);
+
+ /*
+ * if both source and sink are in the DAPM list, the route must already have been
+ * set up above. And if neither are in the DAPM list, the route shouldn't be
+ * handled now.
+ */
+ if (src_widget_in_dapm_list == sink_widget_in_dapm_list)
+ continue;
+
+ /*
+ * At this point either the source widget or the sink widget is in the DAPM list
+ * with a route that might need to be set up. Check the use_count of the widget
+ * that is not in the DAPM list to confirm if it is in use currently before setting
+ * up the route.
+ */
+ if (src_widget_in_dapm_list)
+ swidget = sroute->sink_widget;
+ else
+ swidget = sroute->src_widget;
+
+ mutex_lock(&swidget->setup_mutex);
+ if (!swidget->use_count) {
+ mutex_unlock(&swidget->setup_mutex);
+ continue;
+ }
+
+ if (tplg_ops && tplg_ops->route_setup) {
+ /*
+ * this route will get freed when either the source widget or the sink
+ * widget is freed during hw_free
+ */
+ ret = tplg_ops->route_setup(sdev, sroute);
+ if (!ret)
+ sroute->setup = true;
+ }
+
+ mutex_unlock(&swidget->setup_mutex);
+
+ if (ret < 0)
+ return ret;
+ }
+
return 0;
}
struct snd_sof_widget *pipe_widget;
struct snd_sof_pipeline *spipe;
- if (!swidget)
+ if (!swidget || sdev->dspless_mode_selected)
continue;
spipe = swidget->spipe;
const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
int ret;
- /* Send PCM_FREE IPC to reset pipeline */
- if (pcm_ops && pcm_ops->hw_free && spcm->prepared[substream->stream]) {
- ret = pcm_ops->hw_free(sdev->component, substream);
- if (ret < 0)
- return ret;
- }
+ if (spcm->prepared[substream->stream]) {
+ /* stop DMA first if needed */
+ if (pcm_ops && pcm_ops->platform_stop_during_hw_free)
+ snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP);
- spcm->prepared[substream->stream] = false;
+ /* Send PCM_FREE IPC to reset pipeline */
+ if (pcm_ops && pcm_ops->hw_free) {
+ ret = pcm_ops->hw_free(sdev->component, substream);
+ if (ret < 0)
+ return ret;
+ }
+
+ spcm->prepared[substream->stream] = false;
+ }
- /* stop the DMA */
+ /* reset the DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
if (ret < 0)
return ret;
*/
#define SOF_WIDGET_MAX_NUM_PINS 8
-/* The type of a widget pin is either sink or source */
-#define SOF_PIN_TYPE_SINK 0
-#define SOF_PIN_TYPE_SOURCE 1
+/* Widget pin type */
+#define SOF_PIN_TYPE_INPUT 0
+#define SOF_PIN_TYPE_OUTPUT 1
/* max number of FE PCMs before BEs */
#define SOF_BE_PCM_BASE 16
* @pcm_free: Function pointer for PCM free that can be used for freeing any
* additional memory in the SOF PCM stream structure
* @delay: Function pointer for pcm delay calculation
+ * @reset_hw_params_during_stop: Flag indicating whether the hw_params should be reset during the
+ * STOP pcm trigger
+ * @ipc_first_on_start: Send IPC before invoking platform trigger during
+ * START/PAUSE_RELEASE triggers
+ * @platform_stop_during_hw_free: Invoke the platform trigger during hw_free. This is needed for
+ * IPC4 where a pipeline is only paused during stop/pause/suspend
+ * triggers. The FW keeps the host DMA running in this case and
+ * therefore the host must do the same and should stop the DMA during
+ * hw_free.
*/
struct sof_ipc_pcm_ops {
int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream,
void (*pcm_free)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm);
snd_pcm_sframes_t (*delay)(struct snd_soc_component *component,
struct snd_pcm_substream *substream);
+ bool reset_hw_params_during_stop;
+ bool ipc_first_on_start;
+ bool platform_stop_during_hw_free;
};
/**
SOF_COMP_EXT_TOKENS,
SOF_IN_AUDIO_FORMAT_TOKENS,
SOF_OUT_AUDIO_FORMAT_TOKENS,
- SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS,
- SOF_COPIER_GATEWAY_CFG_TOKENS,
+ SOF_COPIER_DEEP_BUFFER_TOKENS,
SOF_COPIER_TOKENS,
SOF_AUDIO_FMT_NUM_TOKENS,
SOF_COPIER_FORMAT_TOKENS,
struct snd_sof_tuple *tuples;
/*
- * The allowed range for num_sink/source_pins is [0, SOF_WIDGET_MAX_NUM_PINS].
- * Widgets may have zero sink or source pins, for example the tone widget has
- * zero sink pins.
+ * The allowed range for num_input/output_pins is [0, SOF_WIDGET_MAX_NUM_PINS].
+ * Widgets may have zero input or output pins, for example the tone widget has
+ * zero input pins.
*/
- u32 num_sink_pins;
- u32 num_source_pins;
+ u32 num_input_pins;
+ u32 num_output_pins;
/*
- * The sink/source pin binding array, it takes the form of
+ * The input/output pin binding array, it takes the form of
* [widget_name_connected_to_pin0, widget_name_connected_to_pin1, ...],
* with the index as the queue ID.
*
* The array is used for special pin binding. Note that even if there
- * is only one sink/source pin requires special pin binding, pin binding
- * should be defined for all sink/source pins in topology, for pin(s) that
+ * is only one input/output pin requires special pin binding, pin binding
+ * should be defined for all input/output pins in topology, for pin(s) that
* are not used, give the value "NotConnected".
*
* If pin binding is not defined in topology, nothing to parse in the kernel,
- * sink_pin_binding and src_pin_binding shall be NULL.
+ * input_pin_binding and output_pin_binding shall be NULL.
*/
- char **sink_pin_binding;
- char **src_pin_binding;
+ char **input_pin_binding;
+ char **output_pin_binding;
- struct ida src_queue_ida;
- struct ida sink_queue_ida;
+ struct ida output_queue_ida;
+ struct ida input_queue_ida;
void *private; /* core does not touch this */
};
int number_configs;
int current_config;
struct list_head list; /* list in sdev dai list */
+ /* core should not touch this */
+ const void *platform_private;
void *private;
};
struct sof_ipc_flood_priv *priv = cdev->data;
struct device *dev = &cdev->auxdev.dev;
struct sof_ipc_cmd_hdr hdr;
- struct sof_ipc_reply reply;
u64 min_response_time = U64_MAX;
ktime_t start, end, test_end;
u64 avg_response_time = 0;
/* send test IPC's */
while (1) {
start = ktime_get();
- ret = sof_client_ipc_tx_message(cdev, &hdr, &reply, sizeof(reply));
+ ret = sof_client_ipc_tx_message_no_reply(cdev, &hdr);
end = ktime_get();
if (ret < 0)
{
struct sof_ipc_probe_dma_add_params *msg;
size_t size = struct_size(msg, dma, 1);
- struct sof_ipc_reply reply;
int ret;
msg = kmalloc(size, GFP_KERNEL);
msg->dma[0].stream_tag = stream_tag;
msg->dma[0].dma_buffer_size = buffer_size;
- ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply));
+ ret = sof_client_ipc_tx_message_no_reply(cdev, msg);
kfree(msg);
return ret;
}
static int ipc3_probes_deinit(struct sof_client_dev *cdev)
{
struct sof_ipc_cmd_hdr msg;
- struct sof_ipc_reply reply;
msg.size = sizeof(msg);
msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT;
- return sof_client_ipc_tx_message(cdev, &msg, &reply, sizeof(reply));
+ return sof_client_ipc_tx_message_no_reply(cdev, &msg);
}
static int ipc3_probes_info(struct sof_client_dev *cdev, unsigned int cmd,
{
struct sof_ipc_probe_point_add_params *msg;
size_t size = struct_size(msg, desc, num_desc);
- struct sof_ipc_reply reply;
int ret;
msg = kmalloc(size, GFP_KERNEL);
msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD;
memcpy(&msg->desc[0], desc, size - sizeof(*msg));
- ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply));
+ ret = sof_client_ipc_tx_message_no_reply(cdev, msg);
kfree(msg);
return ret;
}
{
struct sof_ipc_probe_point_remove_params *msg;
size_t size = struct_size(msg, buffer_id, num_buffer_id);
- struct sof_ipc_reply reply;
int ret;
msg = kmalloc(size, GFP_KERNEL);
msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE;
memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg));
- ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply));
+ ret = sof_client_ipc_tx_message_no_reply(cdev, msg);
kfree(msg);
return ret;
}
msg.data_size = sizeof(cfg);
msg.data_ptr = &cfg;
- return sof_client_ipc_tx_message(cdev, &msg, NULL, 0);
+ return sof_client_ipc_tx_message_no_reply(cdev, &msg);
}
/**
msg.data_size = 0;
msg.data_ptr = NULL;
- return sof_client_ipc_tx_message(cdev, &msg, NULL, 0);
+ return sof_client_ipc_tx_message_no_reply(cdev, &msg);
}
/**
{
int ret;
+ if (sdev->dspless_mode_selected)
+ return 0;
+
/* Register platform independent client devices */
ret = sof_register_ipc_flood_test(sdev);
if (ret) {
int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg,
void *reply_data, size_t reply_bytes);
+static inline int sof_client_ipc_tx_message_no_reply(struct sof_client_dev *cdev, void *ipc_msg)
+{
+ return sof_client_ipc_tx_message(cdev, ipc_msg, NULL, 0);
+}
int sof_client_ipc_set_get_data(struct sof_client_dev *cdev, void *ipc_msg,
bool set);
#define SOF_DBG_FORCE_NOCODEC BIT(10) /* ignore all codec-related
* configurations
*/
+#define SOF_DBG_DSPLESS_MODE BIT(15) /* Do not initialize and use the DSP */
/* Flag definitions used for controlling the DSP dump behavior */
#define SOF_DBG_DUMP_REGS BIT(0)
spinlock_t ipc_lock; /* lock for IPC users */
spinlock_t hw_lock; /* lock for HW IO access */
+ /*
+ * When true the DSP is not used.
+ * It is set under the following condition:
+ * User sets the SOF_DBG_DSPLESS_MODE flag in sof_debug module parameter
+ * and
+ * the platform advertises that it can support such mode
+ * pdata->desc->dspless_mode_supported is true.
+ */
+ bool dspless_mode_selected;
+
/* Main, Base firmware image */
struct sof_firmware basefw;
}
int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes,
void *reply_data, size_t reply_bytes);
+static inline int sof_ipc_tx_message_no_reply(struct snd_sof_ipc *ipc, void *msg_data,
+ size_t msg_bytes)
+{
+ return sof_ipc_tx_message(ipc, msg_data, msg_bytes, NULL, 0);
+}
int sof_ipc_set_get_data(struct snd_sof_ipc *ipc, void *msg_data,
size_t msg_bytes, bool set);
int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes,
void *reply_data, size_t reply_bytes);
+static inline int sof_ipc_tx_message_no_pm_no_reply(struct snd_sof_ipc *ipc, void *msg_data,
+ size_t msg_bytes)
+{
+ return sof_ipc_tx_message_no_pm(ipc, msg_data, msg_bytes, NULL, 0);
+}
int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes,
size_t reply_bytes);
};
static const struct sof_topology_token comp_pin_tokens[] = {
- {SOF_TKN_COMP_NUM_SINK_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct snd_sof_widget, num_sink_pins)},
- {SOF_TKN_COMP_NUM_SOURCE_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct snd_sof_widget, num_source_pins)},
+ {SOF_TKN_COMP_NUM_INPUT_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_widget, num_input_pins)},
+ {SOF_TKN_COMP_NUM_OUTPUT_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_widget, num_output_pins)},
};
-static const struct sof_topology_token comp_sink_pin_binding_tokens[] = {
- {SOF_TKN_COMP_SINK_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING,
+static const struct sof_topology_token comp_input_pin_binding_tokens[] = {
+ {SOF_TKN_COMP_INPUT_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING,
get_token_string, 0},
};
-static const struct sof_topology_token comp_src_pin_binding_tokens[] = {
- {SOF_TKN_COMP_SRC_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING,
+static const struct sof_topology_token comp_output_pin_binding_tokens[] = {
+ {SOF_TKN_COMP_OUTPUT_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING,
get_token_string, 0},
};
static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm,
int dir)
{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_sof_widget *host_widget;
+ if (sdev->dspless_mode_selected)
+ return 0;
+
host_widget = snd_sof_find_swidget_sname(scomp,
spcm->pcm.caps[dir].name,
dir);
continue;
case SOF_IN_AUDIO_FORMAT_TOKENS:
- case SOF_OUT_AUDIO_FORMAT_TOKENS:
- case SOF_COPIER_GATEWAY_CFG_TOKENS:
- case SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS:
- num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_AUDIO_FORMATS,
+ num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS,
swidget->tuples, swidget->num_tuples);
-
if (num_sets < 0) {
- dev_err(sdev->dev, "Invalid audio format count for %s\n",
+ dev_err(sdev->dev, "Invalid input audio format count for %s\n",
swidget->widget->name);
ret = num_sets;
goto err;
}
-
- if (num_sets > 1) {
- struct snd_sof_tuple *new_tuples;
-
- num_tuples += token_list[object_token_list[i]].count * num_sets;
- new_tuples = krealloc(swidget->tuples,
- sizeof(*new_tuples) * num_tuples, GFP_KERNEL);
- if (!new_tuples) {
- ret = -ENOMEM;
- goto err;
- }
-
- swidget->tuples = new_tuples;
+ break;
+ case SOF_OUT_AUDIO_FORMAT_TOKENS:
+ num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS,
+ swidget->tuples, swidget->num_tuples);
+ if (num_sets < 0) {
+ dev_err(sdev->dev, "Invalid output audio format count for %s\n",
+ swidget->widget->name);
+ ret = num_sets;
+ goto err;
}
break;
default:
break;
}
+ if (num_sets > 1) {
+ struct snd_sof_tuple *new_tuples;
+
+ num_tuples += token_list[object_token_list[i]].count * num_sets;
+ new_tuples = krealloc(swidget->tuples,
+ sizeof(*new_tuples) * num_tuples, GFP_KERNEL);
+ if (!new_tuples) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ swidget->tuples = new_tuples;
+ }
+
/* copy one set of tuples per token ID into swidget->tuples */
ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
object_token_list[i], num_sets, swidget->tuples,
u32 num_pins;
int i;
- if (pin_type == SOF_PIN_TYPE_SINK) {
- pin_binding = swidget->sink_pin_binding;
- num_pins = swidget->num_sink_pins;
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ pin_binding = swidget->input_pin_binding;
+ num_pins = swidget->num_input_pins;
} else {
- pin_binding = swidget->src_pin_binding;
- num_pins = swidget->num_source_pins;
+ pin_binding = swidget->output_pin_binding;
+ num_pins = swidget->num_output_pins;
}
if (pin_binding) {
int ret;
int i;
- if (pin_type == SOF_PIN_TYPE_SINK) {
- num_pins = swidget->num_sink_pins;
- pin_binding_token = comp_sink_pin_binding_tokens;
- token_count = ARRAY_SIZE(comp_sink_pin_binding_tokens);
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ num_pins = swidget->num_input_pins;
+ pin_binding_token = comp_input_pin_binding_tokens;
+ token_count = ARRAY_SIZE(comp_input_pin_binding_tokens);
} else {
- num_pins = swidget->num_source_pins;
- pin_binding_token = comp_src_pin_binding_tokens;
- token_count = ARRAY_SIZE(comp_src_pin_binding_tokens);
+ num_pins = swidget->num_output_pins;
+ pin_binding_token = comp_output_pin_binding_tokens;
+ token_count = ARRAY_SIZE(comp_output_pin_binding_tokens);
}
memset(pin_binding, 0, SOF_WIDGET_MAX_NUM_PINS * sizeof(char *));
ret = -ENOMEM;
goto err;
}
- if (pin_type == SOF_PIN_TYPE_SINK)
- swidget->sink_pin_binding = pb;
+ if (pin_type == SOF_PIN_TYPE_INPUT)
+ swidget->input_pin_binding = pb;
else
- swidget->src_pin_binding = pb;
+ swidget->output_pin_binding = pb;
}
return 0;
swidget->private = NULL;
mutex_init(&swidget->setup_mutex);
- ida_init(&swidget->src_queue_ida);
- ida_init(&swidget->sink_queue_ida);
+ ida_init(&swidget->output_queue_ida);
+ ida_init(&swidget->input_queue_ida);
ret = sof_parse_tokens(scomp, swidget, comp_pin_tokens,
ARRAY_SIZE(comp_pin_tokens), priv->array,
goto widget_free;
}
- if (swidget->num_sink_pins > SOF_WIDGET_MAX_NUM_PINS ||
- swidget->num_source_pins > SOF_WIDGET_MAX_NUM_PINS) {
- dev_err(scomp->dev, "invalid pins for %s: [sink: %d, src: %d]\n",
- swidget->widget->name, swidget->num_sink_pins, swidget->num_source_pins);
+ if (swidget->num_input_pins > SOF_WIDGET_MAX_NUM_PINS ||
+ swidget->num_output_pins > SOF_WIDGET_MAX_NUM_PINS) {
+ dev_err(scomp->dev, "invalid pins for %s: [input: %d, output: %d]\n",
+ swidget->widget->name, swidget->num_input_pins, swidget->num_output_pins);
ret = -EINVAL;
goto widget_free;
}
- if (swidget->num_sink_pins > 1) {
- ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_SINK);
+ if (swidget->num_input_pins > 1) {
+ ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_INPUT);
/* on parsing error, pin binding is not allocated, nothing to free. */
if (ret < 0) {
- dev_err(scomp->dev, "failed to parse sink pin binding for %s\n",
+ dev_err(scomp->dev, "failed to parse input pin binding for %s\n",
w->name);
goto widget_free;
}
}
- if (swidget->num_source_pins > 1) {
- ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_SOURCE);
+ if (swidget->num_output_pins > 1) {
+ ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_OUTPUT);
/* on parsing error, pin binding is not allocated, nothing to free. */
if (ret < 0) {
- dev_err(scomp->dev, "failed to parse source pin binding for %s\n",
+ dev_err(scomp->dev, "failed to parse output pin binding for %s\n",
w->name);
goto widget_free;
}
dev_dbg(scomp->dev,
"tplg: widget %d (%s) is ready [type: %d, pipe: %d, pins: %d / %d, stream: %s]\n",
swidget->comp_id, w->name, swidget->id, index,
- swidget->num_sink_pins, swidget->num_source_pins,
+ swidget->num_input_pins, swidget->num_output_pins,
strnlen(w->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 ? w->sname : "none");
widget_ops = tplg_ops ? tplg_ops->widget : NULL;
if (widget_ops && widget_ops[swidget->id].ipc_free)
widget_ops[swidget->id].ipc_free(swidget);
- ida_destroy(&swidget->src_queue_ida);
- ida_destroy(&swidget->sink_queue_ida);
+ ida_destroy(&swidget->output_queue_ida);
+ ida_destroy(&swidget->input_queue_ida);
- sof_free_pin_binding(swidget, SOF_PIN_TYPE_SINK);
- sof_free_pin_binding(swidget, SOF_PIN_TYPE_SOURCE);
+ sof_free_pin_binding(swidget, SOF_PIN_TYPE_INPUT);
+ sof_free_pin_binding(swidget, SOF_PIN_TYPE_OUTPUT);
kfree(swidget->tuples);
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- struct snd_sof_widget *swidget, *comp_swidget;
const struct sof_ipc_tplg_widget_ops *widget_ops;
struct snd_sof_control *scontrol;
struct snd_sof_pipeline *spipe;
}
}
- /*
- * then update all widget IPC structures. If any of the ipc_setup callbacks fail, the
- * topology will be removed and all widgets will be unloaded resulting in freeing all
- * associated memories.
- */
- list_for_each_entry(swidget, &sdev->widget_list, list) {
- if (widget_ops && widget_ops[swidget->id].ipc_setup) {
- ret = widget_ops[swidget->id].ipc_setup(swidget);
+ /* set up the IPC structures for the pipeline widgets */
+ list_for_each_entry(spipe, &sdev->pipeline_list, list) {
+ struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+ struct snd_sof_widget *swidget;
+
+ /* Update the scheduler widget's IPC structure */
+ if (widget_ops && widget_ops[pipe_widget->id].ipc_setup) {
+ ret = widget_ops[pipe_widget->id].ipc_setup(pipe_widget);
if (ret < 0) {
dev_err(sdev->dev, "failed updating IPC struct for %s\n",
- swidget->widget->name);
+ pipe_widget->widget->name);
return ret;
}
}
- }
-
- /* set the pipe_widget and apply the dynamic_pipeline_widget_flag */
- list_for_each_entry(spipe, &sdev->pipeline_list, list) {
- struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
- /*
- * Apply the dynamic_pipeline_widget flag and set the pipe_widget field
- * for all widgets that have the same pipeline ID as the scheduler widget.
- * Skip the scheduler widgets as they have their pipeline set during widget_ready
- */
- list_for_each_entry(comp_swidget, &sdev->widget_list, list)
- if (comp_swidget->widget->id != snd_soc_dapm_scheduler &&
- comp_swidget->pipeline_id == pipe_widget->pipeline_id) {
- ret = sof_set_widget_pipeline(sdev, spipe, comp_swidget);
+ /* set the pipeline and update the IPC structure for the non scheduler widgets */
+ list_for_each_entry(swidget, &sdev->widget_list, list)
+ if (swidget->widget->id != snd_soc_dapm_scheduler &&
+ swidget->pipeline_id == pipe_widget->pipeline_id) {
+ ret = sof_set_widget_pipeline(sdev, spipe, swidget);
if (ret < 0)
return ret;
+
+ if (widget_ops && widget_ops[swidget->id].ipc_setup) {
+ ret = widget_ops[swidget->id].ipc_setup(swidget);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "failed updating IPC struct for %s\n",
+ swidget->widget->name);
+ return ret;
+ }
+ }
}
}
.bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops),
};
+static int snd_sof_dspless_kcontrol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return 0;
+}
+
+static const struct snd_soc_tplg_kcontrol_ops sof_dspless_io_ops[] = {
+ {SOF_TPLG_KCTL_VOL_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+ {SOF_TPLG_KCTL_BYTES_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+ {SOF_TPLG_KCTL_ENUM_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+ {SOF_TPLG_KCTL_SWITCH_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+};
+
+static int snd_sof_dspless_bytes_ext_get(struct snd_kcontrol *kcontrol,
+ unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return 0;
+}
+
+static int snd_sof_dspless_bytes_ext_put(struct snd_kcontrol *kcontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return 0;
+}
+
+static const struct snd_soc_tplg_bytes_ext_ops sof_dspless_bytes_ext_ops[] = {
+ {SOF_TPLG_KCTL_BYTES_ID, snd_sof_dspless_bytes_ext_get, snd_sof_dspless_bytes_ext_put},
+ {SOF_TPLG_KCTL_BYTES_VOLATILE_RO, snd_sof_dspless_bytes_ext_get},
+};
+
+/* external widget init - used for any driver specific init */
+static int sof_dspless_widget_ready(struct snd_soc_component *scomp, int index,
+ struct snd_soc_dapm_widget *w,
+ struct snd_soc_tplg_dapm_widget *tw)
+{
+ if (WIDGET_IS_DAI(w->id)) {
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_widget *swidget;
+ struct snd_sof_dai dai;
+ int ret;
+
+ swidget = kzalloc(sizeof(*swidget), GFP_KERNEL);
+ if (!swidget)
+ return -ENOMEM;
+
+ memset(&dai, 0, sizeof(dai));
+
+ ret = sof_connect_dai_widget(scomp, w, tw, &dai);
+ if (ret) {
+ kfree(swidget);
+ return ret;
+ }
+
+ swidget->scomp = scomp;
+ swidget->widget = w;
+ mutex_init(&swidget->setup_mutex);
+ w->dobj.private = swidget;
+ list_add(&swidget->list, &sdev->widget_list);
+ }
+
+ return 0;
+}
+
+static int sof_dspless_widget_unload(struct snd_soc_component *scomp,
+ struct snd_soc_dobj *dobj)
+{
+ struct snd_soc_dapm_widget *w = container_of(dobj, struct snd_soc_dapm_widget, dobj);
+
+ if (WIDGET_IS_DAI(w->id)) {
+ struct snd_sof_widget *swidget = dobj->private;
+
+ sof_disconnect_dai_widget(scomp, w);
+
+ if (!swidget)
+ return 0;
+
+ /* remove and free swidget object */
+ list_del(&swidget->list);
+ kfree(swidget);
+ }
+
+ return 0;
+}
+
+static int sof_dspless_link_load(struct snd_soc_component *scomp, int index,
+ struct snd_soc_dai_link *link,
+ struct snd_soc_tplg_link_config *cfg)
+{
+ link->platforms->name = dev_name(scomp->dev);
+
+ /* Set nonatomic property for FE dai links for FE-BE compatibility */
+ if (!link->no_pcm)
+ link->nonatomic = true;
+
+ return 0;
+}
+
+static struct snd_soc_tplg_ops sof_dspless_tplg_ops = {
+ /* external widget init - used for any driver specific init */
+ .widget_ready = sof_dspless_widget_ready,
+ .widget_unload = sof_dspless_widget_unload,
+
+ /* FE DAI - used for any driver specific init */
+ .dai_load = sof_dai_load,
+ .dai_unload = sof_dai_unload,
+
+ /* DAI link - used for any driver specific init */
+ .link_load = sof_dspless_link_load,
+
+ /* vendor specific kcontrol handlers available for binding */
+ .io_ops = sof_dspless_io_ops,
+ .io_ops_count = ARRAY_SIZE(sof_dspless_io_ops),
+
+ /* vendor specific bytes ext handlers available for binding */
+ .bytes_ext_ops = sof_dspless_bytes_ext_ops,
+ .bytes_ext_ops_count = ARRAY_SIZE(sof_dspless_bytes_ext_ops),
+};
+
int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
return ret;
}
- ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw);
+ if (sdev->dspless_mode_selected)
+ ret = snd_soc_tplg_component_load(scomp, &sof_dspless_tplg_ops, fw);
+ else
+ ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw);
+
if (ret < 0) {
dev_err(scomp->dev, "error: tplg component load failed %d\n",
ret);
return 0;
}
-static int sprd_mcdt_remove(struct platform_device *pdev)
+static void sprd_mcdt_remove(struct platform_device *pdev)
{
struct sprd_mcdt_chan *chan, *temp;
list_del(&chan->list);
mutex_unlock(&sprd_mcdt_list_mutex);
-
- return 0;
}
static const struct of_device_id sprd_mcdt_of_match[] = {
static struct platform_driver sprd_mcdt_driver = {
.probe = sprd_mcdt_probe,
- .remove = sprd_mcdt_remove,
+ .remove_new = sprd_mcdt_remove,
.driver = {
.name = "sprd-mcdt",
.of_match_table = sprd_mcdt_of_match,
return ret;
}
-static int stm32_adfsdm_remove(struct platform_device *pdev)
+static void stm32_adfsdm_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static struct platform_driver stm32_adfsdm_driver = {
.of_match_table = stm32_adfsdm_of_match,
},
.probe = stm32_adfsdm_probe,
- .remove = stm32_adfsdm_remove,
+ .remove_new = stm32_adfsdm_remove,
};
module_platform_driver(stm32_adfsdm_driver);
"Could not get x11k parent clock\n");
/* Register mclk provider if requested */
- if (of_find_property(np, "#clock-cells", NULL)) {
+ if (of_property_present(np, "#clock-cells")) {
ret = stm32_i2s_add_mclk_provider(i2s);
if (ret < 0)
return ret;
return 0;
}
-static int stm32_i2s_remove(struct platform_device *pdev)
+static void stm32_i2s_remove(struct platform_device *pdev)
{
snd_dmaengine_pcm_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static int stm32_i2s_probe(struct platform_device *pdev)
.pm = &stm32_i2s_pm_ops,
},
.probe = stm32_i2s_probe,
- .remove = stm32_i2s_remove,
+ .remove_new = stm32_i2s_remove,
};
module_platform_driver(stm32_i2s_driver);
/* Get spdif iec60958 property */
sai->spdif = false;
- if (of_get_property(np, "st,iec60958", NULL)) {
+ if (of_property_present(np, "st,iec60958")) {
if (!STM_SAI_HAS_SPDIF(sai) ||
sai->dir == SNDRV_PCM_STREAM_CAPTURE) {
dev_err(&pdev->dev, "S/PDIF IEC60958 not supported\n");
return 0;
/* Register mclk provider if requested */
- if (of_find_property(np, "#clock-cells", NULL)) {
+ if (of_property_present(np, "#clock-cells")) {
ret = stm32_sai_add_mclk_provider(sai);
if (ret < 0)
return ret;
return 0;
}
-static int stm32_sai_sub_remove(struct platform_device *pdev)
+static void stm32_sai_sub_remove(struct platform_device *pdev)
{
struct stm32_sai_sub_data *sai = dev_get_drvdata(&pdev->dev);
snd_dmaengine_pcm_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
.pm = &stm32_sai_sub_pm_ops,
},
.probe = stm32_sai_sub_probe,
- .remove = stm32_sai_sub_remove,
+ .remove_new = stm32_sai_sub_remove,
};
module_platform_driver(stm32_sai_sub_driver);
return 0;
}
-static int stm32_spdifrx_remove(struct platform_device *pdev)
+static void stm32_spdifrx_remove(struct platform_device *pdev)
{
struct stm32_spdifrx_data *spdifrx = platform_get_drvdata(pdev);
snd_dmaengine_pcm_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static int stm32_spdifrx_probe(struct platform_device *pdev)
.pm = &stm32_spdifrx_pm_ops,
},
.probe = stm32_spdifrx_probe,
- .remove = stm32_spdifrx_remove,
+ .remove_new = stm32_spdifrx_remove,
};
module_platform_driver(stm32_spdifrx_driver);
return ret;
}
-static int sun4i_codec_remove(struct platform_device *pdev)
+static void sun4i_codec_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card);
if (scodec->rst)
reset_control_assert(scodec->rst);
clk_disable_unprepare(scodec->clk_apb);
-
- return 0;
}
static struct platform_driver sun4i_codec_driver = {
.of_match_table = sun4i_codec_of_match,
},
.probe = sun4i_codec_probe,
- .remove = sun4i_codec_remove,
+ .remove_new = sun4i_codec_remove,
};
module_platform_driver(sun4i_codec_driver);
return ret;
}
-static int sun4i_i2s_remove(struct platform_device *pdev)
+static void sun4i_i2s_remove(struct platform_device *pdev)
{
struct sun4i_i2s *i2s = dev_get_drvdata(&pdev->dev);
if (!IS_ERR(i2s->rst))
reset_control_assert(i2s->rst);
-
- return 0;
}
static const struct of_device_id sun4i_i2s_match[] = {
static struct platform_driver sun4i_i2s_driver = {
.probe = sun4i_i2s_probe,
- .remove = sun4i_i2s_remove,
+ .remove_new = sun4i_i2s_remove,
.driver = {
.name = "sun4i-i2s",
.of_match_table = sun4i_i2s_match,
return ret;
}
-static int sun4i_spdif_remove(struct platform_device *pdev)
+static void sun4i_spdif_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
sun4i_spdif_runtime_suspend(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops sun4i_spdif_pm = {
.pm = &sun4i_spdif_pm,
},
.probe = sun4i_spdif_probe,
- .remove = sun4i_spdif_remove,
+ .remove_new = sun4i_spdif_remove,
};
module_platform_driver(sun4i_spdif_driver);
return ret;
}
-static int sun50i_dmic_remove(struct platform_device *pdev)
+static void sun50i_dmic_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
sun50i_dmic_runtime_suspend(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops sun50i_dmic_pm = {
.pm = &sun50i_dmic_pm,
},
.probe = sun50i_dmic_probe,
- .remove = sun50i_dmic_remove,
+ .remove_new = sun50i_dmic_remove,
};
module_platform_driver(sun50i_dmic_driver);
return ret;
}
-static int sun8i_codec_remove(struct platform_device *pdev)
+static void sun8i_codec_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
sun8i_codec_runtime_suspend(&pdev->dev);
-
- return 0;
}
static const struct sun8i_codec_quirks sun8i_a33_quirks = {
.pm = &sun8i_codec_pm_ops,
},
.probe = sun8i_codec_probe,
- .remove = sun8i_codec_remove,
+ .remove_new = sun8i_codec_remove,
};
module_platform_driver(sun8i_codec_driver);
config SND_SOC_TEGRA_MACHINE_DRV
tristate
+config SND_SOC_TEGRA_RT5631
+ tristate "SoC Audio support for Tegra boards using an RT5631 codec"
+ depends on SND_SOC_TEGRA && I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_RT5631
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the RT5631 codec, such as Transformer.
+
config SND_SOC_TEGRA_RT5640
tristate "SoC Audio support for Tegra boards using an RT5640 codec"
depends on I2C && GPIOLIB
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the MAX98090 codec, such as Venice2.
+config SND_SOC_TEGRA_MAX98088
+ tristate "SoC Audio support for Tegra boards using a MAX9808x codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_MAX98088
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the MAX98088 codec, such as LG X3.
+
config SND_SOC_TEGRA_RT5677
tristate "SoC Audio support for Tegra boards using a RT5677 codec"
depends on I2C && GPIOLIB
return 0;
}
-static int tegra186_asrc_platform_remove(struct platform_device *pdev)
+static void tegra186_asrc_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra186_asrc_pm_ops = {
.pm = &tegra186_asrc_pm_ops,
},
.probe = tegra186_asrc_platform_probe,
- .remove = tegra186_asrc_platform_remove,
+ .remove_new = tegra186_asrc_platform_remove,
};
module_platform_driver(tegra186_asrc_driver)
return 0;
}
-static int tegra186_dspk_platform_remove(struct platform_device *pdev)
+static void tegra186_dspk_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra186_dspk_pm_ops = {
.pm = &tegra186_dspk_pm_ops,
},
.probe = tegra186_dspk_platform_probe,
- .remove = tegra186_dspk_platform_remove,
+ .remove_new = tegra186_dspk_platform_remove,
};
module_platform_driver(tegra186_dspk_driver);
ac97->reset = devm_reset_control_get_exclusive(&pdev->dev, "ac97");
if (IS_ERR(ac97->reset)) {
dev_err(&pdev->dev, "Can't retrieve ac97 reset\n");
- return PTR_ERR(ac97->reset);
+ ret = PTR_ERR(ac97->reset);
+ goto err;
}
ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL);
return ret;
}
-static int tegra20_ac97_platform_remove(struct platform_device *pdev)
+static void tegra20_ac97_platform_remove(struct platform_device *pdev)
{
struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(ac97->clk_ac97);
snd_soc_set_ac97_ops(NULL);
-
- return 0;
}
static const struct of_device_id tegra20_ac97_of_match[] = {
.of_match_table = tegra20_ac97_of_match,
},
.probe = tegra20_ac97_platform_probe,
- .remove = tegra20_ac97_platform_remove,
+ .remove_new = tegra20_ac97_platform_remove,
};
module_platform_driver(tegra20_ac97_driver);
return ret;
}
-static int tegra20_i2s_platform_remove(struct platform_device *pdev)
+static void tegra20_i2s_platform_remove(struct platform_device *pdev)
{
tegra_pcm_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct of_device_id tegra20_i2s_of_match[] = {
.pm = &tegra20_i2s_pm_ops,
},
.probe = tegra20_i2s_platform_probe,
- .remove = tegra20_i2s_platform_remove,
+ .remove_new = tegra20_i2s_platform_remove,
};
module_platform_driver(tegra20_i2s_driver);
return 0;
}
-static int tegra_admaif_remove(struct platform_device *pdev)
+static void tegra_admaif_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra_admaif_pm_ops = {
static struct platform_driver tegra_admaif_driver = {
.probe = tegra_admaif_probe,
- .remove = tegra_admaif_remove,
+ .remove_new = tegra_admaif_remove,
.driver = {
.name = "tegra210-admaif",
.of_match_table = tegra_admaif_of_match,
return 0;
}
-static int tegra210_adx_platform_remove(struct platform_device *pdev)
+static void tegra210_adx_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra210_adx_pm_ops = {
.pm = &tegra210_adx_pm_ops,
},
.probe = tegra210_adx_platform_probe,
- .remove = tegra210_adx_platform_remove,
+ .remove_new = tegra210_adx_platform_remove,
};
module_platform_driver(tegra210_adx_driver);
return 0;
}
-static int tegra_ahub_remove(struct platform_device *pdev)
+static void tegra_ahub_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra_ahub_pm_ops = {
static struct platform_driver tegra_ahub_driver = {
.probe = tegra_ahub_probe,
- .remove = tegra_ahub_remove,
+ .remove_new = tegra_ahub_remove,
.driver = {
.name = "tegra210-ahub",
.of_match_table = tegra_ahub_of_match,
return 0;
}
-static int tegra210_amx_platform_remove(struct platform_device *pdev)
+static void tegra210_amx_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra210_amx_pm_ops = {
.pm = &tegra210_amx_pm_ops,
},
.probe = tegra210_amx_platform_probe,
- .remove = tegra210_amx_platform_remove,
+ .remove_new = tegra210_amx_platform_remove,
};
module_platform_driver(tegra210_amx_driver);
return 0;
}
-static int tegra210_dmic_remove(struct platform_device *pdev)
+static void tegra210_dmic_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra210_dmic_pm_ops = {
.pm = &tegra210_dmic_pm_ops,
},
.probe = tegra210_dmic_probe,
- .remove = tegra210_dmic_remove,
+ .remove_new = tegra210_dmic_remove,
};
module_platform_driver(tegra210_dmic_driver)
return 0;
}
-static int tegra210_i2s_remove(struct platform_device *pdev)
+static void tegra210_i2s_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra210_i2s_pm_ops = {
.pm = &tegra210_i2s_pm_ops,
},
.probe = tegra210_i2s_probe,
- .remove = tegra210_i2s_remove,
+ .remove_new = tegra210_i2s_remove,
};
module_platform_driver(tegra210_i2s_driver)
return 0;
}
-static int tegra210_mixer_platform_remove(struct platform_device *pdev)
+static void tegra210_mixer_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra210_mixer_pm_ops = {
.pm = &tegra210_mixer_pm_ops,
},
.probe = tegra210_mixer_platform_probe,
- .remove = tegra210_mixer_platform_remove,
+ .remove_new = tegra210_mixer_platform_remove,
};
module_platform_driver(tegra210_mixer_driver);
return 0;
}
-static int tegra210_mvc_platform_remove(struct platform_device *pdev)
+static void tegra210_mvc_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra210_mvc_pm_ops = {
.pm = &tegra210_mvc_pm_ops,
},
.probe = tegra210_mvc_platform_probe,
- .remove = tegra210_mvc_platform_remove,
+ .remove_new = tegra210_mvc_platform_remove,
};
module_platform_driver(tegra210_mvc_driver)
return 0;
}
-static int tegra210_ope_remove(struct platform_device *pdev)
+static void tegra210_ope_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static int __maybe_unused tegra210_ope_runtime_suspend(struct device *dev)
.pm = &tegra210_ope_pm_ops,
},
.probe = tegra210_ope_probe,
- .remove = tegra210_ope_remove,
+ .remove_new = tegra210_ope_remove,
};
module_platform_driver(tegra210_ope_driver)
return 0;
}
-static int tegra210_sfc_platform_remove(struct platform_device *pdev)
+static void tegra210_sfc_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra210_sfc_pm_ops = {
.pm = &tegra210_sfc_pm_ops,
},
.probe = tegra210_sfc_platform_probe,
- .remove = tegra210_sfc_platform_remove,
+ .remove_new = tegra210_sfc_platform_remove,
};
module_platform_driver(tegra210_sfc_driver)
return ret;
}
-static int tegra30_ahub_remove(struct platform_device *pdev)
+static void tegra30_ahub_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
ahub = NULL;
-
- return 0;
}
static const struct dev_pm_ops tegra30_ahub_pm_ops = {
static struct platform_driver tegra30_ahub_driver = {
.probe = tegra30_ahub_probe,
- .remove = tegra30_ahub_remove,
+ .remove_new = tegra30_ahub_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = tegra30_ahub_of_match,
return ret;
}
-static int tegra30_i2s_platform_remove(struct platform_device *pdev)
+static void tegra30_i2s_platform_remove(struct platform_device *pdev)
{
struct tegra30_i2s *i2s = dev_get_drvdata(&pdev->dev);
tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif);
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
static const struct dev_pm_ops tegra30_i2s_pm_ops = {
.pm = &tegra30_i2s_pm_ops,
},
.probe = tegra30_i2s_platform_probe,
- .remove = tegra30_i2s_platform_remove,
+ .remove_new = tegra30_i2s_platform_remove,
};
module_platform_driver(tegra30_i2s_driver);
};
/* Mic Jack */
+static int coupled_mic_hp_check(void *data)
+{
+ struct tegra_machine *machine = (struct tegra_machine *)data;
+
+ /* Detect mic insertion only if 3.5 jack is in */
+ if (gpiod_get_value_cansleep(machine->gpiod_hp_det) &&
+ gpiod_get_value_cansleep(machine->gpiod_mic_det))
+ return SND_JACK_MICROPHONE;
+
+ return 0;
+}
static struct snd_soc_jack tegra_machine_mic_jack;
gpiod_set_value_cansleep(machine->gpiod_spkr_en,
SND_SOC_DAPM_EVENT_ON(event));
- if (!strcmp(w->name, "Mic Jack"))
+ if (!strcmp(w->name, "Mic Jack") || !strcmp(w->name, "Headset Mic"))
gpiod_set_value_cansleep(machine->gpiod_ext_mic_en,
SND_SOC_DAPM_EVENT_ON(event));
- if (!strcmp(w->name, "Int Mic"))
+ if (!strcmp(w->name, "Int Mic") || !strcmp(w->name, "Internal Mic 2"))
gpiod_set_value_cansleep(machine->gpiod_int_mic_en,
SND_SOC_DAPM_EVENT_ON(event));
SND_SOC_DAPM_HP("Headphones", NULL),
SND_SOC_DAPM_SPK("Speakers", tegra_machine_event),
SND_SOC_DAPM_SPK("Int Spk", tegra_machine_event),
+ SND_SOC_DAPM_SPK("Earpiece", NULL),
SND_SOC_DAPM_MIC("Int Mic", tegra_machine_event),
SND_SOC_DAPM_MIC("Mic Jack", tegra_machine_event),
SND_SOC_DAPM_MIC("Internal Mic 1", NULL),
- SND_SOC_DAPM_MIC("Internal Mic 2", NULL),
- SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic 2", tegra_machine_event),
+ SND_SOC_DAPM_MIC("Headset Mic", tegra_machine_event),
SND_SOC_DAPM_MIC("Digital Mic", NULL),
SND_SOC_DAPM_MIC("Mic", NULL),
SND_SOC_DAPM_LINE("Line In Jack", NULL),
static const struct snd_kcontrol_new tegra_machine_controls[] = {
SOC_DAPM_PIN_SWITCH("Speakers"),
SOC_DAPM_PIN_SWITCH("Int Spk"),
+ SOC_DAPM_PIN_SWITCH("Earpiece"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Internal Mic 1"),
return err;
}
+ tegra_machine_mic_jack_gpio.data = machine;
tegra_machine_mic_jack_gpio.desc = machine->gpiod_mic_det;
+ if (of_property_read_bool(card->dev->of_node,
+ "nvidia,coupled-mic-hp-det")) {
+ tegra_machine_mic_jack_gpio.desc = machine->gpiod_hp_det;
+ tegra_machine_mic_jack_gpio.jack_status_check = coupled_mic_hp_check;
+ }
+
err = snd_soc_jack_add_gpios(&tegra_machine_mic_jack, 1,
&tegra_machine_mic_jack_gpio);
if (err)
return mclk;
}
+static unsigned int tegra_machine_mclk_rate_6mhz(unsigned int srate)
+{
+ unsigned int mclk;
+
+ switch (srate) {
+ case 8000:
+ case 16000:
+ case 64000:
+ mclk = 8192000;
+ break;
+ case 11025:
+ case 22050:
+ case 88200:
+ mclk = 11289600;
+ break;
+ case 96000:
+ mclk = 12288000;
+ break;
+ default:
+ mclk = 256 * srate;
+ break;
+ }
+
+ return mclk;
+}
+
static int tegra_machine_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
* If clock parents are not set in DT, configure here to use clk_out_1
* as mclk and extern1 as parent for Tegra30 and higher.
*/
- if (!of_find_property(dev->of_node, "assigned-clock-parents", NULL) &&
+ if (!of_property_present(dev->of_node, "assigned-clock-parents") &&
!of_machine_is_compatible("nvidia,tegra20")) {
struct clk *clk_out_1, *clk_extern1;
.add_hp_jack = true,
};
+/* MAX98088 machine */
+
+SND_SOC_DAILINK_DEFS(max98088_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_max98088_dai = {
+ .name = "MAX98088",
+ .stream_name = "MAX98088 PCM",
+ .init = tegra_asoc_machine_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(max98088_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_max98088 = {
+ .components = "codec:max98088",
+ .dai_link = &tegra_max98088_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_max98088_data = {
+ .mclk_rate = tegra_machine_mclk_rate_12mhz,
+ .card = &snd_soc_tegra_max98088,
+ .add_common_dapm_widgets = true,
+ .add_common_controls = true,
+ .add_common_snd_ops = true,
+ .add_mic_jack = true,
+ .add_hp_jack = true,
+};
+
/* SGTL5000 machine */
SND_SOC_DAILINK_DEFS(sgtl5000_hifi,
.add_headset_jack = true,
};
+/* RT5631 machine */
+
+SND_SOC_DAILINK_DEFS(rt5631_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5631-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_rt5631_dai = {
+ .name = "RT5631",
+ .stream_name = "RT5631 PCM",
+ .init = tegra_asoc_machine_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(rt5631_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_rt5631 = {
+ .components = "codec:rt5631",
+ .dai_link = &tegra_rt5631_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_rt5631_data = {
+ .mclk_rate = tegra_machine_mclk_rate_6mhz,
+ .card = &snd_soc_tegra_rt5631,
+ .add_common_dapm_widgets = true,
+ .add_common_controls = true,
+ .add_common_snd_ops = true,
+ .add_mic_jack = true,
+ .add_hp_jack = true,
+};
+
static const struct of_device_id tegra_machine_of_match[] = {
{ .compatible = "nvidia,tegra-audio-trimslice", .data = &tegra_trimslice_data },
{ .compatible = "nvidia,tegra-audio-max98090", .data = &tegra_max98090_data },
+ { .compatible = "nvidia,tegra-audio-max98088", .data = &tegra_max98088_data },
+ { .compatible = "nvidia,tegra-audio-max98089", .data = &tegra_max98088_data },
{ .compatible = "nvidia,tegra-audio-sgtl5000", .data = &tegra_sgtl5000_data },
{ .compatible = "nvidia,tegra-audio-wm9712", .data = &tegra_wm9712_data },
{ .compatible = "nvidia,tegra-audio-wm8753", .data = &tegra_wm8753_data },
{ .compatible = "nvidia,tegra-audio-rt5677", .data = &tegra_rt5677_data },
{ .compatible = "nvidia,tegra-audio-rt5640", .data = &tegra_rt5640_data },
{ .compatible = "nvidia,tegra-audio-alc5632", .data = &tegra_rt5632_data },
+ { .compatible = "nvidia,tegra-audio-rt5631", .data = &tegra_rt5631_data },
{},
};
MODULE_DEVICE_TABLE(of, tegra_machine_of_match);
return 0;
}
-static int ams_delta_remove(struct platform_device *pdev)
+static void ams_delta_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
card->dev = NULL;
- return 0;
}
#define DRV_NAME "ams-delta-audio"
.name = DRV_NAME,
},
.probe = ams_delta_probe,
- .remove = ams_delta_remove,
+ .remove_new = ams_delta_remove,
};
module_platform_driver(ams_delta_driver);
return ret;
}
-static int davinci_i2s_remove(struct platform_device *pdev)
+static void davinci_i2s_remove(struct platform_device *pdev)
{
struct davinci_mcbsp_dev *dev = dev_get_drvdata(&pdev->dev);
clk_disable(dev->clk);
clk_put(dev->clk);
dev->clk = NULL;
-
- return 0;
}
static const struct of_device_id davinci_i2s_match[] __maybe_unused = {
static struct platform_driver davinci_mcbsp_driver = {
.probe = davinci_i2s_probe,
- .remove = davinci_i2s_remove,
+ .remove_new = davinci_i2s_remove,
.driver = {
.name = "davinci-mcbsp",
.of_match_table = of_match_ptr(davinci_i2s_match),
return ret;
}
-static int davinci_mcasp_remove(struct platform_device *pdev)
+static void davinci_mcasp_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
-
- return 0;
}
#ifdef CONFIG_PM
static struct platform_driver davinci_mcasp_driver = {
.probe = davinci_mcasp_probe,
- .remove = davinci_mcasp_remove,
+ .remove_new = davinci_mcasp_remove,
.driver = {
.name = "davinci-mcasp",
.pm = &davinci_mcasp_pm_ops,
if (!card->dai_link)
return -ENOMEM;
- compnent = devm_kzalloc(dev, 3 * sizeof(*compnent), GFP_KERNEL);
+ compnent = devm_kzalloc(dev, 2 * sizeof(*compnent), GFP_KERNEL);
if (!compnent)
return -ENOMEM;
card->dai_link->cpus = &compnent[0];
card->dai_link->num_cpus = 1;
card->dai_link->codecs = &compnent[1];
card->dai_link->num_codecs = 1;
- card->dai_link->platforms = &compnent[2];
- card->dai_link->num_platforms = 1;
card->dai_link->name = card->name;
card->dai_link->stream_name = card->name;
card->dai_link->cpus->dai_name = dev_name(ad->dssdev);
- card->dai_link->platforms->name = dev_name(ad->dssdev);
card->dai_link->codecs->name = "snd-soc-dummy";
card->dai_link->codecs->dai_name = "snd-soc-dummy-dai";
card->num_links = 1;
return 0;
}
-static int omap_hdmi_audio_remove(struct platform_device *pdev)
+static void omap_hdmi_audio_remove(struct platform_device *pdev)
{
struct hdmi_audio_data *ad = platform_get_drvdata(pdev);
snd_soc_unregister_card(ad->card);
- return 0;
}
static struct platform_driver hdmi_audio_driver = {
.name = DRV_NAME,
},
.probe = omap_hdmi_audio_probe,
- .remove = omap_hdmi_audio_remove,
+ .remove_new = omap_hdmi_audio_remove,
};
module_platform_driver(hdmi_audio_driver);
return sdma_pcm_platform_register(&pdev->dev, "tx", "rx");
}
-static int asoc_mcbsp_remove(struct platform_device *pdev)
+static void asoc_mcbsp_remove(struct platform_device *pdev)
{
struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev);
if (cpu_latency_qos_request_active(&mcbsp->pm_qos_req))
cpu_latency_qos_remove_request(&mcbsp->pm_qos_req);
-
- return 0;
}
static struct platform_driver asoc_mcbsp_driver = {
},
.probe = asoc_mcbsp_probe,
- .remove = asoc_mcbsp_remove,
+ .remove_new = asoc_mcbsp_remove,
};
module_platform_driver(asoc_mcbsp_driver);
return ret;
}
-static int evea_remove(struct platform_device *pdev)
+static void evea_remove(struct platform_device *pdev)
{
struct evea_priv *evea = platform_get_drvdata(pdev);
clk_disable_unprepare(evea->clk_exiv);
clk_disable_unprepare(evea->clk);
-
- return 0;
}
static const struct of_device_id evea_of_match[] __maybe_unused = {
.of_match_table = of_match_ptr(evea_of_match),
},
.probe = evea_probe,
- .remove = evea_remove,
+ .remove_new = evea_remove,
};
module_platform_driver(evea_codec_driver);
return ret;
}
-static int mop500_remove(struct platform_device *pdev)
+static void mop500_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
mop500_ab8500_remove(card);
mop500_of_node_put();
-
- return 0;
}
static const struct of_device_id snd_soc_mop500_match[] = {
.of_match_table = snd_soc_mop500_match,
},
.probe = mop500_probe,
- .remove = mop500_remove,
+ .remove_new = mop500_remove,
};
module_platform_driver(snd_soc_mop500_driver);
return ret;
}
-static int ux500_msp_drv_remove(struct platform_device *pdev)
+static void ux500_msp_drv_remove(struct platform_device *pdev)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s");
ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp);
-
- return 0;
}
static const struct of_device_id ux500_msp_i2s_match[] = {
.of_match_table = ux500_msp_i2s_match,
},
.probe = ux500_msp_drv_probe,
- .remove = ux500_msp_drv_remove,
+ .remove_new = ux500_msp_drv_remove,
};
module_platform_driver(msp_i2s_driver);
return ret;
}
-static int xlnx_formatter_pcm_remove(struct platform_device *pdev)
+static void xlnx_formatter_pcm_remove(struct platform_device *pdev)
{
int ret = 0;
struct xlnx_pcm_drv_data *adata = dev_get_drvdata(&pdev->dev);
dev_err(&pdev->dev, "audio formatter reset failed\n");
clk_disable_unprepare(adata->axi_clk);
- return 0;
}
static const struct of_device_id xlnx_formatter_pcm_of_match[] = {
static struct platform_driver xlnx_formatter_pcm_driver = {
.probe = xlnx_formatter_pcm_probe,
- .remove = xlnx_formatter_pcm_remove,
+ .remove_new = xlnx_formatter_pcm_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = xlnx_formatter_pcm_of_match,
return ret;
}
-static int xlnx_spdif_remove(struct platform_device *pdev)
+static void xlnx_spdif_remove(struct platform_device *pdev)
{
struct spdif_dev_data *ctx = dev_get_drvdata(&pdev->dev);
clk_disable_unprepare(ctx->axi_clk);
- return 0;
}
static struct platform_driver xlnx_spdif_driver = {
.of_match_table = xlnx_spdif_of_match,
},
.probe = xlnx_spdif_probe,
- .remove = xlnx_spdif_remove,
+ .remove_new = xlnx_spdif_remove,
};
module_platform_driver(xlnx_spdif_driver);
return err;
}
-static int xtfpga_i2s_remove(struct platform_device *pdev)
+static void xtfpga_i2s_remove(struct platform_device *pdev)
{
struct xtfpga_i2s *i2s = dev_get_drvdata(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
xtfpga_i2s_runtime_suspend(&pdev->dev);
- return 0;
}
#ifdef CONFIG_OF
static struct platform_driver xtfpga_i2s_driver = {
.probe = xtfpga_i2s_probe,
- .remove = xtfpga_i2s_remove,
+ .remove_new = xtfpga_i2s_remove,
.driver = {
.name = "xtfpga-i2s",
.of_match_table = of_match_ptr(xtfpga_i2s_of_match),
return -ENODEV;
}
-static int cs4231_remove(struct platform_device *op)
+static void cs4231_remove(struct platform_device *op)
{
struct snd_cs4231 *chip = dev_get_drvdata(&op->dev);
snd_card_free(chip->card);
-
- return 0;
}
static const struct of_device_id cs4231_match[] = {
.of_match_table = cs4231_match,
},
.probe = cs4231_probe,
- .remove = cs4231_remove,
+ .remove_new = cs4231_remove,
};
module_platform_driver(cs4231_driver);
return err;
}
-static int dbri_remove(struct platform_device *op)
+static void dbri_remove(struct platform_device *op)
{
struct snd_card *card = dev_get_drvdata(&op->dev);
snd_dbri_free(card->private_data);
snd_card_free(card);
-
- return 0;
}
static const struct of_device_id dbri_match[] = {
.of_match_table = dbri_match,
},
.probe = dbri_probe,
- .remove = dbri_remove,
+ .remove_new = dbri_remove,
};
module_platform_driver(dbri_sbus_driver);
case USB_SPEED_LOW:
case USB_SPEED_FULL:
case USB_SPEED_HIGH:
- case USB_SPEED_WIRELESS:
case USB_SPEED_SUPER:
case USB_SPEED_SUPER_PLUS:
break;
ep->iface, altset, ep->ep_num);
err = usb_set_interface(chip->dev, ep->iface, altset);
if (err < 0) {
- usb_audio_err(chip, "%d:%d: usb_set_interface failed (%d)\n",
- ep->iface, altset, err);
+ usb_audio_err_ratelimited(
+ chip, "%d:%d: usb_set_interface failed (%d)\n",
+ ep->iface, altset, err);
return err;
}
*/
if (usb_pipein(ep->pipe) || ep->implicit_fb_sync) {
- urb_packs = packs_per_ms;
- /*
- * Wireless devices can poll at a max rate of once per 4ms.
- * For dataintervals less than 5, increase the packet count to
- * allow the host controller to use bursting to fill in the
- * gaps.
- */
- if (snd_usb_get_speed(chip->dev) == USB_SPEED_WIRELESS) {
- int interval = ep->datainterval;
- while (interval < 5) {
- urb_packs <<= 1;
- ++interval;
- }
- }
/* make capture URBs <= 1 ms and smaller than a period */
- urb_packs = min(max_packs_per_urb, urb_packs);
+ urb_packs = min(max_packs_per_urb, packs_per_ms);
while (urb_packs > 1 && urb_packs * maxsize >= ep->cur_period_bytes)
urb_packs >>= 1;
ep->nurbs = MAX_URBS;
{
switch (snd_usb_get_speed(chip->dev)) {
case USB_SPEED_HIGH:
- case USB_SPEED_WIRELESS:
case USB_SPEED_SUPER:
case USB_SPEED_SUPER_PLUS:
if (get_endpoint(alts, 0)->bInterval >= 1 &&
#define usb_audio_err(chip, fmt, args...) \
dev_err(&(chip)->dev->dev, fmt, ##args)
+#define usb_audio_err_ratelimited(chip, fmt, args...) \
+ dev_err_ratelimited(&(chip)->dev->dev, fmt, ##args)
#define usb_audio_warn(chip, fmt, args...) \
dev_warn(&(chip)->dev->dev, fmt, ##args)
#define usb_audio_info(chip, fmt, args...) \
struct card_data *card_data;
struct ctl_data *ctl_data;
snd_config_t *config;
+ char *card_name, *card_longname;
card = -1;
if (snd_card_next(&card) < 0 || card < 0)
goto next_card;
}
+ err = snd_card_get_name(card, &card_name);
+ if (err != 0)
+ card_name = "Unknown";
+ err = snd_card_get_longname(card, &card_longname);
+ if (err != 0)
+ card_longname = "Unknown";
+ ksft_print_msg("Card %d - %s (%s)\n", card,
+ card_name, card_longname);
+
/* Count controls */
snd_ctl_elem_list_malloc(&card_data->ctls);
snd_ctl_elem_list(card_data->handle, card_data->ctls);
bool name_ok = true;
bool check;
+ ksft_print_msg("%d.%d %s\n", ctl->card->card, ctl->elem,
+ ctl->name);
+
/* Only boolean controls should end in Switch */
if (strend(ctl->name, " Switch")) {
if (snd_ctl_elem_info_get_type(ctl->info) != SND_CTL_ELEM_TYPE_BOOLEAN) {
ctl->card->card, ctl->elem);
}
+static void show_values(struct ctl_data *ctl, snd_ctl_elem_value_t *orig_val,
+ snd_ctl_elem_value_t *read_val)
+{
+ long long orig_int, read_int;
+ int i;
+
+ for (i = 0; i < snd_ctl_elem_info_get_count(ctl->info); i++) {
+ switch (snd_ctl_elem_info_get_type(ctl->info)) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ orig_int = snd_ctl_elem_value_get_boolean(orig_val, i);
+ read_int = snd_ctl_elem_value_get_boolean(read_val, i);
+ break;
+
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ orig_int = snd_ctl_elem_value_get_integer(orig_val, i);
+ read_int = snd_ctl_elem_value_get_integer(read_val, i);
+ break;
+
+ case SND_CTL_ELEM_TYPE_INTEGER64:
+ orig_int = snd_ctl_elem_value_get_integer64(orig_val,
+ i);
+ read_int = snd_ctl_elem_value_get_integer64(read_val,
+ i);
+ break;
+
+ case SND_CTL_ELEM_TYPE_ENUMERATED:
+ orig_int = snd_ctl_elem_value_get_enumerated(orig_val,
+ i);
+ read_int = snd_ctl_elem_value_get_enumerated(read_val,
+ i);
+ break;
+
+ default:
+ return;
+ }
+
+ ksft_print_msg("%s.%d orig %lld read %lld, is_volatile %d\n",
+ ctl->name, i, orig_int, read_int,
+ snd_ctl_elem_info_is_volatile(ctl->info));
+ }
+}
+
static bool show_mismatch(struct ctl_data *ctl, int index,
snd_ctl_elem_value_t *read_val,
snd_ctl_elem_value_t *expected_val)
if (err < 1) {
ksft_print_msg("No event generated for %s\n",
ctl->name);
+ show_values(ctl, initial_val, read_val);
ctl->event_missing++;
}
} else {
if (err != 0) {
ksft_print_msg("Spurious event generated for %s\n",
ctl->name);
+ show_values(ctl, initial_val, read_val);
ctl->event_spurious++;
}
}
static void test_ctl_write_valid(struct ctl_data *ctl)
{
bool pass;
- int err;
/* If the control is turned off let's be polite */
if (snd_ctl_elem_info_is_inactive(ctl->info)) {
}
/* Restore the default value to minimise disruption */
- err = write_and_verify(ctl, ctl->def_val, NULL);
- if (err < 0)
- pass = false;
+ write_and_verify(ctl, ctl->def_val, NULL);
ksft_test_result(pass, "write_valid.%d.%d\n",
ctl->card->card, ctl->elem);
}
/* Restore the default value to minimise disruption */
- err = write_and_verify(ctl, ctl->def_val, NULL);
- if (err < 0)
- pass = false;
+ write_and_verify(ctl, ctl->def_val, NULL);
ksft_test_result(pass, "write_invalid.%d.%d\n",
ctl->card->card, ctl->elem);
static void find_pcms(void)
{
char name[32], key[64];
+ char *card_name, *card_longname;
int card, dev, subdev, count, direction, err;
snd_pcm_stream_t stream;
struct pcm_data *pcm_data;
goto next_card;
}
+ err = snd_card_get_name(card, &card_name);
+ if (err != 0)
+ card_name = "Unknown";
+ err = snd_card_get_longname(card, &card_longname);
+ if (err != 0)
+ card_longname = "Unknown";
+ ksft_print_msg("Card %d - %s (%s)\n", card,
+ card_name, card_longname);
+
card_config = conf_by_card(card);
card_data = calloc(1, sizeof(*card_data));
}
if (!skip)
- ksft_test_result(pass, "%s.%s.%d.%d.%d.%s%s%s\n",
+ ksft_test_result(pass, "%s.%s.%d.%d.%d.%s\n",
test_class_name, test_name,
data->card, data->device, data->subdevice,
- snd_pcm_stream_name(data->stream),
- msg[0] ? " " : "", msg);
+ snd_pcm_stream_name(data->stream));
else
- ksft_test_result_skip("%s.%s.%d.%d.%d.%s%s%s\n",
+ ksft_test_result_skip("%s.%s.%d.%d.%d.%s\n",
test_class_name, test_name,
data->card, data->device, data->subdevice,
- snd_pcm_stream_name(data->stream),
- msg[0] ? " " : "", msg);
+ snd_pcm_stream_name(data->stream));
+
+ if (msg[0])
+ ksft_print_msg("%s\n", msg);
pthread_mutex_unlock(&results_lock);