mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
soundwire updates for 6.2
- intel: reorganization of hw_ops callbacks, splitting files etc - qcom: support for v1.7.0 qcom controllers -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEE+vs47OPLdNbVcHzyfBQHDyUjg0cFAmOfHaQACgkQfBQHDyUj g0c0Kw/+LsYw8JeLadF6iVyXZtKdVYqXr2T8tmab49H+fELoJE1he6Cch17lYXYb bExOye1qNJdeVdvnBtqkGliMXXze55n0P3Lrx7ozNKgWvdk991yAU6jgnSnu0OWz Vv9lKKngP1PtjskzAZZALvJKLT6PdnnRDw3Y4AXiS84fdmxjndInFp/dGFvppBDr z+7mvziPag6UlEFEuYJS2YuF9GwtR4uxeO/wLFRlvXIZ3cfkhToLudX/miZMjpTi F1Wi0P8b70FU8DKIL0ZR0iegcXP/oTnb2C4q0fOv9ia8sezkhsRmWjq/1BALmdcy Xmlz7D8e+h8tv+eED6LzQtqt4Izqcr5ohiDfA8a0/YUU3ZiGhhqVel9G2z9+2xKU lmLxipUv7t/X2MN/DEYf8mhGNtBPHZALnFCOyKO2zIQnA0xI0dMJTR5ujd1zBnhV EvZWfsZozTSbQBknv/Gmy0VyeKNhv1n2g5uoImwrBOBZY+Kkaw/42HJvENfuqDaa oBcdRPh/am7RFDCXWme5aw0Dbi7xOD5WmAbJbntW+T8C5ed0Bcnb2fDTDv1bXIZR 35SRhwYS9M+BryZ7Pa+yC6hrbi+qJNHp7bqsiX/iuNT0zeCS8Lyhs/3LHCdFUAbe J4zNBrCUieplsY6f6bpbU5i2xtYlMsEtb55SH43BHYv5Ki/+GXM= =3uDi -----END PGP SIGNATURE----- Merge tag 'soundwire-6.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire Pull soundwire updates from Vinod Koul: "This include bunch of Intel driver code reorganization and support for qcom v1.7.0 controller: - intel: reorganization of hw_ops callbacks, splitting files etc - qcom: support for v1.7.0 qcom controllers" * tag 'soundwire-6.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: soundwire: intel: split auxdevice to different file soundwire: intel: add in-band wake callbacks in hw_ops soundwire: intel: add link power management callbacks in hw_ops soundwire: intel: add bus management callbacks in hw_ops soundwire: intel: add register_dai callback in hw_ops soundwire: intel: add debugfs callbacks in hw_ops soundwire: intel: start using hw_ops dt-bindings: soundwire: Convert text bindings to DT Schema soundwire: cadence: use dai_runtime_array instead of dma_data soundwire: cadence: rename sdw_cdns_dai_dma_data as sdw_cdns_dai_runtime soundwire: qcom: add support for v1.7 Soundwire Controller dt-bindings: soundwire: qcom: add v1.7.0 support soundwire: qcom: make reset optional for v1.6 controller soundwire: qcom: remove unused SWRM_SPECIAL_CMD_ID soundwire: dmi-quirks: add quirk variant for LAPBC710 NUC15
This commit is contained in:
commit
1b6a349a40
@ -1,214 +0,0 @@
|
||||
Qualcomm SoundWire Controller Bindings
|
||||
|
||||
|
||||
This binding describes the Qualcomm SoundWire Controller along with its
|
||||
board specific bus parameters.
|
||||
|
||||
- compatible:
|
||||
Usage: required
|
||||
Value type: <stringlist>
|
||||
Definition: must be "qcom,soundwire-v<MAJOR>.<MINOR>.<STEP>",
|
||||
Example:
|
||||
"qcom,soundwire-v1.3.0"
|
||||
"qcom,soundwire-v1.5.0"
|
||||
"qcom,soundwire-v1.5.1"
|
||||
"qcom,soundwire-v1.6.0"
|
||||
- reg:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: the base address and size of SoundWire controller
|
||||
address space.
|
||||
|
||||
- interrupts:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should specify the SoundWire Controller core and optional
|
||||
wake IRQ
|
||||
|
||||
- interrupt-names:
|
||||
Usage: Optional
|
||||
Value type: boolean
|
||||
Value type: <stringlist>
|
||||
Definition: should be "core" for core and "wakeup" for wake interrupt.
|
||||
|
||||
- wakeup-source:
|
||||
Usage: Optional
|
||||
Value type: boolean
|
||||
Definition: should specify if SoundWire Controller is wake up capable.
|
||||
|
||||
- clock-names:
|
||||
Usage: required
|
||||
Value type: <stringlist>
|
||||
Definition: should be "iface" for SoundWire Controller interface clock
|
||||
|
||||
- clocks:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should specify the SoundWire Controller interface clock
|
||||
|
||||
- #sound-dai-cells:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: must be 1 for digital audio interfaces on the controller.
|
||||
|
||||
- qcom,dout-ports:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: must be count of data out ports
|
||||
|
||||
- qcom,din-ports:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: must be count of data in ports
|
||||
|
||||
- qcom,ports-offset1:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should specify payload transport window offset1 of each
|
||||
data port. Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-offset2:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should specify payload transport window offset2 of each
|
||||
data port. Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-sinterval-low:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be sample interval low of each data port.
|
||||
Out ports followed by In ports. Used for Sample Interval
|
||||
calculation.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-word-length:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be size of payload channel sample.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-block-pack-mode:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be 0 or 1 to indicate the block packing mode.
|
||||
0 to indicate Blocks are per Channel
|
||||
1 to indicate Blocks are per Port.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-block-group-count:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be in range 1 to 4 to indicate how many sample
|
||||
intervals are combined into a payload.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-lane-control:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be in range 0 to 7 to identify which data lane
|
||||
the data port uses.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-hstart:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be number identifying lowerst numbered coloum in
|
||||
SoundWire Frame, i.e. left edge of the Transport sub-frame
|
||||
for each port. Values between 0 and 15 are valid.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,ports-hstop:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be number identifying highest numbered coloum in
|
||||
SoundWire Frame, i.e. the right edge of the Transport
|
||||
sub-frame for each port. Values between 0 and 15 are valid.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- qcom,dports-type:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: should be one of the following types
|
||||
0 for reduced port
|
||||
1 for simple ports
|
||||
2 for full port
|
||||
Out ports followed by In ports.
|
||||
Value of 0xFF indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
- reset:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: Should specify the SoundWire audio CSR reset controller interface,
|
||||
which is required for SoundWire version 1.6.0 and above.
|
||||
|
||||
- reset-names:
|
||||
Usage: optional
|
||||
Value type: <stringlist>
|
||||
Definition: should be "swr_audio_cgcr" for SoundWire audio CSR reset
|
||||
controller interface.
|
||||
|
||||
Note:
|
||||
More Information on detail of encoding of these fields can be
|
||||
found in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
|
||||
= SoundWire devices
|
||||
Each subnode of the bus represents SoundWire device attached to it.
|
||||
The properties of these nodes are defined by the individual bindings.
|
||||
|
||||
= EXAMPLE
|
||||
The following example represents a SoundWire controller on DB845c board
|
||||
which has controller integrated inside WCD934x codec on SDM845 SoC.
|
||||
|
||||
soundwire: soundwire@c85 {
|
||||
compatible = "qcom,soundwire-v1.3.0";
|
||||
reg = <0xc85 0x20>;
|
||||
interrupts = <20 IRQ_TYPE_EDGE_RISING>;
|
||||
clocks = <&wcc>;
|
||||
clock-names = "iface";
|
||||
resets = <&lpass_audiocc LPASS_AUDIO_SWR_TX_CGCR>;
|
||||
reset-names = "swr_audio_cgcr";
|
||||
#sound-dai-cells = <1>;
|
||||
qcom,dports-type = <0>;
|
||||
qcom,dout-ports = <6>;
|
||||
qcom,din-ports = <2>;
|
||||
qcom,ports-sinterval-low = /bits/ 8 <0x07 0x1F 0x3F 0x7 0x1F 0x3F 0x0F 0x0F>;
|
||||
qcom,ports-offset1 = /bits/ 8 <0x01 0x02 0x0C 0x6 0x12 0x0D 0x07 0x0A >;
|
||||
qcom,ports-offset2 = /bits/ 8 <0x00 0x00 0x1F 0x00 0x00 0x1F 0x00 0x00>;
|
||||
|
||||
/* Left Speaker */
|
||||
left{
|
||||
....
|
||||
};
|
||||
|
||||
/* Right Speaker */
|
||||
right{
|
||||
....
|
||||
};
|
||||
};
|
270
Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml
Normal file
270
Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml
Normal file
@ -0,0 +1,270 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/soundwire/qcom,soundwire.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm SoundWire Controller
|
||||
|
||||
maintainers:
|
||||
- Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
||||
- Srinivasa Rao Mandadapu <quic_srivasam@quicinc.com>
|
||||
|
||||
description:
|
||||
The Qualcomm SoundWire controller along with its board specific bus parameters.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,soundwire-v1.3.0
|
||||
- qcom,soundwire-v1.5.0
|
||||
- qcom,soundwire-v1.5.1
|
||||
- qcom,soundwire-v1.6.0
|
||||
- qcom,soundwire-v1.7.0
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: specify the SoundWire controller core.
|
||||
- description: specify the Soundwire controller wake IRQ.
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: core
|
||||
- const: wakeup
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: iface clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: iface
|
||||
|
||||
resets:
|
||||
items:
|
||||
- description: SWR_AUDIO_CGCR RESET
|
||||
|
||||
reset-names:
|
||||
items:
|
||||
- const: swr_audio_cgcr
|
||||
|
||||
'#sound-dai-cells':
|
||||
const: 1
|
||||
|
||||
'#address-cells':
|
||||
const: 2
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
wakeup-source: true
|
||||
|
||||
qcom,din-ports:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: count of data in ports
|
||||
|
||||
qcom,dout-ports:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: count of data out ports
|
||||
|
||||
qcom,ports-word-length:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Size of payload channel sample.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 5
|
||||
|
||||
qcom,ports-sinterval-low:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Sample interval low of each data port.
|
||||
Out ports followed by In ports. Used for Sample Interval calculation.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 8
|
||||
|
||||
qcom,ports-offset1:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Payload transport window offset1 of each data port.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 8
|
||||
|
||||
qcom,ports-offset2:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Payload transport window offset2 of each data port.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 8
|
||||
|
||||
qcom,ports-lane-control:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Identify which data lane the data port uses.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 5
|
||||
|
||||
qcom,ports-block-pack-mode:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Indicate the block packing mode.
|
||||
0 to indicate Blocks are per Channel
|
||||
1 to indicate Blocks are per Port.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 8
|
||||
items:
|
||||
oneOf:
|
||||
- minimum: 0
|
||||
maximum: 1
|
||||
- const: 0xff
|
||||
|
||||
qcom,ports-hstart:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Identifying lowerst numbered coloum in SoundWire Frame,
|
||||
i.e. left edge of the Transport sub-frame for each port.
|
||||
Out ports followed by In ports.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 5
|
||||
items:
|
||||
oneOf:
|
||||
- minimum: 0
|
||||
maximum: 15
|
||||
- const: 0xff
|
||||
|
||||
qcom,ports-hstop:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
Identifying highest numbered coloum in SoundWire Frame,
|
||||
i.e. the right edge of the Transport
|
||||
sub-frame for each port. Out ports followed by In ports.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 5
|
||||
items:
|
||||
oneOf:
|
||||
- minimum: 0
|
||||
maximum: 15
|
||||
- const: 0xff
|
||||
|
||||
qcom,ports-block-group-count:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
description:
|
||||
In range 1 to 4 to indicate how many sample intervals are combined
|
||||
into a payload. Out ports followed by In ports.
|
||||
Value of 0xff indicates that this option is not implemented
|
||||
or applicable for the respective data port.
|
||||
More info in MIPI Alliance SoundWire 1.0 Specifications.
|
||||
minItems: 3
|
||||
maxItems: 5
|
||||
items:
|
||||
oneOf:
|
||||
- minimum: 0
|
||||
maximum: 4
|
||||
- const: 0xff
|
||||
|
||||
label:
|
||||
maxItems: 1
|
||||
|
||||
patternProperties:
|
||||
"^.*@[0-9a-f],[0-9a-f]$":
|
||||
type: object
|
||||
description:
|
||||
Child nodes for a standalone audio codec or speaker amplifier IC.
|
||||
It has RX and TX Soundwire secondary devices.
|
||||
properties:
|
||||
compatible:
|
||||
pattern: "^sdw[0-9a-f]{1}[0-9a-f]{4}[0-9a-f]{4}[0-9a-f]{2}$"
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
- '#sound-dai-cells'
|
||||
- '#address-cells'
|
||||
- '#size-cells'
|
||||
- qcom,dout-ports
|
||||
- qcom,din-ports
|
||||
- qcom,ports-sinterval-low
|
||||
- qcom,ports-offset1
|
||||
- qcom,ports-offset2
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/clock/qcom,lpassaudiocc-sc7280.h>
|
||||
|
||||
soundwire@3210000 {
|
||||
compatible = "qcom,soundwire-v1.6.0";
|
||||
reg = <0x03210000 0x2000>;
|
||||
|
||||
interrupts = <GIC_SPI 155 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<&pdc 130 IRQ_TYPE_LEVEL_HIGH>;
|
||||
|
||||
interrupt-names = "core", "wakeup";
|
||||
|
||||
clocks = <&lpass_rx_macro>;
|
||||
clock-names = "iface";
|
||||
|
||||
qcom,din-ports = <0>;
|
||||
qcom,dout-ports = <5>;
|
||||
|
||||
resets = <&lpass_audiocc LPASS_AUDIO_SWR_RX_CGCR>;
|
||||
reset-names = "swr_audio_cgcr";
|
||||
|
||||
qcom,ports-word-length = /bits/ 8 <0x01 0x07 0x04 0xff 0xff>;
|
||||
qcom,ports-sinterval-low = /bits/ 8 <0x03 0x3f 0x1f 0x03 0x03>;
|
||||
qcom,ports-offset1 = /bits/ 8 <0x00 0x00 0x0b 0x01 0x01>;
|
||||
qcom,ports-offset2 = /bits/ 8 <0x00 0x00 0x0b 0x00 0x00>;
|
||||
qcom,ports-lane-control = /bits/ 8 <0x01 0x00 0x00 0x00 0x00>;
|
||||
qcom,ports-block-pack-mode = /bits/ 8 <0xff 0x00 0x01 0xff 0xff>;
|
||||
qcom,ports-hstart = /bits/ 8 <0xff 0x03 0xff 0xff 0xff>;
|
||||
qcom,ports-hstop = /bits/ 8 <0xff 0x06 0xff 0xff 0xff>;
|
||||
qcom,ports-block-group-count = /bits/ 8 <0xff 0xff 0xff 0xff 0x00>;
|
||||
|
||||
#sound-dai-cells = <1>;
|
||||
#address-cells = <2>;
|
||||
#size-cells = <0>;
|
||||
|
||||
codec@0,4 {
|
||||
compatible = "sdw20217010d00";
|
||||
reg = <0 4>;
|
||||
qcom,rx-port-mapping = <1 2 3 4 5>;
|
||||
};
|
||||
};
|
@ -20,7 +20,7 @@ soundwire-cadence-y := cadence_master.o
|
||||
obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o
|
||||
|
||||
#Intel driver
|
||||
soundwire-intel-y := intel.o intel_init.o dmi-quirks.o
|
||||
soundwire-intel-y := intel.o intel_auxdevice.o intel_init.o dmi-quirks.o
|
||||
obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o
|
||||
|
||||
#Qualcomm driver
|
||||
|
@ -1707,47 +1707,45 @@ int cdns_set_sdw_stream(struct snd_soc_dai *dai,
|
||||
void *stream, int direction)
|
||||
{
|
||||
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
|
||||
struct sdw_cdns_dma_data *dma;
|
||||
struct sdw_cdns_dai_runtime *dai_runtime;
|
||||
|
||||
dai_runtime = cdns->dai_runtime_array[dai->id];
|
||||
|
||||
if (stream) {
|
||||
/* first paranoia check */
|
||||
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dma = dai->playback_dma_data;
|
||||
else
|
||||
dma = dai->capture_dma_data;
|
||||
|
||||
if (dma) {
|
||||
if (dai_runtime) {
|
||||
dev_err(dai->dev,
|
||||
"dma_data already allocated for dai %s\n",
|
||||
"dai_runtime already allocated for dai %s\n",
|
||||
dai->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* allocate and set dma info */
|
||||
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
|
||||
if (!dma)
|
||||
/* allocate and set dai_runtime info */
|
||||
dai_runtime = kzalloc(sizeof(*dai_runtime), GFP_KERNEL);
|
||||
if (!dai_runtime)
|
||||
return -ENOMEM;
|
||||
|
||||
dma->stream_type = SDW_STREAM_PCM;
|
||||
dai_runtime->stream_type = SDW_STREAM_PCM;
|
||||
|
||||
dma->bus = &cdns->bus;
|
||||
dma->link_id = cdns->instance;
|
||||
dai_runtime->bus = &cdns->bus;
|
||||
dai_runtime->link_id = cdns->instance;
|
||||
|
||||
dma->stream = stream;
|
||||
dai_runtime->stream = stream;
|
||||
dai_runtime->direction = direction;
|
||||
|
||||
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dai->playback_dma_data = dma;
|
||||
else
|
||||
dai->capture_dma_data = dma;
|
||||
cdns->dai_runtime_array[dai->id] = dai_runtime;
|
||||
} else {
|
||||
/* for NULL stream we release allocated dma_data */
|
||||
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
kfree(dai->playback_dma_data);
|
||||
dai->playback_dma_data = NULL;
|
||||
} else {
|
||||
kfree(dai->capture_dma_data);
|
||||
dai->capture_dma_data = NULL;
|
||||
/* second paranoia check */
|
||||
if (!dai_runtime) {
|
||||
dev_err(dai->dev,
|
||||
"dai_runtime not allocated for dai %s\n",
|
||||
dai->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* for NULL stream we release allocated dai_runtime */
|
||||
kfree(dai_runtime);
|
||||
cdns->dai_runtime_array[dai->id] = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ struct sdw_cdns_stream_config {
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sdw_cdns_dma_data: Cadence DMA data
|
||||
* struct sdw_cdns_dai_runtime: Cadence DAI runtime data
|
||||
*
|
||||
* @name: SoundWire stream name
|
||||
* @stream: stream runtime
|
||||
@ -81,8 +81,9 @@ struct sdw_cdns_stream_config {
|
||||
* @hw_params: hw_params to be applied in .prepare step
|
||||
* @suspended: status set when suspended, to be used in .prepare
|
||||
* @paused: status set in .trigger, to be used in suspend
|
||||
* @direction: stream direction
|
||||
*/
|
||||
struct sdw_cdns_dma_data {
|
||||
struct sdw_cdns_dai_runtime {
|
||||
char *name;
|
||||
struct sdw_stream_runtime *stream;
|
||||
struct sdw_cdns_pdi *pdi;
|
||||
@ -92,6 +93,7 @@ struct sdw_cdns_dma_data {
|
||||
struct snd_pcm_hw_params *hw_params;
|
||||
bool suspended;
|
||||
bool paused;
|
||||
int direction;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -108,6 +110,7 @@ struct sdw_cdns_dma_data {
|
||||
* @registers: Cadence registers
|
||||
* @link_up: Link status
|
||||
* @msg_count: Messages sent on bus
|
||||
* @dai_runtime_array: runtime context for each allocated DAI.
|
||||
*/
|
||||
struct sdw_cdns {
|
||||
struct device *dev;
|
||||
@ -135,6 +138,8 @@ struct sdw_cdns {
|
||||
struct work_struct work;
|
||||
|
||||
struct list_head list;
|
||||
|
||||
struct sdw_cdns_dai_runtime **dai_runtime_array;
|
||||
};
|
||||
|
||||
#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus)
|
||||
|
@ -90,6 +90,14 @@ static const struct dmi_system_id adr_remap_quirk_table[] = {
|
||||
},
|
||||
.driver_data = (void *)intel_tgl_bios,
|
||||
},
|
||||
{
|
||||
/* quirk used for NUC15 LAPBC710 skew */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "LAPBC710"),
|
||||
},
|
||||
.driver_data = (void *)intel_tgl_bios,
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
|
||||
|
@ -8,10 +8,7 @@
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <sound/soc.h>
|
||||
@ -22,27 +19,6 @@
|
||||
#include "bus.h"
|
||||
#include "intel.h"
|
||||
|
||||
/* IDA min selected to avoid conflicts with HDaudio/iDISP SDI values */
|
||||
#define INTEL_DEV_NUM_IDA_MIN 4
|
||||
|
||||
#define INTEL_MASTER_SUSPEND_DELAY_MS 3000
|
||||
#define INTEL_MASTER_RESET_ITERATIONS 10
|
||||
|
||||
/*
|
||||
* debug/config flags for the Intel SoundWire Master.
|
||||
*
|
||||
* Since we may have multiple masters active, we can have up to 8
|
||||
* flags reused in each byte, with master0 using the ls-byte, etc.
|
||||
*/
|
||||
|
||||
#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0)
|
||||
#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1)
|
||||
#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2)
|
||||
#define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3)
|
||||
|
||||
static int md_flags;
|
||||
module_param_named(sdw_md_flags, md_flags, int, 0444);
|
||||
MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)");
|
||||
|
||||
enum intel_pdi_type {
|
||||
INTEL_PDI_IN = 0,
|
||||
@ -745,10 +721,10 @@ static int intel_free_stream(struct sdw_intel *sdw,
|
||||
* bank switch routines
|
||||
*/
|
||||
|
||||
static int intel_pre_bank_switch(struct sdw_bus *bus)
|
||||
static int intel_pre_bank_switch(struct sdw_intel *sdw)
|
||||
{
|
||||
struct sdw_cdns *cdns = bus_to_cdns(bus);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_cdns *cdns = &sdw->cdns;
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
|
||||
/* Write to register only for multi-link */
|
||||
if (!bus->multi_link)
|
||||
@ -759,10 +735,10 @@ static int intel_pre_bank_switch(struct sdw_bus *bus)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_post_bank_switch(struct sdw_bus *bus)
|
||||
static int intel_post_bank_switch(struct sdw_intel *sdw)
|
||||
{
|
||||
struct sdw_cdns *cdns = bus_to_cdns(bus);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_cdns *cdns = &sdw->cdns;
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
void __iomem *shim = sdw->link_res->shim;
|
||||
int sync_reg, ret;
|
||||
|
||||
@ -824,15 +800,15 @@ static int intel_hw_params(struct snd_pcm_substream *substream,
|
||||
{
|
||||
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_cdns_dma_data *dma;
|
||||
struct sdw_cdns_dai_runtime *dai_runtime;
|
||||
struct sdw_cdns_pdi *pdi;
|
||||
struct sdw_stream_config sconfig;
|
||||
struct sdw_port_config *pconfig;
|
||||
int ch, dir;
|
||||
int ret;
|
||||
|
||||
dma = snd_soc_dai_get_dma_data(dai, substream);
|
||||
if (!dma)
|
||||
dai_runtime = cdns->dai_runtime_array[dai->id];
|
||||
if (!dai_runtime)
|
||||
return -EIO;
|
||||
|
||||
ch = params_channels(params);
|
||||
@ -854,10 +830,10 @@ static int intel_hw_params(struct snd_pcm_substream *substream,
|
||||
sdw_cdns_config_stream(cdns, ch, dir, pdi);
|
||||
|
||||
/* store pdi and hw_params, may be needed in prepare step */
|
||||
dma->paused = false;
|
||||
dma->suspended = false;
|
||||
dma->pdi = pdi;
|
||||
dma->hw_params = params;
|
||||
dai_runtime->paused = false;
|
||||
dai_runtime->suspended = false;
|
||||
dai_runtime->pdi = pdi;
|
||||
dai_runtime->hw_params = params;
|
||||
|
||||
/* Inform DSP about PDI stream number */
|
||||
ret = intel_params_stream(sdw, substream->stream, dai, params,
|
||||
@ -869,7 +845,7 @@ static int intel_hw_params(struct snd_pcm_substream *substream,
|
||||
sconfig.direction = dir;
|
||||
sconfig.ch_count = ch;
|
||||
sconfig.frame_rate = params_rate(params);
|
||||
sconfig.type = dma->stream_type;
|
||||
sconfig.type = dai_runtime->stream_type;
|
||||
|
||||
sconfig.bps = snd_pcm_format_width(params_format(params));
|
||||
|
||||
@ -884,7 +860,7 @@ static int intel_hw_params(struct snd_pcm_substream *substream,
|
||||
pconfig->ch_mask = (1 << ch) - 1;
|
||||
|
||||
ret = sdw_stream_add_master(&cdns->bus, &sconfig,
|
||||
pconfig, 1, dma->stream);
|
||||
pconfig, 1, dai_runtime->stream);
|
||||
if (ret)
|
||||
dev_err(cdns->dev, "add master to stream failed:%d\n", ret);
|
||||
|
||||
@ -898,19 +874,19 @@ static int intel_prepare(struct snd_pcm_substream *substream,
|
||||
{
|
||||
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_cdns_dma_data *dma;
|
||||
struct sdw_cdns_dai_runtime *dai_runtime;
|
||||
int ch, dir;
|
||||
int ret = 0;
|
||||
|
||||
dma = snd_soc_dai_get_dma_data(dai, substream);
|
||||
if (!dma) {
|
||||
dev_err(dai->dev, "failed to get dma data in %s\n",
|
||||
dai_runtime = cdns->dai_runtime_array[dai->id];
|
||||
if (!dai_runtime) {
|
||||
dev_err(dai->dev, "failed to get dai runtime in %s\n",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (dma->suspended) {
|
||||
dma->suspended = false;
|
||||
if (dai_runtime->suspended) {
|
||||
dai_runtime->suspended = false;
|
||||
|
||||
/*
|
||||
* .prepare() is called after system resume, where we
|
||||
@ -921,21 +897,21 @@ static int intel_prepare(struct snd_pcm_substream *substream,
|
||||
*/
|
||||
|
||||
/* configure stream */
|
||||
ch = params_channels(dma->hw_params);
|
||||
ch = params_channels(dai_runtime->hw_params);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
dir = SDW_DATA_DIR_RX;
|
||||
else
|
||||
dir = SDW_DATA_DIR_TX;
|
||||
|
||||
intel_pdi_shim_configure(sdw, dma->pdi);
|
||||
intel_pdi_alh_configure(sdw, dma->pdi);
|
||||
sdw_cdns_config_stream(cdns, ch, dir, dma->pdi);
|
||||
intel_pdi_shim_configure(sdw, dai_runtime->pdi);
|
||||
intel_pdi_alh_configure(sdw, dai_runtime->pdi);
|
||||
sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi);
|
||||
|
||||
/* Inform DSP about PDI stream number */
|
||||
ret = intel_params_stream(sdw, substream->stream, dai,
|
||||
dma->hw_params,
|
||||
dai_runtime->hw_params,
|
||||
sdw->instance,
|
||||
dma->pdi->intel_alh_id);
|
||||
dai_runtime->pdi->intel_alh_id);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -946,11 +922,11 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_cdns_dma_data *dma;
|
||||
struct sdw_cdns_dai_runtime *dai_runtime;
|
||||
int ret;
|
||||
|
||||
dma = snd_soc_dai_get_dma_data(dai, substream);
|
||||
if (!dma)
|
||||
dai_runtime = cdns->dai_runtime_array[dai->id];
|
||||
if (!dai_runtime)
|
||||
return -EIO;
|
||||
|
||||
/*
|
||||
@ -959,10 +935,10 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
|
||||
* DEPREPARED for the first cpu-dai and to RELEASED for the last
|
||||
* cpu-dai.
|
||||
*/
|
||||
ret = sdw_stream_remove_master(&cdns->bus, dma->stream);
|
||||
ret = sdw_stream_remove_master(&cdns->bus, dai_runtime->stream);
|
||||
if (ret < 0) {
|
||||
dev_err(dai->dev, "remove master from stream %s failed: %d\n",
|
||||
dma->stream->name, ret);
|
||||
dai_runtime->stream->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -972,8 +948,8 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
|
||||
return ret;
|
||||
}
|
||||
|
||||
dma->hw_params = NULL;
|
||||
dma->pdi = NULL;
|
||||
dai_runtime->hw_params = NULL;
|
||||
dai_runtime->pdi = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -996,17 +972,14 @@ static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai,
|
||||
static void *intel_get_sdw_stream(struct snd_soc_dai *dai,
|
||||
int direction)
|
||||
{
|
||||
struct sdw_cdns_dma_data *dma;
|
||||
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
|
||||
struct sdw_cdns_dai_runtime *dai_runtime;
|
||||
|
||||
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dma = dai->playback_dma_data;
|
||||
else
|
||||
dma = dai->capture_dma_data;
|
||||
|
||||
if (!dma)
|
||||
dai_runtime = cdns->dai_runtime_array[dai->id];
|
||||
if (!dai_runtime)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return dma->stream;
|
||||
return dai_runtime->stream;
|
||||
}
|
||||
|
||||
static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
|
||||
@ -1014,7 +987,7 @@ static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct sn
|
||||
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_intel_link_res *res = sdw->link_res;
|
||||
struct sdw_cdns_dma_data *dma;
|
||||
struct sdw_cdns_dai_runtime *dai_runtime;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
@ -1025,9 +998,9 @@ static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct sn
|
||||
if (res->ops && res->ops->trigger)
|
||||
res->ops->trigger(dai, cmd, substream->stream);
|
||||
|
||||
dma = snd_soc_dai_get_dma_data(dai, substream);
|
||||
if (!dma) {
|
||||
dev_err(dai->dev, "failed to get dma data in %s\n",
|
||||
dai_runtime = cdns->dai_runtime_array[dai->id];
|
||||
if (!dai_runtime) {
|
||||
dev_err(dai->dev, "failed to get dai runtime in %s\n",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
@ -1042,17 +1015,17 @@ static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct sn
|
||||
* the .trigger callback is used to track the suspend case only.
|
||||
*/
|
||||
|
||||
dma->suspended = true;
|
||||
dai_runtime->suspended = true;
|
||||
|
||||
ret = intel_free_stream(sdw, substream->stream, dai, sdw->instance);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
dma->paused = true;
|
||||
dai_runtime->paused = true;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
dma->paused = false;
|
||||
dai_runtime->paused = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -1091,27 +1064,21 @@ static int intel_component_dais_suspend(struct snd_soc_component *component)
|
||||
for_each_component_dais(component, dai) {
|
||||
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_cdns_dma_data *dma;
|
||||
int stream;
|
||||
struct sdw_cdns_dai_runtime *dai_runtime;
|
||||
int ret;
|
||||
|
||||
dma = dai->playback_dma_data;
|
||||
stream = SNDRV_PCM_STREAM_PLAYBACK;
|
||||
if (!dma) {
|
||||
dma = dai->capture_dma_data;
|
||||
stream = SNDRV_PCM_STREAM_CAPTURE;
|
||||
}
|
||||
dai_runtime = cdns->dai_runtime_array[dai->id];
|
||||
|
||||
if (!dma)
|
||||
if (!dai_runtime)
|
||||
continue;
|
||||
|
||||
if (dma->suspended)
|
||||
if (dai_runtime->suspended)
|
||||
continue;
|
||||
|
||||
if (dma->paused) {
|
||||
dma->suspended = true;
|
||||
if (dai_runtime->paused) {
|
||||
dai_runtime->suspended = true;
|
||||
|
||||
ret = intel_free_stream(sdw, stream, dai, sdw->instance);
|
||||
ret = intel_free_stream(sdw, dai_runtime->direction, dai, sdw->instance);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
@ -1178,6 +1145,7 @@ static int intel_create_dai(struct sdw_cdns *cdns,
|
||||
|
||||
static int intel_register_dai(struct sdw_intel *sdw)
|
||||
{
|
||||
struct sdw_cdns_dai_runtime **dai_runtime_array;
|
||||
struct sdw_cdns_stream_config config;
|
||||
struct sdw_cdns *cdns = &sdw->cdns;
|
||||
struct sdw_cdns_streams *stream;
|
||||
@ -1195,6 +1163,13 @@ static int intel_register_dai(struct sdw_intel *sdw)
|
||||
/* DAIs are created based on total number of PDIs supported */
|
||||
num_dai = cdns->pcm.num_pdi;
|
||||
|
||||
dai_runtime_array = devm_kcalloc(cdns->dev, num_dai,
|
||||
sizeof(struct sdw_cdns_dai_runtime *),
|
||||
GFP_KERNEL);
|
||||
if (!dai_runtime_array)
|
||||
return -ENOMEM;
|
||||
cdns->dai_runtime_array = dai_runtime_array;
|
||||
|
||||
dais = devm_kcalloc(cdns->dev, num_dai, sizeof(*dais), GFP_KERNEL);
|
||||
if (!dais)
|
||||
return -ENOMEM;
|
||||
@ -1423,620 +1398,26 @@ static int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdw_master_read_intel_prop(struct sdw_bus *bus)
|
||||
{
|
||||
struct sdw_master_prop *prop = &bus->prop;
|
||||
struct fwnode_handle *link;
|
||||
char name[32];
|
||||
u32 quirk_mask;
|
||||
const struct sdw_intel_hw_ops sdw_intel_cnl_hw_ops = {
|
||||
.debugfs_init = intel_debugfs_init,
|
||||
.debugfs_exit = intel_debugfs_exit,
|
||||
|
||||
/* Find master handle */
|
||||
snprintf(name, sizeof(name),
|
||||
"mipi-sdw-link-%d-subproperties", bus->link_id);
|
||||
.register_dai = intel_register_dai,
|
||||
|
||||
link = device_get_named_child_node(bus->dev, name);
|
||||
if (!link) {
|
||||
dev_err(bus->dev, "Master node %s not found\n", name);
|
||||
return -EIO;
|
||||
}
|
||||
.check_clock_stop = intel_check_clock_stop,
|
||||
.start_bus = intel_start_bus,
|
||||
.start_bus_after_reset = intel_start_bus_after_reset,
|
||||
.start_bus_after_clock_stop = intel_start_bus_after_clock_stop,
|
||||
.stop_bus = intel_stop_bus,
|
||||
|
||||
fwnode_property_read_u32(link,
|
||||
"intel-sdw-ip-clock",
|
||||
&prop->mclk_freq);
|
||||
.link_power_up = intel_link_power_up,
|
||||
.link_power_down = intel_link_power_down,
|
||||
|
||||
/* the values reported by BIOS are the 2x clock, not the bus clock */
|
||||
prop->mclk_freq /= 2;
|
||||
.shim_check_wake = intel_shim_check_wake,
|
||||
.shim_wake = intel_shim_wake,
|
||||
|
||||
fwnode_property_read_u32(link,
|
||||
"intel-quirk-mask",
|
||||
&quirk_mask);
|
||||
|
||||
if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE)
|
||||
prop->hw_disabled = true;
|
||||
|
||||
prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
|
||||
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_prop_read(struct sdw_bus *bus)
|
||||
{
|
||||
/* Initialize with default handler to read all DisCo properties */
|
||||
sdw_master_read_prop(bus);
|
||||
|
||||
/* read Intel-specific properties */
|
||||
sdw_master_read_intel_prop(bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sdw_master_ops sdw_intel_ops = {
|
||||
.read_prop = intel_prop_read,
|
||||
.override_adr = sdw_dmi_override_adr,
|
||||
.xfer_msg = cdns_xfer_msg,
|
||||
.xfer_msg_defer = cdns_xfer_msg_defer,
|
||||
.reset_page_addr = cdns_reset_page_addr,
|
||||
.set_bus_conf = cdns_bus_conf,
|
||||
.pre_bank_switch = intel_pre_bank_switch,
|
||||
.post_bank_switch = intel_post_bank_switch,
|
||||
.read_ping_status = cdns_read_ping_status,
|
||||
};
|
||||
EXPORT_SYMBOL_NS(sdw_intel_cnl_hw_ops, SOUNDWIRE_INTEL);
|
||||
|
||||
/*
|
||||
* probe and init (aux_dev_id argument is required by function prototype but not used)
|
||||
*/
|
||||
static int intel_link_probe(struct auxiliary_device *auxdev,
|
||||
const struct auxiliary_device_id *aux_dev_id)
|
||||
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct sdw_intel_link_dev *ldev = auxiliary_dev_to_sdw_intel_link_dev(auxdev);
|
||||
struct sdw_intel *sdw;
|
||||
struct sdw_cdns *cdns;
|
||||
struct sdw_bus *bus;
|
||||
int ret;
|
||||
|
||||
sdw = devm_kzalloc(dev, sizeof(*sdw), GFP_KERNEL);
|
||||
if (!sdw)
|
||||
return -ENOMEM;
|
||||
|
||||
cdns = &sdw->cdns;
|
||||
bus = &cdns->bus;
|
||||
|
||||
sdw->instance = auxdev->id;
|
||||
sdw->link_res = &ldev->link_res;
|
||||
cdns->dev = dev;
|
||||
cdns->registers = sdw->link_res->registers;
|
||||
cdns->instance = sdw->instance;
|
||||
cdns->msg_count = 0;
|
||||
|
||||
bus->link_id = auxdev->id;
|
||||
bus->dev_num_ida_min = INTEL_DEV_NUM_IDA_MIN;
|
||||
bus->clk_stop_timeout = 1;
|
||||
|
||||
sdw_cdns_probe(cdns);
|
||||
|
||||
/* Set ops */
|
||||
bus->ops = &sdw_intel_ops;
|
||||
|
||||
/* set driver data, accessed by snd_soc_dai_get_drvdata() */
|
||||
auxiliary_set_drvdata(auxdev, cdns);
|
||||
|
||||
/* use generic bandwidth allocation algorithm */
|
||||
sdw->cdns.bus.compute_params = sdw_compute_params;
|
||||
|
||||
/* avoid resuming from pm_runtime suspend if it's not required */
|
||||
dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND);
|
||||
|
||||
ret = sdw_bus_master_add(bus, dev, dev->fwnode);
|
||||
if (ret) {
|
||||
dev_err(dev, "sdw_bus_master_add fail: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (bus->prop.hw_disabled)
|
||||
dev_info(dev,
|
||||
"SoundWire master %d is disabled, will be ignored\n",
|
||||
bus->link_id);
|
||||
/*
|
||||
* Ignore BIOS err_threshold, it's a really bad idea when dealing
|
||||
* with multiple hardware synchronized links
|
||||
*/
|
||||
bus->prop.err_threshold = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int intel_link_startup(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
int link_flags;
|
||||
bool multi_link;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled) {
|
||||
dev_info(dev,
|
||||
"SoundWire master %d is disabled, ignoring\n",
|
||||
sdw->instance);
|
||||
return 0;
|
||||
}
|
||||
|
||||
link_flags = md_flags >> (bus->link_id * 8);
|
||||
multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK);
|
||||
if (!multi_link) {
|
||||
dev_dbg(dev, "Multi-link is disabled\n");
|
||||
} else {
|
||||
/*
|
||||
* hardware-based synchronization is required regardless
|
||||
* of the number of segments used by a stream: SSP-based
|
||||
* synchronization is gated by gsync when the multi-master
|
||||
* mode is set.
|
||||
*/
|
||||
bus->hw_sync_min_links = 1;
|
||||
}
|
||||
bus->multi_link = multi_link;
|
||||
|
||||
/* Initialize shim, controller */
|
||||
ret = intel_link_power_up(sdw);
|
||||
if (ret)
|
||||
goto err_init;
|
||||
|
||||
/* Register DAIs */
|
||||
ret = intel_register_dai(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "DAI registration failed: %d\n", ret);
|
||||
goto err_power_up;
|
||||
}
|
||||
|
||||
intel_debugfs_init(sdw);
|
||||
|
||||
/* start bus */
|
||||
ret = intel_start_bus(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "bus start failed: %d\n", ret);
|
||||
goto err_power_up;
|
||||
}
|
||||
|
||||
/* Enable runtime PM */
|
||||
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) {
|
||||
pm_runtime_set_autosuspend_delay(dev,
|
||||
INTEL_MASTER_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
}
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_NOT_ALLOWED) {
|
||||
/*
|
||||
* To keep the clock running we need to prevent
|
||||
* pm_runtime suspend from happening by increasing the
|
||||
* reference count.
|
||||
* This quirk is specified by the parent PCI device in
|
||||
* case of specific latency requirements. It will have
|
||||
* no effect if pm_runtime is disabled by the user via
|
||||
* a module parameter for testing purposes.
|
||||
*/
|
||||
pm_runtime_get_noresume(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* The runtime PM status of Slave devices is "Unsupported"
|
||||
* until they report as ATTACHED. If they don't, e.g. because
|
||||
* there are no Slave devices populated or if the power-on is
|
||||
* delayed or dependent on a power switch, the Master will
|
||||
* remain active and prevent its parent from suspending.
|
||||
*
|
||||
* Conditionally force the pm_runtime core to re-evaluate the
|
||||
* Master status in the absence of any Slave activity. A quirk
|
||||
* is provided to e.g. deal with Slaves that may be powered on
|
||||
* with a delay. A more complete solution would require the
|
||||
* definition of Master properties.
|
||||
*/
|
||||
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE))
|
||||
pm_runtime_idle(dev);
|
||||
|
||||
sdw->startup_done = true;
|
||||
return 0;
|
||||
|
||||
err_power_up:
|
||||
intel_link_power_down(sdw);
|
||||
err_init:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void intel_link_remove(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
|
||||
/*
|
||||
* Since pm_runtime is already disabled, we don't decrease
|
||||
* the refcount when the clock_stop_quirk is
|
||||
* SDW_INTEL_CLK_STOP_NOT_ALLOWED
|
||||
*/
|
||||
if (!bus->prop.hw_disabled) {
|
||||
intel_debugfs_exit(sdw);
|
||||
sdw_cdns_enable_interrupt(cdns, false);
|
||||
}
|
||||
sdw_bus_master_delete(bus);
|
||||
}
|
||||
|
||||
int intel_link_process_wakeen_event(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct sdw_intel *sdw;
|
||||
struct sdw_bus *bus;
|
||||
|
||||
sdw = auxiliary_get_drvdata(auxdev);
|
||||
bus = &sdw->cdns.bus;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!intel_shim_check_wake(sdw))
|
||||
return 0;
|
||||
|
||||
/* disable WAKEEN interrupt ASAP to prevent interrupt flood */
|
||||
intel_shim_wake(sdw, false);
|
||||
|
||||
/*
|
||||
* resume the Master, which will generate a bus reset and result in
|
||||
* Slaves re-attaching and be re-enumerated. The SoundWire physical
|
||||
* device which generated the wake will trigger an interrupt, which
|
||||
* will in turn cause the corresponding Linux Slave device to be
|
||||
* resumed and the Slave codec driver to check the status.
|
||||
*/
|
||||
pm_request_resume(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* PM calls
|
||||
*/
|
||||
|
||||
static int intel_resume_child_device(struct device *dev, void *data)
|
||||
{
|
||||
int ret;
|
||||
struct sdw_slave *slave = dev_to_sdw_dev(dev);
|
||||
|
||||
if (!slave->probed) {
|
||||
dev_dbg(dev, "skipping device, no probed driver\n");
|
||||
return 0;
|
||||
}
|
||||
if (!slave->dev_num_sticky) {
|
||||
dev_dbg(dev, "skipping device, never detected on bus\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = pm_request_resume(dev);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_pm_prepare(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if (pm_runtime_suspended(dev) &&
|
||||
pm_runtime_suspended(dev->parent) &&
|
||||
((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
|
||||
!clock_stop_quirks)) {
|
||||
/*
|
||||
* if we've enabled clock stop, and the parent is suspended, the SHIM registers
|
||||
* are not accessible and the shim wake cannot be disabled.
|
||||
* The only solution is to resume the entire bus to full power
|
||||
*/
|
||||
|
||||
/*
|
||||
* If any operation in this block fails, we keep going since we don't want
|
||||
* to prevent system suspend from happening and errors should be recoverable
|
||||
* on resume.
|
||||
*/
|
||||
|
||||
/*
|
||||
* first resume the device for this link. This will also by construction
|
||||
* resume the PCI parent device.
|
||||
*/
|
||||
ret = pm_request_resume(dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Continue resuming the entire bus (parent + child devices) to exit
|
||||
* the clock stop mode. If there are no devices connected on this link
|
||||
* this is a no-op.
|
||||
* The resume to full power could have been implemented with a .prepare
|
||||
* step in SoundWire codec drivers. This would however require a lot
|
||||
* of code to handle an Intel-specific corner case. It is simpler in
|
||||
* practice to add a loop at the link level.
|
||||
*/
|
||||
ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device);
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_suspend(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pm_runtime_suspended(dev)) {
|
||||
dev_dbg(dev, "pm_runtime status: suspended\n");
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
|
||||
!clock_stop_quirks) {
|
||||
|
||||
if (pm_runtime_suspended(dev->parent)) {
|
||||
/*
|
||||
* paranoia check: this should not happen with the .prepare
|
||||
* resume to full power
|
||||
*/
|
||||
dev_err(dev, "%s: invalid config: parent is suspended\n", __func__);
|
||||
} else {
|
||||
intel_shim_wake(sdw, false);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = intel_stop_bus(sdw, false);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot stop bus: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_suspend_runtime(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
|
||||
ret = intel_stop_bus(sdw, false);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot stop bus during teardown: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
} else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET || !clock_stop_quirks) {
|
||||
ret = intel_stop_bus(sdw, true);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot stop bus during clock_stop: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_err(dev, "%s clock_stop_quirks %x unsupported\n",
|
||||
__func__, clock_stop_quirks);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_resume(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
int link_flags;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
link_flags = md_flags >> (bus->link_id * 8);
|
||||
|
||||
if (pm_runtime_suspended(dev)) {
|
||||
dev_dbg(dev, "pm_runtime status was suspended, forcing active\n");
|
||||
|
||||
/* follow required sequence from runtime_pm.rst */
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
link_flags = md_flags >> (bus->link_id * 8);
|
||||
|
||||
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE))
|
||||
pm_runtime_idle(dev);
|
||||
}
|
||||
|
||||
ret = intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s failed: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* make sure all Slaves are tagged as UNATTACHED and provide
|
||||
* reason for reinitialization
|
||||
*/
|
||||
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
|
||||
|
||||
ret = intel_start_bus(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "cannot start bus during resume\n");
|
||||
intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* after system resume, the pm_runtime suspend() may kick in
|
||||
* during the enumeration, before any children device force the
|
||||
* master device to remain active. Using pm_runtime_get()
|
||||
* routines is not really possible, since it'd prevent the
|
||||
* master from suspending.
|
||||
* A reasonable compromise is to update the pm_runtime
|
||||
* counters and delay the pm_runtime suspend by several
|
||||
* seconds, by when all enumeration should be complete.
|
||||
*/
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_resume_runtime(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* unconditionally disable WAKEEN interrupt */
|
||||
intel_shim_wake(sdw, false);
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
|
||||
ret = intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: power_up failed after teardown: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* make sure all Slaves are tagged as UNATTACHED and provide
|
||||
* reason for reinitialization
|
||||
*/
|
||||
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
|
||||
|
||||
ret = intel_start_bus(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot start bus after teardown: %d\n", __func__, ret);
|
||||
intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
} else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) {
|
||||
ret = intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: power_up failed after bus reset: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = intel_start_bus_after_reset(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot start bus after reset: %d\n", __func__, ret);
|
||||
intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
} else if (!clock_stop_quirks) {
|
||||
|
||||
intel_check_clock_stop(sdw);
|
||||
|
||||
ret = intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: power_up failed: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = intel_start_bus_after_clock_stop(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot start bus after clock stop: %d\n", __func__, ret);
|
||||
intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_err(dev, "%s: clock_stop_quirks %x unsupported\n",
|
||||
__func__, clock_stop_quirks);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops intel_pm = {
|
||||
.prepare = intel_pm_prepare,
|
||||
SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
|
||||
SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL)
|
||||
};
|
||||
|
||||
static const struct auxiliary_device_id intel_link_id_table[] = {
|
||||
{ .name = "soundwire_intel.link" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, intel_link_id_table);
|
||||
|
||||
static struct auxiliary_driver sdw_intel_drv = {
|
||||
.probe = intel_link_probe,
|
||||
.remove = intel_link_remove,
|
||||
.driver = {
|
||||
/* auxiliary_driver_register() sets .name to be the modname */
|
||||
.pm = &intel_pm,
|
||||
},
|
||||
.id_table = intel_link_id_table
|
||||
};
|
||||
module_auxiliary_driver(sdw_intel_drv);
|
||||
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_DESCRIPTION("Intel Soundwire Link Driver");
|
||||
|
@ -7,6 +7,7 @@
|
||||
/**
|
||||
* struct sdw_intel_link_res - Soundwire Intel link resource structure,
|
||||
* typically populated by the controller driver.
|
||||
* @hw_ops: platform-specific ops
|
||||
* @mmio_base: mmio base of SoundWire registers
|
||||
* @registers: Link IO registers base
|
||||
* @shim: Audio shim pointer
|
||||
@ -22,6 +23,8 @@
|
||||
* @list: used to walk-through all masters exposed by the same controller
|
||||
*/
|
||||
struct sdw_intel_link_res {
|
||||
const struct sdw_intel_hw_ops *hw_ops;
|
||||
|
||||
void __iomem *mmio_base; /* not strictly needed, useful for debug */
|
||||
void __iomem *registers;
|
||||
void __iomem *shim;
|
||||
@ -47,15 +50,92 @@ struct sdw_intel {
|
||||
#endif
|
||||
};
|
||||
|
||||
int intel_link_startup(struct auxiliary_device *auxdev);
|
||||
int intel_link_process_wakeen_event(struct auxiliary_device *auxdev);
|
||||
#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns)
|
||||
|
||||
struct sdw_intel_link_dev {
|
||||
struct auxiliary_device auxdev;
|
||||
struct sdw_intel_link_res link_res;
|
||||
};
|
||||
#define INTEL_MASTER_RESET_ITERATIONS 10
|
||||
|
||||
#define auxiliary_dev_to_sdw_intel_link_dev(auxiliary_dev) \
|
||||
container_of(auxiliary_dev, struct sdw_intel_link_dev, auxdev)
|
||||
#define SDW_INTEL_CHECK_OPS(sdw, cb) ((sdw) && (sdw)->link_res && (sdw)->link_res->hw_ops && \
|
||||
(sdw)->link_res->hw_ops->cb)
|
||||
#define SDW_INTEL_OPS(sdw, cb) ((sdw)->link_res->hw_ops->cb)
|
||||
|
||||
static inline void sdw_intel_debugfs_init(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, debugfs_init))
|
||||
SDW_INTEL_OPS(sdw, debugfs_init)(sdw);
|
||||
}
|
||||
|
||||
static inline void sdw_intel_debugfs_exit(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, debugfs_exit))
|
||||
SDW_INTEL_OPS(sdw, debugfs_exit)(sdw);
|
||||
}
|
||||
|
||||
static inline int sdw_intel_register_dai(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, register_dai))
|
||||
return SDW_INTEL_OPS(sdw, register_dai)(sdw);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline void sdw_intel_check_clock_stop(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, check_clock_stop))
|
||||
SDW_INTEL_OPS(sdw, check_clock_stop)(sdw);
|
||||
}
|
||||
|
||||
static inline int sdw_intel_start_bus(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, start_bus))
|
||||
return SDW_INTEL_OPS(sdw, start_bus)(sdw);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int sdw_intel_start_bus_after_reset(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, start_bus_after_reset))
|
||||
return SDW_INTEL_OPS(sdw, start_bus_after_reset)(sdw);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int sdw_intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, start_bus_after_clock_stop))
|
||||
return SDW_INTEL_OPS(sdw, start_bus_after_clock_stop)(sdw);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int sdw_intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, stop_bus))
|
||||
return SDW_INTEL_OPS(sdw, stop_bus)(sdw, clock_stop);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int sdw_intel_link_power_up(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, link_power_up))
|
||||
return SDW_INTEL_OPS(sdw, link_power_up)(sdw);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int sdw_intel_link_power_down(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, link_power_down))
|
||||
return SDW_INTEL_OPS(sdw, link_power_down)(sdw);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline int sdw_intel_shim_check_wake(struct sdw_intel *sdw)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, shim_check_wake))
|
||||
return SDW_INTEL_OPS(sdw, shim_check_wake)(sdw);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline void sdw_intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
|
||||
{
|
||||
if (SDW_INTEL_CHECK_OPS(sdw, shim_wake))
|
||||
SDW_INTEL_OPS(sdw, shim_wake)(sdw, wake_enable);
|
||||
}
|
||||
|
||||
#endif /* __SDW_INTEL_LOCAL_H */
|
||||
|
678
drivers/soundwire/intel_auxdevice.c
Normal file
678
drivers/soundwire/intel_auxdevice.c
Normal file
@ -0,0 +1,678 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
|
||||
// Copyright(c) 2015-22 Intel Corporation.
|
||||
|
||||
/*
|
||||
* Soundwire Intel Manager Driver
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <sound/soc.h>
|
||||
#include <linux/soundwire/sdw_registers.h>
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_intel.h>
|
||||
#include "cadence_master.h"
|
||||
#include "bus.h"
|
||||
#include "intel.h"
|
||||
#include "intel_auxdevice.h"
|
||||
|
||||
/* IDA min selected to avoid conflicts with HDaudio/iDISP SDI values */
|
||||
#define INTEL_DEV_NUM_IDA_MIN 4
|
||||
|
||||
#define INTEL_MASTER_SUSPEND_DELAY_MS 3000
|
||||
|
||||
/*
|
||||
* debug/config flags for the Intel SoundWire Master.
|
||||
*
|
||||
* Since we may have multiple masters active, we can have up to 8
|
||||
* flags reused in each byte, with master0 using the ls-byte, etc.
|
||||
*/
|
||||
|
||||
#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0)
|
||||
#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1)
|
||||
#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2)
|
||||
#define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3)
|
||||
|
||||
static int md_flags;
|
||||
module_param_named(sdw_md_flags, md_flags, int, 0444);
|
||||
MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)");
|
||||
|
||||
static int generic_pre_bank_switch(struct sdw_bus *bus)
|
||||
{
|
||||
struct sdw_cdns *cdns = bus_to_cdns(bus);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
|
||||
return sdw->link_res->hw_ops->pre_bank_switch(sdw);
|
||||
}
|
||||
|
||||
static int generic_post_bank_switch(struct sdw_bus *bus)
|
||||
{
|
||||
struct sdw_cdns *cdns = bus_to_cdns(bus);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
|
||||
return sdw->link_res->hw_ops->post_bank_switch(sdw);
|
||||
}
|
||||
|
||||
static int sdw_master_read_intel_prop(struct sdw_bus *bus)
|
||||
{
|
||||
struct sdw_master_prop *prop = &bus->prop;
|
||||
struct fwnode_handle *link;
|
||||
char name[32];
|
||||
u32 quirk_mask;
|
||||
|
||||
/* Find master handle */
|
||||
snprintf(name, sizeof(name),
|
||||
"mipi-sdw-link-%d-subproperties", bus->link_id);
|
||||
|
||||
link = device_get_named_child_node(bus->dev, name);
|
||||
if (!link) {
|
||||
dev_err(bus->dev, "Master node %s not found\n", name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
fwnode_property_read_u32(link,
|
||||
"intel-sdw-ip-clock",
|
||||
&prop->mclk_freq);
|
||||
|
||||
/* the values reported by BIOS are the 2x clock, not the bus clock */
|
||||
prop->mclk_freq /= 2;
|
||||
|
||||
fwnode_property_read_u32(link,
|
||||
"intel-quirk-mask",
|
||||
&quirk_mask);
|
||||
|
||||
if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE)
|
||||
prop->hw_disabled = true;
|
||||
|
||||
prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
|
||||
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_prop_read(struct sdw_bus *bus)
|
||||
{
|
||||
/* Initialize with default handler to read all DisCo properties */
|
||||
sdw_master_read_prop(bus);
|
||||
|
||||
/* read Intel-specific properties */
|
||||
sdw_master_read_intel_prop(bus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sdw_master_ops sdw_intel_ops = {
|
||||
.read_prop = intel_prop_read,
|
||||
.override_adr = sdw_dmi_override_adr,
|
||||
.xfer_msg = cdns_xfer_msg,
|
||||
.xfer_msg_defer = cdns_xfer_msg_defer,
|
||||
.reset_page_addr = cdns_reset_page_addr,
|
||||
.set_bus_conf = cdns_bus_conf,
|
||||
.pre_bank_switch = generic_pre_bank_switch,
|
||||
.post_bank_switch = generic_post_bank_switch,
|
||||
.read_ping_status = cdns_read_ping_status,
|
||||
};
|
||||
|
||||
/*
|
||||
* probe and init (aux_dev_id argument is required by function prototype but not used)
|
||||
*/
|
||||
static int intel_link_probe(struct auxiliary_device *auxdev,
|
||||
const struct auxiliary_device_id *aux_dev_id)
|
||||
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct sdw_intel_link_dev *ldev = auxiliary_dev_to_sdw_intel_link_dev(auxdev);
|
||||
struct sdw_intel *sdw;
|
||||
struct sdw_cdns *cdns;
|
||||
struct sdw_bus *bus;
|
||||
int ret;
|
||||
|
||||
sdw = devm_kzalloc(dev, sizeof(*sdw), GFP_KERNEL);
|
||||
if (!sdw)
|
||||
return -ENOMEM;
|
||||
|
||||
cdns = &sdw->cdns;
|
||||
bus = &cdns->bus;
|
||||
|
||||
sdw->instance = auxdev->id;
|
||||
sdw->link_res = &ldev->link_res;
|
||||
cdns->dev = dev;
|
||||
cdns->registers = sdw->link_res->registers;
|
||||
cdns->instance = sdw->instance;
|
||||
cdns->msg_count = 0;
|
||||
|
||||
bus->link_id = auxdev->id;
|
||||
bus->dev_num_ida_min = INTEL_DEV_NUM_IDA_MIN;
|
||||
bus->clk_stop_timeout = 1;
|
||||
|
||||
sdw_cdns_probe(cdns);
|
||||
|
||||
/* Set ops */
|
||||
bus->ops = &sdw_intel_ops;
|
||||
|
||||
/* set driver data, accessed by snd_soc_dai_get_drvdata() */
|
||||
auxiliary_set_drvdata(auxdev, cdns);
|
||||
|
||||
/* use generic bandwidth allocation algorithm */
|
||||
sdw->cdns.bus.compute_params = sdw_compute_params;
|
||||
|
||||
/* avoid resuming from pm_runtime suspend if it's not required */
|
||||
dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND);
|
||||
|
||||
ret = sdw_bus_master_add(bus, dev, dev->fwnode);
|
||||
if (ret) {
|
||||
dev_err(dev, "sdw_bus_master_add fail: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (bus->prop.hw_disabled)
|
||||
dev_info(dev,
|
||||
"SoundWire master %d is disabled, will be ignored\n",
|
||||
bus->link_id);
|
||||
/*
|
||||
* Ignore BIOS err_threshold, it's a really bad idea when dealing
|
||||
* with multiple hardware synchronized links
|
||||
*/
|
||||
bus->prop.err_threshold = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int intel_link_startup(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
int link_flags;
|
||||
bool multi_link;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled) {
|
||||
dev_info(dev,
|
||||
"SoundWire master %d is disabled, ignoring\n",
|
||||
sdw->instance);
|
||||
return 0;
|
||||
}
|
||||
|
||||
link_flags = md_flags >> (bus->link_id * 8);
|
||||
multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK);
|
||||
if (!multi_link) {
|
||||
dev_dbg(dev, "Multi-link is disabled\n");
|
||||
} else {
|
||||
/*
|
||||
* hardware-based synchronization is required regardless
|
||||
* of the number of segments used by a stream: SSP-based
|
||||
* synchronization is gated by gsync when the multi-master
|
||||
* mode is set.
|
||||
*/
|
||||
bus->hw_sync_min_links = 1;
|
||||
}
|
||||
bus->multi_link = multi_link;
|
||||
|
||||
/* Initialize shim, controller */
|
||||
ret = sdw_intel_link_power_up(sdw);
|
||||
if (ret)
|
||||
goto err_init;
|
||||
|
||||
/* Register DAIs */
|
||||
ret = sdw_intel_register_dai(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "DAI registration failed: %d\n", ret);
|
||||
goto err_power_up;
|
||||
}
|
||||
|
||||
sdw_intel_debugfs_init(sdw);
|
||||
|
||||
/* start bus */
|
||||
ret = sdw_intel_start_bus(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "bus start failed: %d\n", ret);
|
||||
goto err_power_up;
|
||||
}
|
||||
|
||||
/* Enable runtime PM */
|
||||
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) {
|
||||
pm_runtime_set_autosuspend_delay(dev,
|
||||
INTEL_MASTER_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
}
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_NOT_ALLOWED) {
|
||||
/*
|
||||
* To keep the clock running we need to prevent
|
||||
* pm_runtime suspend from happening by increasing the
|
||||
* reference count.
|
||||
* This quirk is specified by the parent PCI device in
|
||||
* case of specific latency requirements. It will have
|
||||
* no effect if pm_runtime is disabled by the user via
|
||||
* a module parameter for testing purposes.
|
||||
*/
|
||||
pm_runtime_get_noresume(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* The runtime PM status of Slave devices is "Unsupported"
|
||||
* until they report as ATTACHED. If they don't, e.g. because
|
||||
* there are no Slave devices populated or if the power-on is
|
||||
* delayed or dependent on a power switch, the Master will
|
||||
* remain active and prevent its parent from suspending.
|
||||
*
|
||||
* Conditionally force the pm_runtime core to re-evaluate the
|
||||
* Master status in the absence of any Slave activity. A quirk
|
||||
* is provided to e.g. deal with Slaves that may be powered on
|
||||
* with a delay. A more complete solution would require the
|
||||
* definition of Master properties.
|
||||
*/
|
||||
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE))
|
||||
pm_runtime_idle(dev);
|
||||
|
||||
sdw->startup_done = true;
|
||||
return 0;
|
||||
|
||||
err_power_up:
|
||||
sdw_intel_link_power_down(sdw);
|
||||
err_init:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void intel_link_remove(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
|
||||
/*
|
||||
* Since pm_runtime is already disabled, we don't decrease
|
||||
* the refcount when the clock_stop_quirk is
|
||||
* SDW_INTEL_CLK_STOP_NOT_ALLOWED
|
||||
*/
|
||||
if (!bus->prop.hw_disabled) {
|
||||
sdw_intel_debugfs_exit(sdw);
|
||||
sdw_cdns_enable_interrupt(cdns, false);
|
||||
}
|
||||
sdw_bus_master_delete(bus);
|
||||
}
|
||||
|
||||
int intel_link_process_wakeen_event(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct sdw_intel *sdw;
|
||||
struct sdw_bus *bus;
|
||||
|
||||
sdw = auxiliary_get_drvdata(auxdev);
|
||||
bus = &sdw->cdns.bus;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!sdw_intel_shim_check_wake(sdw))
|
||||
return 0;
|
||||
|
||||
/* disable WAKEEN interrupt ASAP to prevent interrupt flood */
|
||||
sdw_intel_shim_wake(sdw, false);
|
||||
|
||||
/*
|
||||
* resume the Master, which will generate a bus reset and result in
|
||||
* Slaves re-attaching and be re-enumerated. The SoundWire physical
|
||||
* device which generated the wake will trigger an interrupt, which
|
||||
* will in turn cause the corresponding Linux Slave device to be
|
||||
* resumed and the Slave codec driver to check the status.
|
||||
*/
|
||||
pm_request_resume(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* PM calls
|
||||
*/
|
||||
|
||||
static int intel_resume_child_device(struct device *dev, void *data)
|
||||
{
|
||||
int ret;
|
||||
struct sdw_slave *slave = dev_to_sdw_dev(dev);
|
||||
|
||||
if (!slave->probed) {
|
||||
dev_dbg(dev, "skipping device, no probed driver\n");
|
||||
return 0;
|
||||
}
|
||||
if (!slave->dev_num_sticky) {
|
||||
dev_dbg(dev, "skipping device, never detected on bus\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = pm_request_resume(dev);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_pm_prepare(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if (pm_runtime_suspended(dev) &&
|
||||
pm_runtime_suspended(dev->parent) &&
|
||||
((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
|
||||
!clock_stop_quirks)) {
|
||||
/*
|
||||
* if we've enabled clock stop, and the parent is suspended, the SHIM registers
|
||||
* are not accessible and the shim wake cannot be disabled.
|
||||
* The only solution is to resume the entire bus to full power
|
||||
*/
|
||||
|
||||
/*
|
||||
* If any operation in this block fails, we keep going since we don't want
|
||||
* to prevent system suspend from happening and errors should be recoverable
|
||||
* on resume.
|
||||
*/
|
||||
|
||||
/*
|
||||
* first resume the device for this link. This will also by construction
|
||||
* resume the PCI parent device.
|
||||
*/
|
||||
ret = pm_request_resume(dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Continue resuming the entire bus (parent + child devices) to exit
|
||||
* the clock stop mode. If there are no devices connected on this link
|
||||
* this is a no-op.
|
||||
* The resume to full power could have been implemented with a .prepare
|
||||
* step in SoundWire codec drivers. This would however require a lot
|
||||
* of code to handle an Intel-specific corner case. It is simpler in
|
||||
* practice to add a loop at the link level.
|
||||
*/
|
||||
ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device);
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_suspend(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pm_runtime_suspended(dev)) {
|
||||
dev_dbg(dev, "pm_runtime status: suspended\n");
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
|
||||
!clock_stop_quirks) {
|
||||
|
||||
if (pm_runtime_suspended(dev->parent)) {
|
||||
/*
|
||||
* paranoia check: this should not happen with the .prepare
|
||||
* resume to full power
|
||||
*/
|
||||
dev_err(dev, "%s: invalid config: parent is suspended\n", __func__);
|
||||
} else {
|
||||
sdw_intel_shim_wake(sdw, false);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = sdw_intel_stop_bus(sdw, false);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot stop bus: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_suspend_runtime(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
|
||||
ret = sdw_intel_stop_bus(sdw, false);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot stop bus during teardown: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
} else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET || !clock_stop_quirks) {
|
||||
ret = sdw_intel_stop_bus(sdw, true);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot stop bus during clock_stop: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_err(dev, "%s clock_stop_quirks %x unsupported\n",
|
||||
__func__, clock_stop_quirks);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_resume(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
int link_flags;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
link_flags = md_flags >> (bus->link_id * 8);
|
||||
|
||||
if (pm_runtime_suspended(dev)) {
|
||||
dev_dbg(dev, "pm_runtime status was suspended, forcing active\n");
|
||||
|
||||
/* follow required sequence from runtime_pm.rst */
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
link_flags = md_flags >> (bus->link_id * 8);
|
||||
|
||||
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE))
|
||||
pm_runtime_idle(dev);
|
||||
}
|
||||
|
||||
ret = sdw_intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s failed: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* make sure all Slaves are tagged as UNATTACHED and provide
|
||||
* reason for reinitialization
|
||||
*/
|
||||
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
|
||||
|
||||
ret = sdw_intel_start_bus(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "cannot start bus during resume\n");
|
||||
sdw_intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* after system resume, the pm_runtime suspend() may kick in
|
||||
* during the enumeration, before any children device force the
|
||||
* master device to remain active. Using pm_runtime_get()
|
||||
* routines is not really possible, since it'd prevent the
|
||||
* master from suspending.
|
||||
* A reasonable compromise is to update the pm_runtime
|
||||
* counters and delay the pm_runtime suspend by several
|
||||
* seconds, by when all enumeration should be complete.
|
||||
*/
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_resume_runtime(struct device *dev)
|
||||
{
|
||||
struct sdw_cdns *cdns = dev_get_drvdata(dev);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
u32 clock_stop_quirks;
|
||||
int ret;
|
||||
|
||||
if (bus->prop.hw_disabled || !sdw->startup_done) {
|
||||
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
|
||||
bus->link_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* unconditionally disable WAKEEN interrupt */
|
||||
sdw_intel_shim_wake(sdw, false);
|
||||
|
||||
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
|
||||
|
||||
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
|
||||
ret = sdw_intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: power_up failed after teardown: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* make sure all Slaves are tagged as UNATTACHED and provide
|
||||
* reason for reinitialization
|
||||
*/
|
||||
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
|
||||
|
||||
ret = sdw_intel_start_bus(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot start bus after teardown: %d\n", __func__, ret);
|
||||
sdw_intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) {
|
||||
ret = sdw_intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: power_up failed after bus reset: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sdw_intel_start_bus_after_reset(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot start bus after reset: %d\n", __func__, ret);
|
||||
sdw_intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
} else if (!clock_stop_quirks) {
|
||||
|
||||
sdw_intel_check_clock_stop(sdw);
|
||||
|
||||
ret = sdw_intel_link_power_up(sdw);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s: power_up failed: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sdw_intel_start_bus_after_clock_stop(sdw);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: cannot start bus after clock stop: %d\n", __func__, ret);
|
||||
sdw_intel_link_power_down(sdw);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_err(dev, "%s: clock_stop_quirks %x unsupported\n",
|
||||
__func__, clock_stop_quirks);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops intel_pm = {
|
||||
.prepare = intel_pm_prepare,
|
||||
SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
|
||||
SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL)
|
||||
};
|
||||
|
||||
static const struct auxiliary_device_id intel_link_id_table[] = {
|
||||
{ .name = "soundwire_intel.link" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, intel_link_id_table);
|
||||
|
||||
static struct auxiliary_driver sdw_intel_drv = {
|
||||
.probe = intel_link_probe,
|
||||
.remove = intel_link_remove,
|
||||
.driver = {
|
||||
/* auxiliary_driver_register() sets .name to be the modname */
|
||||
.pm = &intel_pm,
|
||||
},
|
||||
.id_table = intel_link_id_table
|
||||
};
|
||||
module_auxiliary_driver(sdw_intel_drv);
|
||||
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_DESCRIPTION("Intel Soundwire Link Driver");
|
18
drivers/soundwire/intel_auxdevice.h
Normal file
18
drivers/soundwire/intel_auxdevice.h
Normal file
@ -0,0 +1,18 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
|
||||
/* Copyright(c) 2015-2022 Intel Corporation. */
|
||||
|
||||
#ifndef __SDW_INTEL_AUXDEVICE_H
|
||||
#define __SDW_INTEL_AUXDEVICE_H
|
||||
|
||||
int intel_link_startup(struct auxiliary_device *auxdev);
|
||||
int intel_link_process_wakeen_event(struct auxiliary_device *auxdev);
|
||||
|
||||
struct sdw_intel_link_dev {
|
||||
struct auxiliary_device auxdev;
|
||||
struct sdw_intel_link_res link_res;
|
||||
};
|
||||
|
||||
#define auxiliary_dev_to_sdw_intel_link_dev(auxiliary_dev) \
|
||||
container_of(auxiliary_dev, struct sdw_intel_link_dev, auxdev)
|
||||
|
||||
#endif /* __SDW_INTEL_AUXDEVICE_H */
|
@ -17,6 +17,7 @@
|
||||
#include <linux/soundwire/sdw_intel.h>
|
||||
#include "cadence_master.h"
|
||||
#include "intel.h"
|
||||
#include "intel_auxdevice.h"
|
||||
|
||||
static void intel_link_dev_release(struct device *dev)
|
||||
{
|
||||
@ -60,6 +61,7 @@ static struct sdw_intel_link_dev *intel_link_dev_register(struct sdw_intel_res *
|
||||
|
||||
/* Add link information used in the driver probe */
|
||||
link = &ldev->link_res;
|
||||
link->hw_ops = res->hw_ops;
|
||||
link->mmio_base = res->mmio_base;
|
||||
link->registers = res->mmio_base + SDW_LINK_BASE
|
||||
+ (SDW_LINK_SIZE * link_id);
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
#define SWRM_COMP_SW_RESET 0x008
|
||||
#define SWRM_COMP_STATUS 0x014
|
||||
#define SWRM_LINK_MANAGER_EE 0x018
|
||||
#define SWRM_EE_CPU 1
|
||||
#define SWRM_FRM_GEN_ENABLED BIT(0)
|
||||
#define SWRM_COMP_HW_VERSION 0x00
|
||||
#define SWRM_COMP_CFG_ADDR 0x04
|
||||
@ -104,7 +106,6 @@
|
||||
#define SWRM_REG_VAL_PACK(data, dev, id, reg) \
|
||||
((reg) | ((id) << 16) | ((dev) << 20) | ((data) << 24))
|
||||
|
||||
#define SWRM_SPECIAL_CMD_ID 0xF
|
||||
#define MAX_FREQ_NUM 1
|
||||
#define TIMEOUT_MS 100
|
||||
#define QCOM_SWRM_MAX_RD_LEN 0x1
|
||||
@ -694,7 +695,14 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
|
||||
u32p_replace_bits(&val, SWRM_DEF_CMD_NO_PINGS, SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK);
|
||||
ctrl->reg_write(ctrl, SWRM_MCP_CFG_ADDR, val);
|
||||
|
||||
ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
|
||||
if (ctrl->version >= 0x01070000) {
|
||||
ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU);
|
||||
ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL,
|
||||
SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU);
|
||||
} else {
|
||||
ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
|
||||
}
|
||||
|
||||
/* Configure number of retries of a read/write cmd */
|
||||
if (ctrl->version > 0x01050001) {
|
||||
/* Only for versions >= 1.5.1 */
|
||||
@ -1331,8 +1339,8 @@ static int qcom_swrm_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
if (data->sw_clk_gate_required) {
|
||||
ctrl->audio_cgcr = devm_reset_control_get_exclusive(dev, "swr_audio_cgcr");
|
||||
if (IS_ERR_OR_NULL(ctrl->audio_cgcr)) {
|
||||
ctrl->audio_cgcr = devm_reset_control_get_optional_exclusive(dev, "swr_audio_cgcr");
|
||||
if (IS_ERR(ctrl->audio_cgcr)) {
|
||||
dev_err(dev, "Failed to get cgcr reset ctrl required for SW gating\n");
|
||||
ret = PTR_ERR(ctrl->audio_cgcr);
|
||||
goto err_init;
|
||||
@ -1519,7 +1527,13 @@ static int __maybe_unused swrm_runtime_resume(struct device *dev)
|
||||
} else {
|
||||
reset_control_reset(ctrl->audio_cgcr);
|
||||
|
||||
ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
|
||||
if (ctrl->version >= 0x01070000) {
|
||||
ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU);
|
||||
ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL,
|
||||
SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU);
|
||||
} else {
|
||||
ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START);
|
||||
}
|
||||
ctrl->reg_write(ctrl, SWRM_INTERRUPT_CLEAR,
|
||||
SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET);
|
||||
|
||||
@ -1583,6 +1597,7 @@ static const struct of_device_id qcom_swrm_of_match[] = {
|
||||
{ .compatible = "qcom,soundwire-v1.3.0", .data = &swrm_v1_3_data },
|
||||
{ .compatible = "qcom,soundwire-v1.5.1", .data = &swrm_v1_5_data },
|
||||
{ .compatible = "qcom,soundwire-v1.6.0", .data = &swrm_v1_6_data },
|
||||
{ .compatible = "qcom,soundwire-v1.7.0", .data = &swrm_v1_5_data },
|
||||
{/* sentinel */},
|
||||
};
|
||||
|
||||
|
@ -233,6 +233,7 @@ struct sdw_intel_ctx {
|
||||
* struct sdw_intel_res - Soundwire Intel global resource structure,
|
||||
* typically populated by the DSP driver
|
||||
*
|
||||
* @hw_ops: abstraction for platform ops
|
||||
* @count: link count
|
||||
* @mmio_base: mmio base of SoundWire registers
|
||||
* @irq: interrupt number
|
||||
@ -249,6 +250,7 @@ struct sdw_intel_ctx {
|
||||
* @alh_base: sdw alh base.
|
||||
*/
|
||||
struct sdw_intel_res {
|
||||
const struct sdw_intel_hw_ops *hw_ops;
|
||||
int count;
|
||||
void __iomem *mmio_base;
|
||||
int irq;
|
||||
@ -290,4 +292,46 @@ irqreturn_t sdw_intel_thread(int irq, void *dev_id);
|
||||
|
||||
#define SDW_INTEL_QUIRK_MASK_BUS_DISABLE BIT(1)
|
||||
|
||||
struct sdw_intel;
|
||||
|
||||
/* struct intel_sdw_hw_ops - SoundWire ops for Intel platforms.
|
||||
* @debugfs_init: initialize all debugfs capabilities
|
||||
* @debugfs_exit: close and cleanup debugfs capabilities
|
||||
* @register_dai: read all PDI information and register DAIs
|
||||
* @check_clock_stop: throw error message if clock is not stopped.
|
||||
* @start_bus: normal start
|
||||
* @start_bus_after_reset: start after reset
|
||||
* @start_bus_after_clock_stop: start after mode0 clock stop
|
||||
* @stop_bus: stop all bus
|
||||
* @link_power_up: power-up using chip-specific helpers
|
||||
* @link_power_down: power-down with chip-specific helpers
|
||||
* @shim_check_wake: check if a wake was received
|
||||
* @shim_wake: enable/disable in-band wake management
|
||||
* @pre_bank_switch: helper for bus management
|
||||
* @post_bank_switch: helper for bus management
|
||||
*/
|
||||
struct sdw_intel_hw_ops {
|
||||
void (*debugfs_init)(struct sdw_intel *sdw);
|
||||
void (*debugfs_exit)(struct sdw_intel *sdw);
|
||||
|
||||
int (*register_dai)(struct sdw_intel *sdw);
|
||||
|
||||
void (*check_clock_stop)(struct sdw_intel *sdw);
|
||||
int (*start_bus)(struct sdw_intel *sdw);
|
||||
int (*start_bus_after_reset)(struct sdw_intel *sdw);
|
||||
int (*start_bus_after_clock_stop)(struct sdw_intel *sdw);
|
||||
int (*stop_bus)(struct sdw_intel *sdw, bool clock_stop);
|
||||
|
||||
int (*link_power_up)(struct sdw_intel *sdw);
|
||||
int (*link_power_down)(struct sdw_intel *sdw);
|
||||
|
||||
int (*shim_check_wake)(struct sdw_intel *sdw);
|
||||
void (*shim_wake)(struct sdw_intel *sdw, bool wake_enable);
|
||||
|
||||
int (*pre_bank_switch)(struct sdw_intel *sdw);
|
||||
int (*post_bank_switch)(struct sdw_intel *sdw);
|
||||
};
|
||||
|
||||
extern const struct sdw_intel_hw_ops sdw_intel_cnl_hw_ops;
|
||||
|
||||
#endif
|
||||
|
@ -206,6 +206,7 @@ static int hda_sdw_probe(struct snd_sof_dev *sdev)
|
||||
|
||||
memset(&res, 0, sizeof(res));
|
||||
|
||||
res.hw_ops = &sdw_intel_cnl_hw_ops;
|
||||
res.mmio_base = sdev->bar[HDA_DSP_BAR];
|
||||
res.shim_base = hdev->desc->sdw_shim_base;
|
||||
res.alh_base = hdev->desc->sdw_alh_base;
|
||||
@ -1727,3 +1728,4 @@ MODULE_IMPORT_NS(SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
|
||||
MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
|
||||
MODULE_IMPORT_NS(SND_INTEL_SOUNDWIRE_ACPI);
|
||||
MODULE_IMPORT_NS(SOUNDWIRE_INTEL_INIT);
|
||||
MODULE_IMPORT_NS(SOUNDWIRE_INTEL);
|
||||
|
Loading…
Reference in New Issue
Block a user