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:
Linus Torvalds 2022-12-19 08:47:33 -06:00
commit 1b6a349a40
14 changed files with 1237 additions and 950 deletions

View File

@ -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{
....
};
};

View 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>;
};
};

View File

@ -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

View File

@ -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;
}

View File

@ -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)

View File

@ -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"),

View File

@ -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");

View File

@ -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 */

View 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");

View 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 */

View File

@ -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);

View File

@ -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 */},
};

View File

@ -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

View File

@ -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);